前言
上一期講了數組指針的原理,這一期接著上一期講述數組指針的應用以及數組參數、函數參數。
? ? ? ? ?首先看下面的代碼進行上一期內容的復習,pc應該是什么類型?
char* arr[5] = {0};
xxx pc = &arr;
分析:
①首先判斷arr是一個數組,數組的每一個元素類型是char*,所以arr是指針數組;
②&數組名表示整個數組的地址,pc一定是一個數組指針;
③既然是數組指針,可以先寫(*pc)表明是一個指針,指向的是一個元素為char*類型的數組,一共有五個元素,即char* (*pc)[5] pc = &arr;
????????嘗試使用數組指針遍歷數組每一個元素;這里著重理解:若pc是數組指針,那么*pc是數組名,數組名其實就是數組首元素的地址,所以若需要打印數組元素,這里必須兩次解引用。
? ? ? ? 但是正常的人類不會這么來遍歷數組,直接使用一級指針就能完成遍歷的操作,不建議這樣去用。
1.?數組指針常見的用法
1.1 作為參數遍歷二維數組:
????????使用數組指針遍歷一維數組確實有些脫褲子放屁,但是使用數組指針卻可以很輕易的遍歷二維數組。
分析:
????????3*5的二維數組其實是三個int數組拼接而成,所以我們只需要使用數組指針指向第一行,數組指針+1就會跳轉到下一行,每一行中需要對數組指針解引用,可以獲得一維數組名,即一維數組首元素的地址,即類型為int*,此時再加上列j,最后再進行解引用就能獲得每一個元素了。??
??????
1.2 下列是指針還是數組?
// 指針大全
int main()
{int i = 10;int* p1 = &i;int** p2 = &p1; char arr1[5] = { 0 };// p3,p5等價char* p3 = arr1;char p4 = arr1[0];char* p5 = &arr1[0];int* arr2[5] = { 0 }; char(*p6)[5] = &arr1; // ?int* (*p7)[5] = &arr2;// ??int(*parr[10])[5]; // ???return 0;
}
? ?答案揭曉:
// 指針大全 int main() {int i = 10;int* p1 = &i;int** p2 = &p1; // 二級指針,存放一級指針變量的地址char arr1[5] = { 0 };// p3,p5等價char* p3 = arr1;char p4 = arr1[0];char* p5 = &arr1[0];int* arr2[5] = { 0 }; // 指針數組,每一個元素都是int*類型的char(*p6)[5] = &arr1; // 數組指針,指向數組char arr1[5]int* (*p7)[5] = &arr2;// 數組指針,指向數組int* arr2[5]int(*parr[10])[5]; // parr首先和[]結合,所以這是一個數組,去掉這兩個部分,剩下int (*) [5]是一個數組指針// 所以這是一個可以容納10個數組指針的數組return 0; }
? ? ? ? 最后一個?int(*parr[10])[5];有些難理解,我們可以采取的策略是:先定性,再去掉,最后判斷。
①先定性:parr和【】首先結合,所以這一定是一個數組。
②parr[]去掉。
③判斷剩下的部分:int(*)[5],這很顯然是一個數組指針。
④下結論:這是一個可以存放10個數組指針的數組。
2. 數組參數和指針參數
? ? ? ? 寫函數的時候,難免會把指針或者數組名傳遞給參數,我們該如何設計函數呢?
2.1 一維數組傳參
下面的參數正確嗎?
① 正確。一維數組傳參的時候可以不指定數組長度。
②正確。形參和實參一致。
③正確。傳入數組名,本質上就是首元素的地址,每一個元素是int*,可以使用指針接收。
④正確。形參和實參一致。
⑤正確。arr2是一個存放一級指針的數組,直接傳入數組名,就是傳遞首元素的地址,首元素為一級指針,要存放一級指針的地址可以使用二級指針。
2.2 二維數組傳參
下面的參數正確嗎?
①、③正確,②錯誤原因圖中已經標識。
????????二維數組傳入數組名,在之前的例子已經講過了,代表了首元素的地址,而首元素是一個數組,那么就是整個數組的地址,這個數組有五個元素,每一個元素是int類型,這里需要使用數組指針來接收地址。所以只有③正確。
2.3 一級指針傳參
非常簡單,下圖可以直接概括。
2.4 二級指針傳參
①正確。二級指針傳參使用二級指針接受肯定是沒有問題的。
②正確。使用一級指針的地址傳參,當然也是沒有問題的。
反過來想,如果形參使用二級指針,那么實參能傳什么呢?除了剛剛講的前兩種情況,這里可以存儲指針數組名。這是因為指針數組名是數組首元素的地址,首元素是一級指針,換言之也是一級指針的地址。
3.?函數指針介紹
顧名思義指向函數的指針。
????????這要牽扯到如何求函數的地址,我們知道&數組名是取出數組的地址,以此類推,&函數名可以取出函數的地址。
? ? ? ? 如何存放函數的地址呢?這就用到了函數指針,函數指針的定義也非常簡單,首先確定這是一個指針*p,然后在后面加上大括號,確定這是一個函數指針,在大括號里面輸入形參類型,在整個表達式前面標明返回類型。
? ? ? ? 利用函數指針調用函數也就順理成章了,直接解引用之后按照函數的方法直接調用即可。
? ? ? ? 看似這一切都是脫褲子放屁,我為什么不直接調用函數呢?這是因為我們視野所限,后面還有更高級的玩法,例如將函數指針作為函數傳遞傳給另外一個函數......
? ? ? ? 需要注意的一點是,這里的p可以不使用*解引用,因為add本質上是函數的地址,那么p存放的也是函數的地址,如果能夠這樣使用:add(),那么為什么不能這樣使用:p();這里加上*只是為了明確這是一個指針。如果要加上*號,記得要加上括號。
3.1 函數指針的簡單使用
作為另外一個函數的形參,在另外一個函數進行使用。
????????看到這里,你心中仍然覺得這是多此一舉,那我為什么不直接調用呢?其實這涉及到面向對象的內容,試想一下:如果有四個方法,分別是加減乘除的功能,這四個方法除了方法名和方法體不一樣,形參和返回值都是一樣的,這時候我們只需要傳入不同的方法名給這個函數指針,就能實現不同的方法,這就是多態!
3.2 函數指針的題目
首先來看一段來自《C陷阱和缺陷》這本書中非常有意思的代碼。
①首先看void(*)(),如果這是void(*p)(),這就是函數指針,此時把變量名去掉,這就是變量的類型;類比int* p去掉p,int* 就是變量的類型一樣。
②整體對這個變量類型大括號,后面再放一個0,這就是將0強制類型轉換為函數指針類型,地址為0的地方存在一個函數,這個函數不需要返回值也不需要形參。
③然后對整體函數指針進行解引用,再進行函數的調用。
總結:以上代碼就是一次函數的調用,調用的是0作為地址的函數,這個函數沒有返回值沒有形參。
以下是這本書的原話:
? ? ? ? 繼續看一段來自這本書的一段代碼。
分析:
①從signal入手,signal首先和()結合,()內部一個是整型類型,一個是函數指針類型,這是函數的聲明,函數的聲明的形參只需要注明形參的類型即可,例如:int add(int,int)。
②將signal(int,void(*)(int))去掉之后,看剩下的部分,void(*)(int),這毫無疑問是聲明函數的返回值。
③所以總結一下,這個就是一個函數的聲明,返回值是一個函數指針。
我們可以使用typedef簡化一下:如此一來,一眼就能看出這是一個函數聲明。
3.3 函數指針的用途
給出一個需求,計算兩個數字的四則運算。
?
????????我們發現,在swich語句中代碼存在大量冗余,只有一行不一樣,這一行只是調用的方法不一樣,剩下的部分都一樣,我們該如何解決?????????
? ? ? ? 使用函數指針就可以完美解決,創建一個函數,形參是函數指針,根據傳入的具體實參的不同,調用不同的方法,這個其實就是面向對象的多態。
回調函數也是這么做的,實現定義好函數指針,適時地進行調用函數。
? ? ? ? 今天的內容就到這了,下一期是最后一期關于指針的內容,如果對你有幫助,可以點贊收藏評論,一鍵三連,你的支持是我更新的最大動力!!?