先看代碼,這個代碼很簡單,看你能不能準確地說出答案。
#include <stdio.h>
?
struct A {
??????? int a;
};
?
struct A g_ta = {
??????? .a = 1,
};
?
struct A g_tb = {
??????? .a = 2,
};
?
void fun1(struct A * p1)
{
??????? p1->a = 3;
}
?
void fun2(struct A * p2)
{
??????? p2 = &g_tb;
}
?
void fun3(struct A ** p3)
{
??????? *p3 = &g_tb;
}
?
int main()
{
??????? struct A *p = &g_ta;
??????? printf("p->a = %d\n",p->a);
?
??????? fun1(p);
??????? printf("p->a = %d\n",p->a);
?
??????? fun2(p);
??????? printf("p->a = %d\n",p->a);
?
??????? fun3(&p);
??????? printf("p->a = %d \n",p->a);
??????? return 0;
}
?
gcc編譯,運行結果如下:
p->a = 1
p->a = 3
p->a = 3
p->a = 2
對了嗎?如果你對了,說明你對指針和函數參數傳遞已經理解。
如果你和我一樣,答案和打印結果相悖,繼續看。
第一行和第二行輸出應該沒問題,很簡單的,是通過指針方式進行傳值的,所以改變形參的值,實參也會跟著改變。
再看第三行輸出,為什么經過fun2 后值不是p->a = 2,而是p->a = 3?fun2不也是通過指針方式來進行傳值的嗎?怎么沒有改變p的值呢?
再看第四行輸出,p->a = 2?怎么形參為指針的指針就對了呢?
?
帶著疑問看下面:
首先不要覺得指針是個很神奇的東西,我之前一直對指針不理解,或者是理解不透徹,這兩天在寫電子書的代碼,感覺對指針這個東西有了些頓悟,我可以告訴你指針就是一個變量,他和別的變量(比如int,char)沒有什么本質的區別,都是一個內存A里存放變量的值!唯一區別就是指針被多設計了一個 * 號,該 * 號的意思就是將這個內存A里面的值當成另一個內存B的地址,并取出這個內存B的值,說到這里,這就是指針的全部!
下面是函數參數傳遞,回頭看我說的一句話“通過指針方式進行傳值的,所以改變形參的值,實參也會跟著改變”,這句話其實是障眼法,學C語言的時候老師就告我們,值傳遞方式,形參改變不能影響實參,而地址傳遞傳遞是可以的,這句話不能算錯,但是不能說對,地址傳遞方式形參改變確實能影響實參,但是也是要看改變的是什么,影不影響實參是有道理的!
為了更好得說明,我將內存抽塊象成如下小方塊,一個方塊代表一塊內存。
另注,1. 我將一個結構體看成一塊小內存,2. 變量的內存地址是我假設的。3.地址就是指此塊內存的地址,每塊內存都有自己地址。
變量 |
值 |
地?? 址 |
現在開始分析代碼:
?
首先申明兩個struct A全局變量,其內存模型如下,(地址是我假設的,以下都是,不再贅述) ??
? ? ?g_ta
g_ta |
1 |
0x10 |
?
?
? ? ??
? ? ?g_tb
g_tb |
2 |
0x11 |
?
?
?
在main數里面申明了結構體A指針,其指向g_ta,其內存模型如下
? ? ? ? P
p |
0x10 |
0x50 |
?
?
?
注意看,p的值就是 0x10,g_ta的地址,沒什么特別的。
現在開始分析fun2,fun2弄懂了fun1自然就理解了,首先p經過了fun1之后,將g_ta里面的值變成了3,所以,現在他們內存情況如下:(這三塊內存方格是分開的,貼到這里就連在一起了,以下同樣,不再贅述。。。)
? ? ? g_ta ? ? ? ? ? ?g_tb ? ? ? ? ? ?p
g_ta |
3 |
0x10 |
g_tb |
2 |
0x11 |
p |
0x10 |
0x50 |
?
??????
?
調用fun2(p);
?????? ? | |
?????? ? \/?
void fun2(struct A * p2)
{
??????? p2 = &g_tb;
}
進入函數fun2,會生成一個和形參類型相同副本,其將實參值拷貝,即結構體指針p2,其內存模型如下 ? ? ? ? ? ? ? ? ? ??
? ? ? ? p ? ? ? ? ? ? ? ?p2
p |
0x10 |
0x50 |
p2 |
0x10 |
0xa0 |
?
?
?
可以看到,他和指針p的不同就是內存地址不同,而他們的值是相同的,都表示g_ta的地址。
p2 = &g_tb;
這句就是將g_tb的地址賦值給p2,那么p2內存模型就會變成如下:
? ? ? ? ?p ? ? ? ? ? ? ? ?p2
p |
0x10 |
0x50 |
p2 |
0x11 |
0xa0 |
?
?
?
好了,p2的值不是改變了嗎,p2確實指向了g_tb了啊?怎么打印得不對呢?哈哈,你再看看,printf打印得是p啊,p指向的還是是g_ta,當然輸出的是3了啊!p2在退出fun2時候就自動銷毀了!
跟著內存模型來看,很容易理解吧,那下面繼續來看fun3,fun3為什么就能正確地打印出g_tb的值呢?繼續用內存模型來分析:
此時內存情況如下:
? ? ? ?g_ta ? ? ? ? ?g_tb ? ? ? ? ? ? ?p
g_ta |
3 |
0x10 |
g_tb |
2 |
0x11 |
? p? |
0x10 |
0x50 |
?
??????
? ?
執行函數fun3(&p);
????????????? ?? | |
????????????? ?? \/
void fun3(struct A ** p3)
{
??????? *p3 = &g_tb;
}
經過fun3,將p的地址 0x50傳給了fun3,這個0x50沒有什么特別得,他就是一個數值,對于fun3來說他根本不知道這個0x50代表什么意思!所以我們就要告訴fun3,這個0x50是個內存地址,這個地址內存里面的內容仍然是一個地址!轉變成C語言來理解就是要設計一個形參,這個形參能夠進行兩次取內存值的操作,好,struct A ** p3就應運而生了!
?p3:指針的指針,感覺好復雜啊!其實一點都不復雜,說到底p3就是一個指針嘛,不過他指向的內存里面的數據也是指針類型罷了,既然p3還是一個指針那再來看看他的內存模型
? ? ?g_ta ? ? ? ? ? ?g_tb ? ? ? ? ? ?p ? ? ? ? ? ? ? ?? p3
g_ta |
3 |
0x10 |
g_tb |
2 |
0x11 |
p |
0x10 |
0x50 |
P3 |
0x50 |
0xa1 |
????????????????????
?
? ? ??
進過fun3,實參傳遞了p的地址0x50給p3,所以p3的值就是0x50,看清楚了!
執行*p3= &g_tb;
*p3就是取地址為0x50內存的值,地址為0x50的內存里面存放是誰的值啊?
對!就是p的值,所以這條代碼其實改變了的是p的值,將g_tb的值賦給了p!
? ? g_ta ? ? ? ? ? ? ?g_tb ? ? ? ? ? ? ?p???????????????? p3
g_ta |
3 |
0x10 |
g_tb |
2 |
0x11 |
p |
0x11 |
0x50 |
P3 |
0x50 |
0xa1 |
????????????????????
?
? ? ??
好了,基本都已經清楚了,退出fun3,p3銷毀,打印p指向的值,就是p->a = 2
分析已經結束了,還有fun1,可以自己用這種內存模型方法來自己分析一下 :)
?
總結:想要改變指針指向的內容,就傳指針給函數,想要改變指針的值,那就得傳遞指針的地址了,相應的函數參數就要設計成指針的指針了!
?
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
