傳值、傳指標、傳引用
值傳遞、指標傳遞、引用傳遞的區別
c語言的規則很簡單:“所有的引數都是傳值呼叫”。在這句話的基礎上,我們來分析值傳遞、指標傳遞、引用傳遞的區別。
一、值傳遞
值傳遞,這與C函式的性質有關。C函式的所有引數均以“傳值呼叫”方式進行傳遞,這意味著函式值將獲得引數值的一份拷貝,函式可以放心修改這個拷貝值,而不必擔心會修改呼叫程式實際傳給他的引數。
我們先來看實現函式swap1:
void swap1(int a,int b)
{
printf("\n\n傳值的示例函式swap1\n");
printf("形參a的地址:%p\n",&a);
printf("形參b的地址:%p \n",&b);
int temp;
temp = a;
a = b;
b = temp;
printf("形參a的值:%d ",a);
printf("形參b的值:%d ",b);
}
main函式如下:
int a,b;
a = 5;
b = 6;
printf("實參a的值:%d ",a);
printf("實參a的地址:%p\n",&a);
printf("實參b的值:%d ",b);
printf("實參b的地址:%p\n",&b);
swap1(a,b);
printf ("\n\n傳值交換後a,b的值:\n");
printf("實參a的值:%d \n",a);
printf("實參b的值:%d \n",b);
執行結果如下:
實參a的地址為:0018FF44,實參b的地址為0018FF40
棧區(stack)— 程式執行時由編譯器自動分配,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。程式結束時由編譯器自動釋放。因此,引數a,b是存放在棧區的。在Windows下,棧是向低地址擴充套件的資料結構,是一塊連續的記憶體的區域。因此,我們會看到a,b的地址是呈減少的趨勢。
因此,按照“值傳遞”的思想,形參是實參的拷貝,程式會開闢一塊新的棧區為形參。它們進行交換操作是在這塊新的棧區裡面,並不影響實參的那一塊記憶體。
執行完swap1函式,形參的記憶體中a,b的值發生了變化,但並不影響實參的的值。
在函式呼叫時,引數是從右到左讀的,所以b的地址比a的地址高
二、指標傳遞
我們來看實現函式swap2:
void swap2(int *a,int *b)
{
printf("\n\n傳指標的示例函式swap2\n");
printf("形參a的地址:%p\n",&a);
printf("形參b的地址:%p\n",&b);
int temp;
temp = *a;
*a = *b;
*b = temp;
printf("形參a的值:%p ",a);
printf("形參b的值:%p ",b);
}
main函式呼叫:
swap2(&a,&b);
printf("\n\n傳指標交換後a,b的值:\n");
printf("實參a的值:%d \n",a);
printf("實參b的值:%d \n",b);
執行結果:
因此我們可以看到,形參的值實際是實參的地址,因為c語言是值傳遞,main函式中傳入的是&a,&b即a,b的地址,所以形參中儲存的也是a和b的地址。
然後執行:
temp = *a;
*a = *b;
*b = temp;
*a是什麼意思?*a是地址為a的記憶體中所儲存的函式值。所以上面三條語句的意思就是地址為a的記憶體中所儲存的函式值與地址為b的記憶體中所儲存的函式值進行交換。如下圖:
因此,他修改的是實參的內容。執行完後實參的內容應該是這樣的:
所以,這個時候a的值為6,b的值為5。
三、引用傳遞
我們來看實現函式swap3:
void swap3(int &a,int &b)
{
printf("\n\n傳引用的示例函式swap3\n");
printf("形參a的地址:%p\n",&a);
printf("形參b的地址:%p\n",&b);
int temp;
temp = a;
a = b;
b = temp;
printf("形參a的值:%d ",a);
printf("形參b的值:%d ",b);
}
main函式呼叫:
swap3(a,b);
printf("\n\n傳引用交換後a,b的值:\n");
printf("實參a的值:%d \n",a);
printf("實參b的值:%d \n",b);
執行結果截圖:
我們可以看到形參的地址與實參的地址相同,調出彙編程式碼:
71: swap3(a,b);
0040D9B8 lea eax,[ebp-8]
0040D9BB push eax
0040D9BC lea ecx,[ebp-4]
0040D9BF push ecx
0040D9C0 call @ILT+15(swap3) (00401014)
0040D9C5 add esp,8
lea eax,[ebp-8]是將b的偏移地址送入eax,
這個是swap2的彙編程式碼,我們可以看到二者是一樣的。
66: swap2(&a,&b);
0040D979 lea edx,[ebp-8]
0040D97C push edx
0040D97D lea eax,[ebp-4]
0040D980 push eax
0040D981 call @ILT+10(swap2) (0040100f)
0040D986 add esp,8
所以,對於這裡的swap3和前面的swap2來講,堆疊中的函式引數存放的都是地址,在函式中操作的方式是一致的。但是,對swap3來說這個地址是主調函式通過將實參變數的偏移地址壓棧而傳遞進來的–這是引用傳遞;而對swap2來說,這個地址是主調函式通過將實參變數的值壓棧而傳遞進來的–這是值傳遞,只不過由於這個實參變數是指標變數所以其值是地址而已。
這裡的關鍵點在於,同樣是地址,一個是引用傳遞中的變數地址,一個是值傳遞中的指標變數的值。
四、總結
其實指標傳遞和值傳遞本質都是值傳遞,值傳遞是傳輸了要傳遞的變數的一個副本。在C++中有引用傳遞,就是將要傳遞的變數的地址傳到了被呼叫函式中,如果在被呼叫函式中改變,那麼就會在呼叫函式中改變。
Reference:
1.函式引數是如何傳遞的
2.《C和指標》第七章函式。