(C++)函數參數傳遞中的一級指針和二級指針
(C++)函數參數傳遞中的一級指針和二級指針
主要內容:
1、一級指針和二級指針
2、函數指針傳遞的例子
3、什麽時候需要傳遞二級指針?
4、二級指針在鏈表中的使用
1、一級指針和二級指針
一級指針:即我們一般說的指針,就是內存地址;
二級指針:指向指針的指針,就是地址的地址;
如:
int a=1;
int *p=&a; // p為a變量的地址,通過*p可以得到a的值
int **q=&p; // q為p指針的地址,通過**q可以得到a的值
2、函數指針傳遞的例子
程序1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include<stdio.h> void fun( int *p){
int b=100;
p=&b;
}
int main(){
int a=10;
int *q;
q=&a;
printf ( "%d\n" ,*q);
fun(q);
printf ( "%d\n" ,*q);
return 0;
}
|
運行結果:
10
10
程序2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include<stdio.h>
void fun( int **p){
int b=100;
*p=&b;
}
int main(){
int a=10;
int *q;
q=&a;
printf ( "%d\n" ,*q);
fun(&q);
printf ( "%d\n" ,*q);
return 0;
}
|
運行結果:
10
100
程序3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include<stdio.h>
#include<stdlib.h>
void myMalloc( char *s){
s=( char *) malloc (100);
}
int main()
{
char *p=NULL;
myMalloc(p);
if (p==NULL)
printf ( "P is not changed!\n" );
else {
printf ( "P has been changed!\n" );
free (p);
}
return 0;
}
|
運行結果:
P is not changed!
程序4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include<stdio.h>
#include<stdlib.h>
void myMalloc( char **s){
*s=( char *) malloc (100);
}
int main()
{
char *p=NULL;
myMalloc(&p);
if (p==NULL)
printf ( "P is not changed!\n" );
else {
printf ( "P has been changed!\n" );
free (p);
}
return 0;
}
|
運行結果:
P has been changed!
3、什麽時候需要傳遞二級指針?
通過上述例子,我們可以看到,在某些情況下,函數參數傳遞一級指針時,在函數體內對指針做變動,也不會對原始指針產生變化,而傳遞二級指針時,則可以,這是為什麽呢?
在傳遞一級指針時,只有對指針所指向的內存變量做操作才是有效的;
在傳遞二級指針時,只有對指針的指向做改變才是有效的;
下面做簡單的分析:
在函數傳遞參數時,編譯器總會為每個函數參數制作一個副本,即拷貝;
例如:
void fun(int *p),指針參數p的副本為_p,編譯器使_p=p,_p和p指向相同的內存空間,如果在函數內修改了_p所指向的內容,就會導致p的內容也做相應的改變;
但如果在函數內_p申請了新的內存空間或者指向其他內存空間,則_p指向了新的內存空間,而p依舊指向原來的內存空間,因此函數返回後p還是原來的p。
這樣的話,不但沒有實現功能,反而每次都申請新的內存空間,而又得不到釋放,因為沒有將該內存空間的地址傳遞出來,容易造成內存泄露。
void fun(int **p),如果函數參數是指針的地址,則可以通過該參數p將新分配或新指向的內存地址傳遞出來,這樣就實現了有效的指針操作。
如果覺得二級指針比較難理解,也可以通過函數返回值的形式來傳遞動態內存(切記不能返回棧內存),如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include<stdio.h>
#include<stdlib.h>
char * myMalloc(){
char *s=( char *) malloc (100);
return s;
}
int main()
{
char *p=NULL;
p=myMalloc();
if (p==NULL)
printf ( "P is not changed!\n" );
else {
printf ( "P has been changed\n" );
free (p);
}
return 0;
}
|
知道了上述這些,就不難理解上面四個小程序的執行結果了。
4、二級指針在鏈表中的使用
在鏈表或者樹的操作中,也需要用到二級指針,
比如創建鏈表的頭指針:
在初始化鏈表函數中,傳入頭指針,並在函數中為該指針分配空間,此時就應該使用二級指針,如void initLinklist(Node **head);
而在添加刪除結點的過程中,我們並沒有改變函數參數指針的指向,而是通過傳入的指針如Node *head,找到要刪除結點的位置,並未對該指針做改變,因此退出函數後,該指針無影響。
(1) c++中的懸浮指針:聲明了但沒有被付值的指針,它指向內存中的任意一個空間。避免懸浮指針的一個方法是開始就付值為NULL
(2)“野指針”不是NULL指針,是指向“垃圾”內存的指針。人們一般不會錯用NULL指針,因為用if語句很容易判斷。但是“野指針”是很危險的,if語句對它不起作用。野指針的成因主要有兩種:
一、指針變量沒有被初始化。任何指針變量剛被創建時不會自動成為NULL指針,它的缺省值是隨機的,它會亂指一氣。所以,指針變量在創建的同時應當被初始化,要麽將指針設置為NULL,要麽讓它指向合法的內存。
二、指針p被free或者delete之後,沒有置為NULL,讓人誤以為p是個合法的指針。別看free和delete的名字惡狠狠的(尤其是delete),它們只是把指針所指的內存給釋放掉,但並沒有把指針本身幹掉。通常會用語句if (p != NULL)進行防錯處理。很遺憾,此時if語句起不到防錯作用,因為即便p不是NULL指針,它也不指向合法的內存塊。例:
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的內存被釋放,但是p所指的地址仍然不變
if(p != NULL) // 沒有起到防錯作用
strcpy(p, “world”); // 出錯
三、另外一個要註意的問題:不要返回指向棧內存的指針或引用,因為棧內存在函數結束時會被釋放。strlen是對char*的,string不行,這個很容易讓人誤解啊
四、我們易犯的錯誤:
對於第二個錯誤很容易在C++中出現,比如在類的定義時的構造函數和析構函數,如果在構造函數中動態開辟(new),在析構函數中要釋放,然而我們一般都delete釋放內存後就結束了,殊不知,指向先前內存的指針就成了野指針(迷途指針),稍有不慎,就會出錯,當你向未知區域賦值時,運氣好的話會是程序運行錯誤,要是運氣不佳,很可能引起系統崩潰!
解決方法:將將要指向未知區域的指針(剛定義的或是釋放內存的指針)等於NULL或指向常量,使用指針之前再做判斷null
無論在什麽情況下delete之後是否要設置為NULL?唯一的判斷標準就是以後會不會再用它, 如果以後有可能用,就一定設置為NULL,否則就不必, 除非是對軟件的性能要求很強,否則盡管每次delete後都設置NULL好了 這樣做,永遠是不會運行出錯的, 潛在的後果就是,這個賦NULL值的操作,浪費了你萬分之一秒不到的時間。
五 、知識補充:
一般我們常說的內存泄漏是指堆內存的泄漏。堆內存是指程序從堆中分配的,大小任意的(內存塊的大小可以在程序運行期決定),使用完後必須顯示釋放的內存。應用程序一般使用malloc,realloc,new等函數從堆中分配到一塊內存,使用完後,程序必須負責相應的調用free或delete釋放該內存塊,否則,這塊內存就不能被再次使用,我們就說這塊內存泄漏了
(C++)函數參數傳遞中的一級指針和二級指針