最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

C/C++編程筆記:指針篇!從內(nèi)存理解指針,讓你完全搞懂指針

2020-11-08 22:14 作者:C語言編程__Plus  | 我要投稿

注:這篇文章好好看完一定會讓你掌握好指針的本質(zhì)

C語言最核心的知識就是指針,所以,這一篇的文章主題是「指針與內(nèi)存模型」

說到指針,就不可能脫離開內(nèi)存,學會指針的人分為兩種,一種是不了解內(nèi)存模型,另外一種則是了解。

不了解的對指針的理解就停留在“指針就是變量的地址”這句話,會比較害怕使用指針,特別是各種高級操作。

而了解內(nèi)存模型的則可以把指針用得爐火純青,各種 byte 隨意操作,讓人直呼 666。


一、內(nèi)存本質(zhì)

編程的本質(zhì)其實就是操控數(shù)據(jù),數(shù)據(jù)存放在內(nèi)存中。

因此,如果能更好地理解內(nèi)存的模型,以及 C 如何管理內(nèi)存,就能對程序的工作原理洞若觀火,從而使編程能力更上一層樓。

大家真的別認為這是空話,我大一整年都不敢用 C 寫上千行的程序也很抗拒寫 C。

因為一旦上千行,經(jīng)常出現(xiàn)各種莫名其妙的內(nèi)存錯誤,一不小心就發(fā)生了 coredump...... 而且還無從排查,分析不出原因。

相比之下,那時候最喜歡 Java,在 Java 里隨便怎么寫都不會發(fā)生類似的異常,頂多偶爾來個?NullPointerException?,也是比較好排查的。

直到后來對內(nèi)存和指針有了更加深刻的認識,才慢慢會用 C 寫上千行的項目,也很少會再有內(nèi)存問題了。(過于自信

「指針存儲的是變量的內(nèi)存地址」這句話應該任何講 C 語言的書都會提到吧。

所以,要想徹底理解指針,首先要理解 C 語言中變量的存儲本質(zhì),也就是內(nèi)存。

1.1 內(nèi)存編址

計算機的內(nèi)存是一塊用于存儲數(shù)據(jù)的空間,由一系列連續(xù)的存儲單元組成,就像下面這樣,

每一個單元格都表示 1 個 Bit,一個 bit 在 EE 專業(yè)的同學看來就是高低電位,而在 CS 同學看來就是 0、1 兩種狀態(tài)。

由于 1 個 bit 只能表示兩個狀態(tài),所以大佬們規(guī)定 8個 bit 為一組,命名為 byte。

并且將 byte 作為內(nèi)存尋址的最小單元,也就是給每個 byte 一個編號,這個編號就叫內(nèi)存的?地址?。

這就相當于,我們給小區(qū)里的每個單元、每個住戶都分配一個門牌號: 301、302、403、404、501......

在生活中,我們需要保證門牌號唯一,這樣就能通過門牌號很精準的定位到一家人。

同樣,在計算機中,我們也要保證給每一個 byte 的編號都是唯一的,這樣才能夠保證每個編號都能訪問到唯一確定的 byte。

1.2 內(nèi)存地址空間

上面我們說給內(nèi)存中每個 byte 唯一的編號,那么這個編號的范圍就決定了計算機可尋址內(nèi)存的范圍。

所有編號連起來就叫做內(nèi)存的地址空間,這和大家平時常說的電腦是 32 位還是 64 位有關(guān)。

早期 Intel 8086、8088 的 CPU 就是只支持 16 位地址空間,?寄存器?和?地址總線?都是 16 位,這意味著最多對?2^16 = 64 Kb?的內(nèi)存編號尋址。

這點內(nèi)存空間顯然不夠用,后來,80286 在 8086 的基礎(chǔ)上將?地址總線?和?地址寄存器?擴展到了20 位,也被叫做 A20 地址總線。

當時在寫 mini os 的時候,還需要通過 BIOS 中斷去啟動 A20 地址總線的開關(guān)。

但是,現(xiàn)在的計算機一般都是 32 位起步了,32 位意味著可尋址的內(nèi)存范圍是?2^32 byte = 4GB?。

所以,如果你的電腦是 32 位的,那么你裝超過 4G 的內(nèi)存條也是無法充分利用起來的。

好了,這就是內(nèi)存和內(nèi)存編址。

1.3 變量的本質(zhì)

有了內(nèi)存,接下來我們需要考慮,int、double 這些變量是如何存儲在 0、1 單元格的。

在 C 語言中我們會這樣定義變量:

int a = 999;char c = 'c';

當你寫下一個變量定義的時候,實際上是向內(nèi)存申請了一塊空間來存放你的變量。

我們都知道 int 類型占 4 個字節(jié),并且在計算機中數(shù)字都是用補碼(不了解補碼的記得去百度)表示的。

999?換算成補碼就是:?0000 0011 1110 0111

這里有 4 個byte,所以需要四個單元格來存儲:

有沒有注意到,我們把高位的字節(jié)放在了低地址的地方。

那能不能反過來呢?

當然,這就引出了?大端和小端。

像上面這種將高位字節(jié)放在內(nèi)存低地址的方式叫做?大端

反之,將低位字節(jié)放在內(nèi)存低地址的方式就叫做?小端?:

上面只說明了 int 型的變量如何存儲在內(nèi)存,而 float、char 等類型實際上也是一樣的,都需要先轉(zhuǎn)換為補碼。

對于多字節(jié)的變量類型,還需要按照大端或者小端的格式,依次將字節(jié)寫入到內(nèi)存單元。

記住上面這兩張圖,這就是編程語言中所有變量的在內(nèi)存中的樣子,不管是 int、char、指針、數(shù)組、結(jié)構(gòu)體、對象... 都是這樣放在內(nèi)存的。

二、指針是什么東西?

2.1 變量放在哪?

上面我說,定義一個變量實際就是向計算機申請了一塊內(nèi)存來存放。

那如果我們要想知道變量到底放在哪了呢?

可以通過運算符?&?來取得變量實際的地址,這個值就是變量所占內(nèi)存塊的起始地址。

(PS: 實際上這個地址是虛擬地址,并不是真正物理內(nèi)存上的地址

我們可以把這個地址打印出來:

printf("%x", &a);

大概會是像這樣的一串數(shù)字:?0x7ffcad3b8f3c

2.2 指針本質(zhì)

上面說,我們可以通過?&?符號獲取變量的內(nèi)存地址,那獲取之后如何來表示這是一個?地址?,而不是一個普通的值呢?

也就是在 C 語言中如何表示地址這個概念呢?

對,就是指針,你可以這樣:

int *pa = &a;

pa 中存儲的就是變量?a?的地址,也叫做指向?a?的指針。

在這里我想談幾個看起來有點無聊的話題:

為什么我們需要指針?直接用變量名不行嗎?

當然可以,但是變量名是有局限的。

變量名的本質(zhì)是什么?

是變量地址的符號化,變量是為了讓我們編程時更加方便,對人友好,可計算機可不認識什么變量?a?,它只知道地址和指令。

所以當你去查看 C 語言編譯后的匯編代碼,就會發(fā)現(xiàn)變量名消失了,取而代之的是一串串抽象的地址。

你可以認為,編譯器會自動維護一個映射,將我們程序中的變量名轉(zhuǎn)換為變量所對應的地址,然后再對這個地址去進行讀寫。

也就是有這樣一個映射表存在,將變量名自動轉(zhuǎn)化為地址:

a ?| 0x7ffcad3b8f3c c ?| 0x7ffcad3b8f2c h ?| 0x7ffcad3b8f4c ....

說的好!

可是我還是不知道指針存在的必要性,那么問題來了,看下面代碼:

int func(...) { ?... };int main() { ? ?int a; ? ?func(...); };

假設(shè)我有一個需求:

要求在?func?函數(shù)里要能夠修改?main?函數(shù)里的變量?a?,這下咋整,在?main?函數(shù)里可以直接通過變量名去讀寫?a?所在內(nèi)存。

但是在?func?函數(shù)里是看不見?a?的呀。

你說可以通過?&?取地址符號,將?a?的地址傳遞進去:

int func(int address) { ?.... };int main() { ? ?int a; ? ?func(&a); };

這樣在?func?里就能獲取到?a?的地址,進行讀寫了。

理論上這是完全沒有問題的,但是問題在于:

編譯器該如何區(qū)分一個 int 里你存的到底是 int 類型的值,還是另外一個變量的地址(即指針)。

這如果完全靠我們編程人員去人腦記憶了,會引入復雜性,并且無法通過編譯器檢測一些語法錯誤。

而通過?int *?去定義一個指針變量,會非常明確:?這就是另外一個 int 型變量的地址。

編譯器也可以通過類型檢查來排除一些編譯錯誤。

這就是指針存在的必要性。

實際上任何語言都有這個需求,只不過很多語言為了安全性,給指針戴上了一層枷鎖,將指針包裝成了引用。

可能大家學習的時候都是自然而然的接受指針這個東西,但是還是希望這段啰嗦的解釋對你有一定啟發(fā)。

同時,在這里提點小問題:

既然指針的本質(zhì)都是變量的內(nèi)存首地址,即一個 int 類型的整數(shù)。

那為什么還要有各種類型呢?

比如 int 指針,float 指針,這個類型影響了指針本身存儲的信息嗎?

這個類型會在什么時候發(fā)揮作用?

2.3 解引用

上面的問題,就是為了引出指針解引用的。

pa?中存儲的是?a?變量的內(nèi)存地址,那如何通過地址去獲取?a?的值呢?

這個操作就叫做?解引用?,在 C 語言中通過運算符?*?就可以拿到一個指針所指地址的內(nèi)容了。

比如?*pa?就能獲得?a?的值。

我們說指針存儲的是變量內(nèi)存的首地址,那編譯器怎么知道該從首地址開始取多少個字節(jié)呢?

這就是指針類型發(fā)揮作用的時候,編譯器會根據(jù)指針的所指元素的類型去判斷應該取多少個字節(jié)。

如果是 int 型的指針,那么編譯器就會產(chǎn)生提取四個字節(jié)的指令,char 則只提取一個字節(jié),以此類推。

下面是指針內(nèi)存示意圖:

pa?指針首先是一個變量,它本身也占據(jù)一塊內(nèi)存,這塊內(nèi)存里存放的就是?a?變量的首地址。

當解引用的時候,就會從這個首地址連續(xù)劃出 4 個 byte,然后按照 int 類型的編碼方式解釋。

2.4 活學活用

別看這個地方很簡單,但卻是深刻理解指針的關(guān)鍵。

舉兩個例子來詳細說明:

比如:

float f = 1.0;short c = *(short*)&f;

你能解釋清楚上面過程,對于?f?變量,在內(nèi)存層面發(fā)生了什么變化嗎?

或者?c?的值是多少?1 ?

實際上,從內(nèi)存層面來說,?f?什么都沒變。

如圖:

假設(shè)這是?f?在內(nèi)存中的位模式,這個過程實際上就是把?f?的前兩個 byte 取出來然后按照 short 的方式解釋,然后賦值給?c?。

詳細過程如下:

  1. &f?取得?f?的首地址

  2. (short*)&f

上面第二步什么都沒做,這個表達式只是說 :

“噢,我認為?f?這個地址放的是一個 short 類型的變量”

最后當去解引用的時候?*(short*)&f?時,編譯器會取出前面兩個字節(jié),并且按照 short 的編碼方式去解釋,并將解釋出的值賦給?c?變量。

這個過程?f?的位模式?jīng)]有發(fā)生任何改變,變的只是解釋這些位的方式。

當然,這里最后的值肯定不是 1,至于是什么,大家可以去真正算一下。

那反過來,這樣呢?

short c = 1;float f = *(float*)&c;

如圖:

具體過程和上述一樣,但上面肯定不會報錯,這里卻不一定。

為什么?

(float*)&c?會讓我們從?c?的首地址開始取四個字節(jié),然后按照 float 的編碼方式去解釋。

但是?c?是 short 類型只占兩個字節(jié),那肯定會訪問到相鄰后面兩個字節(jié),這時候就發(fā)生了內(nèi)存訪問越界。

當然,如果只是讀,大概率是沒問題的。

但是,有時候需要向這個區(qū)域?qū)懭胄碌闹?,比如?/p>

*(float*)&c = 1.0;

那么就可能發(fā)生 coredump,也就是訪存失敗。

另外,就算是不會 coredump,這種也會破壞這塊內(nèi)存原有的值,因為很可能這是是其它變量的內(nèi)存空間,而我們?nèi)ジ采w了人家的內(nèi)容,肯定會導致隱藏的 bug。

如果你理解了上面這些內(nèi)容,那么使用指針一定會更加的自如。

2.6 看個小問題

講到這里,我們來看一個問題,這是一位群友問的,這是他的需求:

這是他寫的代碼:

他把 double 寫進文件再讀出來,然后發(fā)現(xiàn)打印的值對不上。

而關(guān)鍵的地方就在于這里:

char buffer[4]; ...printf("%f %x\n", *buffer, *buffer);

他可能認為?buffer?是一個指針(準確說是數(shù)組),對指針解引用就該拿到里面的值,而里面的值他認為是從文件讀出來的 4 個byte,也就是之前的 float 變量。

注意,這一切都是他認為的,實際上編譯器會認為:

“哦,?buffer?是 char類型的指針,那我取第一個字節(jié)出來就好了”。

然后把第一個字節(jié)的值傳遞給了 printf 函數(shù),printf 函數(shù)會發(fā)現(xiàn),?%f?要求接收的是一個 float 浮點數(shù),那就會自動把第一個字節(jié)的值轉(zhuǎn)換為一個浮點數(shù)打印出來。

這就是整個過程。

錯誤關(guān)鍵就是,這個同學誤認為,任何指針解引用都是拿到里面“我們認為的那個值”,實際上編譯器并不知道,編譯器只會傻傻的按照指針的類型去解釋。

所以這里改成:

printf("%f %x\n", *(float*)buffer, *(float*)buffer);

相當于明確的告訴編譯器:

“?buffer?指向的這個地方,我放的是一個 float,你給我按照 float 去解釋”

三、 結(jié)構(gòu)體和指針

結(jié)構(gòu)體內(nèi)包含多個成員,這些成員之間在內(nèi)存中是如何存放的呢?

比如:

struct fraction { ? ?int num; // 整數(shù)部分 ? ?int denom; // 小數(shù)部分};struct fraction fp; fp.num = 10; fp.denom = 2;

這是一個定點小數(shù)結(jié)構(gòu)體,它在內(nèi)存占 8 個字節(jié)(這里不考慮內(nèi)存對齊),兩個成員域是這樣存儲的:

我們把 10 放在了結(jié)構(gòu)體中基地址偏移為 0 的域,2 放在了偏移為 4 的域。

接下來我們做一個正常人永遠不會做的操作:

((fraction*)(&fp.denom))->num = 5; ((fraction*)(&fp.denom))->denom = 12; printf("%d\n", fp.denom); // 輸出多少?

上面這個究竟會輸出多少呢?自己先思考下噢~

接下來我分析下這個過程發(fā)生了什么:

首先,?&fp.denom?表示取結(jié)構(gòu)體 fp 中 denom 域的首地址,然后以這個地址為起始地址取 8 個字節(jié),并且將它們看做一個 fraction 結(jié)構(gòu)體。

在這個新結(jié)構(gòu)體中,最上面四個字節(jié)變成了 denom 域,而 fp 的 denom 域相當于新結(jié)構(gòu)體的 num 域。

因此:

((fraction*)(&fp.denom))->num = 5

實際上改變的是?fp.denom?,而

((fraction*)(&fp.denom))->denom = 12

則是將最上面四個字節(jié)賦值為 12。

當然,往那四字節(jié)內(nèi)存寫入值,結(jié)果是無法預測的,可能會造成程序崩潰,因為也許那里恰好存儲著函數(shù)調(diào)用棧幀的關(guān)鍵信息,也可能那里沒有寫入權(quán)限。

大家初學 C 語言的很多 coredump 錯誤都是類似原因造成的。

所以最后輸出的是 5。

為什么要講這種看起來莫名其妙的代碼?

就是為了說明結(jié)構(gòu)體的本質(zhì)其實就是一堆的變量打包放在一起,而訪問結(jié)構(gòu)體中的域,就是通過結(jié)構(gòu)體的起始地址,也叫基地址,然后加上域的偏移。

其實,C++、Java 中的對象也是這樣存儲的,無非是他們?yōu)榱藢崿F(xiàn)某些面向?qū)ο蟮奶匦?,會在?shù)據(jù)成員以外,添加一些 Head 信息,比如C++ 的虛函數(shù)表。

實際上,我們是完全可以用 C 語言去模仿的。

這就是為什么一直說 C 語言是基礎(chǔ),你真正懂了 C 指針和內(nèi)存,對于其它語言你也會很快的理解其對象模型以及內(nèi)存布局。

四、多級指針

說起多級指針這個東西,我以前大一,最多理解到 2 級,再多真的會把我繞暈,經(jīng)常也會寫錯代碼。

你要是給我寫個這個:?int ******p?能把我搞崩潰,我估計很多同學現(xiàn)在就是這種情況

其實,多級指針也沒那么復雜,就是指針的指針的指針的指針......非常簡單。

今天就帶大家認識一下多級指針的本質(zhì)。

首先,我要說一句話,沒有多級指針這種東西,指針就是指針,多級指針只是為了我們方便表達而取的邏輯概念。

首先看下生活中的快遞柜:

這種大家都用過吧,豐巢或者超市儲物柜都是這樣,每個格子都有一個編號,我們只需要拿到編號,然后就能找到對應的格子,取出里面的東西。

這里的格子就是內(nèi)存單元,編號就是地址,格子里放的東西就對應存儲在內(nèi)存中的內(nèi)容。

假設(shè)我把一本書,放在了 03 號格子,然后把 03 這個編號告訴你,你就可以根據(jù) 03 去取到里面的書。

那如果我把書放在 05 號格子,然后在 03 號格子只放一個小紙條,上面寫著:「書放在 05 號」。

你會怎么做?

當然是打開 03 號格子,然后取出了紙條,根據(jù)上面內(nèi)容去打開 05 號格子得到書。

這里的 03 號格子就叫指針,因為它里面放的是指向其它格子的小紙條(地址)而不是具體的書。

明白了嗎?

那我如果把書放在 07 號格子,然后在 05 號格子 放一個紙條:「書放在 07號」,同時在03號格子放一個紙條「書放在 05號」

這里的 03 號格子就叫二級指針,05 號格子就叫指針,而 07 號就是我們平常用的變量。

依次,可類推出 N 級指針。

所以你明白了嗎?同樣的一塊內(nèi)存,如果存放的是別的變量的地址,那么就叫指針,存放的是實際內(nèi)容,就叫變量。

int a;int *pa = &a;int **ppa = &pa;int ***pppa = &ppa;

上面這段代碼,?pa?就叫一級指針,也就是平時常說的指針,?ppa?就是二級指針。

內(nèi)存示意圖如下:

不管幾級指針有兩個最核心的東西:

  • 指針本身也是一個變量,需要內(nèi)存去存儲,指針也有自己的地址

  • 指針內(nèi)存存儲的是它所指向變量的地址

這就是我為什么多級指針是邏輯上的概念,實際上一塊內(nèi)存要么放實際內(nèi)容,要么放其它變量地址,就這么簡單。

怎么去解讀?int **a?這種表達呢?

int ** a?可以把它分為兩部分看,即?int*?和?*a?,后面?*a?中的?*?表示?a?是一個指針變量,前面的?int*?表示指針變量?a

只能存放?int*?型變量的地址。

對于二級指針甚至多級指針,我們都可以把它拆成兩部分。

首先不管是多少級的指針變量,它首先是一個指針變量,指針變量就是一個?*?,其余的?*?表示的是這個指針變量只能存放什么類型變量的地址。

比如?int****a?表示指針變量?a?只能存放?int***?型變量的地址。

五、指針與數(shù)組

5.1 一維數(shù)組

數(shù)組是 C 自帶的基本數(shù)據(jù)結(jié)構(gòu),徹底理解數(shù)組及其用法是開發(fā)高效應用程序的基礎(chǔ)。

數(shù)組和指針表示法緊密關(guān)聯(lián),在合適的上下文中可以互換。

如下:

int array[10] = {10, 9, 8, 7};printf("%d\n", *array); ?// ? ? 輸出 10printf("%d\n", array[0]); ?// 輸出 10printf("%d\n", array[1]); ?// 輸出 9printf("%d\n", *(array+1)); // 輸出 9int *pa = array;printf("%d\n", *pa); ?// ? ? 輸出 10printf("%d\n", pa[0]); ?// 輸出 10printf("%d\n", pa[1]); ?// 輸出 9printf("%d\n", *(pa+1)); // 輸出 9

在內(nèi)存中,數(shù)組是一塊連續(xù)的內(nèi)存空間:

第 0 個元素的地址稱為數(shù)組的首地址,數(shù)組名實際就是指向數(shù)組首地址,當我們通過?array[1]或者?*(array + 1)?去訪問數(shù)組元素的時候。

實際上可以看做?address[offset]?,?address?為起始地址,?offset?為偏移量,但是注意這里的偏移量?offset?不是直接和?address?相加,而是要乘以數(shù)組類型所占字節(jié)數(shù),也就是:?address + sizeof(int) * offset?。

學過匯編的同學,一定對這種方式不陌生,這是匯編中尋址方式的一種:基址變址尋址。

看完上面的代碼,很多同學可能會認為指針和數(shù)組完全一致,可以互換,這是完全錯誤的。

盡管數(shù)組名字有時候可以當做指針來用,但數(shù)組的名字不是指針。

最典型的地方就是在 sizeof:

printf("%u", sizeof(array));printf("%u", sizeof(pa));

第一個將會輸出 40,因為?array?包含有 10 個int類型的元素,而第二個在 32 位機器上將會輸出 4,也就是指針的長度。

為什么會這樣呢?

站在編譯器的角度講,變量名、數(shù)組名都是一種符號,它們都是有類型的,它們最終都要和數(shù)據(jù)綁定起來。

變量名用來指代一份數(shù)據(jù),數(shù)組名用來指代一組數(shù)據(jù)(數(shù)據(jù)集合),它們都是有類型的,以便推斷出所指代的數(shù)據(jù)的長度。

對,數(shù)組也有類型,我們可以將 int、float、char 等理解為基本類型,將數(shù)組理解為由基本類型派生得到的稍微復雜一些的類型,

數(shù)組的類型由元素的類型和數(shù)組的長度共同構(gòu)成。而?sizeof?就是根據(jù)變量的類型來計算長度的,并且計算的過程是在編譯期,而不會在程序運行時。

編譯器在編譯過程中會創(chuàng)建一張專門的表格用來保存變量名及其對應的數(shù)據(jù)類型、地址、作用域等信息。

sizeof?是一個操作符,不是函數(shù),使用?sizeof?時可以從這張表格中查詢到符號的長度。

所以,這里對數(shù)組名使用?sizeof?可以查詢到數(shù)組實際的長度。

pa?僅僅是一個指向 int 類型的指針,編譯器根本不知道它指向的是一個整數(shù),還是一堆整數(shù)。

雖然在這里它指向的是一個數(shù)組,但數(shù)組也只是一塊連續(xù)的內(nèi)存,沒有開始和結(jié)束標志,也沒有額外的信息來記錄數(shù)組到底多長。

所以對?pa?使用?sizeof?只能求得的是指針變量本身的長度。

也就是說,編譯器并沒有把?pa?和數(shù)組關(guān)聯(lián)起來,?pa?僅僅是一個指針變量,不管它指向哪里,?sizeof?求得的永遠是它本身所占用的字節(jié)數(shù)。

5.2 二維數(shù)組

大家不要認為二維數(shù)組在內(nèi)存中就是按行、列這樣二維存儲的,實際上,不管二維、三維數(shù)組... 都是編譯器的語法糖。

存儲上和一維數(shù)組沒有本質(zhì)區(qū)別,舉個例子:

int array[3][3] = {{1, 2,3}, {4, 5,6},{7, 8, 9}}; array[1][1] = 5;

或許你以為在內(nèi)存中?array?數(shù)組會像一個二維矩陣:

可實際上它是這樣的:

和一維數(shù)組沒有什么區(qū)別,都是一維線性排列。

當我們像?array[1][1]?這樣去訪問的時候,編譯器會怎么去計算我們真正所訪問元素的地址呢?

為了更加通用化,假設(shè)數(shù)組定義是這樣的:

int array[n][m]

訪問:?array[a][b]

那么被訪問元素地址的計算方式就是:?array + (m * a + b)

這個就是二維數(shù)組在內(nèi)存中的本質(zhì),其實和一維數(shù)組是一樣的,只是語法糖包裝成一個二維的樣子。

六、神奇的 void 指針

想必大家一定看到過 void 的這些用法:

void func();int func1(void);

在這些情況下,void 表達的意思就是沒有返回值或者參數(shù)為空。

但是對于 void 型指針卻表示通用指針,可以用來存放任何數(shù)據(jù)類型的引用。

下面的例子就 是一個 void 指針:

void *ptr;

void 指針最大的用處就是在 C 語言中實現(xiàn)泛型編程,因為任何指針都可以被賦給 void 指針,void 指針也可以被轉(zhuǎn)換回原來的指針類型, 并且這個過程指針實際所指向的地址并不會發(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)換,但是當你用 C 寫大型軟件或者寫一些通用庫的時候,一定離不開 void 指針,這是 C 泛型的基石,比如 std 庫里的 sort 函數(shù)申明是這樣的:

void qsort(void *base,int nelem,int width,int (*fcmp)(const void *,const void *));

所有關(guān)于具體元素類型的地方全部用 void 代替。

void 還可以用來實現(xiàn) C 語言中的多態(tài),這是一個挺好玩的東西。

不過也有需要注意的:

  • 不能對 void 指針解引用

    比如:

int num;void *pv = (void*)# *pv = 4; // 錯誤

為什么?

因為解引用的本質(zhì)就是編譯器根據(jù)指針所指的類型,然后從指針所指向的內(nèi)存連續(xù)取 N 個字節(jié),然后將這 N 個字節(jié)按照指針的類型去解釋。

比如 int *型指針,那么這里 N 就是 4,然后按照 int 的編碼方式去解釋數(shù)字。

但是 void,編譯器是不知道它到底指向的是 int、double、或者是一個結(jié)構(gòu)體,所以編譯器沒法對 void 型指針解引用。

七、花式秀技

很多同學認為 C 就只能面向過程編程,實際上利用指針,我們一樣可以在 C 中模擬出對象、繼承、多態(tài)等東西。

也可以利用 void 指針實現(xiàn)泛型編程,也就是 Java、C++ 中的模板。

大家如果對 C 實現(xiàn)面向?qū)ο?、模板、繼承這些感興趣的話,可以積極一點,點贊,留言~

學習C/C++編程知識,提升C/C++編程能力,歡迎關(guān)注UP一起來成長!
另外,UP在主頁上傳了一些學習
C/C++編程的視頻教程,有興趣或者正在學習的小伙伴一定要去看一看哦!會對你有幫助的~


C/C++編程筆記:指針篇!從內(nèi)存理解指針,讓你完全搞懂指針的評論 (共 條)

分享到微博請遵守國家法律
西林县| 两当县| 福海县| 安龙县| 邯郸市| 郑州市| 旺苍县| 屯昌县| 龙州县| 互助| 海城市| 漾濞| 和顺县| 县级市| 蒲城县| 永嘉县| 湖北省| 涿州市| 陆良县| 和政县| 哈巴河县| 平南县| 扶沟县| 惠州市| 吉隆县| 黄冈市| 甘泉县| 湛江市| 泾川县| 深泽县| 张北县| 平凉市| 泰和县| 如皋市| 潍坊市| 苍梧县| 合川市| 神木县| 正镶白旗| 乌鲁木齐县| 勃利县|