www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當(dāng)前位置:首頁 > 公眾號精選 > 大魚機器人
[導(dǎo)讀]說到指針,估計還是有很多小伙伴都還是云里霧里的,有點“知其然,而不知其所以然”。但是,不得不說,學(xué)了指針,C語言才能算是入門了。指針是C語言的「精華」,可以說,對對指針的掌握程度,「直接決定」了你C語言的編程能力。

點擊上方“大魚機器人”,選擇“置頂/星標(biāo)公眾號”

福利干貨,第一時間送達!

ID:技術(shù)讓夢想更偉大

整理:李肖遙


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


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

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

通過變量名來訪問變量,是一種「相對安全」的方式。因為只有你定義了它,你才能夠訪問相應(yīng)的變量。這就是對內(nèi)存的基本認(rèn)知。但是,如果光知道這一點的話,其實你還是不知道內(nèi)存是如何存放變量的,因為底層是如何工作的,你依舊不清楚。

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

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

初識指針和指針變量

那么我們現(xiàn)在就來切入正題,指針是個什么東西呢?

所謂指針,就是內(nèi)存地址(下文簡稱地址)。C語言中設(shè)立了專門的「指針變量」來存儲指針,和「普通變量」不一樣的是,指針變量存儲的是「地址」。

定義指針

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

很簡單:類型名* 指針變量名

char*?pa;//定義一個字符變量的指針,名稱為pa
int*?pb;//定義一個整型變量的指針,名稱為pb
float*?pc;//定義一個浮點型變量的指針,名稱為pc

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

取地址運算符和取值運算符

獲取某個變量的地址,使用取地址運算符&,如:

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

如果反過來,你要訪問指針變量指向的數(shù)據(jù),那么你就要使用取值運算符*,如:

printf("%c,?%d\n",?*pa,?*pb);

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

直接通過變量名來訪問變量的值稱之為直接訪問,通過指針這樣的形式訪問稱之為間接訪問,因此取值運算符有時候也成為「間接運算符」。

比如:

//Example?01
//代碼來源于網(wǎng)絡(luò),非個人原創(chuàng)
#include?
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 01
a = f
f = 123
now, a = c
now, f = 124
sizeof pa = 4
sizeof pf = 4
the addr of a is: 00EFF97F
the addr of f is: 00EFF970

避免訪問未初始化的指針

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

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

指針和數(shù)組

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

數(shù)組的地址

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

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

//Example?02
int?main(void)
{
????int?a;
????int*?p?=?&a;
????
????printf("請輸入一個整數(shù):");
????scanf("%d",?&a);//此處需要&
????printf("a?=?%d\n",?a);
????
????printf("請再輸入一個整數(shù):");
????scanf("%d",?p);//此處不需要&
????printf("a?=?%d\n",?a);
????
????return?0;
}

程序運行如下:

//Consequence 02
請輸入一個整數(shù):1
a = 1
請再輸入一個整數(shù):2
a = 2

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

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

//Example?03
#include?
int?main(void)
{
????char?url[100];
????url[99]?=?'\0';
????printf("請輸入TechZone的域名:");
????scanf("%s",?url);//此處也不用&
????printf("你輸入的域名是:%s\n",?url);
????return?0;
}

程序執(zhí)行如下:

//Consequence 03
請輸入TechZone的域名:www.techzone.ltd
你輸入的域名是:www.techzone.ltd

因此很好推理:數(shù)組名其實就是一個「地址信息」,實際上就是數(shù)組「第一個元素的地址」。咱們試試把第一個元素的地址和數(shù)組的地址做個對比就知道了:

//Example?03?V2
#include?
int?main(void)
{
????char?url[100];
????printf("請輸入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;
}

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

//Comsequense 03 V2
請輸入TechZone的域名:www.techzone.ltd
你輸入的域名是:www.techzone.ltd
url的地址為:0063F804
url[0]的地址為:0063F804
兩者一致!

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

指向數(shù)組的指針

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

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

指針的運算

當(dāng)指針指向數(shù)組元素的時候,可以對指針變量進行「加減」運算,+n表示指向p指針?biāo)赶虻脑氐?strong style="color: rgb(127, 194, 194);">「下n個元素」,-n表示指向p指針?biāo)赶虻脑氐?strong style="color: rgb(127, 194, 194);">「上n個元素」。并不是將地址加1。

如:

//Example?04
#include?
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

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

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

其實使用指針法來訪問數(shù)組的元素,不一定需要定義一個指向數(shù)組的單獨的指針變量,因為數(shù)組名自身就是指向數(shù)組「第一個元素」的指針,因此指針法可以直接作用于數(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?->?00AFF840
b?=?1,?b+1?=?2,?b+2?=?3

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

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

//Example?05
//代碼來源于網(wǎng)絡(luò)
#include?
#include?
int?main(void)
{
????char*?str?=?"I?love?TechZone!";
????int?i,?length;
????
????length?=?strlen(str);
????
????for?(i?=?0;?i?????{
????????printf("%c",?str[i]);
????}
????printf("\n");
????
????return?0;
}

程序運行如下:

//Consequence 05
I love TechZone!

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

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

...
for?(i?=?0,?i?{
????printf("%c",?*(str?+?i));
}

這就相當(dāng)于利用了指針法來讀取。

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

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

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

比如筆者之前看過的一個例子:

//Example?06
//代碼來源于網(wǎng)絡(luò)
#include?
int?main(void)
{
????char?str[]?=?"I?love?TechZone!";
????int?count?=?0;
????
????while?(*str++?!=?'\0')
????{
????????count++;
????}
????printf("總共有%d個字符。\n",?count);
????
????return?0;
}

當(dāng)編譯器報錯的時候,你可能會開始懷疑你學(xué)了假的C語言語法:

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

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

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

看上去貌似沒啥毛病,但是,看看編譯器告訴我們的東西:表達式必須是可修改的左值

++的操作對象是str,那么str到底是不是「左值」呢?

如果是左值的話,那么就必須滿足左值的條件。

  1. 擁有用于識別和定位一個存儲位置的標(biāo)識符
  2. 存儲值可修改

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

//Example?06?V2
//代碼來源于網(wǎng)絡(luò)
#include?
int?main(void)
{
????char?str[]?=?"I?love?TechZone!";
????char*?target?=?str;
????int?count?=?0;
????
????while?(*target++?!=?'\0')
????{
????????count++;
????}
????printf("總共有%d個字符。\n",?count);
????
????return?0;
}

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

//Consequence 06 V2
總共有16個字符。

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

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

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

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

單個的我們都可以判斷,但是組合起來就有些難度了。

答案:

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

我們挨個來分析。

指針數(shù)組

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

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

你可以定義5個變量,然后挨個取地址來初始化。

不過這樣太繁瑣了,但是,并不是說指針數(shù)組就沒什么用。

比如:

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

結(jié)果如下:

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

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

數(shù)組指針

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

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

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

//Example?08
#include?
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報出以下的錯誤:

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

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

//Example?08?V2
#include?
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
錯誤(活動) E0144 "int *" 類型的值不能用于初始化 "int (*)[5]" 類型的實體
錯誤 C2440 “初始化”: 無法從“int [5]”轉(zhuǎn)換為“int (*)[5]”
警告 C4477 “printf”: 格式字符串“%d”需要類型“int”的參數(shù),但可變參數(shù) 1 擁有了類型“int *”

可是怎么還是有問題呢?

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

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

我們原本以為,指針p是指向數(shù)組的指針,但是實際上「并不是」。仔細想想就會發(fā)現(xiàn),這個指針實際上是指向的數(shù)組的「第一個元素」,而不是指向數(shù)組。因為數(shù)組里面的元素在內(nèi)存中都是挨著個兒存放的,因此只需要知道第一個元素的地址,就可以訪問到后面的所有元素。

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

//Example?08?V3
//Example?08?V2
#include?
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;
}

程序運行如下:

//Consequence 08
1
2
3
4
5

指針和二維數(shù)組

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

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

array

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

如果你用sizeof()去測試arrayarray+1的話,就可以測試出來這樣的結(jié)論。

*(array+1)

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

*(*(array+1)+2)

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

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

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

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

我們在上一節(jié)里面講過,在初始化二維數(shù)組的時候是可以偷懶的:

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

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

int(*p)[3];

那么組合起來是什么意思呢?

int(*p)[3]?=?array;

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

其實C語言的指針非常靈活,同樣的代碼用不同的角度去解讀,就可以有不同的應(yīng)用。

那么如何使用指針來訪問二維數(shù)組呢?沒錯,就是使用「數(shù)組指針」

//Example?09
#include?
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;
}

運行結(jié)果:

//Consequence 09
0 1 2 3
4 5 6 7
8 9 10 11

void指針

void實際上是無類型的意思。如果你嘗試用它來定義一個變量,編譯器肯定會「報錯」,因為不同類型所占用的內(nèi)存有可能「不一樣」。但是如果定義的是一個指針,那就沒問題。void類型中指針可以指向「任何一個類型」的數(shù)據(jù),也就是說,任何類型的指針都可以賦值給void指針。

將任何類型的指針轉(zhuǎn)換為void是沒有問題的。但是如果你要反過來,那就需要「強制類型轉(zhuǎn)換」。此外,不要對void指針「直接解引用」,因為編譯器其實并不知道void指針會存放什么樣的類型。

//Example?10
#include?
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);
}

這樣會報錯:

//Error in Example 10
錯誤 C2100 非法的間接尋址
錯誤 C2100 非法的間接尋址

如果一定要這么做,那么可以用「強制類型轉(zhuǎn)換」

//Example?10?V2
#include?
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指針幾乎可以「通吃」所有類型,所以間接使得不同類型的指針轉(zhuǎn)換變得合法,如果代碼中存在不合理的轉(zhuǎn)換,編譯器也不會報錯。

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

NULL指針

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

#define?NULL?((void?*)0)

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

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

//Example?11
#include?
int?main(void)
{
????int*?p1;
????int*?p2?=?NULL;
????printf("%d\n",?*p1);
????printf("%d\n",?*p2);
????return?0;
}

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

指向指針的指針

開始套娃了。其實只要你理解了指針的概念,也就沒什么大不了的。

//Example?12
#include?
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 12
num: 1
*p: 1
**p: 1
&p: 004FF960, pp: 004FF960
&num: 004FF96C, p: 004FF96C, *pp: 004FF96C

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

指針數(shù)組和指向指針的指針

那么,指向指針的指針有什么用呢?

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

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

然后我們需要將這些書進行分類。我們發(fā)現(xiàn),其中有一本是寫Python的,其他都是C語言的。這時候指向指針的指針就派上用場了。首先,我們剛剛定義了一個指針數(shù)組,也就是說,里面的所有元素的類型「都是指針」,而數(shù)組名卻又可以用指針的形式來「訪問」,因此就可以使用「指向指針的指針」來指向指針數(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];
...

因為字符串的取地址值實際上就是其「首地址」,也就是一個「指向字符指針的指針」,所以可以這樣賦值。

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

常量和指針

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

520, 'a'

或者是這樣的:

#define?MAX?1000
#define?B?'b'

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

const?int?max?=?1000;
const?char?a?=?'a';

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

指向常量的指針

強大的指針當(dāng)然也是可以指向被const修飾過的變量,但這就意味著「不能通過」指針來修改它所引用的值??偨Y(jié)一下,就是以下4點:

  1. 指針可以修改為指向不同的變量
  2. 指針可以修改為指向不同的常量
  3. 可以通過解引用來讀取指針指向的數(shù)據(jù)
  4. 不可以通過解引用來修改指針指向的數(shù)據(jù)

常量指針

指向非常量的常量指針

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

...
int*?const?p?=?#
...

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

  1. 指針自身不能夠被修改
  2. 指針指向的值可以被修改

指向常量的常量指針

在定義普通變量的時候也用const修飾,就得到了這樣的指針。不過由于限制太多,一般很少用到:

...
int?num?=?100;
const?int?cnum?=?200;
const?int*?const?p?=?&cnum;
...

http://www.techzone.ltd/post/CPointer/


-END-

|?整理文章為傳播相關(guān)技術(shù),版權(quán)歸原作者所有?|

|?如有侵權(quán),請聯(lián)系刪除?|


往期好文合集

C語言高效編程與代碼優(yōu)化

經(jīng)常遇到的10大C語言基礎(chǔ)算法(珍藏版源碼)

C語言常用標(biāo)準(zhǔn)庫解讀

??最 后 ??
?

若覺得文章不錯,轉(zhuǎn)發(fā)分享,也是我們繼續(xù)更新的動力。

5T資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,PCB、FPGA、DSP、labview、單片機、等等!
在公眾號內(nèi)回復(fù)「更多資源」,即可免費獲取,期待你的關(guān)注~

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險,如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應(yīng)對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學(xué)會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉