C指針的這些使用技巧,掌握后立刻提升一個(gè)Level
一、前言
半個(gè)月前寫(xiě)的那篇關(guān)于指針最底層原理的文章,得到了很多朋友的認(rèn)可(鏈接: C語(yǔ)言指針-從底層原理到花式技巧,用圖文和代碼幫你講解透徹),特別是對(duì)剛學(xué)習(xí)C語(yǔ)言的小伙伴來(lái)說(shuō),很容易就從根本上理解指針到底是什么、怎么用,這也讓我堅(jiān)信一句話;用心寫(xiě)出的文章,一定會(huì)被讀者感受到!在寫(xiě)這篇文章的時(shí)候,我列了一個(gè)提綱,寫(xiě)到后面的時(shí)候,發(fā)現(xiàn)已經(jīng)超過(guò)一萬(wàn)字了,但是提綱上還有最后一個(gè)主題沒(méi)有寫(xiě)。如果繼續(xù)寫(xiě)下去,文章體積就太大了,于是就留下了一個(gè)尾巴。
今天,我就把這個(gè)尾巴給補(bǔ)上去:主要是介紹指針在應(yīng)用程序的編程中,經(jīng)常使用的技巧。如果之前的那篇文章勉強(qiáng)算是“道”層面的話,那這篇文章就屬于“術(shù)”的層面。主要通過(guò) 8 個(gè)示例程序來(lái)展示在 C 語(yǔ)言應(yīng)用程序中,關(guān)于指針使用的常見(jiàn)套路,希望能給你帶來(lái)收獲。
記得我在校園里學(xué)習(xí)C語(yǔ)言的時(shí)候,南師大的黃鳳良老師花了大半節(jié)課的時(shí)間給我們解釋指針,現(xiàn)在最清楚地記得老師說(shuō)過(guò)的一句話就是:指針就是地址,地址就是指針!
二、八個(gè)示例
1. 開(kāi)胃菜:修改主調(diào)函數(shù)中的數(shù)據(jù)
- // 交換 2 個(gè) int 型數(shù)據(jù)
- void demo1_swap_data(int *a, int *b)
- {
- int tmp = *a;
- *a = *b;
- *b = tmp;
- }
- void demo1()
- {
- int i = 1;
- int j = 2;
- printf("before: i = %d, j = %d \n", i, j);
- demo1_swap_data(&i, &j);
- printf("after: i = %d, j = %d \n", i, j);
- }
這個(gè)代碼不用解釋了,大家一看就明白。如果再過(guò)多解釋的話,好像在侮辱智商。
2. 在被調(diào)用函數(shù)中,分配系統(tǒng)資源
代碼的目的是:在被調(diào)用函數(shù)中,從堆區(qū)分配 size 個(gè)字節(jié)的空間,返回給主調(diào)函數(shù)中的 pData 指針。
- void demo2_malloc_heap_error(char *buf, int size)
- {
- buf = (char *)malloc(size);
- printf("buf = 0x%x \n", buf);
- }
- void demo2_malloc_heap_ok(char **buf, int size)
- {
- *buf = (char *)malloc(size);
- printf("*buf = 0x%x \n", *buf);
- }
- void demo2()
- {
- int size = 1024;
- char *pData = NULL;
- // 錯(cuò)誤用法
- demo2_malloc_heap_error(pData, size);
- printf("&pData = 0x%x, pData = 0x%x \n", &pData, pData);
- // 正確用法
- demo2_malloc_heap_ok(&pData, size);
- printf("&pData = 0x%x, pData = 0x%x \n", &pData, pData);
- free(pData);
- }
2.1 錯(cuò)誤用法
剛進(jìn)入被調(diào)用函數(shù) demo2_malloc_heap_error 的時(shí)候,形參 buff 是一個(gè) char* 型指針,它的值等于 pData 變量的值,也就是說(shuō) buff 與 pData 的值相同(都為 NULL),內(nèi)存模型如圖:

在被調(diào)用函數(shù)中執(zhí)行 malloc 語(yǔ)句之后,從堆區(qū)申請(qǐng)得到的地址空間賦值給 buf,就是說(shuō)它就指向了這個(gè)新的地址空間,而 pData 里仍然是NULL,內(nèi)存模型如下:

從圖中可以看到,pData 的內(nèi)存中一直是 NULL,沒(méi)有指向任何堆空間。另外,由于形參 buf 是放在函數(shù)的棧區(qū)的,從被調(diào)函數(shù)中返回的時(shí)候,堆區(qū)這塊申請(qǐng)的空間就被泄漏了。
2.2 正確用法
剛進(jìn)入被調(diào)用函數(shù) demo2_malloc_heap_error 的時(shí)候,形參 buf 是一個(gè) char* 型的二級(jí)指針,就是說(shuō) buf 里的值是另一個(gè)指針變量的地址,在這個(gè)示例中 buf 里的值就是 pData 這個(gè)指針變量的地址,內(nèi)存模型如下:

在被調(diào)用函數(shù)中執(zhí)行 malloc 語(yǔ)句之后,從堆區(qū)申請(qǐng)得到的地址空間賦值給 *buf,因?yàn)?buf = &pData,所以 *buf 就相當(dāng)于是 pData,那么從堆區(qū)申請(qǐng)得到的地址空間就賦值 pData 變量,內(nèi)存模型如下:

從被調(diào)函數(shù)中返回之后,pData 就正確的得到了一塊堆空間,別忘了使用之后要主動(dòng)釋放。
3. 傳遞函數(shù)指針
從上篇文章中我們知道,函數(shù)名本身就代表一個(gè)地址,在這個(gè)地址中存儲(chǔ)著函數(shù)體中定義的一連串指令碼,只要給這個(gè)地址后面加上一個(gè)調(diào)用符(小括號(hào)),就進(jìn)入這個(gè)函數(shù)中執(zhí)行。在實(shí)際程序中,函數(shù)名常常作為函數(shù)參數(shù)來(lái)進(jìn)行傳遞。
熟悉C++的小伙伴都知道,在標(biāo)準(zhǔn)庫(kù)中對(duì)容器類(lèi)型的數(shù)據(jù)進(jìn)行各種算法操作時(shí),可以傳入用戶(hù)自己的提供的算法函數(shù)(如果不傳入函數(shù),標(biāo)準(zhǔn)庫(kù)就使用默認(rèn)的)。
下面是一個(gè)示例代碼,對(duì)一個(gè) int 行的數(shù)組進(jìn)行排序,排序函數(shù) demo3_handle_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_handle_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_handle_data(a, size, demo3_algorithm_decend); // 降序排列
- demo3_handle_data(a, size, demo3_algorithm_ascend); // 升序排列
- for (int i = 0; i < size; ++i)
- printf("%d ", a[i]);
- printf("\n");
- }
這個(gè)就不用畫(huà)圖了,函數(shù)指針 pf 就指向了傳入的那個(gè)函數(shù)地址,在排序的時(shí)候
直接調(diào)用就可以了。
4. 指向結(jié)構(gòu)體的指針
在嵌入式開(kāi)發(fā)中,指向結(jié)構(gòu)體的指針使用特別廣泛,這里以智能家居中的一條控制指令來(lái)舉例。在一個(gè)智能家居系統(tǒng)中,存在各種各樣的設(shè)備(插座、電燈、電動(dòng)窗簾等),每個(gè)設(shè)備的控制指令都是不一樣的,因此可以在每個(gè)設(shè)備的控制指令結(jié)構(gòu)體中的最前面,放置所有指令都需要的、通用的成員變量,這些變量可以稱(chēng)為指令頭(指令頭中包含一個(gè)代表命令類(lèi)型的枚舉變量)。
當(dāng)處理一條控制指令時(shí),先用一個(gè)通用命令(指令頭)的指針來(lái)接收指令,然后根據(jù)命令類(lèi)型枚舉變量來(lái)區(qū)分,把控制指令強(qiáng)制轉(zhuǎn)換成具體的那個(gè)設(shè)備的數(shù)據(jù)結(jié)構(gòu),這樣就可以獲取到控制指令中特定的控制數(shù)據(jù)了。
本質(zhì)上,與 Java/C++ 中的接口、基類(lèi)的概念類(lèi)似。
- // 指令類(lèi)型枚舉
- 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; // 指令類(lèi)型
- 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:斷開(kāi), 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ù)指令頭中的命令類(lèi)型,把指令強(qiáng)制轉(zhuǎn)換成具體設(shè)備的指令
- if (CMD_TYPE_CONTROL_SWITCH == pcmd->cmdType)
- {
- // 類(lèi)型強(qiáng)制轉(zhuǎn)換
- CmdControlSwitch *cmd = pcmd;
- printf("control switch. slot = %d, state = %d \n", cmd->slot, cmd->state);
- }
- else if (CMD_TYPE_CONTROL_LAMP == pcmd->cmdType)
- {
- // 類(lèi)型強(qiáng)制轉(zhuǎn)換
- CmdControlLamp * cmd = pcmd;
- printf("control lamp. color = 0x%x, brightness = %d \n", cmd->color, cmd->brightness);
- }
- }
- void demo4()
- {
- // 指令1:控制一個(gè)開(kāi)關(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è)示例在上篇文章中演示過(guò),為了完整性,這里再貼一下。
- 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 \n", a, b, p[0](a, b));
- printf("%d - %d = %d \n", a, b, p[1](a, b));
- printf("%d * %d = %d \n", a, b, p[2](a, b));
- printf("%d / %d = %d \n", a, b, p[3](a, b));
- }
6. 在結(jié)構(gòu)體中使用柔性數(shù)組
先不解釋概念,我們先來(lái)看一個(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 \n", size);
- // 分配一個(gè)結(jié)構(gòu)體指針
- ArraryMemberStruct_NotGood *ams = (ArraryMemberStruct_NotGood *)malloc(size);
- ams->num = 1;
- // 為結(jié)構(gòu)體中的 data 指針?lè)峙淇臻g
- ams->data = (char *)malloc(1024);
- strcpy(ams->data, "hello");
- printf("ams->data = %s \n", ams->data);
- // 打印結(jié)構(gòu)體指針、成員變量的地址
- printf("ams = 0x%x \n", ams);
- printf("ams->num = 0x%x \n", &ams->num);
- printf("ams->data = 0x%x \n", 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ò)。這樣使用起來(lái),是不是有點(diǎn)麻煩?
于是,C99 標(biāo)準(zhǔn)就定義了一個(gè)語(yǔ)法:flexible array member(柔性數(shù)組),直接上代碼(下面的代碼如果編譯時(shí)遇到警告,請(qǐng)檢查下編譯器對(duì)這個(gè)語(yǔ)法的支持):
- // 一個(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 \n", size);
- // 為結(jié)構(gòu)體指針?lè)峙淇臻g
- ArraryMemberStruct_Good *ams = (ArraryMemberStruct_Good *)malloc(size + 1024);
- strcpy(ams->data, "hello");
- printf("ams->data = %s \n", ams->data);
- // 打印結(jié)構(gòu)體指針、成員變量的地址
- printf("ams = 0x%x \n", ams);
- printf("ams->num = 0x%x \n", &ams->num);
- printf("ams->data = 0x%x \n", ams->data);
- // 釋放空間
- free(ams);
- }
打印結(jié)果如下:

與第一個(gè)例子中有下面幾個(gè)不同點(diǎn):
- 結(jié)構(gòu)體的大小變成了 4;
- 為結(jié)構(gòu)體指針?lè)峙淇臻g時(shí),除了結(jié)構(gòu)體本身的大小外,還申請(qǐng)了 data 需要的空間大小;
- 不需要為 data 單獨(dú)分配空間了;
- 釋放空間時(shí),直接釋放結(jié)構(gòu)體指針即可;
是不是用起來(lái)簡(jiǎn)單多了?!這就是柔性數(shù)組的好處。
- 從語(yǔ)法上來(lái)說(shuō),柔性數(shù)組就是指結(jié)構(gòu)體中最后一個(gè)元素個(gè)數(shù)未知的數(shù)組,也可以理解為長(zhǎng)度為 0,那么就可以讓這個(gè)結(jié)構(gòu)體稱(chēng)為可變長(zhǎng)的。
- 前面說(shuō)過(guò),數(shù)組名就代表一個(gè)地址,是一個(gè)不變的地址常量。在結(jié)構(gòu)體中,數(shù)組名僅僅是一個(gè)符號(hào)而已,只代表一個(gè)偏移量,不會(huì)占用具體的空間。
另外,柔性數(shù)組可以是任意類(lèi)型。這里示例大家多多體會(huì),在很多通訊類(lèi)的處理場(chǎng)景中,常常見(jiàn)到這種用法。
7. 通過(guò)指針來(lái)獲取結(jié)構(gòu)體中成員變量的偏移量
這個(gè)標(biāo)題讀起來(lái)似乎有點(diǎn)拗口,拆分一下:在一個(gè)結(jié)構(gòu)體變量中,可以利用指針操作的技巧,獲取某個(gè)成員變量的地址、距離結(jié)構(gòu)體變量的開(kāi)始地址、之間的偏移量。
在 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 \n", &os);
- printf("&os->a = 0x%x \n", &os.a);
- printf("&os->b = 0x%x \n", &os.b);
- printf("&os->c = 0x%x \n", &os.c);
- printf("===== \n");
- // 打印成員變量地址,與結(jié)構(gòu)體變量開(kāi)始地址,之間的偏移量
- printf("offset: a = %d \n", (char *)&os.a - (char *)&os);
- printf("offset: b = %d \n", (char *)&os.b - (char *)&os);
- printf("offset: c = %d \n", (char *)&os.c - (char *)&os);
- printf("===== \n");
- // 通過(guò)指針的強(qiáng)制類(lèi)型轉(zhuǎn)換來(lái)獲取偏移量
- printf("offset: a = %d \n", (size_t) &((OffsetStruct*)0)->a);
- printf("offset: b = %d \n", (size_t) &((OffsetStruct*)0)->b);
- printf("offset: c = %d \n", (size_t) &((OffsetStruct*)0)->c);
- printf("===== \n");
- // 利用宏定義來(lái)得到成員變量的偏移量
- printf("offset: a = %d \n", offsetof(OffsetStruct, a));
- printf("offset: b = %d \n", offsetof(OffsetStruct, b));
- printf("offset: c = %d \n", offsetof(OffsetStruct, c));
- }
先來(lái)看打印結(jié)果:
前面 4 行的打印信息不需要解釋了,直接看下面這個(gè)內(nèi)存模型即可理解。

下面這個(gè)語(yǔ)句也不需要多解釋?zhuān)褪前褍蓚€(gè)地址的值進(jìn)行相減,得到距離結(jié)構(gòu)體變量開(kāi)始地址的偏移量,注意:需要把地址強(qiáng)轉(zhuǎn)成 char* 型之后,才可以相減。
- printf("offset: a = %d \n", (char *)&os.a - (char *)&os);
下面這條語(yǔ)句需要好好理解:
- printf("offset: a = %d \n", (size_t) &((OffsetStruct*)0)->a);
數(shù)字 0 看成是一個(gè)地址,也就是一個(gè)指針。上篇文章解釋過(guò),指針就代表內(nèi)存中的一塊空間,至于你把這塊空間里的數(shù)據(jù)看作是什么,這個(gè)隨便你,你只要告訴編譯器,編譯器就按照你的意思去操作這些數(shù)據(jù)。
現(xiàn)在我們把 0 這個(gè)地址里的數(shù)據(jù)看成是一個(gè) OffsetStruct 結(jié)構(gòu)體變量(通過(guò)強(qiáng)制轉(zhuǎn)換來(lái)告訴編譯器),這樣就得到了一個(gè) OffsetStruct 結(jié)構(gòu)體指針(下圖中綠色橫線),然后得到該指針變量中的成員變量 a(藍(lán)色橫線),再然后通過(guò)取地址符 & 得到 a 的地址(橙色橫線),最后把這個(gè)地址強(qiáng)轉(zhuǎn)成 size_t 類(lèi)型(紅色橫線)。
因?yàn)檫@個(gè)結(jié)構(gòu)體指針變量是從 0 地址開(kāi)始的,因此,成員變量 a 的地址就是 a 距離結(jié)構(gòu)體變量開(kāi)始地址的偏移量。
上面的描述過(guò)程,如果感覺(jué)拗口,請(qǐng)結(jié)合下面這張圖再讀幾遍:

上面這張圖如果能看懂的話,那么最后一種通過(guò)宏定義獲取偏移量的打印語(yǔ)句也就明白了,無(wú)非就是把代碼抽象成宏定義了,方便調(diào)用:
- #define offsetof(TYPE, MEMBER) ((size_t) &(((TYPE*)0)->MEMBER))
- printf("offset: a = %d \n", offsetof(OffsetStruct, a));
可能有小伙伴提出:獲取這個(gè)偏移量有什么用啊?那就請(qǐng)接著看下面的示例 8。
8. 通過(guò)結(jié)構(gòu)體中成員變量的指針,來(lái)獲取該結(jié)構(gòu)體的指針
標(biāo)題同樣比較拗口,直接結(jié)合代碼來(lái)看:
- 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 同樣是來(lái)自于 Linux 內(nèi)核中的(大家平常沒(mé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; // 定義相同類(lèi)型的變量m
- printf("n = %d, m = %d \n", n, m);
- // 定義結(jié)構(gòu)體變量,并初始化
- OffsetStruct os = {1, 2, 3};
- // 打印結(jié)構(gòu)體變量的地址、成員變量的值(方便后面驗(yàn)證)
- printf("&os = 0x%x \n", &os);
- printf("os.a = %d, os.b = %d, os.c = %d \n", os.a, os.b, os.c);
- printf("===== \n");
- // 假設(shè)只知道某個(gè)成員變量的地址
- int *pc = &os.c;
- OffsetStruct *p = NULL;
- // 根據(jù)成員變量的地址,得到結(jié)構(gòu)體變量的地址
- p = container_of(pc, OffsetStruct, c);
- // 打印指針的地址、成員變量的值
- printf("p = 0x%x \n", p);
- printf("p->a = %d, p->b = %d, p->c = %d \n", p->a, p->b, p->c);
- }
先看打印結(jié)果:

首先要清楚宏定義中參數(shù)的類(lèi)型:
- ptr: 成員變量的指針;
- type: 結(jié)構(gòu)體類(lèi)型;
- member:成員變量的名稱(chēng);
這里的重點(diǎn)就是理解宏定義 container_of,結(jié)合下面這張圖,把宏定義拆開(kāi)來(lái)進(jìn)行描述:

宏定義中的第 1 條語(yǔ)句分析:
- 綠色橫線:把數(shù)字 0 看成是一個(gè)指針,強(qiáng)轉(zhuǎn)成結(jié)構(gòu)體 type 類(lèi)型;
- 藍(lán)色橫線:獲取該結(jié)構(gòu)體指針中的成員變量 member;
- 橙色橫線:利用 typeof 關(guān)鍵字,獲取該 member 的類(lèi)型,然后定義這個(gè)類(lèi)型的一個(gè)指針變量 __mptr;
- 紅色橫線:把宏參數(shù) ptr 賦值給 __mptr 變量;
宏定義中的第 2 條語(yǔ)句分析:
- 綠色橫線:利用 demo7 中的 offset 宏定義,得到成員變量 member 距離結(jié)構(gòu)體變量開(kāi)始地址的偏移量,而這個(gè)成員變量指針剛才已經(jīng)知道了,就是 __mptr;
- 藍(lán)色橫線:把 __mptr 這個(gè)地址,減去它自己距離結(jié)構(gòu)體變量開(kāi)始地址的偏移量,就得到了該結(jié)構(gòu)體變量的開(kāi)始地址;
- 橙色橫線:最后把這個(gè)指針(此時(shí)是 char* 型),強(qiáng)轉(zhuǎn)成結(jié)構(gòu)體 type 類(lèi)型的指針;
三、總結(jié)
上面這 8 個(gè)關(guān)于指針的用法掌握之后,再去處理子字符、數(shù)組、鏈表等數(shù)據(jù),基本上就是熟練度和工作量的問(wèn)題了。希望大家都能用好指針這個(gè)神器,提高程序程序執(zhí)行效率。