其實(shí),對(duì)于C 或者C++ ,最難的一塊地方估計(jì)就是指針了。指針是強(qiáng)大的,但也是很多人載在這里的地方。
前段時(shí)間寫了一篇文章《C ++之 數(shù)組與指針的異同 》對(duì)C 和C ++中的指針做了一個(gè)初步的講解。這次將講解一下指針作為函數(shù)參數(shù)傳遞的問(wèn)題。
很多人對(duì)于指針的使用是有所了解的,但還是經(jīng)常會(huì)載在指針的問(wèn)題上,是因?yàn)檫€不夠了解指針的本質(zhì),其實(shí)如果了解指針的本質(zhì),對(duì)指針的使用也就一目了然了。
作為C 的初學(xué)者,經(jīng)常會(huì)遇到指針作為函數(shù)參數(shù)傳遞的兩個(gè)經(jīng)典的問(wèn)題。這里,我將透過(guò)指針的本質(zhì)來(lái)來(lái)講解這兩個(gè)問(wèn)題,這樣以后無(wú)論你遇到什么樣的指針問(wèn)題,如果你以這樣的方法來(lái)分析指針也許就迎刃而解了!
首先,第一個(gè)問(wèn)題是這樣的: 寫一個(gè)函數(shù),交換兩個(gè)參數(shù)中的值。
初學(xué)者往往會(huì)這樣寫:
void exchange(int x, int y) { int p=x; x = y; y = p; }
之后,你會(huì)查找資料了解到應(yīng)該這樣寫: void exchange(int *x, int *y) { int *p=x; *x = *y; *y = *p; }
第二個(gè)問(wèn)題是,寫一個(gè)給某個(gè)指針?lè)峙鋬?nèi)存的函數(shù): 初學(xué)者往往是這樣寫: void my_malloc(void* p, int size) { p = malloc(sizeof(int)*size); }
然后又查在資料,知道應(yīng)該這么寫: void my_malloc(void** p, int size) { *p = malloc(sizeof(int)*size); }
雖然,網(wǎng)上很多這樣的討論,也有很多人做過(guò)很多的解釋,但始終都無(wú)法給出一個(gè)令人一目了然,并可以長(zhǎng)久記住的說(shuō)法,這篇文章就是想試圖解決這樣的問(wèn)題,給初學(xué)者一個(gè)原理性的了解!
首先,一定一定記住一點(diǎn), 指針和變量一樣,也是有地址的,只不過(guò)變量的值被解釋成一個(gè)值,而指針的值被解釋成一個(gè)地址。
下面,我們看一下代碼: void main() { int x; int *p; }
我們看這個(gè)函數(shù)的內(nèi)存結(jié)構(gòu):
這是一個(gè)函數(shù)的棧結(jié)構(gòu),我們可以看到,變量和指針都占用了4 個(gè)字節(jié)。而且,由于我們對(duì)它們沒(méi)有初始化,所以變量x 和指針p 里的內(nèi)容都是隨機(jī)的,就是說(shuō)x 的值是不確定的,p 有可能指向某個(gè)內(nèi)存地址,如果現(xiàn)在對(duì)p 操作也許會(huì)導(dǎo)致程序崩潰。
<!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } -->
其實(shí),我們記住了,指針也是有地址的 這個(gè)概念,很多問(wèn)題就迎刃而解了。
下面,我來(lái)分析一下,指針作為函數(shù)參數(shù)傳遞的情況。 <!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } --> 如果,我們的代碼是這樣的,你看會(huì)怎么樣: int main(int argc, char* argv[]) { int *a = new int(10); func(a); return 0; }
第一個(gè)要說(shuō)的當(dāng)然是:指針也是有地址的。 第二個(gè)要說(shuō)的是:當(dāng)給一個(gè)函數(shù)的參數(shù)傳遞一個(gè)變量是,這個(gè)變量是復(fù)制過(guò)去的。
對(duì)于第二點(diǎn),我們?cè)诶斫?SPAN lang=EN-US>void exchange(int x, int y) 函數(shù)想交換這兩個(gè)變量的的值時(shí)就應(yīng)該理解了。 例如: int a; int b; exchange(a,b); 不能交換a 和b 的值,因?yàn)榇藭r(shí)exchange(a,b) 中的a 和b 并不是原來(lái)的a 和b 變量,它們只不過(guò)是被復(fù)制過(guò)去了。
有了這兩個(gè)概念,就不難理解指針作為函數(shù)參數(shù)傳遞的問(wèn)題。
首先,我們來(lái)看下上面的代碼中的a 指針和p 指針的內(nèi)存結(jié)構(gòu)。
我們看到,當(dāng)我們以a 作為func 函數(shù)的參數(shù)傳遞進(jìn)去的時(shí)候,函數(shù)復(fù)制了這個(gè)指針,但這兩個(gè)指針的內(nèi)容是一樣的,也就是說(shuō)是指向同一個(gè)內(nèi)存,即10 。
如果你還不了解的話,我就通過(guò)一段代碼和測(cè)試再來(lái)說(shuō)明:
view plaincopy to clipboardprint? #include <stdio.h> void func(int* p) { printf("*p = %d\n", *p); printf("&p = %p\n", &p); } int main(int argc, char *argv[]) { int *a = new int(10); printf("*a = %d\n", *a); printf("&a = %p\n", &a); func(a); return 0; }
編譯:g++ -g -Wall test1.cpp 運(yùn)行:./a.out 輸出: *a = 10 &a = 0xbfd4447c *p = 10 &p = 0xbfd44460
我們看到輸出,a 指向的地址的值和p 指向的地址里的值是一樣的,都是10 。然而,對(duì)于指針a 和p 來(lái)說(shuō),它們自身的地址是不一樣的,所以我們看到,函數(shù)func 復(fù)制了指針a 給p ,它們的值一樣,但有不同的地址,是不同的指針。
我們?cè)龠M(jìn)一步: view plaincopy to clipboardprint? #include <stdio.h> void func(int* p) { printf("*p = %d\n", *p); printf("&p = %p\n", &p); printf("&*p = %p\n", &*p); } int main(int argc, char *argv[]) { int *a = new int(10); printf("*a = %d\n", *a); printf("&a = %p\n", &a); printf("&*a = %p\n", &*a); func(a); return 0; }
編譯輸出: *a = 10 &a = 0xbfe1c77c &*a = 0x94b6008 *p = 10 &p = 0xbfe1c760 &*p = 0x94b6008
我們可以進(jìn)一步看到,a 指針?biāo)赶虻闹档牡刂泛?SPAN lang=EN-US>p 指針?biāo)赶虻闹档牡刂肥且粯拥?,都?SPAN lang=EN-US> 0x94b6008 ,就如同上圖所示,為了加深印象,再看一下這個(gè)圖 ,然后再對(duì)比一下程序輸出 ,然后在體會(huì)一下我在上面提到的兩點(diǎn),一點(diǎn)是:指針是有地址的 。另一點(diǎn)是:函數(shù)的參數(shù)是復(fù)制過(guò)去的 。
說(shuō)到這里,我們?cè)倩氐轿恼麻_(kāi)始時(shí)提到的兩個(gè)問(wèn)題,一個(gè)是交換問(wèn)題:
void exchange(int *x, int *y) { int *p=x; *x = *y; *y = *p; }
那么這樣為什么可以交換: int a = 2; int b = 3; exchange(&a, &b);
上我們以a 和b 的地址傳遞給exchange 函數(shù)時(shí),函數(shù)復(fù)制了這兩個(gè)地址,并賦值給x 和y 這個(gè)兩個(gè)指針,這兩個(gè)指針是指向變量a 和b 的,它們的圖形如下:
那么,當(dāng)我們反引用指針時(shí): int *p=x; *x = *y; *y = *p;
我們操作的是a 和b 里面的變量的值,所以,我們交換a 和b 的值就成功了。
我們?cè)賮?lái)看下第二個(gè)問(wèn)題: void my_malloc(void* p, int size) { p = malloc(sizeof(int)*size); } 當(dāng)這樣時(shí): int *a; my_malloc(a, 10); 為什么這個(gè)會(huì)失??!
下面,我來(lái)分析一下: 當(dāng)我們調(diào)用my_malloc(a, 10); 函數(shù),而函數(shù)還沒(méi)執(zhí)行到p = malloc(size); 語(yǔ)句時(shí),情況是這樣的:
我們看到a 和p 的指針的值都是一樣的,都是指向某個(gè)不確定的地址。 這時(shí),我們執(zhí)行這個(gè)語(yǔ)句: p = malloc(sizeof(int)*size); 我們把這個(gè)語(yǔ)句分開(kāi)兩部分來(lái)看,一個(gè)是先執(zhí)行malloc(sizeof(int)*size) ,然后在執(zhí)行賦值語(yǔ)句,把malloc(sizeof(int)*size) 的返回值付給p 。 第一步:先執(zhí)行malloc(sizeof(int)*size) ;(這里我們只考慮malloc 分配內(nèi)存成功的情況)
第二步:把執(zhí)行malloc(sizeof(int)*size) 的返回值付給了p ,如下圖:
由上圖,我們可以知道,這就是為什么,我們還是不能給a 分配地址的了。
下面我們來(lái)分析這個(gè): void my_malloc(void** p, int size) { *p = malloc(sizeof(int)*size); }
int *a; my_malloc(&a , 10); 這樣執(zhí)行,為什么會(huì)成功!
我們看到,當(dāng)執(zhí)行函數(shù) my_malloc(void** p, int size); 但還沒(méi)有執(zhí)行 *p = malloc(sizeof(int)*size); 語(yǔ)句時(shí),它們的內(nèi)存結(jié)構(gòu)圖如下所示:
其實(shí)這里,我們可以把二維指針和一維指針當(dāng)成和變量一樣,也是有地址的。只不過(guò)它的解釋不一樣而已。 變量:里面的值是一個(gè)數(shù)值。 一維指針:里面的值是個(gè)地址,而這個(gè)地址里的值是個(gè)數(shù)值。 二維指針:里面的值是個(gè)地址,而這個(gè)地址里的值也是個(gè)地址。
那么,我看著圖來(lái)解釋p : p 里面是一個(gè)地址,這個(gè)地址是&a ,即是a 指針的地址值,而a 指針地址里面的值也是個(gè)地址,這個(gè)地址是指向一個(gè)不確定的地方,說(shuō)得坳口,慢慢對(duì)比圖來(lái)理解就會(huì)好了!
執(zhí)行malloc(size) 后的圖如下:
然后在執(zhí)行賦值語(yǔ)句: *p = malloc(sizeof(int)*size); 后,如下圖所示:
然后,我們就給指針a 分配內(nèi)存成功了。
|