C語(yǔ)言中威力最大的指針底層原理和使用技巧講解
四、指向不同數(shù)據(jù)類型的指針
1. 數(shù)值型指針
通過(guò)上面的介紹,指向數(shù)值型變量的指針已經(jīng)很明白了,需要注意的就是指針?biāo)赶虻臄?shù)據(jù)類型。
2. 字符串指針
字符串在內(nèi)存中的表示有2種:
用一個(gè)數(shù)組來(lái)表示,例如:char name1[8] = "zhangsan";用一個(gè)char *指針來(lái)表示,例如:char *name2 = "zhangsan";
name1在內(nèi)存中占據(jù)8個(gè)字節(jié),其中存儲(chǔ)了8個(gè)字符的ASCII碼值;name2在內(nèi)存中占據(jù)9個(gè)字節(jié),因?yàn)槌舜鎯?chǔ)8個(gè)字符的ASCII碼值,在最后一個(gè)字符'n'的后面還額外存儲(chǔ)了一個(gè)'',用來(lái)標(biāo)識(shí)字符串結(jié)束。
對(duì)于字符串來(lái)說(shuō),使用指針來(lái)操作是非常方便的,例如:變量字符串name2:
char *name2 = "zhangsan";
char *p = name2;
while (*p 。 '')
{
printf("%c ", *p);
p = p + 1;
}
在while的判斷條件中,檢查p指針指向的字符是否為結(jié)束符''。在循環(huán)體重,打印出當(dāng)前指向的字符之后,對(duì)指針比那里進(jìn)行自增操作,因?yàn)橹羔榩所指向的數(shù)據(jù)類型是char,每個(gè)char在內(nèi)存中占據(jù)一個(gè)字節(jié),因此指針p在自增1之后,就指向下一個(gè)存儲(chǔ)空間。
也可以把循環(huán)體中的2條語(yǔ)句寫成1條語(yǔ)句:
printf("%c ", *p++);
假如一個(gè)指針指向的數(shù)據(jù)類型為int型,那么執(zhí)行p = p + 1;之后,指針p中存儲(chǔ)的地址值將會(huì)增加4,因?yàn)橐粋(gè)int型數(shù)據(jù)在內(nèi)存中占據(jù)4個(gè)字節(jié)的空間,如下所示:
思考一個(gè)問(wèn)題:void*型指針能夠遞增嗎?如下測(cè)試代碼:
int a[3] = {1, 2, 3};
void *p = a;
printf("1: p = 0x%x ", p);
p = p + 1;
printf("2: p = 0x%x ", p);
打印結(jié)果如下:
1: p = 0x733748c0
2: p = 0x733748c1
說(shuō)明void*型指針在自增時(shí),是按照一個(gè)字節(jié)的跨度來(lái)計(jì)算的。
3. 指針數(shù)組與數(shù)組指針
這2個(gè)說(shuō)法經(jīng)常會(huì)混淆,至少我是如此,先看下這2條語(yǔ)句:
int *p1[3]; // 指針數(shù)組
int (*p2)[3]; // 數(shù)組指針
3.1 指針數(shù)組
第1條語(yǔ)句中:中括號(hào)[]的優(yōu)先級(jí)高,因此與p1先結(jié)合,表示一個(gè)數(shù)組,這個(gè)數(shù)組中有3個(gè)元素,這3個(gè)元素都是指針,它們指向的是int型數(shù)據(jù)?梢赃@樣來(lái)理解:如果有這個(gè)定義char p[3],很容易理解這是一個(gè)有3個(gè)char型元素的數(shù)組,那么把char換成int*,意味著數(shù)組里的元素類型是int*型(指向int型數(shù)據(jù)的指針)。內(nèi)存模型如下(注意:三個(gè)指針指向的地址并不一定是連續(xù)的):
如果向指針數(shù)組中的元素賦值,需要逐個(gè)把變量的地址賦值給指針元素:
int a = 1, b = 2, c = 3;
char *p1[3];
p1[0] = &a;
p1[1] = &b;
p1[2] = &c;
3.2 數(shù)組指針
第2條語(yǔ)句中:小括號(hào)讓p2與*結(jié)合,表示p2是一個(gè)指針,這個(gè)指針指向了一個(gè)數(shù)組,數(shù)組中有3個(gè)元素,每一個(gè)元素的類型是int型?梢赃@樣來(lái)理解:如果有這個(gè)定義int p[3],很容易理解這是一個(gè)有3個(gè)int型元素的數(shù)組,那么把數(shù)組名p換成是*p2,也就是p2是一個(gè)指針,指向了這個(gè)數(shù)組。內(nèi)存模型如下(注意:指針指向的地址是一個(gè)數(shù)組,其中的3個(gè)元素是連續(xù)放在內(nèi)存中的):
在前面我們說(shuō)到取地址操作符&,用來(lái)獲得一個(gè)變量的地址。凡事都有特殊情況,對(duì)于獲取地址來(lái)說(shuō),下面幾種情況不需要使用&操作符:
字符串字面量作為右值時(shí),就代表這個(gè)字符串在內(nèi)存中的首地址;數(shù)組名就代表這個(gè)數(shù)組的地址,也等于這個(gè)數(shù)組的第一個(gè)元素的地址;函數(shù)名就代表這個(gè)函數(shù)的地址。
因此,對(duì)于一下代碼,三個(gè)printf語(yǔ)句的打印結(jié)果是相同的:
int a[3] = {1, 2, 3};
int (*p2)[3] = a;
printf("0x%x ", a);
printf("0x%x ", &a);
printf("0x%x ", p2);
思考一下,如果對(duì)這里的p2指針執(zhí)行p2 = p2 + 1;操作,p2中的值將會(huì)增加多少?
答案是12個(gè)字節(jié)。因?yàn)閜2指向的是一個(gè)數(shù)組,這個(gè)數(shù)組中包含3個(gè)元素,每個(gè)元素占據(jù)4個(gè)字節(jié),那么這個(gè)數(shù)組在內(nèi)存中一共占據(jù)12個(gè)字節(jié),因此p2在加1之后,就跳過(guò)12個(gè)字節(jié)。
4. 二維數(shù)組和指針
一維數(shù)組在內(nèi)存中是連續(xù)分布的多個(gè)內(nèi)存單元組成的,而二維數(shù)組在內(nèi)存中也是連續(xù)分布的多個(gè)內(nèi)存單元組成的,從內(nèi)存角度來(lái)看,一維數(shù)組和二維數(shù)組沒(méi)有本質(zhì)差別。
和一維數(shù)組類似,二維數(shù)組的數(shù)組名表示二維數(shù)組的第一維數(shù)組中首元素的首地址,用代碼來(lái)說(shuō)明:
int a[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // 二維數(shù)組
int (*p0)[3] = NULL; // p0是一個(gè)指針,指向一個(gè)數(shù)組
int (*p1)[3] = NULL; // p1是一個(gè)指針,指向一個(gè)數(shù)組
int (*p2)[3] = NULL; // p2是一個(gè)指針,指向一個(gè)數(shù)組
p0 = a[0];
p1 = a[1];
p2 = a[2];
printf("0: %d %d %d ", *(*p0 + 0), *(*p0 + 1), *(*p0 + 2));
printf("1: %d %d %d ", *(*p1 + 0), *(*p1 + 1), *(*p1 + 2));
printf("2: %d %d %d ", *(*p2 + 0), *(*p2 + 1), *(*p2 + 2));
打印結(jié)果是:
0: 1 2 3
1: 4 5 6
2: 7 8 9
我們拿第一個(gè)printf語(yǔ)句來(lái)分析:p0是一個(gè)指針,指向一個(gè)數(shù)組,數(shù)組中包含3個(gè)元素,每個(gè)元素在內(nèi)存中占據(jù)4個(gè)字節(jié),F(xiàn)在我們想獲取這個(gè)數(shù)組中的數(shù)據(jù),如果直接對(duì)p0執(zhí)行加1操作,那么p0將會(huì)跨過(guò)12個(gè)字節(jié)(就等于p1中的值了),因此需要使用解引用操作符*,把p0轉(zhuǎn)為指向int型的指針,然后再執(zhí)行加1操作,就可以得到數(shù)組中的int型數(shù)據(jù)了。
5. 結(jié)構(gòu)體指針
C語(yǔ)言中的基本數(shù)據(jù)類型是預(yù)定義的,結(jié)構(gòu)體是用戶定義的,在指針的使用上可以進(jìn)行類比,唯一有區(qū)別的就是在結(jié)構(gòu)體指針中,需要使用->箭頭操作符來(lái)獲取結(jié)構(gòu)體中的成員變量,例如:
typedef struct
{
int age;
char name[8];
} Student;
Student s;
s.a(chǎn)ge = 20;
strcpy(s.name, "lisi");
Student *p = &s;
printf("age = %d, name = %s ", p->age, p->name);
看起來(lái)似乎沒(méi)有什么技術(shù)含量,如果是結(jié)構(gòu)體數(shù)組呢?例如:
Student s[3];
Student *p = &s;
printf("size of Student = %d ", sizeof(Student));
printf("1: 0x%x, 0x%x ", s, p);
p++;
printf("2: 0x%x ", p);
打印結(jié)果是:
size of Student = 12
1: 0x4c02ac00, 0x4c02ac00
2: 0x4c02ac0c
在執(zhí)行p++操作后,p需要跨過(guò)的空間是一個(gè)結(jié)構(gòu)體變量在內(nèi)存中占據(jù)的大。12個(gè)字節(jié)),所以此時(shí)p就指向了數(shù)組中第2個(gè)元素的首地址,內(nèi)存模型如下:
6. 函數(shù)指針
每一個(gè)函數(shù)在經(jīng)過(guò)編譯之后,都變成一個(gè)包含多條指令的集合,在程序被加載到內(nèi)存之后,這個(gè)指令集合被放在代碼區(qū),我們?cè)诔绦蛑惺褂煤瘮?shù)名就代表了這個(gè)指令集合的開始地址。
函數(shù)指針,本質(zhì)上仍然是一個(gè)指針,只不過(guò)這個(gè)指針變量中存儲(chǔ)的是一個(gè)函數(shù)的地址。函數(shù)最重要特性是什么?可以被調(diào)用!因此,當(dāng)定義了一個(gè)函數(shù)指針并把一個(gè)函數(shù)地址賦值給這個(gè)指針時(shí),就可以通過(guò)這個(gè)函數(shù)指針來(lái)調(diào)用函數(shù)。
如下示例代碼:
int add(int x,int y)
{
return x+y;
}
int main()
{
int a = 1, b = 2;
int (*p)(int, int);
p = add;
printf("%d + %d = %d", a, b, p(a, b));
}
前文已經(jīng)說(shuō)過(guò),函數(shù)的名字就代表函數(shù)的地址,所以函數(shù)名add就代表了這個(gè)加法函數(shù)在內(nèi)存中的地址。int (*p)(int, int);這條語(yǔ)句就是用來(lái)定義一個(gè)函數(shù)指針,它指向一個(gè)函數(shù),這個(gè)函數(shù)必須符合下面這2點(diǎn)(學(xué)名叫:函數(shù)簽名):
有2個(gè)int型的參數(shù);有一個(gè)int型的返回值。
代碼中的add函數(shù)正好滿足這個(gè)要求,因此,可以把a(bǔ)dd賦值給函數(shù)指針p,此時(shí)p就指向了內(nèi)存中這個(gè)函數(shù)存儲(chǔ)的地址,后面就可以用函數(shù)指針p來(lái)調(diào)用這個(gè)函數(shù)了。
在示例代碼中,函數(shù)指針p是直接定義的,那如果想定義2個(gè)函數(shù)指針,難道需要像下面這樣定義嗎?
int (*p)(int, int);
int (*p2)(int, int);
這里的參數(shù)比較簡(jiǎn)單,如果函數(shù)很復(fù)雜,這樣的定義方式豈不是要煩死?可以用typedef關(guān)鍵字來(lái)定義一個(gè)函數(shù)指針類型:
typedef int (*pFunc)(int, int);
然后用這樣的方式pFunc p1, p2;來(lái)定義多個(gè)函數(shù)指針就方便多了。注意:只能把與函數(shù)指針類型具有相同簽名的函數(shù)賦值給p1和p2,也就是參數(shù)的個(gè)數(shù)、類型要相同,返回值也要相同。
注意:這里有幾個(gè)小細(xì)節(jié)稍微了解一下:
在賦值函數(shù)指針時(shí),使用p = &a;也是可以的;使用函數(shù)指針調(diào)用時(shí),使用(*p)(a, b);也是可以的。
這里沒(méi)有什么特殊的原理需要講解,最終都是編譯器幫我們處理了這里的細(xì)節(jié),直接記住即可。
函數(shù)指針整明白之后,再和數(shù)組結(jié)合在一起:函數(shù)指針數(shù)組。示例代碼如下:
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }
int main()
{
int a = 4, b = 2;
int (*p[4])(int, int);
p[0] = add;
p[1] = sub;
p[2] = mul;
p[3] = divide;
printf("%d + %d = %d ", a, b, p[0](a, b));
printf("%d - %d = %d ", a, b, p[1](a, b));
printf("%d * %d = %d ", a, b, p[2](a, b));
printf("%d / %d = %d ", a, b, p[3](a, b));
}
這條語(yǔ)句不太好理解:int (*p[4])(int, int);,先分析中間部分,標(biāo)識(shí)符p與中括號(hào)[]結(jié)合(優(yōu)先級(jí)高),所以p是一個(gè)數(shù)組,數(shù)組中有4個(gè)元素;然后剩下的內(nèi)容表示一個(gè)函數(shù)指針,那么就說(shuō)明數(shù)組中的元素類型是函數(shù)指針,也就是其他函數(shù)的地址,內(nèi)存模型如下:
如果還是難以理解,那就回到指針的本質(zhì)概念上:指針就是一個(gè)地址!這個(gè)地址中存儲(chǔ)的內(nèi)容是什么根本不重要,重要的是你告訴計(jì)算機(jī)這個(gè)內(nèi)容是什么。如果你告訴它:這個(gè)地址里存放的內(nèi)容是一個(gè)函數(shù),那么計(jì)算機(jī)就去調(diào)用這個(gè)函數(shù)。那么你是如何告訴計(jì)算機(jī)的呢,就是在定義指針變量的時(shí)候,僅此而已!
五、總結(jié)
我已經(jīng)把自己知道的所有指針相關(guān)的概念、語(yǔ)法、使用場(chǎng)景都作了講解,就像一個(gè)小酒館的掌柜,把自己的美酒佳肴都呈現(xiàn)給你,但愿你已經(jīng)酒足飯飽!
如果以上的內(nèi)容太多,一時(shí)無(wú)法消化,那么下面的這兩句話就作為飯后甜點(diǎn)為您奉上,在以后的編程中,如果遇到指針相關(guān)的困惑,就想一想這兩句話,也許能讓你茅塞頓開。
指針就是地址,地址就是指針。指針就是指向內(nèi)存中的一塊空間,至于如何來(lái)解釋/操作這塊空間,由這個(gè)指針的類型來(lái)決定。
另外還有一點(diǎn)囑咐,那就是學(xué)習(xí)任何一門編程語(yǔ)言,一定要弄清楚內(nèi)存模型,內(nèi)存模型,內(nèi)存模型!
祝您好運(yùn)!
發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長(zhǎng)度6~500個(gè)字
最新活動(dòng)更多
-
10月31日立即下載>> 【限時(shí)免費(fèi)下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報(bào)名>>> 【在線會(huì)議】多物理場(chǎng)仿真助跑新能源汽車
-
11月28日立即報(bào)名>>> 2024工程師系列—工業(yè)電子技術(shù)在線會(huì)議
-
12月19日立即報(bào)名>> 【線下會(huì)議】OFweek 2024(第九屆)物聯(lián)網(wǎng)產(chǎn)業(yè)大會(huì)
-
即日-12.26火熱報(bào)名中>> OFweek2024中國(guó)智造CIO在線峰會(huì)
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍(lán)皮書》
推薦專題
- 1 【一周車話】沒(méi)有方向盤和踏板的車,你敢坐嗎?
- 2 特斯拉發(fā)布無(wú)人駕駛車,還未迎來(lái)“Chatgpt時(shí)刻”
- 3 特斯拉股價(jià)大跌15%:Robotaxi離落地還差一個(gè)蘿卜快跑
- 4 馬斯克給的“驚喜”夠嗎?
- 5 大模型“新星”開啟變現(xiàn)競(jìng)速
- 6 海信給AI電視打樣,12大AI智能體全面升級(jí)大屏體驗(yàn)
- 7 打完“價(jià)格戰(zhàn)”,大模型還要比什么?
- 8 馬斯克致敬“國(guó)產(chǎn)蘿卜”?
- 9 神經(jīng)網(wǎng)絡(luò),誰(shuí)是盈利最強(qiáng)企業(yè)?
- 10 比蘋果偉大100倍!真正改寫人類歷史的智能產(chǎn)品降臨
- 高級(jí)軟件工程師 廣東省/深圳市
- 自動(dòng)化高級(jí)工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級(jí)銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市