C++ 函數指針詳細總結
1、函數指針
函數指針顧名思義,就是指向函數的指針。
和數據類似,C++當中函數也有地址,函數的地址是存儲函數機器語言代碼的內存地址。我們可以將另外一個函數的地址作為參數傳入函數,從而實現函數的靈活調用。
1.1 獲取函數地址
獲取函數地址的方法非常簡單,隻要使用函數名(後面不跟參數和括號)即可。比如我們有一個函數叫做think,那麼think()是調用函數拿到結果,而think
則是獲取函數的地址。
如果我們想要將think函數當做參數傳入另外一個函數,我們可以這麼寫:
func(think);
1.2 聲明函數指針
聲明函數指針和聲明函數類似,我們聲明一個函數可以這麼寫:
double process(int);
而我們聲明函數指針則可以寫成這樣:
double (*pt)(int);
如果我們把(*pt)
替換成函數名的話,這其實就是一個函數的聲明。如果(*pt)
是函數的話,那麼pt自然就是指向函數的指針瞭。
1.3 函數指針傳參
如果我們要實現一個函數,它的一個參數是一個函數指針,它的寫法和剛才一樣:
double func(double x, double (*pt)(int));
在這個聲明當中,它的第二個參數是一個函數指針。指向的函數接收一個int參數,返回一個double
結果。
1.4 調用函數
最後, 我們來看下通過指針調用函數的部分。
其實也非常簡單,因為我們前面說瞭(*pt)
的效果和函數是一樣的,我們之前通過函數名調用函數,那麼我們隻需要改成通過(*pt)
調用即可。
如:
double process(int); double (*pt)(int); pt = process; cout << (*pt)(5) << endl;
2、函數指針進階
簡單的函數指針比較簡單,但對於復雜的情況則顯得有些恐怖。下面我們來看下C++ primer
當中提供的一些例子:
const double* f1(const double ar[], int n); const double* f2(const double [], int); const double* f3(const double *, int);
這三個函數看起來長得不一樣,但其實是等價的。因為在函數參數列表當中,數組和指針是等價的。其次我們可以在函數的原型中省略掉變量名,因此const double ar[]
可以簡化成const double []
,也可以寫成const double *。
有瞭這三個函數之後,假設我們要聲明一個指針,指向這三個函數。根據我們前文當中說過的,可以將函數名替換成(*pt)來實現:
const double* (*pt)(const double *, int) = f1;
其實這個語句看起來就有些復雜瞭,整個語句的可讀性很差。如果不是知道這裡用的是一個函數指針,乍一看想要看明白估計不太容易。我們可以分成兩個部分來理解,其中const double *
是一個整體,表示函數的返回值類型是一個const double *也就是一個常量浮點數的地址。其次(*pt)
是一個整體,代替瞭函數名,表示這是一個指向函數的指針。
在C++11當中提供瞭叫做auto
的新特性,它可以幫助變量自動識別對應的類型,可以解決一些類型特別復雜的問題,比如:
auto p2 = f2;
函數指針有兩種調用方法,除瞭可以使用(*p2)
的方式調用之外,
也可以直接使用名稱調用:
const double* x = p2(ar, 3); const double* y = (*p2)(ar, 3);
顯然前者更好,更清楚。這裡其實有一個疑問,為什麼這兩種方式都可以執行呢?這是因為當我們執行auto p2 = f2的時候,其實是執行的auto p2 = &f2
,C++
會隱式地將函數轉換成函數的地址。因為函數的值本身就是一個地址,所以這兩種方式才都能正確地運行。
問題還沒有結束,假如我們要定義一個指向函數的指針數組呢?這應該怎麼聲明?
也就是const double* (*pt)(const double *, int)
這樣一個類型的數組,它應該怎麼聲明,這個方括號應該放在那裡?
正確答案是放在括號裡:
const double* (*pt[3])(const double *, int);
因為運算符[]的優先級高於*,因此*pt[3]
表示pt是一個長度為3的指針數組。其他的內容表明瞭該指針的類型。
由於我們定義的是一個數組,所以這裡不能使用auto
,因為自動類型推斷隻能用於單值初始化而不能用於初始化列表。
到這裡還沒結束,還有更恐怖的,如果我們想要定義一個指向這個數組的指針,應該怎麼辦呢?如果使用auto可以寫成:
auto ptr = &pt;
如果不使用auto呢?首先我們可以想到,這個聲明是基於pt的,我們需要在pt的聲明上加上一個*,但問題是加在哪裡呢?
進一步分析,會發現我們需要指出這是一個指針,而不是數組。意味著核心的部分應該寫成(*ptr)[3],表示這是一個指向長度為3的數組的指針。因為[]的優先級更高,所以需要使用括號。如果寫成*ptr[3]表示這是長度為3的指針數組。
我們進一步倒推,(*ptr)[3]這個數組當中的元素是什麼類型呢?是指向函數的指針,所以寫出來結果是這樣:
const double *(*(*ptr)[3])(const double*, int) = &pt;
很明顯,這樣的定義非常非常的難以理解。而且這還不是最復雜的情況,比如函數的返回類型又是一個指向一個函數的指針……明擺著告訴我們含義我們仍然要推敲一會,如果在一段不明的代碼當中遇到,可能會直接抓狂吧……
也正因此,C++11當中推出瞭auto
特性,可以簡化這種情況。
多說一句題外話,golang
語言當中將變量的類型放在變量的後面而不是前面,其中一個原因就是為瞭解決類似情況的復雜性。
如果是golang來定義同樣的內容,會是這樣的:
func f2(arr []float64, n int) *float64 { // todo } // 函數指針 var p1 func([]float64, int) *float64 = f2; // 函數指針數組 var pt [3]func([]float64, int) *float64; // 函數指針數組的指針 var ptr *[3]func([]float64, int) *float64 = &pt;
很明顯,雖然變量類型寫在變量後面剛開始會不太習慣,但是很明顯這樣要清晰很多。
到此這篇關於C++ 函數指針內容總結的文章就介紹到這瞭,更多相關C++ 函數指針內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
文章轉自公眾號:Coder梁(ID:Coder_LT)