我們在學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 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標準的宣告規則,是用來解決如何建立宣告的,而右左法則是用來解決如何辯識一個宣告的。
右左法則使用:
首先從最裡面的圓括號(應該是識別符號)看起,然後往右看,再往左看;
每當遇到圓括號時,就應該調轉閱讀方向;
一旦解析完圓括號裡面所有東西,就跳出圓括號;
重複這個過程知道整個宣告解析完畢。
解析:
從識別符號p開始,p先與[]結合形成一個陣列,然後與*結合,表示是一個指標陣列;
然後跳出這個圓括號,往後看,發現了一個函數的參數列,說明陣列裡面裝的是函數指標;
在跳出圓括號,往前看返回型別,可以確定函數指標的型別。
解析:
fun與*結合形成指標;
往後看是一個參數列,說明是一個函數指標,只不過引數裡面還有一個函數指標;
解析:
fun與*結合,形成指標;
往後看發現了一個[5]說明是一個指向陣列的指標;
再往前看,發現有一個*,說明陣列裡面存的是指標;
跳出圓括號往後看,發現了參數列,說明陣列裡面存的是函數指標;
再往前看可以確定函數指標的返回型別。
解析:
fun與*結合,形成指標;
往後看發現了參數列,說明fun是一個函數指標;
往前看遇到了*說明,函數指標的返回型別是一個指標,是什麼指標繼續往後解析;
往後看發現了[5] 說明是一個陣列指標,最前面一個int,說明fun這個函數指標的返回型別是一個陣列的指標
型別為int (*)[5]
解析:
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