您好,登錄后才能下訂單哦!
這篇“C語言中指針有什么作用”文章的知識點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“C語言中指針有什么作用”文章吧。
編程的本質(zhì)其實(shí)就是更好的操控?cái)?shù)據(jù),而我們的數(shù)據(jù)是存放在內(nèi)存中。
因此,如果能更好地理解內(nèi)存的模型,以及 C 如何管理內(nèi)存,就能對程序的工作原理洞若觀火,從而使編程能力更上一層樓。
大家真的別認(rèn)為這是空話,我大一整年都不敢用 C 寫上千行的程序也很抗拒寫 C。
只有到最后課程要求用C寫一個(gè)地鐵管理系統(tǒng)和自主學(xué)習(xí)寫紅黑樹完整的寫了超過千行的代碼
因?yàn)橐坏┥锨?,?jīng)常出現(xiàn)各種莫名其妙的內(nèi)存錯(cuò)誤,一不小心就發(fā)生了 coredump...... 而且還無從排查,分析不出原因。
直到后來對內(nèi)存和指針有了更加深刻的認(rèn)識,才慢慢會用 C\C++ 寫上千行的項(xiàng)目,也很少會再有內(nèi)存問題了。
「指針存儲的是變量的內(nèi)存地址」這句話應(yīng)該任何講 C 語言的書都會提到吧。
所以,要想徹底理解指針,首先要理解 C 語言中變量的存儲本質(zhì),也就是內(nèi)存。
計(jì)算機(jī)的內(nèi)存是一塊用于存儲數(shù)據(jù)的空間,由一系列連續(xù)的存儲單元組成,就像下面這樣,
每一個(gè)單元格都表示 1 個(gè) Bit,一個(gè) bit 在 EE 專業(yè)的同學(xué)看來就是高低電位,而在 CS 同學(xué)看來就是 0、1 兩種狀態(tài)。
由于 1 個(gè) bit 只能表示兩個(gè)狀態(tài),所以大佬們規(guī)定 8個(gè) bit 為一組,命名為 byte。
并且將 byte 作為內(nèi)存尋址的最小單元,也就是給每個(gè) byte 一個(gè)編號,這個(gè)編號就叫內(nèi)存的地址。
這就相當(dāng)于,我們給小區(qū)里的每個(gè)單元、每個(gè)住戶都分配一個(gè)門牌號: 301、302、403、404、501......
在生活中,我們需要保證門牌號唯一,這樣就能通過門牌號很精準(zhǔn)的定位到一家人。
同樣,在計(jì)算機(jī)中,我們也要保證給每一個(gè) byte 的編號都是唯一的,這樣才能夠保證每個(gè)編號都能訪問到唯一確定的 byte。
上面我們說給內(nèi)存中每個(gè) byte 唯一的編號,那么這個(gè)編號的范圍就決定了計(jì)算機(jī)可尋址內(nèi)存的范圍。
所有編號連起來就叫做內(nèi)存的地址空間,這和大家平時(shí)常說的電腦是 32 位還是 64 位有關(guān)。
早期 Intel 8086、8088 的 CPU 就是只支持 16 位地址空間,寄存器和地址總線都是 16 位,這意味著最多對 2^16 = 64 Kb
的內(nèi)存編號尋址。
這點(diǎn)內(nèi)存空間顯然不夠用,后來,80286 在 8086 的基礎(chǔ)上將地址總線和地址寄存器擴(kuò)展到了20 位,也被叫做 A20 地址總線。
如果是寫 mini os 的時(shí)候,還需要通過 BIOS 中斷去啟動 A20 地址總線的開關(guān)。
但是,現(xiàn)在的計(jì)算機(jī)一般都是 32 位起步了,32 位意味著可尋址的內(nèi)存范圍是 2^32 byte = 4GB
。
所以,如果你的電腦是 32 位的,那么你裝超過 4G 的內(nèi)存條也是無法充分利用起來的。
好了,這就是內(nèi)存和內(nèi)存編址。
有了內(nèi)存,接下來我們需要考慮,int、double 這些變量是如何存儲在 0、1 單元格的。
在 C 語言中我們會這樣定義變量:
int a = 999; char c = 'c';
當(dāng)你寫下一個(gè)變量定義的時(shí)候,實(shí)際上是向內(nèi)存申請了一塊空間來存放你的變量。
我們都知道 int 類型占 4 個(gè)字節(jié),并且在計(jì)算機(jī)中數(shù)字都是用補(bǔ)碼(不了解補(bǔ)碼的記得去百度)表示的。
999 換算成補(bǔ)碼就是:0000 0011 1110 0111
c 這里有 4 個(gè)byte,所以需要四個(gè)單元格來存儲:
有沒有注意到,我們把高位的字節(jié)放在了低地址的地方。
那能不能反過來呢?
當(dāng)然,這就引出了大端和小端。
像上面這種將高位字節(jié)放在內(nèi)存低地址的方式叫做大端
反之,將低位字節(jié)放在內(nèi)存低地址的方式就叫做小端:
上面只說明了 int 型的變量如何存儲在內(nèi)存,而 float、char 等類型實(shí)際上也是一樣的,都需要先轉(zhuǎn)換為補(bǔ)碼。
對于多字節(jié)的變量類型,還需要按照大端或者小端的格式,依次將字節(jié)寫入到內(nèi)存單元。
記住上面這兩張圖,這就是編程語言中所有變量的在內(nèi)存中的樣子,不管是 int、char、指針、數(shù)組、結(jié)構(gòu)體、對象... 都是這樣放在內(nèi)存的。
上面我說,定義一個(gè)變量實(shí)際就是向計(jì)算機(jī)申請了一塊內(nèi)存來存放。
那如果我們要想知道變量到底放在哪了呢?
可以通過運(yùn)算符&
來取得變量實(shí)際的地址,這個(gè)值就是變量所占內(nèi)存塊的起始地址。
(PS: 實(shí)際上這個(gè)地址是虛擬地址,并不是真正物理內(nèi)存上的地址
我們可以把這個(gè)地址打印出來:
printf("%x", &a);
大概會是像這樣的一串?dāng)?shù)字:0x7ffcad3b8f3c
上面說,我們可以通過&
符號獲取變量的內(nèi)存地址,那獲取之后如何來表示這是一個(gè)地址,而不是一個(gè)普通的值呢?
也就是在 C 語言中如何表示地址這個(gè)概念呢?
對,就是指針,你可以這樣:
int *pa = &a;
pa 中存儲的就是變量 a
的地址,也叫做指向 a
的指針。
在這里我想談幾個(gè)看起來有點(diǎn)無聊的話題:
為什么我們需要指針?直接用變量名不行嗎?
當(dāng)然可以,但是變量名是有局限的。
變量名的本質(zhì)是什么?
是變量地址的符號化,變量是為了讓我們編程時(shí)更加方便,對人友好,可計(jì)算機(jī)可不認(rèn)識什么變量 a
,它只知道地址和指令。
所以當(dāng)你去查看 C 語言編譯后的匯編代碼,就會發(fā)現(xiàn)變量名消失了,取而代之的是一串串抽象的地址。
你可以認(rèn)為,編譯器會自動維護(hù)一個(gè)映射,將我們程序中的變量名轉(zhuǎn)換為變量所對應(yīng)的地址,然后再對這個(gè)地址去進(jìn)行讀寫。
也就是有這樣一個(gè)映射表存在,將變量名自動轉(zhuǎn)化為地址:
a | 0x7ffcad3b8f3c
c | 0x7ffcad3b8f2c
h | 0x7ffcad3b8f4c
....
說的好!
可是我還是不知道指針存在的必要性,那么問題來了,看下面代碼:
int func(...) { ... }; int main() { int a; func(...); };
假設(shè)我有一個(gè)需求:
要求在
func
函數(shù)里要能夠修改main
函數(shù)里的變量a
,這下咋整,在main
函數(shù)里可以直接通過變量名去讀寫a
所在內(nèi)存。但是在
func
函數(shù)里是看不見a
的呀。
你說可以通過&
取地址符號,將 a
的地址傳遞進(jìn)去:
int func(int address) { .... }; int main() { int a; func(&a); };
這樣在func
里就能獲取到 a
的地址,進(jìn)行讀寫了。
理論上這是完全沒有問題的,但是問題在于:
編譯器該如何區(qū)分一個(gè) int 里你存的到底是 int 類型的值,還是另外一個(gè)變量的地址(即指針)。
這如果完全靠我們編程人員去人腦記憶了,會引入復(fù)雜性,并且無法通過編譯器檢測一些語法錯(cuò)誤。
而通過int *
去定義一個(gè)指針變量,會非常明確:這就是另外一個(gè) int 型變量的地址。
編譯器也可以通過類型檢查來排除一些編譯錯(cuò)誤。
這就是指針存在的必要性。
實(shí)際上任何語言都有這個(gè)需求,只不過很多語言為了安全性,給指針戴上了一層枷鎖,將指針包裝成了引用。
可能大家學(xué)習(xí)的時(shí)候都是自然而然的接受指針這個(gè)東西,但是還是希望這段啰嗦的解釋對你有一定啟發(fā)。
同時(shí),在這里提點(diǎn)小問題:
既然指針的本質(zhì)都是變量的內(nèi)存首地址,即一個(gè) int 類型的整數(shù)。
那為什么還要有各種類型呢?
比如 int 指針,float 指針,這個(gè)類型影響了指針本身存儲的信息嗎?
這個(gè)類型會在什么時(shí)候發(fā)揮作用?
上面的問題,就是為了引出指針解引用的。
pa
中存儲的是a
變量的內(nèi)存地址,那如何通過地址去獲取a
的值呢?
這個(gè)操作就叫做解引用,在 C 語言中通過運(yùn)算符 *
就可以拿到一個(gè)指針?biāo)傅刂返膬?nèi)容了。
比如*pa
就能獲得a
的值。
我們說指針存儲的是變量內(nèi)存的首地址,那編譯器怎么知道該從首地址開始取多少個(gè)字節(jié)呢?
這就是指針類型發(fā)揮作用的時(shí)候,編譯器會根據(jù)指針的所指元素的類型去判斷應(yīng)該取多少個(gè)字節(jié)。
如果是 int 型的指針,那么編譯器就會產(chǎn)生提取四個(gè)字節(jié)的指令,char 則只提取一個(gè)字節(jié),以此類推。
下面是指針內(nèi)存示意圖:
pa
指針首先是一個(gè)變量,它本身也占據(jù)一塊內(nèi)存,這塊內(nèi)存里存放的就是 a
變量的首地址。
當(dāng)解引用的時(shí)候,就會從這個(gè)首地址連續(xù)劃出 4 個(gè) byte,然后按照 int 類型的編碼方式解釋。
別看這個(gè)地方很簡單,但卻是深刻理解指針的關(guān)鍵。
舉兩個(gè)例子來詳細(xì)說明:
比如:
float f = 1.0; short c = *(short*)&f;
你能解釋清楚上面過程,對于 f
變量,在內(nèi)存層面發(fā)生了什么變化嗎?
或者 c
的值是多少?1 ?
實(shí)際上,從內(nèi)存層面來說,f
什么都沒變。
如圖:
假設(shè)這是f
在內(nèi)存中的位模式,這個(gè)過程實(shí)際上就是把 f
的前兩個(gè) byte 取出來然后按照 short 的方式解釋,然后賦值給 c
。
詳細(xì)過程如下:
1.&f取得f 的首地址(short*)&f
2.上面第二步什么都沒做,這個(gè)表達(dá)式只是說 :
“噢,我認(rèn)為f
這個(gè)地址放的是一個(gè) short 類型的變量”
最后當(dāng)去解引用的時(shí)候*(short*)&f
時(shí),編譯器會取出前面兩個(gè)字節(jié),并且按照 short 的編碼方式去解釋,并將解釋出的值賦給 c
變量。
這個(gè)過程 f
的位模式?jīng)]有發(fā)生任何改變,變的只是解釋這些位的方式。
當(dāng)然,這里最后的值肯定不是 1,至于是什么,大家可以去真正算一下。
那反過來,這樣呢?
short c = 1; float f = *(float*)&c;
如圖:
具體過程和上述一樣,但上面肯定不會報(bào)錯(cuò),這里卻不一定。
為什么?
(float*)&c
會讓我們從c
的首地址開始取四個(gè)字節(jié),然后按照 float 的編碼方式去解釋。
但是c
是 short 類型只占兩個(gè)字節(jié),那肯定會訪問到相鄰后面兩個(gè)字節(jié),這時(shí)候就發(fā)生了內(nèi)存訪問越界。
當(dāng)然,如果只是讀,大概率是沒問題的。
但是,有時(shí)候需要向這個(gè)區(qū)域?qū)懭胄碌闹?,比如?/p>
*(float*)&c = 1.0;
那么就可能發(fā)生 coredump,也就是訪存失敗。
另外,就算是不會 coredump,這種也會破壞這塊內(nèi)存原有的值,因?yàn)楹芸赡苓@是是其它變量的內(nèi)存空間,而我們?nèi)ジ采w了人家的內(nèi)容,肯定會導(dǎo)致隱藏的 bug。
如果你理解了上面這些內(nèi)容,那么使用指針一定會更加的自如。
講到這里,我們來看一個(gè)問題,這是一位C語言交流群的群友問的,這是他的需求:
這是他寫的代碼:
他把 double 寫進(jìn)文件再讀出來,然后發(fā)現(xiàn)打印的值對不上。
而關(guān)鍵的地方就在于這里:
char buffer[4]; ... printf("%f %x\n", *buffer, *buffer);
他可能認(rèn)為 buffer
是一個(gè)指針(準(zhǔn)確說是數(shù)組),對指針解引用就該拿到里面的值,而里面的值他認(rèn)為是從文件讀出來的 4 個(gè)byte,也就是之前的 float 變量。
注意,這一切都是他認(rèn)為的,實(shí)際上編譯器會認(rèn)為:
“哦,buffer
是 char類型的指針,那我取第一個(gè)字節(jié)出來就好了”。
然后把第一個(gè)字節(jié)的值傳遞給了 printf 函數(shù),printf 函數(shù)會發(fā)現(xiàn),%f
要求接收的是一個(gè) float 浮點(diǎn)數(shù),那就會自動把第一個(gè)字節(jié)的值轉(zhuǎn)換為一個(gè)浮點(diǎn)數(shù)打印出來。
這就是整個(gè)過程。
錯(cuò)誤關(guān)鍵就是,這個(gè)同學(xué)誤認(rèn)為,任何指針解引用都是拿到里面“我們認(rèn)為的那個(gè)值”,實(shí)際上編譯器并不知道,編譯器只會傻傻的按照指針的類型去解釋。
所以這里改成:
printf("%f %x\n", *(float*)buffer, *(float*)buffer);
相當(dāng)于明確的告訴編譯器:
“buffer
指向的這個(gè)地方,我放的是一個(gè) float,你給我按照 float 去解釋”
結(jié)構(gòu)體內(nèi)包含多個(gè)成員,這些成員之間在內(nèi)存中是如何存放的呢?
比如:
struct fraction { int num; // 整數(shù)部分 int denom; // 小數(shù)部分 }; struct fraction fp; fp.num = 10; fp.denom = 2;
這是一個(gè)定點(diǎn)小數(shù)結(jié)構(gòu)體,它在內(nèi)存占 8 個(gè)字節(jié)(這里不考慮內(nèi)存對齊),兩個(gè)成員域是這樣存儲的:
image-20201030214416842
我們把 10 放在了結(jié)構(gòu)體中基地址偏移為 0 的域,2 放在了偏移為 4 的域。
接下來我們做一個(gè)正常人永遠(yuǎn)不會做的操作:
((fraction*)(&fp.denom))->num = 5; ((fraction*)(&fp.denom))->denom = 12; printf("%d\n", fp.denom); // 輸出多少?
上面這個(gè)究竟會輸出多少呢?自己先思考下噢~
接下來我分析下這個(gè)過程發(fā)生了什么:
首先,&fp.denom
表示取結(jié)構(gòu)體 fp 中 denom 域的首地址,然后以這個(gè)地址為起始地址取 8 個(gè)字節(jié),并且將它們看做一個(gè) fraction 結(jié)構(gòu)體。
在這個(gè)新結(jié)構(gòu)體中,最上面四個(gè)字節(jié)變成了 denom 域,而 fp 的 denom 域相當(dāng)于新結(jié)構(gòu)體的 num 域。
因此:
((fraction*)(&fp.denom))->num = 5
實(shí)際上改變的是 fp.denom
,而
((fraction*)(&fp.denom))->denom = 12
則是將最上面四個(gè)字節(jié)賦值為 12。
當(dāng)然,往那四字節(jié)內(nèi)存寫入值,結(jié)果是無法預(yù)測的,可能會造成程序崩潰,因?yàn)橐苍S那里恰好存儲著函數(shù)調(diào)用棧幀的關(guān)鍵信息,也可能那里沒有寫入權(quán)限。
大家初學(xué) C 語言的很多 coredump 錯(cuò)誤都是類似原因造成的。
所以最后輸出的是 5。
為什么要講這種看起來莫名其妙的代碼?
就是為了說明結(jié)構(gòu)體的本質(zhì)其實(shí)就是一堆的變量打包放在一起,而訪問結(jié)構(gòu)體中的域,就是通過結(jié)構(gòu)體的起始地址,也叫基地址,然后加上域的偏移。
其實(shí),C++、Java 中的對象也是這樣存儲的,無非是他們?yōu)榱藢?shí)現(xiàn)某些面向?qū)ο蟮奶匦?,會在?shù)據(jù)成員以外,添加一些 Head 信息,比如C++ 的虛函數(shù)表。
實(shí)際上,我們是完全可以用 C 語言去模仿的。
這就是為什么一直說 C 語言是基礎(chǔ),你真正懂了 C 指針和內(nèi)存,對于其它語言你也會很快的理解其對象模型以及內(nèi)存布局。
說起多級指針這個(gè)東西,我以前大一,最多理解到 2 級,再多真的會把我繞暈,經(jīng)常也會寫錯(cuò)代碼。
你要是給我寫個(gè)這個(gè):int ******p
能把我搞崩潰,我估計(jì)很多同學(xué)現(xiàn)在就是這種情況?
其實(shí),多級指針也沒那么復(fù)雜,就是指針的指針的指針的指針......非常簡單。
今天就帶大家認(rèn)識一下多級指針的本質(zhì)。
首先,我要說一句話,沒有多級指針這種東西,指針就是指針,多級指針只是為了我們方便表達(dá)而取的邏輯概念。
首先看下生活中的快遞柜:
這種大家都用過吧,豐巢或者超市儲物柜都是這樣,每個(gè)格子都有一個(gè)編號,我們只需要拿到編號,然后就能找到對應(yīng)的格子,取出里面的東西。
這里的格子就是內(nèi)存單元,編號就是地址,格子里放的東西就對應(yīng)存儲在內(nèi)存中的內(nèi)容。
假設(shè)我把一本書,放在了 03 號格子,然后把 03 這個(gè)編號告訴你,你就可以根據(jù) 03 去取到里面的書。
那如果我把書放在 05 號格子,然后在 03 號格子只放一個(gè)小紙條,上面寫著:「書放在 05 號」。
你會怎么做?
當(dāng)然是打開 03 號格子,然后取出了紙條,根據(jù)上面內(nèi)容去打開 05 號格子得到書。
這里的 03 號格子就叫指針,因?yàn)樗锩娣诺氖侵赶蚱渌褡拥男〖垪l(地址)而不是具體的書。
明白了嗎?
那我如果把書放在 07 號格子,然后在 05 號格子 放一個(gè)紙條:「書放在 07號」,同時(shí)在03號格子放一個(gè)紙條「書放在 05號」
這里的 03 號格子就叫二級指針,05 號格子就叫指針,而 07 號就是我們平常用的變量。
依次,可類推出 N 級指針。
所以你明白了嗎?同樣的一塊內(nèi)存,如果存放的是別的變量的地址,那么就叫指針,存放的是實(shí)際內(nèi)容,就叫變量。
int a; int *pa = &a; int **ppa = &pa; int ***pppa = &ppa;
上面這段代碼,pa
就叫一級指針,也就是平時(shí)常說的指針,ppa
就是二級指針。
內(nèi)存示意圖如下:
不管幾級指針有兩個(gè)最核心的東西:
指針本身也是一個(gè)變量,需要內(nèi)存去存儲,指針也有自己的地址指針內(nèi)存存儲的是它所指向變量的地址
這就是我為什么多級指針是邏輯上的概念,實(shí)際上一塊內(nèi)存要么放實(shí)際內(nèi)容,要么放其它變量地址,就這么簡單。
怎么去解讀int **a
這種表達(dá)呢?
int ** a` 可以把它分為兩部分看,即`int*` 和 `*a`,后面 `*a` 中的`*`表示 `a` 是一個(gè)指針變量,前面的 `int*` 表示指針變量`a
只能存放 int*
型變量的地址。
對于二級指針甚至多級指針,我們都可以把它拆成兩部分。
首先不管是多少級的指針變量,它首先是一個(gè)指針變量,指針變量就是一個(gè)*
,其余的*
表示的是這個(gè)指針變量只能存放什么類型變量的地址。
比如int****a
表示指針變量 a
只能存放int***
型變量的地址。
數(shù)組是 C 自帶的基本數(shù)據(jù)結(jié)構(gòu),徹底理解數(shù)組及其用法是開發(fā)高效應(yīng)用程序的基礎(chǔ)。
數(shù)組和指針表示法緊密關(guān)聯(lián),在合適的上下文中可以互換。
如下:
int array[10] = {10, 9, 8, 7}; printf("%d\n", *array); // 輸出 10 printf("%d\n", array[0]); // 輸出 10 printf("%d\n", array[1]); // 輸出 9 printf("%d\n", *(array+1)); // 輸出 9 int *pa = array; printf("%d\n", *pa); // 輸出 10 printf("%d\n", pa[0]); // 輸出 10 printf("%d\n", pa[1]); // 輸出 9 printf("%d\n", *(pa+1)); // 輸出 9
在內(nèi)存中,數(shù)組是一塊連續(xù)的內(nèi)存空間:
第 0 個(gè)元素的地址稱為數(shù)組的首地址,數(shù)組名實(shí)際就是指向數(shù)組首地址,當(dāng)我們通過array[1]
或者*(array + 1)
去訪問數(shù)組元素的時(shí)候。
實(shí)際上可以看做 address[offset]
,address
為起始地址,offset
為偏移量,但是注意這里的偏移量offset
不是直接和 address
相加,而是要乘以數(shù)組類型所占字節(jié)數(shù),也就是: address + sizeof(int) * offset
。
學(xué)過匯編的同學(xué),一定對這種方式不陌生,這是匯編中尋址方式的一種:基址變址尋址。
看完上面的代碼,很多同學(xué)可能會認(rèn)為指針和數(shù)組完全一致,可以互換,這是完全錯(cuò)誤的。
盡管數(shù)組名字有時(shí)候可以當(dāng)做指針來用,但數(shù)組的名字不是指針。
最典型的地方就是在 sizeof
:
printf("%u", sizeof(array)); printf("%u", sizeof(pa));
第一個(gè)將會輸出 40,因?yàn)?nbsp;array
包含有 10 個(gè)int類型的元素,而第二個(gè)在 32 位機(jī)器上將會輸出 4,也就是指針的長度。
為什么會這樣呢?
站在編譯器的角度講,變量名、數(shù)組名都是一種符號,它們都是有類型的,它們最終都要和數(shù)據(jù)綁定起來。
變量名用來指代一份數(shù)據(jù),數(shù)組名用來指代一組數(shù)據(jù)(數(shù)據(jù)集合),它們都是有類型的,以便推斷出所指代的數(shù)據(jù)的長度。
對,數(shù)組也有類型,我們可以將 int、float、char 等理解為基本類型,將數(shù)組理解為由基本類型派生得到的稍微復(fù)雜一些的類型,數(shù)組的類型由元素的類型和數(shù)組的長度共同構(gòu)成。而 sizeof
就是根據(jù)變量的類型來計(jì)算長度的,并且計(jì)算的過程是在編譯期,而不會在程序運(yùn)行時(shí)。
編譯器在編譯過程中會創(chuàng)建一張專門的表格用來保存變量名及其對應(yīng)的數(shù)據(jù)類型、地址、作用域等信息。
sizeof
是一個(gè)操作符,不是函數(shù),使用 sizeof
時(shí)可以從這張表格中查詢到符號的長度。
所以,這里對數(shù)組名使用sizeof
可以查詢到數(shù)組實(shí)際的長度。
pa
僅僅是一個(gè)指向 int 類型的指針,編譯器根本不知道它指向的是一個(gè)整數(shù),還是一堆整數(shù)。
雖然在這里它指向的是一個(gè)數(shù)組,但數(shù)組也只是一塊連續(xù)的內(nèi)存,沒有開始和結(jié)束標(biāo)志,也沒有額外的信息來記錄數(shù)組到底多長。
所以對 pa
使用 sizeof
只能求得的是指針變量本身的長度。
也就是說,編譯器并沒有把 pa
和數(shù)組關(guān)聯(lián)起來,pa
僅僅是一個(gè)指針變量,不管它指向哪里,sizeof
求得的永遠(yuǎn)是它本身所占用的字節(jié)數(shù)。
大家不要認(rèn)為二維數(shù)組在內(nèi)存中就是按行、列這樣二維存儲的,實(shí)際上,不管二維、三維數(shù)組... 都是編譯器的語法糖。
存儲上和一維數(shù)組沒有本質(zhì)區(qū)別,舉個(gè)例子:
int array[3][3] = {{1, 2,3}, {4, 5,6},{7, 8, 9}}; array[1][1] = 5;
或許你以為在內(nèi)存中 array
數(shù)組會像一個(gè)二維矩陣:
1 2 3
4 5 6
7 8 9
可實(shí)際上它是這樣的:
1 2 3 4 5 6 7 8 9
和一維數(shù)組沒有什么區(qū)別,都是一維線性排列。
當(dāng)我們像 array[1][1]
這樣去訪問的時(shí)候,編譯器會怎么去計(jì)算我們真正所訪問元素的地址呢?
為了更加通用化,假設(shè)數(shù)組定義是這樣的:
int array[n][m]
訪問: array[a][b]
那么被訪問元素地址的計(jì)算方式就是: array + (m * a + b)
這個(gè)就是二維數(shù)組在內(nèi)存中的本質(zhì),其實(shí)和一維數(shù)組是一樣的,只是語法糖包裝成一個(gè)二維的樣子。
想必大家一定看到過 void 的這些用法:
void func(); int func1(void);
在這些情況下,void 表達(dá)的意思就是沒有返回值或者參數(shù)為空。
但是對于 void 型指針卻表示通用指針,可以用來存放任何數(shù)據(jù)類型的引用。
下面的例子就 是一個(gè) void 指針:
void *ptr;
void 指針最大的用處就是在 C 語言中實(shí)現(xiàn)泛型編程,因?yàn)槿魏沃羔樁伎梢员毁x給 void 指針,void 指針也可以被轉(zhuǎn)換回原來的指針類型, 并且這個(gè)過程指針實(shí)際所指向的地址并不會發(fā)生變化。
比如:
int num; int *pi = # printf("address of pi: %p\n", pi); void* pv = pi; pi = (int*) pv; printf("address of pi: %p\n", pi);
這兩次輸出的值都會是一樣:
平常可能很少會這樣去轉(zhuǎn)換,但是當(dāng)你用 C 寫大型軟件或者寫一些通用庫的時(shí)候,一定離不開 void 指針,這是 C 泛型的基石,比如 std 庫里的 sort 函數(shù)申明是這樣的:
void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));
所有關(guān)于具體元素類型的地方全部用 void 代替。
void 還可以用來實(shí)現(xiàn) C 語言中的多態(tài),這是一個(gè)挺好玩的東西。
不過也有需要注意的:
不能對 void 指針解引用
比如:
int num; void *pv = (void*)# *pv = 4; // 錯(cuò)誤
為什么?
因?yàn)榻庖玫谋举|(zhì)就是編譯器根據(jù)指針?biāo)傅念愋停缓髲闹羔標(biāo)赶虻膬?nèi)存連續(xù)取 N 個(gè)字節(jié),然后將這 N 個(gè)字節(jié)按照指針的類型去解釋。
比如 int *型指針,那么這里 N 就是 4,然后按照 int 的編碼方式去解釋數(shù)字。
但是 void,編譯器是不知道它到底指向的是 int、double、或者是一個(gè)結(jié)構(gòu)體,所以編譯器沒法對 void 型指針解引用。
以上就是關(guān)于“C語言中指針有什么作用”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。