聊聊指针
指針是什么?
指針和其他的int, float等類(lèi)似, 是一種類(lèi)型. 有類(lèi)型就有相應(yīng)類(lèi)型的變量和常量. 本文主要討論變量的情況.
指針變量就是一種變量, 和其他種類(lèi)的變量類(lèi)似, 但指針和其他變量又有區(qū)別.
首先C語(yǔ)言作為一種類(lèi)型語(yǔ)言, 每個(gè)變量都會(huì)有幾個(gè)屬性.
變量名稱(chēng).
變量類(lèi)型.
變量的值.
例如int a = 3, 變量名稱(chēng)就是a, 變量類(lèi)型是int, 變量的值是3, 如果不提供初始值, 那么變量的值可能是一個(gè)隨機(jī)值.
也就是說(shuō), 任何時(shí)候看到一個(gè)變量, 就會(huì)有這3個(gè)屬性.
對(duì)于指針變量, 可以認(rèn)為有4個(gè)屬性.
指針變量的名稱(chēng).
指針變量的類(lèi)型, 即指針類(lèi)型.
指針變量的值, 即一個(gè)地址.
指針變量的值所指向的內(nèi)存里的數(shù)據(jù)類(lèi)型. 本文稱(chēng)做"指向類(lèi)型".
可以看到指針變量的關(guān)鍵在于指針?biāo)赶虻膬?nèi)存里面數(shù)據(jù)的類(lèi)型.
例如int a = 3; int *b = &a;
指針變量名稱(chēng)是b, 指針變量類(lèi)型是指針, 變量b的值是變量a的內(nèi)存地址. 變量b所指向的內(nèi)存的數(shù)據(jù)類(lèi)型是int.?
指針變量多了一個(gè)"變量b所指向的內(nèi)存的數(shù)據(jù)類(lèi)型是int”, 本文將指針變量所指向的內(nèi)存的數(shù)據(jù)類(lèi)型稱(chēng)做指向類(lèi)型.
任何時(shí)候看到一個(gè)指針就需要關(guān)注4點(diǎn)內(nèi)容: 名稱(chēng), 指針類(lèi)型, 指針值, 指向類(lèi)型. 搞清楚這幾個(gè)內(nèi)容, 就可以弄明白指針怎么回事, 當(dāng)然還要記憶 一些例外的情形.
類(lèi)型
對(duì)于C語(yǔ)言來(lái)說(shuō), 搞清楚變量的類(lèi)型相當(dāng)重要, 涉及到指針的時(shí)候就更加重要. 看到一個(gè)指針變量后需要理解其指向類(lèi)型.
例如char * const * (*next)(), next是一個(gè)指針, 那么其指向類(lèi)型是什么? 這個(gè)聲明/定義比較復(fù)雜, 日常編程可能就會(huì)碰到比較 復(fù)雜的情況, 所以要搞清楚指針首先要懂得怎么看一個(gè)聲明/定義的變量的類(lèi)型.
如果看到一個(gè)變量的聲明或者定義, 那么就需要弄明白變量的類(lèi)型. 在<<c專(zhuān)家編程>>這本書(shū)中有一部分內(nèi)容專(zhuān)門(mén)講解怎么分析 一個(gè)變量的類(lèi)型, 值得參考.
理解類(lèi)型的規(guī)則
從變量名稱(chēng)開(kāi)始讀取, 然后依照優(yōu)先級(jí)按順序處理.
優(yōu)先級(jí)從高到低 a. 括號(hào)內(nèi)優(yōu)先級(jí)高. b. 后綴操作符, ()表示一個(gè)函數(shù), []表示一個(gè)數(shù)組. c. 前綴操作符,?*表示"指向...的指針"
如果const, volatile后面為類(lèi)型(int, long等), 那么作用于類(lèi)型, 其他情況下作用于const, volatile左邊的指針*.
按照上面的規(guī)則來(lái)理解next的類(lèi)型
括號(hào)內(nèi)的優(yōu)先級(jí)最高, 即首先看(*next)
next左邊為*, 因此next是一個(gè)指針類(lèi)型
然后后綴()的優(yōu)先級(jí)更高, 因此next是一個(gè)指針, 指向一個(gè)函數(shù).
接著是const右邊的*, 表示next是一個(gè)指針, 指向一個(gè)函數(shù), 該函數(shù)返回值類(lèi)型為一個(gè)指針.
char * const看作一個(gè)整體為指向字符的常量指針.
整個(gè)來(lái)說(shuō): next是一個(gè)指針, 指向一個(gè)函數(shù), 函數(shù)的返回值也是一個(gè)指針, 指向一個(gè)類(lèi)型為char的常量指針.
更詳細(xì)的可以參考<<c專(zhuān)家編程>>
類(lèi)型有什么用?
C語(yǔ)言為類(lèi)型語(yǔ)言, 即每個(gè)變量都有類(lèi)型. 類(lèi)型在變量的賦值, 函數(shù)傳參, 編譯檢查等等方面都會(huì)用到.
類(lèi)型可以確定數(shù)據(jù)的大小和操作.
例如int a = 3, 那么在內(nèi)存中會(huì)存儲(chǔ)一個(gè)數(shù)據(jù)3, 那么對(duì)于int類(lèi)型具體來(lái)說(shuō).
這個(gè)數(shù)據(jù)3會(huì)占用4字節(jié)(常見(jiàn)32位機(jī)器與64位機(jī)器上int類(lèi)型占用4字節(jié)). 實(shí)際上是有4字節(jié)的內(nèi)存, 內(nèi)容是0×00000003. 因此int類(lèi)型就規(guī)定了占用的內(nèi)存大小.
對(duì)于int類(lèi)型就可以進(jìn)行+,-,*,/等操作, 但是不能進(jìn)行取指針值(*a)的操作. 能夠進(jìn)行什么操作, 也是由類(lèi)型規(guī)定的.
那么對(duì)于指針來(lái)說(shuō), 其指向類(lèi)型就非常重要, 指向類(lèi)型就規(guī)定了指針的值所指向的內(nèi)存的數(shù)據(jù)是什么類(lèi)型, 也就是占用多大內(nèi)存, 可以進(jìn)行什么操作.
sizeof
只要類(lèi)型確定, 那么便可以用sizeof計(jì)算類(lèi)型占用的內(nèi)存大小, 這個(gè)是編譯階段便可以確定的.
對(duì)于指針類(lèi)型來(lái)說(shuō), 所有指針類(lèi)型占用的內(nèi)存大小基本都是一樣的, 例如在32bit的機(jī)器上占用4字節(jié), 在64bit的機(jī)器上占用8字節(jié).
下面代碼的變量a和變量b都是指針類(lèi)型, 但是指向類(lèi)型不同. 因此sizeof(a)和sizeof(b)的值相等, 但是sizeof(*a)和sizeof(*b)不相等.
int *a; double *b;sizeof(a) == sizeof(b); sizeof(*a) != sizeof(*b);指針類(lèi)型的操作
可以對(duì)指針變量進(jìn)行+操作.
double a[3] = {1, 2, 3}; double *b = a;printf("b: %p, content: %f\n", b, *b); printf("b+1: %p, content: %f\n", b+1, *(b+1));int c[3] = {1, 2, 3}; int *d = d;printf("d: %p, content: %d\n", d, *d); printf("d+1: %p, content: %d\n", d+1, *(d+1));運(yùn)行結(jié)果
b: 0x7fff5f9ec7e0, content: 1.000000 b+1: 0x7fff5f9ec7e8, content: 2.000000 d: 0x7fff5f9ec7d0, content: 1 d+1: 0x7fff5f9ec7d4, content: 2可以看到b+1的值比b要大8. d+1的值比b要大4. b+1實(shí)際上是指向a[1]的內(nèi)存地址. d+1是指向c[1]的內(nèi)存地址.
有如下公式成立, 指針做加法后的指針變量值和指向類(lèi)型占用的內(nèi)存大小相關(guān).
指針變量 + 數(shù)字 = 指針變量值 + 數(shù)字 * sizeof(指向類(lèi)型)
可以看到指向類(lèi)型除了告訴你指針指向的內(nèi)存里面的數(shù)據(jù)類(lèi)型, 在指針變量的相關(guān)運(yùn)算上也是有用的.
數(shù)組類(lèi)型
數(shù)組與指針有一定的相似, 同時(shí)又很不一樣.
數(shù)組與指針的關(guān)鍵區(qū)別在于數(shù)組名是一個(gè)常量(和const常量不同). const常量表示變量的內(nèi)容不會(huì)變化, 實(shí)際上還是一個(gè)變量. 這里所說(shuō)的數(shù)組名為一個(gè)常量, 可以理解數(shù)組名稱(chēng)是一個(gè)內(nèi)存地址值, 例如0×7fff5f9ec7d4.
以下面的例子來(lái)說(shuō), a本身不會(huì)占用內(nèi)存, 占用內(nèi)存的是a[0], a[1], a[2], 實(shí)際上a所表示的這塊內(nèi)存才是數(shù)組變量.
int a[10] = {0}; int *b = a; int (*d)[10]= &a; int c;c = a[1]; c = b[1];那么a[1]和b[1]的區(qū)別就在于數(shù)組是一個(gè)常量, 而不是變量(變量本身需要占用內(nèi)存).
執(zhí)行c = a[1]是直接從a表示的內(nèi)存地址偏移4字節(jié)的內(nèi)存中取數(shù)據(jù). 僅包含一次內(nèi)存讀操作.
執(zhí)行c = b[1]是首先從內(nèi)存中取出變量b的值, 然后將變量b的值偏移4字節(jié), 然后從這個(gè)地址的內(nèi)存中取數(shù)據(jù). 包含2次內(nèi)存讀操作. 第一次是讀取變量b的值.
數(shù)組的其他幾個(gè)需要注意的地方。
數(shù)組名稱(chēng)相當(dāng)于地址常量, 那么這個(gè)地址指向一段內(nèi)存, 因此這個(gè)地址本身會(huì)有指向類(lèi)型, 其指向類(lèi)型就是數(shù)組的元素類(lèi)型. 例如int a[10], 那么a的指向類(lèi)型就是int, 因此a+1結(jié)果實(shí)際上指向a[1].
sizeof(a)是計(jì)算整個(gè)數(shù)組的類(lèi)型.?sizeof(*a)是計(jì)算其指向類(lèi)型的大小.
可以對(duì)數(shù)組名進(jìn)行&a操作(取地址), 實(shí)際上&a的指針值和a的指針值一樣, 而且也是個(gè)地址常量, 但是&a的指向類(lèi)型 是int [10], 即指向一個(gè)包含10個(gè)int元素的數(shù)組, 所以sizeof(*&a), 計(jì)算&a的指向類(lèi)型的占用內(nèi)存大小就是40.
數(shù)組作為函數(shù)參數(shù)傳遞后, 在函數(shù)內(nèi)使用等價(jià)于指針. 因?yàn)楹瘮?shù)傳參是進(jìn)行值傳遞, 相當(dāng)于有一個(gè)指針變量記錄數(shù)組的地址值.
可以看到有的時(shí)候a看作一個(gè)數(shù)組(例如sizeof(a)是計(jì)算數(shù)組的內(nèi)存占用), 有時(shí)候a看作一個(gè)地址常量(例如計(jì)算sizeof(*a)和a+1的時(shí)候).?
還有的時(shí)候完全是比較特殊的使用(例如&a得到的指向類(lèi)型為int [10]的地址常量).
函數(shù)指針
函數(shù)名本身也是一個(gè)地址常量, 其指向類(lèi)型為一個(gè)函數(shù). 實(shí)際指向的是函數(shù)在內(nèi)存中的指令集合的起始位置.
int foo(int a) {return a; }int (*p_foo)(int a) = foo;printf("%d, %d, %d\n", sizeof(foo), sizeof(*foo), sizeof(&foo)); printf("%d, %d\n", sizeof(p_foo), sizeof(*p_foo));輸出值如下:
1, 1, 8 8, 1對(duì)函數(shù)名本身計(jì)算類(lèi)型占用內(nèi)存大小, 其值為1, 對(duì)于函數(shù)名的指向類(lèi)型計(jì)算內(nèi)存占用大小其值也為1.
foo, *foo, &foo的類(lèi)型相同, 但是sizeof(&foo)結(jié)果為8.
函數(shù)指針可以進(jìn)行多次解引用,?*****p_foo == *p_foo = p_foo.
函數(shù)指針可以進(jìn)行調(diào)用,?p_foo(3);
以上幾點(diǎn)可以認(rèn)為是函數(shù)的特殊情形, 直接記憶.
可以將函數(shù)的指令看作是一個(gè)unsigned char []的數(shù)組. 這樣函數(shù)名就好像是一個(gè)數(shù)組名一樣, 都是地址常量, 其指向類(lèi)型為unsigned char類(lèi)型. 但是函數(shù)指令的數(shù)組的長(zhǎng)度是未知的, 因此編譯器默認(rèn)輸出sizeof(foo)為1, sizeof(*foo)相當(dāng)于是sizeof(unsigned char)為1.
強(qiáng)制類(lèi)型轉(zhuǎn)換
很多時(shí)候涉及到指針和強(qiáng)制類(lèi)型轉(zhuǎn)換就會(huì)感覺(jué)比較麻煩, 實(shí)際上只要抓住類(lèi)型這個(gè)關(guān)鍵點(diǎn)也可以很簡(jiǎn)單.
強(qiáng)制類(lèi)型轉(zhuǎn)換的關(guān)鍵是一段內(nèi)存, 這段內(nèi)存里面的數(shù)據(jù)你把它當(dāng)作什么類(lèi)型來(lái)看待.
double a = 23.456; int *b = (int *) &a;那么變量a有一段內(nèi)存(8個(gè)字節(jié)), 里面存儲(chǔ)了23.456(按標(biāo)準(zhǔn)浮點(diǎn)格式存儲(chǔ)). 然后指針b指向這段內(nèi)存, 而且指針b的指向類(lèi)型是int, 因此指針b認(rèn)為這段內(nèi)存里面存儲(chǔ)的是一個(gè)int類(lèi)型的數(shù)據(jù).
小結(jié)
本文內(nèi)容不算全面, 但關(guān)鍵點(diǎn)都有.
每次看到指針的時(shí)候, 記住4個(gè)特征, 不管如何進(jìn)行類(lèi)型轉(zhuǎn)換, 多少級(jí)指針, 是否包含函數(shù)指針等, 看到指針 就思考下面4點(diǎn), 尤其是3,4. 練習(xí)多了之后就會(huì)發(fā)現(xiàn)指針本身不是很難, 難的是怎么判斷數(shù)據(jù)的類(lèi)型.
指針名稱(chēng)
指針類(lèi)型
指針內(nèi)容: 指向什么地方.
指向類(lèi)型: 指向內(nèi)存的數(shù)據(jù)類(lèi)型是什么.
-END-
猜你喜歡
看完這篇文章,還不會(huì)做平衡小車(chē),你來(lái)打我。
為什么在中國(guó)電子工程師不如搞軟件的?
博士學(xué)歷真的很重要嗎?
?最 后??
?若覺(jué)得文章不錯(cuò),轉(zhuǎn)發(fā)分享,也是我們繼續(xù)更新的動(dòng)力。
5T資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,PCB、FPGA、DSP、labview、單片機(jī)、等等!
在公眾號(hào)內(nèi)回復(fù)「更多資源」,即可免費(fèi)獲取,期待你的關(guān)注~
長(zhǎng)按識(shí)別圖中二維碼關(guān)注
總結(jié)
- 上一篇: 曝iQOO Neo8系列将搭载骁龙8+/
- 下一篇: 再论PID,PID其实很简单。。。