0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

10分鐘掌握C語(yǔ)言指針

璟琰乀 ? 來(lái)源:嵌入式資訊精選 ? 作者:嵌入式資訊精選 ? 2020-11-04 16:37 ? 次閱讀

說(shuō)到指針,估計(jì)還是有很多小伙伴都還是云里霧里的,有點(diǎn)“知其然,而不知其所以然”。但是,不得不說(shuō),學(xué)了指針,C語(yǔ)言才能算是入門(mén)了。指針是C語(yǔ)言的「精華」,可以說(shuō),對(duì)對(duì)指針的掌握程度,「直接決定」了你C語(yǔ)言的編程能力。

在講指針之前,我們先來(lái)了解下變量在「內(nèi)存」中是如何存放的。

在程序中定義一個(gè)變量,那么在程序編譯的過(guò)程中,系統(tǒng)會(huì)根據(jù)你定義變量的類(lèi)型來(lái)分配「相應(yīng)尺寸」的內(nèi)存空間。那么如果要使用這個(gè)變量,只需要用變量名去訪問(wèn)即可。

通過(guò)變量名來(lái)訪問(wèn)變量,是一種「相對(duì)安全」的方式。因?yàn)橹挥心愣x了它,你才能夠訪問(wèn)相應(yīng)的變量。這就是對(duì)內(nèi)存的基本認(rèn)知。但是,如果光知道這一點(diǎn)的話(huà),其實(shí)你還是不知道內(nèi)存是如何存放變量的,因?yàn)榈讓邮侨绾喂ぷ鞯?,你依舊不清楚。

那么如果要繼續(xù)深究的話(huà),你就需要把變量在內(nèi)存中真正的樣子是什么搞清楚。內(nèi)存的最小索引單元是1字節(jié),那么你其實(shí)可以把內(nèi)存比作一個(gè)超級(jí)大的「字符型數(shù)組」。在上一節(jié)我們講過(guò),數(shù)組是有下標(biāo)的,我們是通過(guò)數(shù)組名和下標(biāo)來(lái)訪問(wèn)數(shù)組中的元素。那么內(nèi)存也是一樣,只不過(guò)我們給它起了個(gè)新名字:地址。每個(gè)地址可以存放「1字節(jié)」的數(shù)據(jù),所以如果我們需要定義一個(gè)整型變量,就需要占據(jù)4個(gè)內(nèi)存單元。

那么,看到這里你可能就明白了:其實(shí)在程序運(yùn)行的過(guò)程中,完全不需要變量名的參與。變量名只是方便我們進(jìn)行代碼的編寫(xiě)和閱讀,只有程序員和編譯器知道這個(gè)東西的存在。而編譯器還知道具體的變量名對(duì)應(yīng)的「內(nèi)存地址」,這個(gè)是我們不知道的,因此編譯器就像一個(gè)橋梁。當(dāng)讀取某一個(gè)變量的時(shí)候,編譯器就會(huì)找到變量名所對(duì)應(yīng)的地址,讀取對(duì)應(yīng)的值。

初識(shí)指針和指針變量那么我們現(xiàn)在就來(lái)切入正題,指針是個(gè)什么東西呢?

所謂指針,就是內(nèi)存地址(下文簡(jiǎn)稱(chēng)地址)。C語(yǔ)言中設(shè)立了專(zhuān)門(mén)的「指針變量」來(lái)存儲(chǔ)指針,和「普通變量」不一樣的是,指針變量存儲(chǔ)的是「地址」。

定義指針

指針變量也有類(lèi)型,實(shí)際上取決于地址指向的值的類(lèi)型。那么如何定義指針變量呢:

很簡(jiǎn)單:類(lèi)型名* 指針變量名char* pa;//定義一個(gè)字符變量的指針,名稱(chēng)為paint* pb;//定義一個(gè)整型變量的指針,名稱(chēng)為pbfloat* pc;//定義一個(gè)浮點(diǎn)型變量的指針,名稱(chēng)為pc

注意,指針變量一定要和指向的變量的類(lèi)型一樣,不然類(lèi)型不同可能在內(nèi)存中所占的位置不同,如果定義錯(cuò)了就可能導(dǎo)致出錯(cuò)。

取地址運(yùn)算符和取值運(yùn)算符

獲取某個(gè)變量的地址,使用取地址運(yùn)算符&,如:

char* pa = &a;int* pb = &f;

如果反過(guò)來(lái),你要訪問(wèn)指針變量指向的數(shù)據(jù),那么你就要使用取值運(yùn)算符*,如:

printf(“%c, %d\n”, *pa, *pb);

這里你可能發(fā)現(xiàn),定義指針的時(shí)候也使用了*,這里屬于符號(hào)的「重用」,也就是說(shuō)這種符號(hào)在不同的地方就有不同的用意:在定義的時(shí)候表示「定義一個(gè)指針變量」,在其他的時(shí)候則用來(lái)「獲取指針變量指向的變量的值」。

直接通過(guò)變量名來(lái)訪問(wèn)變量的值稱(chēng)之為直接訪問(wèn),通過(guò)指針這樣的形式訪問(wèn)稱(chēng)之為間接訪問(wèn),因此取值運(yùn)算符有時(shí)候也成為「間接運(yùn)算符」。

比如:

//Example 01//代碼來(lái)源于網(wǎng)絡(luò),非個(gè)人原創(chuàng)#include 《stdio.h》int main(void){ char a = ‘f’; int f = 123; char* pa = &a; int* pf = &f; printf(“a = %c\n”, *pa); printf(“f = %d\n”, *pf); *pa = ‘c’; *pf += 1; printf(“now, a = %c\n”, *pa); printf(“now, f = %d\n”, *pf); printf(“sizeof pa = %d\n”, sizeof(pa)); printf(“sizeof pf = %d\n”, sizeof(pf)); printf(“the addr of a is: %p\n”, pa); printf(“the addr of f is: %p\n”, pf); return 0;}

程序?qū)崿F(xiàn)如下:

//Consequence 01a = ff = 123now, a = cnow, f = 124sizeof pa = 4sizeof pf = 4the addr of a is: 00EFF97Fthe addr of f is: 00EFF970

避免訪問(wèn)未初始化的指針

void f(){ int* a; *a = 10;}

像這樣的代碼是十分危險(xiǎn)的。因?yàn)橹羔榓到底指向哪里,我們不知道。就和訪問(wèn)未初始化的普通變量一樣,會(huì)返回一個(gè)「隨機(jī)值」。但是如果是在指針里面,那么就有可能覆蓋到「其他的內(nèi)存區(qū)域」,甚至可能是系統(tǒng)正在使用的「關(guān)鍵區(qū)域」,十分危險(xiǎn)。不過(guò)這種情況,系統(tǒng)一般會(huì)駁回程序的運(yùn)行,此時(shí)程序會(huì)被「中止」并「報(bào)錯(cuò)」。要是萬(wàn)一中獎(jiǎng)的話(huà),覆蓋到一個(gè)合法的地址,那么接下來(lái)的賦值就會(huì)導(dǎo)致一些有用的數(shù)據(jù)被「莫名其妙地修改」,這樣的bug是十分不好排查的,因此使用指針的時(shí)候一定要注意初始化。

指針和數(shù)組有些讀者可能會(huì)有些奇怪,指針和數(shù)組又有什么關(guān)系?這倆貨明明八竿子打不著井水不犯河水。別著急,接著往下看,你的觀點(diǎn)有可能會(huì)改變。

數(shù)組的地址

我們剛剛說(shuō)了,指針實(shí)際上就是變量在「內(nèi)存中的地址」,那么如果有細(xì)心的小伙伴就可能會(huì)想到,像數(shù)組這樣的一大摞變量的集合,它的地址是啥呢?

我們知道,從標(biāo)準(zhǔn)輸入流中讀取一個(gè)值到變量中,用的是scanf函數(shù),一般貌似在后面都要加上&,這個(gè)其實(shí)就是我們剛剛說(shuō)的「取地址運(yùn)算符」。如果你存儲(chǔ)的位置是指針變量的話(huà),那就不需要。

//Example 02int main(void){ int a; int* p = &a; printf(“請(qǐng)輸入一個(gè)整數(shù):”); scanf(“%d”, &a);//此處需要& printf(“a = %d\n”, a); printf(“請(qǐng)?jiān)佥斎胍粋€(gè)整數(shù):”); scanf(“%d”, p);//此處不需要& printf(“a = %d\n”, a); return 0;}

程序運(yùn)行如下:

//Consequence 02請(qǐng)輸入一個(gè)整數(shù):1a = 1請(qǐng)?jiān)佥斎胍粋€(gè)整數(shù):2a = 2

在普通變量讀取的時(shí)候,程序需要知道這個(gè)變量在內(nèi)存中的地址,因此需要&來(lái)取地址完成這個(gè)任務(wù)。而對(duì)于指針變量來(lái)說(shuō),本身就是「另外一個(gè)」普通變量的「地址信息」,因此直接給出指針的值就可以了。

試想一下,我們?cè)谑褂胹canf函數(shù)的時(shí)候,是不是也有不需要使用&的時(shí)候?就是在讀取「字符串」的時(shí)候:

//Example 03#include 《stdio.h》int main(void){ char url[100]; url[99] = ‘\0’; printf(“請(qǐng)輸入TechZone的域名:”); scanf(“%s”, url);//此處也不用& printf(“你輸入的域名是:%s\n”, url); return 0;}

程序執(zhí)行如下:

//Consequence 03請(qǐng)輸入TechZone的域名:www.techzone.ltd你輸入的域名是:www.techzone.ltd

因此很好推理:數(shù)組名其實(shí)就是一個(gè)「地址信息」,實(shí)際上就是數(shù)組「第一個(gè)元素的地址」。咱們?cè)囋嚢训谝粋€(gè)元素的地址和數(shù)組的地址做個(gè)對(duì)比就知道了:

//Example 03 V2#include 《stdio.h》int main(void){ char url[100]; printf(“請(qǐng)輸入TechZone的域名:”); url[99] = ‘\0’; scanf(“%s”, url); printf(“你輸入的域名是:%s\n”, url); printf(“url的地址為:%p\n”, url); printf(“url[0]的地址為:%p\n”, &url[0]); if (url == &url[0]) { printf(“兩者一致!”); } else { printf(“兩者不一致!”); } return 0;}

程序運(yùn)行結(jié)果為:

//Comsequense 03 V2請(qǐng)輸入TechZone的域名:www.techzone.ltd你輸入的域名是:www.techzone.ltdurl的地址為:0063F804url[0]的地址為:0063F804兩者一致!

這么看,應(yīng)該是實(shí)錘了。那么數(shù)組后面的元素也就是依次往后放置,有興趣的也可以自己寫(xiě)代碼嘗試把它們輸出看看。

指向數(shù)組的指針

剛剛我們驗(yàn)證了數(shù)組的地址就是數(shù)組第一個(gè)元素的地址。那么指向數(shù)組的指針自然也就有兩種定義的方法:

。。.char* p;//方法1p = a;//方法2p = &a[0];

指針的運(yùn)算

當(dāng)指針指向數(shù)組元素的時(shí)候,可以對(duì)指針變量進(jìn)行「加減」運(yùn)算,+n表示指向p指針?biāo)赶虻脑氐摹赶耼個(gè)元素」,-n表示指向p指針?biāo)赶虻脑氐摹干蟦個(gè)元素」。并不是將地址加1。

如:

//Example 04#include 《stdio.h》int main(void){ int a[] = { 1,2,3,4,5 }; int* p = a; printf(“*p = %d, *(p+1) = %d, *(p+2) = %d\n”, *p, *(p + 1), *(p + 2)); printf(“*p -》 %p, *(p+1) -》 %p, *(p+2) -》 %p\n”, p, p + 1, p + 2); return 0;}

執(zhí)行結(jié)果如下:

//Consequence 04*p = 1, *(p+1) = 2, *(p+2) = 3*p -》 00AFF838, *(p+1) -》 00AFF83C, *(p+2) -》 00AFF840

有的小伙伴可能會(huì)想,編譯器是怎么知道訪問(wèn)下一個(gè)元素而不是地址直接加1呢?

其實(shí)就在我們定義指針變量的時(shí)候,就已經(jīng)告訴編譯器了。如果我們定義的是整型數(shù)組的指針,那么指針加1,實(shí)際上就是加上一個(gè)sizeof(int)的距離。相對(duì)于標(biāo)準(zhǔn)的下標(biāo)訪問(wèn),使用指針來(lái)間接訪問(wèn)數(shù)組元素的方法叫做指針?lè)ā?/p>

其實(shí)使用指針?lè)▉?lái)訪問(wèn)數(shù)組的元素,不一定需要定義一個(gè)指向數(shù)組的單獨(dú)的指針變量,因?yàn)閿?shù)組名自身就是指向數(shù)組「第一個(gè)元素」的指針,因此指針?lè)梢灾苯幼饔糜跀?shù)組名:

。。.printf(“p -》 %p, p+1 -》 %p, p+2 -》 %p\n”, a, a+1, a+2);printf(“a = %d, a+1 = %d, a+2 = %d”, *a, *(a+1), *(a+2));。。.

執(zhí)行結(jié)果如下:

p -》 00AFF838, p+1 -》 00AFF83C, p+2 -》 00AFF840b = 1, b+1 = 2, b+2 = 3

現(xiàn)在你是不是感覺(jué),數(shù)組和指針有點(diǎn)像了呢?不過(guò)筆者先提醒,數(shù)組和指針雖然非常像,但是絕對(duì)「不是」一種東西。

甚至你還可以直接用指針來(lái)定義字符串,然后用下標(biāo)法來(lái)讀取每一個(gè)元素:

//Example 05//代碼來(lái)源于網(wǎng)絡(luò)#include 《stdio.h》#include 《string.h》int main(void){ char* str = “I love TechZone!”; int i, length; length = strlen(str); for (i = 0; i 《 length, i++) { printf(“%c”, str[i]); } printf(“\n”); return 0;}

程序運(yùn)行如下:

//Consequence 05I love TechZone!

在剛剛的代碼里面,我們定義了一個(gè)「字符指針」變量,并且初始化成指向一個(gè)字符串。后來(lái)的操作,不僅在它身上可以使用「字符串處理函數(shù)」,還可以用「下標(biāo)法」訪問(wèn)字符串中的每一個(gè)字符。

當(dāng)然,循環(huán)部分這樣寫(xiě)也是沒(méi)毛病的:

。。.for (i = 0, i 《 length, i++){ printf(“%c”, *(str + i));}

這就相當(dāng)于利用了指針?lè)▉?lái)讀取。

指針和數(shù)組的區(qū)別

剛剛說(shuō)了許多指針和數(shù)組相互替換的例子,可能有的小伙伴又開(kāi)始說(shuō):“這倆貨不就是一個(gè)東西嗎?”

隨著你對(duì)指針和數(shù)組越來(lái)越了解,你會(huì)發(fā)現(xiàn),C語(yǔ)言的創(chuàng)始人不會(huì)這么無(wú)聊去創(chuàng)建兩種一樣的東西,還叫上不同的名字。指針和數(shù)組終究是「不一樣」的。

比如筆者之前看過(guò)的一個(gè)例子:

//Example 06//代碼來(lái)源于網(wǎng)絡(luò)#include 《stdio.h》int main(void){ char str[] = “I love TechZone!”; int count = 0; while (*str++ != ‘\0’) { count++; } printf(“總共有%d個(gè)字符。\n”, count); return 0;}

當(dāng)編譯器報(bào)錯(cuò)的時(shí)候,你可能會(huì)開(kāi)始懷疑你學(xué)了假的C語(yǔ)言語(yǔ)法:

//Error in Example 06錯(cuò)誤(活動(dòng)) E0137 表達(dá)式必須是可修改的左值錯(cuò)誤 C2105 “++”需要左值

我們知道,*str++ != ‘\0’是一個(gè)復(fù)合表達(dá)式,那么就要遵循「運(yùn)算符優(yōu)先級(jí)」來(lái)看。具體可以回顧《C語(yǔ)言運(yùn)算符優(yōu)先級(jí)及ASCII對(duì)照表》。

str++比*str的優(yōu)先級(jí)「更高」,但是自增運(yùn)算符要在「下一條語(yǔ)句」的時(shí)候才能生效。所以這個(gè)語(yǔ)句的理解就是,先取出str所指向的值,判斷是否為\0,若是,則跳出循環(huán),然后str指向下一個(gè)字符的位置。

看上去貌似沒(méi)啥毛病,但是,看看編譯器告訴我們的東西:表達(dá)式必須是可修改的左值++的操作對(duì)象是str,那么str到底是不是「左值」呢?

如果是左值的話(huà),那么就必須滿(mǎn)足左值的條件。

擁有用于識(shí)別和定位一個(gè)存儲(chǔ)位置的標(biāo)識(shí)符

存儲(chǔ)值可修改

第一點(diǎn),數(shù)組名str是可以滿(mǎn)足的,因?yàn)閿?shù)組名實(shí)際上就是定位數(shù)組第一個(gè)元素的位置。但是第二點(diǎn)就不滿(mǎn)足了,數(shù)組名實(shí)際上是一個(gè)地址,地址是「不可以」修改的,它是一個(gè)常量。如果非要利用上面的思路來(lái)實(shí)現(xiàn)的話(huà),可以將代碼改成這樣:

//Example 06 V2//代碼來(lái)源于網(wǎng)絡(luò)#include 《stdio.h》int main(void){ char str[] = “I love TechZone!”; char* target = str; int count = 0; while (*target++ != ‘\0’) { count++; } printf(“總共有%d個(gè)字符。\n”, count); return 0;}

這樣就可以正常執(zhí)行了:

//Consequence 06 V2總共有16個(gè)字符。

這樣我們就可以得出:數(shù)組名只是一個(gè)「地址」,而指針是一個(gè)「左值」。

指針數(shù)組?數(shù)組指針?

看下面的例子,你能分辨出哪個(gè)是指針數(shù)組,哪個(gè)是數(shù)組指針嗎?

int* p1[5];int(*p2)[5];

單個(gè)的我們都可以判斷,但是組合起來(lái)就有些難度了。

答案:

int* p1[5];//指針數(shù)組int(*p2)[5];//數(shù)組指針

我們挨個(gè)來(lái)分析。

指針數(shù)組

數(shù)組下標(biāo)[]的優(yōu)先級(jí)是最高的,因此p1是一個(gè)有5個(gè)元素的「數(shù)組」。那么這個(gè)數(shù)組的類(lèi)型是什么呢?答案就是int*,是「指向整型變量的指針」。因此這是一個(gè)「指針數(shù)組」。

那么這樣的數(shù)組應(yīng)該怎么樣去初始化呢?

你可以定義5個(gè)變量,然后挨個(gè)取地址來(lái)初始化。

不過(guò)這樣太繁瑣了,但是,并不是說(shuō)指針數(shù)組就沒(méi)什么用。

比如:

//Example 07#include 《stdio.h》int main(void){ char* p1[5] = { “人生苦短,我用Python?!?, “PHP是世界上最好的語(yǔ)言!”, “One more thing.。?!?, “一個(gè)好的程序員應(yīng)該是那種過(guò)單行線(xiàn)都要往兩邊看的人。”, “C語(yǔ)言很容易讓你犯錯(cuò)誤;C++看起來(lái)好一些,但當(dāng)你用它時(shí),你會(huì)發(fā)現(xiàn)會(huì)死的更慘?!?}; int i; for (i = 0; i 《 5; i++) { printf(“%s\n”, p1[i]); } return 0;}

結(jié)果如下:

//Consequence 07人生苦短,我用Python。PHP是世界上最好的語(yǔ)言!One more thing.。。一個(gè)好的程序員應(yīng)該是那種過(guò)單行線(xiàn)都要往兩邊看的人。C語(yǔ)言很容易讓你犯錯(cuò)誤;C++看起來(lái)好一些,但當(dāng)你用它時(shí),你會(huì)發(fā)現(xiàn)會(huì)死的更慘。

這樣是不是比二維數(shù)組來(lái)的更加直接更加通俗呢?

數(shù)組指針

()和[]在優(yōu)先級(jí)里面屬于「同級(jí)」,那么就按照「先后順序」進(jìn)行。

int(*p2)將p2定義為「指針」, 后面跟隨著一個(gè)5個(gè)元素的「數(shù)組」,p2就指向這個(gè)數(shù)組。因此,數(shù)組指針是一個(gè)「指針」,它指向的是一個(gè)數(shù)組。

但是,如果想對(duì)數(shù)組指針初始化的時(shí)候,千萬(wàn)要小心,比如:

//Example 08#include 《stdio.h》int main(void){ int(*p2)[5] = {1, 2, 3, 4, 5}; int i; for (i = 0; i 《 5; i++) { printf(“%d\n”, *(p2 + i)); } return 0;}

Visual Studio 2019報(bào)出以下的錯(cuò)誤:

//Error and Warning in Example 08錯(cuò)誤(活動(dòng)) E0146 初始值設(shè)定項(xiàng)值太多錯(cuò)誤 C2440 “初始化”: 無(wú)法從“initializer list”轉(zhuǎn)換為“int (*)[5]”警告 C4477 “printf”: 格式字符串“%d”需要類(lèi)型“int”的參數(shù),但可變參數(shù) 1 擁有了類(lèi)型“int *”

這其實(shí)是一個(gè)非常典型的錯(cuò)誤使用指針的案例,編譯器提示說(shuō)這里有一個(gè)「整數(shù)」賦值給「指針變量」的問(wèn)題,因?yàn)閜2歸根結(jié)底還是指針,所以應(yīng)該給它傳遞一個(gè)「地址」才行,更改一下:

//Example 08 V2#include 《stdio.h》int main(void){ int temp[5] = {1, 2, 3, 4, 5}; int(*p2)[5] = temp; int i; for (i = 0; i 《 5; i++) { printf(“%d\n”, *(p2 + i)); } return 0;}

//Error and Warning in Example 08 V2錯(cuò)誤(活動(dòng)) E0144 “int *” 類(lèi)型的值不能用于初始化 “int (*)[5]” 類(lèi)型的實(shí)體錯(cuò)誤 C2440 “初始化”: 無(wú)法從“int [5]”轉(zhuǎn)換為“int (*)[5]”警告 C4477 “printf”: 格式字符串“%d”需要類(lèi)型“int”的參數(shù),但可變參數(shù) 1 擁有了類(lèi)型“int *”

可是怎么還是有問(wèn)題呢?

我們回顧一下,指針是如何指向數(shù)組的。

int temp[5] = {1, 2, 3, 4, 5};int* p = temp;

我們?cè)疽詾椋羔榩是指向數(shù)組的指針,但是實(shí)際上「并不是」。仔細(xì)想想就會(huì)發(fā)現(xiàn),這個(gè)指針實(shí)際上是指向的數(shù)組的「第一個(gè)元素」,而不是指向數(shù)組。因?yàn)閿?shù)組里面的元素在內(nèi)存中都是挨著個(gè)兒存放的,因此只需要知道第一個(gè)元素的地址,就可以訪問(wèn)到后面的所有元素。

但是,這么來(lái)看的話(huà),指針p指向的就是一個(gè)「整型變量」的指針,并不是指向「數(shù)組」的指針。而剛剛我們用的數(shù)組指針,才是指向數(shù)組的指針。因此,應(yīng)該將「數(shù)組的地址」傳遞給數(shù)組指針,而不是將第一個(gè)元素的地址傳入,盡管它們值相同,但是「含義」確實(shí)不一樣:

//Example 08 V3//Example 08 V2#include 《stdio.h》int main(void){ int temp[5] = {1, 2, 3, 4, 5}; int(*p2)[5] = &temp;//此處取地址 int i; for (i = 0; i 《 5; i++) { printf(“%d\n”, *(*p2 + i)); } return 0;}

程序運(yùn)行如下:

//Consequence 0812345

指針和二維數(shù)組

在上一節(jié)《C語(yǔ)言之?dāng)?shù)組》我們講過(guò)「二維數(shù)組」的概念,并且我們也知道,C語(yǔ)言的二維數(shù)組其實(shí)在內(nèi)存中也是「線(xiàn)性存放」的。

假設(shè)我們定義了:int array[4][5]array

array作為數(shù)組的名稱(chēng),顯然應(yīng)該表示的是數(shù)組的「首地址」。由于二維數(shù)組實(shí)際上就是一維數(shù)組的「線(xiàn)性拓展」,因此array應(yīng)該就是指的指向包含5個(gè)元素的數(shù)組的指針。

如果你用sizeof()去測(cè)試array和array+1的話(huà),就可以測(cè)試出來(lái)這樣的結(jié)論。

*(array+1)

首先從剛剛的問(wèn)題我們可以得出,array+1同樣也是指的指向包含5個(gè)元素的數(shù)組的指針,因此*(array+1)就是相當(dāng)于array[1],而這剛好相當(dāng)于array[1][0]的數(shù)組名。因此*(array+1)就是指第二行子數(shù)組的第一個(gè)元素的地址。

*(*(array+1)+2)

有了剛剛的結(jié)論,我們就不難推理出,這個(gè)實(shí)際上就是array[1][2]。是不是感覺(jué)非常簡(jiǎn)單呢?

總結(jié)一下,就是下面的這些結(jié)論,記住就好,理解那當(dāng)然更好:

*(array + i) == array[i]*(*(array + i) + j) == array[i][j]*(*(*(array + i) + j) + k) == array[i][j][k]。。.

數(shù)組指針和二維數(shù)組

我們?cè)谏弦还?jié)里面講過(guò),在初始化二維數(shù)組的時(shí)候是可以偷懶的:

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

剛剛我們又說(shuō)過(guò),定義一個(gè)數(shù)組指針是這樣的:

int(*p)[3];

那么組合起來(lái)是什么意思呢?

int(*p)[3] = array;

通過(guò)剛剛的說(shuō)明,我們可以知道,array是指向一個(gè)3個(gè)元素的數(shù)組的「指針」,所以這里完全可以將array的值賦值給p。

其實(shí)C語(yǔ)言的指針?lè)浅l`活,同樣的代碼用不同的角度去解讀,就可以有不同的應(yīng)用。

那么如何使用指針來(lái)訪問(wèn)二維數(shù)組呢?沒(méi)錯(cuò),就是使用「數(shù)組指針」:

//Example 09#include 《stdio.h》int main(void){ int array[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} }; int(*p)[4]; int i, j; p = array; for (i = 0, i 《 3, i++) { for (j = 0, j 《 4, j++) { printf(“%2d ”, *(*(p+i) + j)); } printf(“\n”); } return 0;}

運(yùn)行結(jié)果:

//Consequence 090 1 2 34 5 6 78 9 10 11

void指針void實(shí)際上是無(wú)類(lèi)型的意思。如果你嘗試用它來(lái)定義一個(gè)變量,編譯器肯定會(huì)「報(bào)錯(cuò)」,因?yàn)椴煌?lèi)型所占用的內(nèi)存有可能「不一樣」。但是如果定義的是一個(gè)指針,那就沒(méi)問(wèn)題。void類(lèi)型中指針可以指向「任何一個(gè)類(lèi)型」的數(shù)據(jù),也就是說(shuō),任何類(lèi)型的指針都可以賦值給void指針。

將任何類(lèi)型的指針轉(zhuǎn)換為void是沒(méi)有問(wèn)題的。但是如果你要反過(guò)來(lái),那就需要「強(qiáng)制類(lèi)型轉(zhuǎn)換」。此外,不要對(duì)void指針「直接解引用」,因?yàn)榫幾g器其實(shí)并不知道void指針會(huì)存放什么樣的類(lèi)型。

//Example 10#include 《stdio.h》int main(void){ int num = 1024; int* pi = # char* ps = “TechZone”; void* pv; pv = pi; printf(“pi:%p,pv:%p\n”, pi, pv); printf(“*pv:%d\n”, *pv); pv = ps; printf(“ps:%p,pv:%p\n”, ps, pv); printf(“*pv:%s\n”, *pv);}

這樣會(huì)報(bào)錯(cuò):

//Error in Example 10錯(cuò)誤 C2100 非法的間接尋址錯(cuò)誤 C2100 非法的間接尋址

如果一定要這么做,那么可以用「強(qiáng)制類(lèi)型轉(zhuǎn)換」:

//Example 10 V2#include 《stdio.h》int main(void){ int num = 1024; int* pi = # char* ps = “TechZone”; void* pv; pv = pi; printf(“pi:%p,pv:%p\n”, pi, pv); printf(“*pv:%d\n”, *(int*)pv); pv = ps; printf(“ps:%p,pv:%p\n”, ps, pv); printf(“*pv:%s\n”, pv);}

當(dāng)然,使用void指針一定要小心,由于void指針幾乎可以「通吃」所有類(lèi)型,所以間接使得不同類(lèi)型的指針轉(zhuǎn)換變得合法,如果代碼中存在不合理的轉(zhuǎn)換,編譯器也不會(huì)報(bào)錯(cuò)。

因此,void指針能不用則不用,后面講函數(shù)的時(shí)候,還可以解鎖更多新的玩法。

NULL指針在C語(yǔ)言中,如果一個(gè)指針不指向任何數(shù)據(jù),那么就稱(chēng)之為「空指針」,用「NULL」來(lái)表示。NULL其實(shí)是一個(gè)宏定義:

#define NULL ((void *)0)

在大部分的操作系統(tǒng)中,地址0通常是一個(gè)「不被使用」的地址,所以如果一個(gè)指針指向NULL,就意味著不指向任何東西。為什么一個(gè)指針要指向NULL呢?

其實(shí)這反而是一種比較指的推薦的「編程風(fēng)格」——當(dāng)你暫時(shí)還不知道該指向哪兒的時(shí)候,就讓它指向NULL,以后不會(huì)有太多的麻煩,比如:

//Example 11#include 《stdio.h》int main(void){ int* p1; int* p2 = NULL; printf(“%d\n”, *p1); printf(“%d\n”, *p2); return 0;}

第一個(gè)指針未被初始化。在有的編譯器里面,這樣未初始化的變量就會(huì)被賦予「隨機(jī)值」。這樣指針被稱(chēng)為「迷途指針」,「野指針」或者「懸空指針」。如果后面的代碼對(duì)這類(lèi)指針解引用,而這個(gè)地址又剛好是合法的話(huà),那么就會(huì)產(chǎn)生莫名其妙的結(jié)果,甚至導(dǎo)致程序的崩潰。因此養(yǎng)成良好的習(xí)慣,在暫時(shí)不清楚的情況下使用NULL,可以節(jié)省大量的后期調(diào)試的時(shí)間。

指向指針的指針開(kāi)始套娃了。其實(shí)只要你理解了指針的概念,也就沒(méi)什么大不了的。

//Example 12#include 《stdio.h》int main(void){ int num = 1; int* p = # int** pp = &p; printf(“num: %d\n”, num); printf(“*p: %d\n”, *p); printf(“**p: %d\n”, **pp); printf(“&p: %p, pp: %p\n”, &p, pp); printf(“&num: %p, p: %p, *pp: %p\n”, &num, p, *pp); return 0;}

程序結(jié)果如下:

//Consequence 12num: 1*p: 1**p: 1&p: 004FF960, pp: 004FF960&num: 004FF96C, p: 004FF96C, *pp: 004FF96C

當(dāng)然你也可以無(wú)限地套娃,一直指下去。不過(guò)這樣會(huì)讓代碼可讀性變得「很差」,過(guò)段時(shí)間可能你自己都看不懂你寫(xiě)的代碼了。

指針數(shù)組和指向指針的指針那么,指向指針的指針有什么用呢?

它可不是為了去創(chuàng)造混亂代碼,在一個(gè)經(jīng)典的實(shí)例里面,就可以體會(huì)到它的用處:

char* Books[] = { “《C專(zhuān)家編程》”, “《C和指針》”, “《C的陷阱與缺陷》”, “《C Primer Plus》”, “《Python基礎(chǔ)教程(第三版)》”};

然后我們需要將這些書(shū)進(jìn)行分類(lèi)。我們發(fā)現(xiàn),其中有一本是寫(xiě)Python的,其他都是C語(yǔ)言的。這時(shí)候指向指針的指針就派上用場(chǎng)了。首先,我們剛剛定義了一個(gè)指針數(shù)組,也就是說(shuō),里面的所有元素的類(lèi)型「都是指針」,而數(shù)組名卻又可以用指針的形式來(lái)「訪問(wèn)」,因此就可以使用「指向指針的指針」來(lái)指向指針數(shù)組:

。。.char** Python;char** CLang[4];Python = &Books[5];CLang[0] = &Books[0];CLang[1] = &Books[1];CLang[2] = &Books[2];CLang[3] = &Books[3];。。.

因?yàn)樽址娜〉刂分祵?shí)際上就是其「首地址」,也就是一個(gè)「指向字符指針的指針」,所以可以這樣賦值。

這樣,我們就利用指向指針的指針完成了對(duì)書(shū)籍的分類(lèi),這樣既避免了浪費(fèi)多余的內(nèi)存,而且當(dāng)其中的書(shū)名要修改,只需要改一次即可,代碼的靈活性和安全性都得到了提升。

常量和指針常量,在我們目前的認(rèn)知里面,應(yīng)該是這樣的:

520, ‘a(chǎn)’

或者是這樣的:

#define MAX 1000#define B ‘b’

常量和變量最大的區(qū)別,就是前者「不能夠被修改」,后者可以。那么在C語(yǔ)言中,可以將變量變成像具有常量一樣的特性,利用const即可。

const int max = 1000;const char a = ‘a(chǎn)’;

在const關(guān)鍵字的作用下,變量就會(huì)「失去」本來(lái)具有的可修改的特性,變成“只讀”的屬性。

指向常量的指針強(qiáng)大的指針當(dāng)然也是可以指向被const修飾過(guò)的變量,但這就意味著「不能通過(guò)」指針來(lái)修改它所引用的值。總結(jié)一下,就是以下4點(diǎn):

指針可以修改為指向不同的變量

指針可以修改為指向不同的常量

可以通過(guò)解引用來(lái)讀取指針指向的數(shù)據(jù)

不可以通過(guò)解引用來(lái)修改指針指向的數(shù)據(jù)

常量指針指向非常量的常量指針

指針本身作為一種「變量」,也是可以修改的。因此,指針也是可以被const修飾的,只不過(guò)位置稍稍「發(fā)生了點(diǎn)變化」:

。。.int* const p = #。。.

這樣的指針有如下的特性:

指針自身不能夠被修改

指針指向的值可以被修改

責(zé)任編輯:haq

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • C語(yǔ)言
    +關(guān)注

    關(guān)注

    180

    文章

    7605

    瀏覽量

    137002
  • 程序
    +關(guān)注

    關(guān)注

    117

    文章

    3788

    瀏覽量

    81105
  • 指針
    +關(guān)注

    關(guān)注

    1

    文章

    480

    瀏覽量

    70576
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    C語(yǔ)言程序設(shè)計(jì)教程第4版第8講:指針

    C語(yǔ)言指針講解
    發(fā)表于 11-20 14:10 ?0次下載

    技術(shù)干貨驛站 ▏深入理解C語(yǔ)言掌握C語(yǔ)言條件判斷,從if到switch的應(yīng)用

    在編程中,條件判斷語(yǔ)句是控制程序流程的核心元素之一。它們使得程序能夠根據(jù)不同的輸入和狀態(tài),做出相應(yīng)的決策。特別是在C語(yǔ)言中,條件判斷語(yǔ)句的使用極為廣泛,涵蓋了從簡(jiǎn)單的if語(yǔ)句到更復(fù)雜的switch
    的頭像 發(fā)表于 11-09 01:10 ?361次閱讀
    技術(shù)干貨驛站 ▏深入理解<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>:<b class='flag-5'>掌握</b><b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>條件判斷,從if到switch的應(yīng)用

    C語(yǔ)言指針學(xué)習(xí)筆記

    本文從底層內(nèi)存分析,徹底讓讀者明白C語(yǔ)言指針的本質(zhì)。
    的頭像 發(fā)表于 11-05 17:40 ?249次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b><b class='flag-5'>指針</b>學(xué)習(xí)筆記

    C語(yǔ)言指針運(yùn)算符詳解

    C語(yǔ)言中,當(dāng)你有一個(gè)指向數(shù)組中某個(gè)元素的指針時(shí),你可以對(duì)該指針執(zhí)行某些算術(shù)運(yùn)算,例如加法或減法。這些運(yùn)算可以用來(lái)遍歷數(shù)組中的元素,如ptr[i]等價(jià)于*(ptr + i)。然而,如果
    的頭像 發(fā)表于 10-30 11:16 ?257次閱讀

    C語(yǔ)言指針詳細(xì)解析

    可以對(duì)數(shù)據(jù)本身,也可以對(duì)存儲(chǔ)數(shù)據(jù)的變量地址進(jìn)行操作。 指針是一個(gè)占據(jù)存儲(chǔ)空間的實(shí)體在這一段空間起始位置的相對(duì)距離值。在C/C++語(yǔ)言中,指針
    發(fā)表于 09-14 10:03

    技術(shù)干貨驛站 ▏深入理解C語(yǔ)言掌握程序結(jié)構(gòu)知識(shí)

    在計(jì)算機(jī)編程的世界中,C語(yǔ)言被廣泛認(rèn)可為一門(mén)強(qiáng)大而高效的編程語(yǔ)言,其簡(jiǎn)潔的語(yǔ)法和直接的指令使得它成為了許多程序員的首選。了解C語(yǔ)言的程序結(jié)構(gòu)
    的頭像 發(fā)表于 07-27 08:45 ?1426次閱讀
    技術(shù)干貨驛站 ▏深入理解<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>:<b class='flag-5'>掌握</b>程序結(jié)構(gòu)知識(shí)

    按照這樣學(xué)習(xí)C語(yǔ)言,成為卷王不是夢(mèng)!

    在計(jì)算機(jī)編程領(lǐng)域,C語(yǔ)言被譽(yù)為一種強(qiáng)大而靈活的編程語(yǔ)言掌握C語(yǔ)言不僅可以讓你輕松駕馭各種編程
    的頭像 發(fā)表于 07-06 08:04 ?327次閱讀
    按照這樣學(xué)習(xí)<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>,成為卷王不是夢(mèng)!

    面試中的高頻問(wèn)題:指針函數(shù)與函數(shù)指針,你能完美應(yīng)對(duì)嗎?

    一直覺(jué)得C語(yǔ)言較其他語(yǔ)言最偉大的地方就是C語(yǔ)言中的指針,有些人認(rèn)為
    的頭像 發(fā)表于 06-22 08:11 ?1742次閱讀
    面試中的高頻問(wèn)題:<b class='flag-5'>指針</b>函數(shù)與函數(shù)<b class='flag-5'>指針</b>,你能完美應(yīng)對(duì)嗎?

    Keil+C51中對(duì)雙數(shù)據(jù)指針的直接利用

    Keil+C51中對(duì)雙數(shù)據(jù)指針的直接利用
    發(fā)表于 06-18 10:15 ?0次下載

    如何用C語(yǔ)言實(shí)現(xiàn)高效查找(二法)

    今天給分享一下使用C語(yǔ)言實(shí)現(xiàn)二算法,主要包含以下幾部分內(nèi)容:二查找算法介紹二查找算法使用場(chǎng)景二
    的頭像 發(fā)表于 06-04 08:04 ?1165次閱讀
    如何用<b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>實(shí)現(xiàn)高效查找(二<b class='flag-5'>分</b>法)

    嵐圖發(fā)布“琥珀電池”,續(xù)航達(dá)900km,10分鐘充電即可滿(mǎn)足450km續(xù)航?

    琥珀電池基于800V平臺(tái)研發(fā),具備5C超快速充電性能,能實(shí)現(xiàn)10分鐘內(nèi)為車(chē)輛補(bǔ)充450公里續(xù)航里程。此外,該電池在-10℃至40℃的寬泛溫度范圍內(nèi)均能正常工作。
    的頭像 發(fā)表于 04-24 17:38 ?1387次閱讀

    提高C代碼可讀性的編寫(xiě)技巧與策略

    指針C 語(yǔ)言的靈魂,是 C 比其他語(yǔ)言更靈活,更強(qiáng)大的地方。所以學(xué)習(xí) C
    發(fā)表于 04-23 18:25 ?514次閱讀

    C語(yǔ)言函數(shù)指針六大應(yīng)用場(chǎng)景詳解

    函數(shù)指針是一種非常強(qiáng)大的編程工具,它可以讓我們以更加靈活的方式編寫(xiě)程序。在本文中,我們將介紹 6 個(gè)函數(shù)指針的高級(jí)應(yīng)用場(chǎng)景,并貼出相應(yīng)的代碼案例和解釋。
    的頭像 發(fā)表于 04-23 18:19 ?901次閱讀

    C語(yǔ)言指針用法

    C語(yǔ)言編程中善用指針可以簡(jiǎn)化一些任務(wù)的處理,而對(duì)于一些任務(wù)(比如動(dòng)態(tài)內(nèi)存分配),必須要有指針才行的。也就是說(shuō)精通C
    發(fā)表于 03-05 14:22 ?361次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語(yǔ)言</b>的<b class='flag-5'>指針</b>用法

    理想5C超充樁啟用,12分鐘可充電500公里

    據(jù)悉,目前每個(gè)超充站配有1-2根理想5C樁和2-7根理想2C樁供顧客選擇。擁有理想MEGA車(chē)輛的消費(fèi)者,若使用理想5C樁,只需短短12分鐘便可達(dá)到充電500公里的效果;而如果選擇2
    的頭像 發(fā)表于 03-01 11:19 ?1224次閱讀