我們在學C語言時,指標是我們最頭疼的問題之一,針對C語言指標,博主根據自己的實際學到的知識以及開發經驗,總結了以下使用C語言指標時常見問題。
指標做函式引數
學習函式的時候,講了函式的引數都是值複製,在函式里面改變形參的值,實參並不會發生改變。
如果想要透過形參改變實參的值,就需要傳入指標了。
注意:雖然指標能在函式里面改變實參的值,但是函式傳參還是值複製。不過指標雖然是值複製,但是卻指向的同一片記憶體空間。
指標做函式返回值
返回指標的函式,也叫作指標函式。
和普通函式一樣,只是返回值型別不同而已,先看一下下面這個函式,非常熟悉對不!
int fun(int x,int y);
接下來看另外一個函式宣告
int* fun(int x,int y);
這樣一對比,發現所謂的指標函式也沒什麼特別的。
注意:
-
不要返回臨時變數的地址
-
可以返回動態申請的空間的地址
-
可以返回靜態變數和全域性變數的地址
函式指標
如果在程式中定義了一個函式,那麼在執行時系統就會為這個函式程式碼分配一段儲存空間,這段儲存空間的首地址稱為這個函式的地址。而且函式名錶示的就是這個地址。既然是地址我們就可以定義一個指標變數來存放,這個指標變數就叫作函式指標變數,簡稱函式指標。
函式指標定義
函式返回值型別 (* 指標變數名) (函式引數列表);
-
“函式返回值型別”表示該指標變數所指向函式的 返回值型別;
-
“函式引數列表”表示該指標變數所指向函式的引數列表。
那麼怎麼判斷一個指標變數是指向變數的指標,還是指向函式的指標變數呢?
-
看變數名的後面有沒有帶有形參型別的圓括號,如果有就是指向函式的指標變數,即函式指標,如果沒有就是指向變數的指標變數。
-
函式指標沒有++和 --運算
函式指標使用
定義一個實現兩個數相加的函式。
int add(int a,int b)
{
return a+b;
}
int main()
{
int (*pfun)(int,int) = add;
int res = pfun(5,3);
printf("res:%d\n",res);
return 0;
}
在給函式指標pfun賦值時,可以直接用add賦值,也可以用&add賦值,效果是一樣的。
在使用函式指標時,同樣也有兩種方式,1,pfun(5,3); 2,(*pfun)(5,3)
案例
計算器
用函式指標實現一個簡單的計算器,支援+、-、*、/、%
//plus sub multi divide mod //加 減 乘 除 取餘
當功能太多時,switch語句太長,因此不是一種好的程式設計風格。好的設計理念應該是把具體的操作和和選擇操作的程式碼分開。
函式指標作為轉換表
轉換表就是一個函式指標陣列。
#include<stdio.h>
#include<math.h>
// 轉換表
// 轉換表 step1:
//(1.1)宣告 轉檯轉移函式
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
double hypotenuse(double, double);
//(1.2)宣告並初始化一個函式指標陣列 pfunc:陣列 陣列元素:函式指標 返回值:double型資料
double(*pfunc[])(double, double) = { add, sub, mul, div, hypotenuse };//5個轉移狀態
//狀態轉移函式的實現
double add(double a, double b){ return a + b;}
double sub(double a, double b){ return a - b; }
double mul(double a, double b){ return a * b; }
double div(double a, double b){ return a / b; }
double hypotenuse(double a, double b){ return sqrt(pow(a, 2) + pow(b, 2)); }
void test()
{
//轉換表 step2:呼叫 函式指標陣列
int n = sizeof(pfunc) / sizeof(pfunc[0]);//轉移表中 包含的元素個數(狀態轉移函式個數)
for (int i = 0; i < n; ++i){
printf("%.2lf\n",pfunc[i](3, 4));
}
}
int main()
{
test();
return 0;
}
typedef
一,使用typedef為現有型別建立別名,給變數定義一個易於記憶且意義明確的新名字。
-
型別過長,用typedef可以簡化一下
typedef unsigned int UInt32
-
還可以定義陣列型別
typedef int IntArray[10];
IntArray arr; //相當於int arr[10]
二、使用typedef簡化一些比較複雜的型別宣告。
例如:
typedef int (*CompareCallBack)(int,int);
上述宣告引入了PFUN型別作為函式指標的同義字,該函式有兩個型別分別為int、int、char引數,以及一個型別為int的返回值。通常,當某個函式的引數是一個回撥函式時,可能會用到typedef簡化宣告。 例如,承接上面的示例,我們再列舉下列示例:
int callBackTest(int a,int b,CompareCallBack cmp);
callBackTest函式的引數有一個CompareCallBack型別的回撥函式。在這個示例中,如果不用typedef,callBackTest函式宣告如下:
int callBackTest(int a,int b,int (*cmp)(int,int));
從上面兩條函式宣告可以看出,不使用typedef的情況下,callBackTest函式的宣告覆雜得多,不利於程式碼的理解,並且增加的出錯風險。
所以,在某些複雜的型別宣告中,使用typedef進行宣告的簡化是很有必要的。
回撥函式
首先要明確的一點是,函式也可以作為函式的引數來傳遞。
當做函式引數傳入的函式,稱之為 回撥函式(至於為什麼要叫“回撥函式”,不能叫別的呢?其實這只是人為規定的一個名字。你也可以叫“maye專屬函式”,但是到時候你又會問為什麼要叫“maye專屬函式”,它特麼的總的有個名字吧!所以叫“回撥函式”就是王八的屁股:規定!)。
實現一個與型別無關的查詢函式
如何看懂複雜的指標
指標大家都學過了,簡單的指標相信大家都不放在眼裡,就不再贅述,但是複雜的你能理解嗎?能理解指標就學的差不多了,至於如何運用只要你看懂指標就知道應該給它賦什麼值,怎麼用。
-
首先我們們一起來看看這個:
int (*fun)(int *p)
-
首先需要分析這個是不是一個指標,如果是,是什麼指標?如果不是,那是什麼?
-
根據(*fun)可知,fun是一個指標
-
然後看fun的後面是一個函式引數列表,可以確定是一個指向函式的指標
-
指向的函式的返回值是什麼型別呢,再回頭看看最前面發現是一個int
-
最後我們可以根據這個函式指標寫出對應的函式
-
-
結果如下:
int foo(int *p)
{
return 0;
}
右左法則
上面我們分析了一個函式指標,那結果是如何得出來的呢?全靠經驗嗎,NO,其實是有方法的。
這個方法叫做右左法則:
-
右左法則不是C標準裡面的內容,它是從C標準的宣告規定中歸納出來的方法。C標準的宣告規則,是用來解決如何建立宣告的,而右左法則是用來解決如何辯識一個宣告的。
-
右左法則使用:
-
首先從最裡面的圓括號(應該是識別符號)看起,然後往右看,再往左看;
-
每當遇到圓括號時,就應該調轉閱讀方向;
-
一旦解析完圓括號裡面所有東西,就跳出圓括號;
-
重複這個過程知道整個宣告解析完畢。
-
案例走起
1.int (*p[5])(int*)
解析:
-
從識別符號p開始,p先與[]結合形成一個陣列,然後與*結合,表示是一個指標陣列;
-
然後跳出這個圓括號,往後看,發現了一個函式的引數列表,說明陣列裡面裝的是函式指標;
-
在跳出圓括號,往前看返回型別,可以確定函式指標的型別。
2. int (*fun)(int *p,int (*pf)(int *))
解析:
-
fun與*結合形成指標;
-
往後看是一個引數列表,說明是一個函式指標,只不過引數裡面還有一個函式指標;
-
3. int (*(*fun)[5])(int *p)
解析:
-
fun與*結合,形成指標;
-
往後看發現了一個[5]說明是一個指向陣列的指標;
-
再往前看,發現有一個*,說明陣列裡面存的是指標;
-
跳出圓括號往後看,發現了引數列表,說明陣列裡面存的是函式指標;
-
再往前看可以確定函式指標的返回型別。
4. int (*(*fun)(int *p))[5]
解析:
-
fun與*結合,形成指標;
-
往後看發現了引數列表,說明fun是一個函式指標;
-
往前看遇到了*說明,函式指標的返回型別是一個指標,是什麼指標繼續往後解析;
-
往後看發現了[5] 說明是一個陣列指標,最前面一個int,說明fun這個函式指標的返回型別是一個陣列的指標
型別為int (*)[5]
5. int(*(*fun())())()
解析:
-
fun與()結合,說明fun是一個函式;
-
往前看發現了一個*,說明函式返回型別為指標,什麼指標呢?
-
往後看發現了引數列表,fun函式返回的是一個函式指標,那這個函式指標的返回型別是什麼呢?
-
往前看又發現了一個*,說明函式指標返回型別也是一個指標,那這個指標是什麼指標呢?
-
往後看又發現了一個引數列表,說明是個函式指標,往前看這個函式指標返回的是int型別
總結
實際當中,需要宣告一個複雜指標時,如果把整個宣告寫成上面所示的形式,對程式可讀性是一大損害。應該用typedef來對宣告逐層分解,增強可讀性
指標變數有兩種型別:指標變數的型別和指標所指向的物件的型別
指標變數的型別 只要把指標宣告語句裡的指標名字去掉,剩下的部分就是這個指標的型別。
-
int* ptr; //指標的型別是int
-
char* ptr; //指標的型別是char
-
int** ptr; //指標的型別是int**
-
int(*ptr)[3]; //指標的型別是int()[3]
-
int*(*ptr)[4]; //指標的型別是int*(*)[4]
指標變數指向的物件的型別
-
你只須把指標宣告語句中的指標名字和名字左邊的指標宣告符*去掉,剩下的就是指標所指向的型別。
-
int*ptr; //指標所指向的型別是int
-
char*ptr; //指標所指向的的型別是char
-
int**ptr; //指標所指向的的型別是int*
-
int(*ptr)[3]; //指標所指向的的型別是int()[3]
-
int*(*ptr)[4]; //指標所指向的的型別是int*()[4]
-
注意事項:
-
指標變數也是變數,也有儲存空間,存的是別的變數的地址。
-
要注意指標的值,和指向的物件的值得區別
-
普通變數中的記憶體空間存放的是,數值或字元等。 ----直接存取
-
指標變數中的記憶體空間存放的是,另外一個普通變數的地址。----間接存取
-
-
連續定義多個指標變數時,容易犯錯誤,比如:int *p,p1;只有p是指標變數,p1是整型變數
-
避免使用為初始化的指標,很多執行錯誤都是由於這個原因導致的,而且這種錯誤又不能被編譯器檢查所以很難被發現,解決方法:初始化為NULL,報錯就能很快找到原因
-
指標賦值時一定要保證型別匹配,由於指標型別確定指標所指向物件的型別,操作指標是才能知道按什麼型別去操作
-
在用動態分配完記憶體之後一定要判斷是否分配成功,分配成功後才能使用。
-
在使用完之後一定要釋放,釋放後必須把指標置為NULL
-