習(xí)———指針)
前言本篇學(xué)習(xí)指針的相關(guān)內(nèi)容包括內(nèi)存和地址的相關(guān)概念指針基本概念定義調(diào)用數(shù)組與指針函數(shù)與指針二級(jí)指針野指針與懸空指針萬(wàn)能指針等等相關(guān)內(nèi)容。以及C語(yǔ)言的右左法則來(lái)輔助理解。一、基本概念1.1 內(nèi)存與地址內(nèi)存可以看作一列大小相等的格子他們連成一條“內(nèi)存條”每個(gè)格子內(nèi)能夠存放兩個(gè)16進(jìn)制大小的數(shù)據(jù)即8bit數(shù)據(jù)每個(gè)格子又有一個(gè)地址編號(hào)記錄他們存在的位置。就像一個(gè)單元樓每層占用的地方一樣大內(nèi)部如何構(gòu)造存儲(chǔ)不同數(shù)據(jù)它的大小是固定的每層都有一個(gè)地址編號(hào)方便我們直接找到要去的地方。指針就是一種記錄地址并指向地址所在位置的類型。1.2 指針類型指針是一種專門用來(lái)處理“地址”的特殊數(shù)據(jù)類型本質(zhì)就是地址地址就是內(nèi)存單元的編號(hào)。1.3 定義格式基類型 *指針變量名基類型基類型表示的是指針?biāo)赶虻牡刂匪诳臻g的數(shù)據(jù)的類型。*在指針定義時(shí)是類型修飾符用來(lái)表示這個(gè)變量是指針類型。在運(yùn)算時(shí)是解引用運(yùn)算。指針變量名命名規(guī)則與標(biāo)識(shí)符一致。// 舉例 int? p null; /* 表示定義了一個(gè)指針變量 p指向一個(gè)地址這個(gè)地址上的空間存放的是 int 型數(shù)據(jù)。*/1.4 指針的大小需要注意指針是一種“地址”類型它的大小與基類型無(wú)關(guān)它的大小與操作系統(tǒng)的地址總線相關(guān)。一個(gè)32位操作系統(tǒng)的地址總線位32位即4字節(jié)1字節(jié)8bit32位--32/84字節(jié)所以它的指針大小為4字節(jié)大小固定不變。同理64位操作系統(tǒng)的指針大小位8字節(jié)。1.5 解引用 *解引用分為三步且解引用的運(yùn)算數(shù)類型必須為指針類型。1找通過(guò)指針找到地址所在位置2偏移通過(guò)基類型的大小圈定內(nèi)存。如int型4字節(jié)就取4個(gè)字節(jié)的大小。3用將地址內(nèi)的數(shù)據(jù)當(dāng)作對(duì)應(yīng)基類型的變量操作。1.6 實(shí)現(xiàn)被調(diào)修改主調(diào)指針可以實(shí)現(xiàn)被調(diào)修改主調(diào)。C語(yǔ)言中的傳遞都是值傳遞如果是一個(gè)普通變量通過(guò)值傳遞到函數(shù)內(nèi)后函數(shù)內(nèi)的變量接收這個(gè)值而這個(gè)變量在函數(shù)內(nèi)的生命周期僅限變量定義到函數(shù)結(jié)束的過(guò)程所以無(wú)法做到普通變量的值修改。指針變量卻不同指針傳遞的是地址的值。只要有地址就能找到對(duì)應(yīng)的數(shù)據(jù)就能直接對(duì)數(shù)據(jù)進(jìn)行修改。1.7 野指針與懸空指針野指針沒(méi)有指向過(guò)有效內(nèi)存地址是亂的。常見(jiàn)于沒(méi)有初始化指針。懸空指針指向過(guò)有效內(nèi)存但內(nèi)存沒(méi)了指針還留著舊地址。常見(jiàn)于指針指向的地址釋放后堆內(nèi)存釋放未置空。解析舉例一個(gè)人A和另一個(gè)人B見(jiàn)面A把B的樣子記住了B走后A可以隨意修改他記住的B的樣貌對(duì)B沒(méi)有任何影響這就是普通變量的值傳遞。然后A在名片上知道了B的住址他通過(guò)住址指針找到了B并在B的臉上亂涂亂畫直接對(duì)B造成了影響這就是指針的被調(diào)修改主調(diào)通過(guò)一個(gè)住址就能對(duì)B進(jìn)行修改。后來(lái)A不小心把名片搞臟了地址發(fā)生了改變變成一個(gè)并不存在的地址。如果A沒(méi)有驗(yàn)證就按照這個(gè)地址去找會(huì)隨機(jī)找到一個(gè)地址這個(gè)地址是不確定的。即指針未初始化地址值是隨機(jī)的屬于野指針。后來(lái)A找回了B的地址但是B已經(jīng)搬走了換成了別的人住此時(shí)A再按照地址找雖然能找到那個(gè)房子但是找不到B了。即指針指向的地址內(nèi)的數(shù)據(jù)被釋放了給了“別人”但是指針還是指向那個(gè)地址此時(shí)指針屬于懸空指針。1.8 萬(wàn)能指針 void *沒(méi)有定義指針的類型可以傳遞所有類型。然后通過(guò)強(qiáng)制轉(zhuǎn)換得到需要的類型。不能直接引用必須強(qiáng)制轉(zhuǎn)換。1.9 指針的基礎(chǔ)運(yùn)算單個(gè)指針可以進(jìn)行加減操作兩個(gè)指針之間只能在特定情況下做減法運(yùn)算。其余的都是未定義行為。指針pi表示p移動(dòng)i個(gè)基類型的地址p-i同理p1-p2表示兩個(gè)指針地址之間的差值。解析就類似一列相同的屋子的地址A、B、C他的占地大小我們就可以說(shuō)是基類型大小。我們可以說(shuō)A的前面第X個(gè)屋子是B后面第X個(gè)屋子是C但不能說(shuō)A*X有什么含義。這是無(wú)法定義的。兩個(gè)屋子AB之間相減我們可以得到他們相距幾個(gè)屋子但相加也是沒(méi)有什么意義的。二、數(shù)組與指針2.1 指針傳遞數(shù)組的原因當(dāng)我們需要傳遞一個(gè)數(shù)組數(shù)據(jù)時(shí)如果按照以前的方法 將數(shù)組數(shù)據(jù)通過(guò)循環(huán)一個(gè)個(gè)傳遞到函數(shù)內(nèi)這樣的方式即麻煩又占用空間類比到其他的數(shù)據(jù)如果每次調(diào)用數(shù)據(jù)時(shí)都要傳遞他們的值是非常影響效率和空間的所以我們使用指針只需要傳遞他們的地址知道他們占用了多少空間就可以取得里面的數(shù)據(jù)。利用指針傳遞的方式提高了效率降低了空間占用率。2.2 整型一維數(shù)組指針在學(xué)習(xí)函數(shù)時(shí)我們知道數(shù)組的變量名代表了首元素地址而只要知道數(shù)組的長(zhǎng)度就能得到數(shù)組的所有元素。2.2.1 定義所以我們可以直接定義一個(gè)指針變量記錄數(shù)組的首元素地址int[] a new int[10]; int[] p a;2.2.2 取值我們又知道數(shù)組的其他元素的下標(biāo)含義就是距離首元素的距離。那么我們可以得到// 在 C# 中數(shù)組下標(biāo)即元素位置 p a; // 首元素引用 p[1] a[1]; p[2] a[2]; // ……此時(shí)我們做到了用指針來(lái)表示數(shù)組。但這只是找到了地址如果需要使用地址里的值還需要進(jìn)行解引用運(yùn)算 *(p 1) a[1]。那么我們只需要傳入指針和數(shù)組的長(zhǎng)度就能在函數(shù)里對(duì)數(shù)組進(jìn)行修改了。此處需要注意指針變量的大小是固定的和操作系統(tǒng)有關(guān)。所以使用sizeof取得的值也是固定的這個(gè)值并不是數(shù)組的長(zhǎng)度。2.2.3 示例// 遍歷打印 static void PrintArray(int[] arr) { foreach (int item in arr) { Console.Write(${item,3}); } Console.WriteLine(); }// 整型一維數(shù)組求最大值 static int MaxArray(int[] arr) { int max arr[0]; foreach (int item in arr) { if (item max) max item; } return max; }//逆序 void reverseArray(int *begin, int *end) { while (begin end) { swap(begin, end); begin; --end; } }//選擇排序 void selectSort(int *begin, int *end) { while (begin end) { int *i begin; while (i end) { if (*i *begin) swap(i, begin); i; } begin; } }//冒泡排序 void bubbleSort(int *begin, int *end) { while (begin end) { int *i begin; while (i end) { if (*i *(i 1)) swap(i, i 1); i; } --end; } }//插入排序 void insertSort(int *begin, int *end) { int *p; for (p begin 1; p end; p) { int t *p; int *q p; while (q begin *(q - 1) t) { *q *(q - 1); --q; } *q t; } }//二分查找 int *binaryFindIter(int *begin, int *end, int f) { while (begin end) { int *mid begin (end - begin) / 2; if (*mid f) end mid - 1; else if (*mid f) begin mid 1; else return mid; } return NULL; }//二分查找遞歸 int *binaryFindRec(int *begin, int *end, int f) { if (begin end) return NULL; int *mid begin (end - begin) / 2; if (*mid f) return binaryFindRec(begin, mid - 1, f); else if (*mid f) return binaryFindRec(mid 1, end, f); else return mid; }//快速排序 void quickSort(int *begin, int *end) { if (end begin) return; int *p begin; int *q end; int *k begin; while (begin end) { while (begin end *end *k) --end; while (begin end *begin *k) begin; swap(begin, end); } swap(k, begin); quickSort(p, end - 1); quickSort(begin 1, q); }2.3 字符型一維數(shù)組指針與整型數(shù)組同理但如果是單個(gè)字符的字符型我們按整型的傳入首地址指針和長(zhǎng)度就可以。但是如果是字符串字符串有結(jié)束標(biāo)志\0那么就不用傳入數(shù)組長(zhǎng)度直接判斷到結(jié)束標(biāo)志時(shí)結(jié)束就可以了。2.3.1 const與字符串常量區(qū)問(wèn)題1字符串常量區(qū)在進(jìn)行字符串處理時(shí)會(huì)出現(xiàn)報(bào)錯(cuò)原因是字符串常量區(qū)的字符串是不可修改的只讀類型的常量。字符串常量區(qū)指的是當(dāng)使用 雙引號(hào)定義字符串時(shí)這個(gè)定義出來(lái)的字符串存儲(chǔ)的位置在字符串常量區(qū)如果直接使用指針引用這個(gè)字符串的地址指向的是常量區(qū)地址是無(wú)法修改的所以會(huì)報(bào)錯(cuò)。而數(shù)組在定義時(shí)會(huì)先開(kāi)辟一塊數(shù)組大小的空間然后進(jìn)行賦值運(yùn)算將字符串常量區(qū)的值賦值到數(shù)組中。char *s hello; //錯(cuò)誤 char s[10] hello;//正確2const關(guān)鍵字修飾在C語(yǔ)言中存在關(guān)鍵字const可以限定某個(gè)變量為只讀模式。存在就近原則。const char *p限定 *p 只讀內(nèi)容不可改 char * const p限定 p 只讀指向不可改 const char * const p指向和內(nèi)容都不可改。當(dāng)存在只讀數(shù)據(jù)時(shí)盡量都加上const修飾防止修改不能修改的數(shù)據(jù)造成錯(cuò)誤。2.3.2 示例//遍歷 void printStr(char *s) { while (*s ! \0) { putchar(*s); } putchar(\n); }//求字符串長(zhǎng)度 int Strlen(const char *s) { const char *p s; while (*p ! \0) p; return (int)(p - s); }//strcpy char *Strcpy(char *dest, const char *src) { char *ret dest; while ((*dest *src) ! \0) { dest; src; } return ret; }//strcat char *Strcat(char *dest, const char *src) { char *ret dest; while (*dest ! \0) dest; while (*src ! \0) *dest *src; *dest \0; return ret; }//strcmp int Strcmp(const char *s1, const char *s2) { while (*s1 *s2 *s1 ! \0) { s1; s2; } return *s1 - *s2; }//strncmp int Strncmp(const char *s1, const char *s2, int n) { if (n 0) return 0; while (n 1 *s1 *s2 *s1 ! \0) { s1; s2; --n; } return *s1 - *s2; }//逆序 void reverseStr(char *begin, char *end) { while (begin end) { char t *begin; *begin *end; *end-- t; } }//字符轉(zhuǎn)整數(shù) int strToInt(char *begin, char *end) { if (end begin) return *end - 0; return (*end - 0) strToInt(begin, end - 1) * 10; }//單詞逆序 void reverseWords(char *begin, char *end) { reverseStr(begin, end); char *left begin, *right begin; while (right end 1) { if (*right || *right \0) { reverseStr(left, right - 1); left right 1; } right; } }2.4 整型二維數(shù)組指針2.4.1 定義格式int *p[n] 本質(zhì)是int[n] *p 基類型為數(shù)組類型 數(shù)組指針指向一維數(shù)組的指針2.4.2 取值定義時(shí)p 表示一個(gè)變量元素。*p 表示一個(gè)數(shù)組內(nèi)的變量元素。**p 表示一個(gè)數(shù)組的數(shù)組的變量元素就是二維變量元素取值時(shí)p 表示二維數(shù)組首地址*p 表示行首元素的數(shù)據(jù)**p 表示數(shù)組元素?cái)?shù)據(jù)pi 第i行的整行地址*pi第i行的首元素地址*pi 第一行首元素的下i個(gè)元素地址*pij i行j列的元素地址**pij i行j列的元素值二維數(shù)組理解為一維數(shù)組的數(shù)組a[2][3]表示兩行三列可以理解成{a1[3],a2[3]}兩個(gè)長(zhǎng)度為3的數(shù)組在一個(gè)數(shù)組里。而一個(gè)a[3]里又有三個(gè)元素。類似于這樣a0[3]----a00 a01 a02a1[3]----a10 a11 a12取值時(shí)對(duì)p運(yùn)算相當(dāng)于二維數(shù)組中的行地址運(yùn)算和一維數(shù)組移動(dòng)類似,就是移動(dòng)行。對(duì)*p運(yùn)算相當(dāng)于對(duì)行地址里的數(shù)據(jù)進(jìn)行運(yùn)算就是對(duì)列地址運(yùn)算。對(duì)**p運(yùn)算相當(dāng)于再去列地址里的數(shù)據(jù)進(jìn)行運(yùn)算就是二維數(shù)組的每個(gè)元素進(jìn)行運(yùn)算了。2.4.3 示例//初始化 int a[3][4] {1,2,3,4,5,6,7,8,9,10,11,12}; int (*p)[4] a; // p 指向 a[0] 這一行 // *(*(pi)j) 等價(jià)于 a[i][j]//遍歷 void printMatrix(int (*a)[4], int rows) { for (int i 0; i rows; i) { for (int j 0; j 4; j) { printf(%3d, *(*(a i) j)); } putchar(\n); } }//指針函數(shù)求最大值 int *maxMatrix(int (*a)[4], int rows) { int *ret *a; for (int i 0; i rows; i) for (int j 0; j 4; j) if (*ret *(*(a i) j)) ret *(a i) j; return ret; }2.5 字符型二維數(shù)組指針字符型的二維數(shù)組在利用指針傳遞時(shí)如果是單個(gè)字符元素和整型相似。如果是字符串就相當(dāng)于基類型為字符串的一維數(shù)組。char s[ 5 ][ 10 ]; char *p[ 10 ] s; 指針表示指向一個(gè)一維數(shù)組類型存放的基類型是長(zhǎng)度10以內(nèi)的字符串?dāng)?shù)據(jù)只需要對(duì)*p運(yùn)算就能得到某一行的字符串?dāng)?shù)據(jù)。2.5.1 示例//定義 char s[5][10] {12,34,56,78,910}; char (*p)[10] s; // *(pi) 是第 i 行首地址類型 char*//遍歷 void printStrMatrix(char (*p)[10], int rows) { for (int i 0; i rows; i) { printf(%s\n, *(p i)); } }2.6 指針數(shù)組指針數(shù)組表示一個(gè)數(shù)組數(shù)組內(nèi)的基類型是指針類型。2.6.1 示例//遍歷 void printPtrArray(char *s[], int len) { for (int i 0; i len; i) printf(%s\n, s[i]); }//逆序交換指針 void reversePtrArray(char *s[], int len) { for (int i 0; i len / 2; i) { char *t s[i]; s[i] s[len - 1 - i]; s[len - 1 - i] t; } }//選擇排序。交換指針 void selectSortStr(char *s[], int len) { for (int i 0; i len - 1; i) { int min i; for (int j i 1; j len; j) if (strcmp(s[j], s[min]) 0) min j; char *t s[i]; s[i] s[min]; s[min] t; } }//冒泡排序。交換指針 void bubbleSortStr(char *s[], int len) { for (int i 0; i len - 1; i) for (int j 0; j len - 1 - i; j) if (strcmp(s[j], s[j 1]) 0) { char *t s[j]; s[j] s[j 1]; s[j 1] t; } }//插入排序。交換指針 void insertSortStr(char *s[], int len) { for (int i 1; i len; i) { char *k s[i]; int j i - 1; while (j 0 strcmp(s[j], k) 0) { s[j 1] s[j]; --j; } s[j 1] k; } }//找最大字符串 char *maxStr(char **s, int len) { char *ret *s; for (int i 0; i len; i) if (strcmp(ret, *(s i)) 0) ret *(s i); return ret; }三、函數(shù)與指針3.1 函數(shù)指針函數(shù)指針表示一個(gè)指針指向了一個(gè)函數(shù)。3.1.1 定義int (*p)int int 表示一個(gè)指針變量。指向返回值為int類型帶有兩個(gè)int類型形參的函數(shù)函數(shù)名表示了它的地址類似數(shù)組去掉標(biāo)識(shí)符剩下的就是函數(shù)的返回值類型。3.1.2 函數(shù)回調(diào)把函數(shù)的地址傳遞到另一個(gè)函數(shù)。應(yīng)用場(chǎng)景通用算法qsortbsearch事件驅(qū)動(dòng)編程底層開(kāi)發(fā)GUI編程按鈕點(diǎn)擊事件中斷處理狀態(tài)機(jī)與命令模式經(jīng)典示例qsortvoid qsort(void *base, size_t nmemb, size_t size,int (*compar)(const void *, const void *));描述qsort() 函數(shù)用于對(duì)一個(gè)包含 nmemb 個(gè)元素、每個(gè)元素大小為 size 的數(shù)組進(jìn)行排序。base 參數(shù)指向數(shù)組的起始位置。數(shù)組中的內(nèi)容將根據(jù)比較函數(shù)進(jìn)行升序排序該比較函數(shù)由 compar 指向并通過(guò)兩個(gè)指向被比較對(duì)象的參數(shù)調(diào)用。比較函數(shù)必須返回一個(gè)整數(shù)值當(dāng)?shù)谝粋€(gè)參數(shù)被認(rèn)為小于、等于或大于第二個(gè)參數(shù)時(shí)分別返回小于零、等于零或大于零的值。如果兩個(gè)元素在比較中被視為相等則它們?cè)谂判蚝蟮臄?shù)組中的順序是未定義的。qsort_r() 函數(shù)與 qsort() 函數(shù)相同唯一的區(qū)別在于比較函數(shù) compar 需要傳入第三個(gè)參數(shù)。通過(guò) arg 參數(shù)傳遞給比較函數(shù)的指針使得比較函數(shù)無(wú)需使用全局變量來(lái)傳遞任意參數(shù)因此具有可重入性可在多線程環(huán)境中安全使用。3.1.3 示例//函數(shù)指針 int add(int a, int b) { return a b; } int main(void) { int (*p)(int, int) add; // p 指向 add printf(%d\n, (*p)(1, 2)); // 輸出 3 return 0; }//排序回調(diào) void selectSortCb(int *a, int len, int (*key)(int)) { for (int i 0; i len - 1; i) for (int j i 1; j len; j) if (key(a[i]) key(a[j])) { int t a[i]; a[i] a[j]; a[j] t; } } // 調(diào)用selectSortCb(a, 10, func2); 按平方值排序//函數(shù)指針數(shù)組實(shí)現(xiàn)四則運(yùn)算 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 divi(int a, int b) { return a / b; } int (*ops[4])(int, int) { add, sub, mul, divi }; // 調(diào)用ops[i](10, 4)3.2 指針函數(shù)表示一個(gè)函數(shù)函數(shù)的返回值類型為指針。3.2.1 定義int *pintint 表示一個(gè)函數(shù)返回值為整型指針3.2.2 示例//返回最大元素地址 int *maxArray(int *begin, int *end) { int *ret begin; while (begin end) { if (*begin *ret) ret begin; begin; } return ret; }//返回最長(zhǎng)字符串 char *maxStr(char **s, int len) { char *ret *s; for (int i 0; i len; i) if (strcmp(ret, *(s i)) 0) ret *(s i); return ret; }//Memcpy void *Memcpy(void *dest, const void *src, int n) { char *d (char *)dest; const char *s (const char *)src; while (n--) *d *s; return dest; }//二分查找返回元素地址 int *binaryFindIter(int *begin, int *end, int f) { while (begin end) { int *mid begin (end - begin) / 2; if (*mid f) end mid - 1; else if (*mid f) begin mid 1; else return mid; } return NULL; }四、二級(jí)指針4.1 如何理解C語(yǔ)言是值傳遞當(dāng)一個(gè)指針作為形參傳入函數(shù)時(shí)這個(gè)指針的地址值被函數(shù)內(nèi)的指針變量接收是局部變量只能修改它指向的地址內(nèi)的數(shù)據(jù)因?yàn)楹瘮?shù)內(nèi)外的指針指向的是同一個(gè)地址修改同一個(gè)地址的值從而做到被調(diào)修改主調(diào)。 但如果對(duì)這個(gè)一級(jí)指針進(jìn)行修改它依舊是被作為形參局部變量修改無(wú)法帶出它的指針值。于是當(dāng)需要修改一級(jí)指針的指向時(shí)需要使用二級(jí)來(lái)修改。就是取一級(jí)指針的地址值通過(guò)二級(jí)指針指向一級(jí)指針地址值然后傳遞修改。一級(jí)指針作為函數(shù)形參被傳遞時(shí)函數(shù)內(nèi)部修改的是一級(jí)指針指向的值。 這里一級(jí)指針有一片空間這個(gè)空間存放的是一個(gè)指向的地址的地址值就像定義一個(gè)變量?jī)?nèi)存里存放的是它的變量值這個(gè)變量被傳遞時(shí)只是傳遞它的值的大小傳遞到函數(shù)后重新開(kāi)辟一塊空間來(lái)存放這個(gè)數(shù)據(jù)。所以我們無(wú)法對(duì)這個(gè)變量做出修改。所以我們無(wú)法修改這個(gè)一級(jí)指針因?yàn)楹瘮?shù)收到的是一個(gè)地址值并新開(kāi)辟一塊空間來(lái)存放這個(gè)地址值。 但是這個(gè)數(shù)據(jù)的地址沒(méi)”死亡“前是不會(huì)變化的它里面的數(shù)據(jù)就能通過(guò)函數(shù)修改。4.2 三個(gè)應(yīng)用場(chǎng)景1被調(diào)修改主調(diào)2指針數(shù)組傳參3動(dòng)態(tài)創(chuàng)建真正的二維數(shù)組在堆區(qū)申請(qǐng)一個(gè) 3 行 4 列 的二維數(shù)組非連續(xù)空間常用于字符串?dāng)?shù)組int **arr (int **)malloc(3 * sizeof(int *));// 申請(qǐng) 3 個(gè)行指針 for(int i 0; i 3; i) { arr[i] (int *)malloc(4 * sizeof(int));// 為每一行申請(qǐng) 4 個(gè)整型空間 } // 使用完之后必須逐行 free再 free(arr)4.3 易錯(cuò)點(diǎn)不要越級(jí)賦值int a 10; int **pp a; // 報(bào)錯(cuò) a 的地址里裝的是 int而 pp 期望的是指針的地址。二級(jí)指針步長(zhǎng)二級(jí)指針pp1移動(dòng)的字節(jié)數(shù)卻決于操作系統(tǒng)對(duì)應(yīng)的指針的大小。跨過(guò)的是一個(gè)一級(jí)指針的大小32位4字節(jié)64位8字節(jié)*pp類型*pp是int *類型值為地址4.4 示例//二級(jí)指針修改外部指針動(dòng)態(tài)分配 void getMemory(char **p, int num) { *p (char *)malloc(num); } // 調(diào)用char *str NULL; getMemory(str, 100);//二級(jí)指針遍歷數(shù)組 void printByPtr(char **s, int len) { for (int i 0; i len; i) printf(%s\n, *(s i)); }//二級(jí)指針交換字符串指針 void swapPtr(char **s1, char **s2) { char *t *s1; *s1 *s2; *s2 t; }//二級(jí)指針選擇排序 void selectSortPtr(char **begin, char **end) { while (begin end) { char **min begin; for (char **i begin; i end; i) if (strcmp(*i, *min) 0) min i; swapPtr(min, begin); begin; } }//二級(jí)指針二分查找 char *binaryFindStr(char **begin, char **end, char *f) { while (begin end) { char **mid begin (end - begin) / 2; int cmp strcmp(f, *mid); if (cmp 0) begin mid 1; else if (cmp 0) end mid - 1; else return *mid; } return NULL; }五、右左法則在分析指針與其他的數(shù)據(jù)類型結(jié)合時(shí)常常分不清變量的含義到底是什么。此時(shí)使用右左法則來(lái)分析變量的類型是什么是整型指針數(shù)組指針函數(shù)指針函數(shù)指針數(shù)組5.1 核心標(biāo)準(zhǔn)5.2 分析步驟5.3 舉例1、普通的指針和數(shù)組 int *p1; 先向右沒(méi)有 再向左確認(rèn)p是指針 再向左int指針指向的類型是int型數(shù)據(jù) int arr1[5]; 向右[ ]變量是一個(gè)數(shù)組 向左int數(shù)組內(nèi)的元素是int型 2、指針數(shù)組和數(shù)組指針 int *p2[5]; // 指針數(shù)組 先向右數(shù)組確認(rèn)變量是一個(gè)數(shù)組類型 向左指針數(shù)組內(nèi)的元素為指針類型 向左int指針指向的類型為int型 總結(jié)定義一個(gè)數(shù)組變量數(shù)組元素的基類型為指向整型的指針 指針數(shù)組一個(gè)基類型為指針的數(shù)組 int (*p3)[5]; // 數(shù)組指針 向右括號(hào)返回向左 向左指針變量類型為指針類型 再向右數(shù)組指針指向的是數(shù)組 再向左int數(shù)組元素的類型為int 總結(jié)定義了一個(gè)指針變量。指向一個(gè)基類型為整型的數(shù)組 數(shù)組指針指向數(shù)組的指針 3、指針函數(shù)和函數(shù)指針 int *f1(int); // 指針函數(shù) 變量向右int表示一個(gè)函數(shù)函數(shù)內(nèi)的參數(shù)類型為int帶參函數(shù) 向左*指針表示函數(shù)返回值為指針 再向左int表示返回的指針指向整型 總結(jié)定義一個(gè)函數(shù)變量函數(shù)的返回值類型為一個(gè)指向整型的指針 指針函數(shù)一個(gè)函數(shù)返回值是指針 注意不要返回局部變量地址生命周期有限 int (*f2)(int); // 函數(shù)指針 先向右強(qiáng)制向左*指針表示變量為指針 向右int表示這個(gè)指針變量指向函數(shù) 再向左函數(shù)的返回值類型為整型 總結(jié)定義一個(gè)指針變量指向一個(gè)返回值類型為整型的函數(shù) 函數(shù)指針一個(gè)指針指向的是函數(shù) 4、進(jìn)階函數(shù)指針數(shù)組 int (*f3[5])(int); // 函數(shù)指針數(shù)組 變量向右變量類型為數(shù)組類型 向左數(shù)組元素為指針 向右指針指向函數(shù) 向左函數(shù)返回值為int 總結(jié)定義一個(gè)數(shù)組數(shù)組基類型為指向一個(gè)返回值為int型的函數(shù)的指針 如果把變量外的去掉會(huì)報(bào)錯(cuò) int *f35 假設(shè)正確按右左法則分析向右是數(shù)組再向右是函數(shù)數(shù)組的基類型是函數(shù)再向左函數(shù)返回值是整型指針。 但是有一個(gè)問(wèn)題函數(shù)的大小是不確定的所以無(wú)法定義函數(shù)數(shù)組。 5、返回?cái)?shù)組指針的函數(shù) int (*f5(int))[5]; // 返回?cái)?shù)組指針的函數(shù) 向右變量類型為函數(shù) 向左函數(shù)返回值為指針 向右指針指向數(shù)組 向左數(shù)組類型為int型 總結(jié)定義一個(gè)函數(shù)變量返回值為指向一個(gè)基類型為int型的數(shù)組的指針 // 如果用 typedef 就一目了然 typedef int(*pArr)[5]; pArr f6(int); 6、二級(jí)指針 int **pp p; // 二級(jí)指針 變量向右向左變量類型為指針類型 向左指針指向的是一個(gè)指針 向左。指針指向的指針指向的是一個(gè)整型int 說(shuō)明最后的數(shù)據(jù)類型為整型5.4 總結(jié)總結(jié)在判斷變量類型時(shí)先判斷它最先和誰(shuí)結(jié)合確認(rèn)它的變量類型然后第二個(gè)是修飾這個(gè)已經(jīng)結(jié)合的變量第三個(gè)修飾已經(jīng)修飾過(guò)的結(jié)合變量……依次類推。就是后結(jié)合的修飾上一級(jí)結(jié)合的。六、總結(jié)指針的主要作用是被調(diào)修改主調(diào)還有減少重復(fù)的數(shù)據(jù)傳遞優(yōu)化空間利用率。指針學(xué)習(xí)以“地址”為總開(kāi)關(guān)先抓定義與解引用找地址、按基類型偏移、按類型使用再沿“一維數(shù)組→字符串→二維數(shù)組指針/指針數(shù)組→函數(shù)指針/指針函數(shù)→二級(jí)指針→右左法則”這條主線展開(kāi)一維整型指針重點(diǎn)練遍歷、反轉(zhuǎn)、選擇/冒泡/插入/快速排序和二分查找迭代與遞歸字符型指針重點(diǎn)練長(zhǎng)度、復(fù)制、連接、比較、反轉(zhuǎn)、轉(zhuǎn)整數(shù)和單詞逆序二維數(shù)組指針抓int (*p)[n]、char (*p)[n]的行指針本質(zhì)指針數(shù)組抓“元素是指針、排序交換的是指針”函數(shù)指針抓回調(diào)與函數(shù)指針數(shù)組指針函數(shù)抓“返回指針的函數(shù)”二級(jí)指針抓“被調(diào)修改主調(diào)、指針數(shù)組傳參、動(dòng)態(tài)二維”全程用右左法則辨析復(fù)雜聲明并始終防野指針/懸空指針、字符串常量區(qū)只讀、const修飾、\0終止、二分邊界begin end、釋放后置NULL、不返回局部變量地址這些坑。