C指針:8 個(gè)關(guān)于指針的用法
3. 傳遞函數(shù)指針
從上篇文章中我們知道,函數(shù)名本身就代表一個(gè)地址,在這個(gè)地址中存儲(chǔ)著函數(shù)體中定義的一連串指令碼,只要給這個(gè)地址后面加上一個(gè)調(diào)用符(小括號(hào)),就進(jìn)入這個(gè)函數(shù)中執(zhí)行。在實(shí)際程序中,函數(shù)名常常作為函數(shù)參數(shù)來進(jìn)行傳遞。
熟悉C++的小伙伴都知道,在標(biāo)準(zhǔn)庫中對(duì)容器類型的數(shù)據(jù)進(jìn)行各種算法操作時(shí),可以傳入用戶自己的提供的算法函數(shù)(如果不傳入函數(shù),標(biāo)準(zhǔn)庫就使用默認(rèn)的)。
下面是一個(gè)示例代碼,對(duì)一個(gè) int 行的數(shù)組進(jìn)行排序,排序函數(shù) demo3_h(yuǎn)andle_data 的最后一個(gè)參數(shù)是一個(gè)函數(shù)指針,因此需要傳入一個(gè)具體的排序算法函數(shù)。示例中有 2 個(gè)候選函數(shù)可以使用:
降序排列: demo3_algorithm_decend;升序排列: demo3_algorithm_ascend;typedef int BOOL;#define FALSE 0#define TRUE 1
BOOL demo3_algorithm_decend(int a, int b){ return a > b;}
BOOL demo3_algorithm_ascend(int a, int b){ return a < b;}
typedef BOOL (*Func)(int, int);void demo3_h(yuǎn)andle_data(int *data, int size, Func pf){ for (int i = 0; i < size - 1; ++i) { for (int j = 0; j < size - 1 - i; ++j) { // 調(diào)用傳入的排序函數(shù) if (pf(data[j], data[j+1])) { int tmp = data[j]; data[j] = data[j + 1]; data[j + 1] = tmp; } } }}
void demo3(){ int a[5] = {5, 1, 9, 2, 6}; int size = sizeof(a)/sizeof(int); // 調(diào)用排序函數(shù),需要傳遞排序算法函數(shù) //demo3_h(yuǎn)andle_data(a, size, demo3_algorithm_decend); // 降序排列 demo3_h(yuǎn)andle_data(a, size, demo3_algorithm_ascend); // 升序排列 for (int i = 0; i < size; ++i) printf("%d ", a[i]); printf("");}這個(gè)就不用畫圖了,函數(shù)指針 pf 就指向了傳入的那個(gè)函數(shù)地址,在排序的時(shí)候直接調(diào)用就可以了。
4. 指向結(jié)構(gòu)體的指針
在嵌入式開發(fā)中,指向結(jié)構(gòu)體的指針使用特別廣泛,這里以智能家居中的一條控制指令來舉例。在一個(gè)智能家居系統(tǒng)中,存在各種各樣的設(shè)備(插座、電燈、電動(dòng)窗簾等),每個(gè)設(shè)備的控制指令都是不一樣的,因此可以在每個(gè)設(shè)備的控制指令結(jié)構(gòu)體中的最前面,放置所有指令都需要的、通用的成員變量,這些變量可以稱為指令頭(指令頭中包含一個(gè)代表命令類型的枚舉變量)。
當(dāng)處理一條控制指令時(shí),先用一個(gè)通用命令(指令頭)的指針來接收指令,然后根據(jù)命令類型枚舉變量來區(qū)分,把控制指令強(qiáng)制轉(zhuǎn)換成具體的那個(gè)設(shè)備的數(shù)據(jù)結(jié)構(gòu),這樣就可以獲取到控制指令中特定的控制數(shù)據(jù)了。
本質(zhì)上,與 Java/C++ 中的接口、基類的概念類似。
// 指令類型枚舉typedef enum _CMD_TYPE_ { CMD_TYPE_CONTROL_SWITCH = 1, CMD_TYPE_CONTROL_LAMP,} CMD_TYPE;
// 通用的指令數(shù)據(jù)結(jié)構(gòu)(指令頭)typedef struct _CmdBase_ { CMD_TYPE cmdType; // 指令類型 int deviceId; // 設(shè)備 Id} CmdBase;
typedef struct _CmdControlSwitch_ { // 前 2 個(gè)參數(shù)是指令頭 CMD_TYPE cmdType; int deviceId; // 下面都有這個(gè)指令私有的數(shù)據(jù) int slot; // 排插上的哪個(gè)插口 int state; // 0:斷開, 1:接通} CmdControlSwitch;
typedef struct _CmdControlLamp_ { // 前 2 個(gè)參數(shù)是指令頭 CMD_TYPE cmdType; int deviceId; // 下面都有這個(gè)指令私有的數(shù)據(jù) int color; // 顏色 int brightness; // 亮度} CmdControlLamp;
// 參數(shù)是指令頭指針void demo4_control_device(CmdBase *pcmd){ // 根據(jù)指令頭中的命令類型,把指令強(qiáng)制轉(zhuǎn)換成具體設(shè)備的指令 if (CMD_TYPE_CONTROL_SWITCH == pcmd->cmdType) { // 類型強(qiáng)制轉(zhuǎn)換 CmdControlSwitch *cmd = pcmd; printf("control switch. slot = %d, state = %d ", cmd->slot, cmd->state); } else if (CMD_TYPE_CONTROL_LAMP == pcmd->cmdType) { // 類型強(qiáng)制轉(zhuǎn)換 CmdControlLamp * cmd = pcmd; printf("control lamp. color = 0x%x, brightness = %d ", cmd->color, cmd->brightness); }}
void demo4(){ // 指令1:控制一個(gè)開關(guān) CmdControlSwitch cmd1 = {CMD_TYPE_CONTROL_SWITCH, 1, 3, 0}; demo4_control_device(&cmd1);
// 指令2:控制一個(gè)燈泡 CmdControlLamp cmd2 = {CMD_TYPE_CONTROL_LAMP, 2, 0x112233, 90}; demo4_control_device(&cmd2);}
5. 函數(shù)指針數(shù)組
這個(gè)示例在上篇文章中演示過,為了完整性,這里再貼一下。
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; }
void demo5(){ 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));}
6. 在結(jié)構(gòu)體中使用柔性數(shù)組
先不解釋概念,我們先來看一個(gè)代碼示例:
// 一個(gè)結(jié)構(gòu)體,成員變量 data 是指針typedef struct _ArraryMemberStruct_NotGood_ { int num; char *data;} ArraryMemberStruct_NotGood;
void demo6_not_good(){ // 打印結(jié)構(gòu)體的內(nèi)存大小 int size = sizeof(ArraryMemberStruct_NotGood); printf("size = %d ", size);
// 分配一個(gè)結(jié)構(gòu)體指針 ArraryMemberStruct_NotGood *ams = (ArraryMemberStruct_NotGood *)malloc(size); ams->num = 1;
// 為結(jié)構(gòu)體中的 data 指針分配空間 ams->data = (char *)malloc(1024); strcpy(ams->data, "hello"); printf("ams->data = %s ", ams->data);
// 打印結(jié)構(gòu)體指針、成員變量的地址 printf("ams = 0x%x ", ams); printf("ams->num = 0x%x ", &ams->num); printf("ams->data = 0x%x ", ams->data);
// 釋放空間 free(ams->data); free(ams);}
在我的電腦上,打印結(jié)果如下:
可以看到:該結(jié)構(gòu)體一共有 8 個(gè)字節(jié)(int 型占 4 個(gè)字節(jié),指針型占 4 個(gè)字節(jié))。
結(jié)構(gòu)體中的 data 成員是一個(gè)指針變量,需要單獨(dú)為它申請(qǐng)一塊空間才可以使用。而且在結(jié)構(gòu)體使用之后,需要先釋放 data,然后釋放結(jié)構(gòu)體指針 ams,順序不能錯(cuò)。這樣使用起來,是不是有點(diǎn)麻煩?
于是,C99 標(biāo)準(zhǔn)就定義了一個(gè)語法:flexible array member(柔性數(shù)組),直接上代碼(下面的代碼如果編譯時(shí)遇到警告,請(qǐng)檢查下編譯器對(duì)這個(gè)語法的支持):
// 一個(gè)結(jié)構(gòu)體,成員變量是未指明大小的數(shù)組typedef struct _ArraryMemberStruct_Good_ { int num; char data[];} ArraryMemberStruct_Good;
void demo6_good(){ // 打印結(jié)構(gòu)體的大小 int size = sizeof(ArraryMemberStruct_Good); printf("size = %d ", size);
// 為結(jié)構(gòu)體指針分配空間 ArraryMemberStruct_Good *ams = (ArraryMemberStruct_Good *)malloc(size + 1024);
strcpy(ams->data, "hello"); printf("ams->data = %s ", ams->data);
// 打印結(jié)構(gòu)體指針、成員變量的地址 printf("ams = 0x%x ", ams); printf("ams->num = 0x%x ", &ams->num); printf("ams->data = 0x%x ", ams->data);
// 釋放空間 free(ams);}
打印結(jié)果如下:
與第一個(gè)例子中有下面幾個(gè)不同點(diǎn):
結(jié)構(gòu)體的大小變成了 4;為結(jié)構(gòu)體指針分配空間時(shí),除了結(jié)構(gòu)體本身的大小外,還申請(qǐng)了 data 需要的空間大;不需要為 data 單獨(dú)分配空間了;釋放空間時(shí),直接釋放結(jié)構(gòu)體指針即可;
是不是用起來簡單多了?!這就是柔性數(shù)組的好處。
從語法上來說,柔性數(shù)組就是指結(jié)構(gòu)體中最后一個(gè)元素個(gè)數(shù)未知的數(shù)組,也可以理解為長度為 0,那么就可以讓這個(gè)結(jié)構(gòu)體稱為可變長的。
前面說過,數(shù)組名就代表一個(gè)地址,是一個(gè)不變的地址常量。在結(jié)構(gòu)體中,數(shù)組名僅僅是一個(gè)符號(hào)而已,只代表一個(gè)偏移量,不會(huì)占用具體的空間。
另外,柔性數(shù)組可以是任意類型。這里示例大家多多體會(huì),在很多通訊類的處理場景中,常常見到這種用法。
7. 通過指針來獲取結(jié)構(gòu)體中成員變量的偏移量
這個(gè)標(biāo)題讀起來似乎有點(diǎn)拗口,拆分一下:在一個(gè)結(jié)構(gòu)體變量中,可以利用指針操作的技巧,獲取某個(gè)成員變量的地址、距離結(jié)構(gòu)體變量的開始地址、之間的偏移量。
在 Linux 內(nèi)核代碼中你可以看到很多地方都利用了這個(gè)技巧,代碼如下:
#define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE*)0)->MEMBER))
typedef struct _OffsetStruct_ { int a; int b; int c;} OffsetStruct;
void demo7(){ OffsetStruct os; // 打印結(jié)構(gòu)體變量、成員變量的地址 printf("&os = 0x%x ", &os); printf("&os->a = 0x%x ", &os.a(chǎn)); printf("&os->b = 0x%x ", &os.b); printf("&os->c = 0x%x ", &os.c); printf("===== "); // 打印成員變量地址,與結(jié)構(gòu)體變量開始地址,之間的偏移量 printf("offset: a = %d ", (char *)&os.a(chǎn) - (char *)&os); printf("offset: b = %d ", (char *)&os.b - (char *)&os); printf("offset: c = %d ", (char *)&os.c - (char *)&os); printf("===== "); // 通過指針的強(qiáng)制類型轉(zhuǎn)換來獲取偏移量 printf("offset: a = %d ", (size_t) &((OffsetStruct*)0)->a); printf("offset: b = %d ", (size_t) &((OffsetStruct*)0)->b); printf("offset: c = %d ", (size_t) &((OffsetStruct*)0)->c); printf("===== "); // 利用宏定義來得到成員變量的偏移量 printf("offset: a = %d ", offsetof(OffsetStruct, a)); printf("offset: b = %d ", offsetof(OffsetStruct, b)); printf("offset: c = %d ", offsetof(OffsetStruct, c));}
先來看打印結(jié)果:
前面 4 行的打印信息不需要解釋了,直接看下面這個(gè)內(nèi)存模型即可理解。
下面這個(gè)語句也不需要多解釋,就是把兩個(gè)地址的值進(jìn)行相減,得到距離結(jié)構(gòu)體變量開始地址的偏移量,注意:需要把地址強(qiáng)轉(zhuǎn)成 char* 型之后,才可以相減。
printf("offset: a = %d ", (char *)&os.a(chǎn) - (char *)&os);
下面這條語句需要好好理解:
printf("offset: a = %d ", (size_t) &((OffsetStruct*)0)->a);
數(shù)字 0 看成是一個(gè)地址,也就是一個(gè)指針。上篇文章解釋過,指針就代表內(nèi)存中的一塊空間,至于你把這塊空間里的數(shù)據(jù)看作是什么,這個(gè)隨便你,你只要告訴編譯器,編譯器就按照你的意思去操作這些數(shù)據(jù)。
現(xiàn)在我們把 0 這個(gè)地址里的數(shù)據(jù)看成是一個(gè) OffsetStruct 結(jié)構(gòu)體變量(通過強(qiáng)制轉(zhuǎn)換來告訴編譯器),這樣就得到了一個(gè) OffsetStruct 結(jié)構(gòu)體指針(下圖中綠色橫線),然后得到該指針變量中的成員變量 a(藍(lán)色橫線),再然后通過取地址符 & 得到 a 的地址(橙色橫線),最后把這個(gè)地址強(qiáng)轉(zhuǎn)成 size_t 類型(紅色橫線)。
因?yàn)檫@個(gè)結(jié)構(gòu)體指針變量是從 0 地址開始的,因此,成員變量 a 的地址就是 a 距離結(jié)構(gòu)體變量開始地址的偏移量。
上面的描述過程,如果感覺拗口,請(qǐng)結(jié)合下面這張圖再讀幾遍:
上面這張圖如果能看懂的話,那么最后一種通過宏定義獲取偏移量的打印語句也就明白了,無非就是把代碼抽象成宏定義了,方便調(diào)用:
#define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE*)0)->MEMBER))
printf("offset: a = %d ", offsetof(OffsetStruct, a));
可能有小伙伴提出:獲取這個(gè)偏移量有什么用。磕蔷驼(qǐng)接著看下面的示例 8。
8. 通過結(jié)構(gòu)體中成員變量的指針,來獲取該結(jié)構(gòu)體的指針
標(biāo)題同樣比較拗口,直接結(jié)合代碼來看:
typedef struct _OffsetStruct_ { int a; int b; int c;} OffsetStruct;
假設(shè)有一個(gè) OffsetStruct 結(jié)構(gòu)體變量 os,我們只知道 os 中成員變量 c 的地址(指針),那么我們想得到變量 os 的地址(指針),應(yīng)該怎么做?這就是標(biāo)題所描述的目的。
下面代碼中的宏定義 container_of 同樣是來自于 Linux 內(nèi)核中的(大家平常沒事時(shí)多挖掘,可以發(fā)現(xiàn)很多好東西)。
#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) );})
void demo8(){ // 下面 3 行僅僅是演示 typeof 關(guān)鍵字的用法 int n = 1; typeof(n) m = 2; // 定義相同類型的變量m printf("n = %d, m = %d ", n, m);
// 定義結(jié)構(gòu)體變量,并初始化 OffsetStruct os = {1, 2, 3}; // 打印結(jié)構(gòu)體變量的地址、成員變量的值(方便后面驗(yàn)證) printf("&os = 0x%x ", &os); printf("os.a(chǎn) = %d, os.b = %d, os.c = %d ", os.a(chǎn), os.b, os.c);
printf("===== "); // 假設(shè)只知道某個(gè)成員變量的地址 int *pc = &os.c; OffsetStruct *p = NULL; // 根據(jù)成員變量的地址,得到結(jié)構(gòu)體變量的地址 p = container_of(pc, OffsetStruct, c); // 打印指針的地址、成員變量的值 printf("p = 0x%x ", p); printf("p->a = %d, p->b = %d, p->c = %d ", p->a, p->b, p->c);}
先看打印結(jié)果:
首先要清楚宏定義中參數(shù)的類型:
ptr: 成員變量的指針;type: 結(jié)構(gòu)體類型;member:成員變量的名稱;
這里的重點(diǎn)就是理解宏定義 container_of,結(jié)合下面這張圖,把宏定義拆開來進(jìn)行描述:
宏定義中的第 1 條語句分析:
綠色橫線:把數(shù)字 0 看成是一個(gè)指針,強(qiáng)轉(zhuǎn)成結(jié)構(gòu)體 type 類型;藍(lán)色橫線:獲取該結(jié)構(gòu)體指針中的成員變量 member;橙色橫線:利用 typeof 關(guān)鍵字,獲取該 member 的類型,然后定義這個(gè)類型的一個(gè)指針變量 __mptr;紅色橫線:把宏參數(shù) ptr 賦值給 __mptr 變量;
宏定義中的第 2 條語句分析:
綠色橫線:利用 demo7 中的 offset 宏定義,得到成員變量 member 距離結(jié)構(gòu)體變量開始地址的偏移量,而這個(gè)成員變量指針剛才已經(jīng)知道了,就是 __mptr;藍(lán)色橫線:把 __mptr 這個(gè)地址,減去它自己距離結(jié)構(gòu)體變量開始地址的偏移量,就得到了該結(jié)構(gòu)體變量的開始地址;橙色橫線:最后把這個(gè)指針(此時(shí)是 char* 型),強(qiáng)轉(zhuǎn)成結(jié)構(gòu)體 type 類型的指針;
三、總結(jié)
上面這 8 個(gè)關(guān)于指針的用法掌握之后,再去處理子字符、數(shù)組、鏈表等數(shù)據(jù),基本上就是熟練度和工作量的問題了。希望大家都能用好指針這個(gè)神器,提高程序程序執(zhí)行效率。
面對(duì)代碼,永無bug;面對(duì)生活,春暖花開!祝您好運(yùn)!
原創(chuàng)不易,如果這篇文章有幫助,請(qǐng)轉(zhuǎn)發(fā)、分享給您的朋友,道哥在此表示感謝!
【原創(chuàng)聲明】
作者:道哥
發(fā)表評(píng)論
請(qǐng)輸入評(píng)論內(nèi)容...
請(qǐng)輸入評(píng)論/評(píng)論長度6~500個(gè)字
最新活動(dòng)更多
-
10月31日立即下載>> 【限時(shí)免費(fèi)下載】TE暖通空調(diào)系統(tǒng)高效可靠的組件解決方案
-
即日-11.13立即報(bào)名>>> 【在線會(huì)議】多物理場仿真助跑新能源汽車
-
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中國智造CIO在線峰會(huì)
-
即日-2025.8.1立即下載>> 《2024智能制造產(chǎn)業(yè)高端化、智能化、綠色化發(fā)展藍(lán)皮書》
推薦專題
- 1 【一周車話】沒有方向盤和踏板的車,你敢坐嗎?
- 2 特斯拉發(fā)布無人駕駛車,還未迎來“Chatgpt時(shí)刻”
- 3 特斯拉股價(jià)大跌15%:Robotaxi離落地還差一個(gè)蘿卜快跑
- 4 馬斯克給的“驚喜”夠嗎?
- 5 打完“價(jià)格戰(zhàn)”,大模型還要比什么?
- 6 馬斯克致敬“國產(chǎn)蘿卜”?
- 7 神經(jīng)網(wǎng)絡(luò),誰是盈利最強(qiáng)企業(yè)?
- 8 比蘋果偉大100倍!真正改寫人類歷史的智能產(chǎn)品降臨
- 9 諾獎(jiǎng)進(jìn)入“AI時(shí)代”,人類何去何從?
- 10 Open AI融資后成萬億獨(dú)角獸,AI人才之爭開啟
- 高級(jí)軟件工程師 廣東省/深圳市
- 自動(dòng)化高級(jí)工程師 廣東省/深圳市
- 光器件研發(fā)工程師 福建省/福州市
- 銷售總監(jiān)(光器件) 北京市/海淀區(qū)
- 激光器高級(jí)銷售經(jīng)理 上海市/虹口區(qū)
- 光器件物理工程師 北京市/海淀區(qū)
- 激光研發(fā)工程師 北京市/昌平區(qū)
- 技術(shù)專家 廣東省/江門市
- 封裝工程師 北京市/海淀區(qū)
- 結(jié)構(gòu)工程師 廣東省/深圳市