C++中如何定義指向函數指針的指針?

打個比方已經定義了一個函數指針p:

int (*p)(int);

那麼請問如何定義指向拍的指針,即如題所述指向函數指針的指針?


/* Readable!!! */
typedef int func(int);
typedef func* func_ptr;
typedef func_ptr* func_ptr_ptr;

func_ptr_ptr p;


在實踐中,請大家最好按照 @dyntkj 的回答去定義。

我來說個道理,知道這個道理後,我對C語言中定義類型這件小事立馬就一通百通了。

這個道理就是

定義的樣子,和使用的時候的樣子是一樣一樣的。

舉個最簡單的例子。

int a;

這定義了一個整型。

int *p;

這定義了一個指向整數的指針。那麼這個指針怎麼變成整數呢?很簡單,只需要 *p 就可以了。我們驚奇的發現,定義時和使用時的形式是一模一樣的。這不是個巧合,是語言設計者有意為之的。

int *p 的意思就是讓 *p 為整形。那麼 p 自然是指向整數的指針了。

再來看題主的例子。

int (*p)(int);

這個p是什麼我們不知道,但我們知道 (*p)(int) 是個整數,所以, *p 是個 返回整數的函數。而p就是指向這種函數的指針啦。

那麼如何定義函數指針的指針呢?簡單點說,就是加個 * 就行了。

假如不幸的是,我們一開始加錯了地方,變成了:

int *(*p)(int);

我們來用我們的腦袋檢驗一下(你需要熟悉運算符優先順序(這裡又有一個口訣:小括弧的運算級最高)): *(*p)(int) 是個整數,所以 (*p)(int) 是個整數的指針。 所以 *p 是個函數,返回一個整數指針,所以p是個函數指針。

那麼我們,將*挪一下地方。

int (**p)(int);

再來檢驗一下:(**p)(int)是個整數。 **p 是個函數,返回一個整數。於是 *p 是個函數的指針。 p 是個函數指針的指針。

完美。

-------------- 一看就知道我是分割線 --------------------

有的時候,有的人(就是我小時候),會直接這樣定義函數的指針的指針:

int **p(int);

他還振振有詞:這很顯然呀。

我們來看看他錯在哪裡:

小括弧的優先順序最高,當你寫下這個語句的時候: **p(int)

實際上是先執行 p(int) 的,所以,這個定義就是 **p(int) 是個整數,所以 *p(int) 就是整數的指針,而p(int) 就是指針的指針,所以p就是函數的指針(實際上最後一步是個語法糖。不過我們這裡就不追究啦)

漲姿勢的都點贊吧。

=======

更新:原來《C陷阱與缺陷》第一章講的就是這個!


int (**p)(int);


我來回答一下吧,乾脆把問題擴大得更廣泛、通用一點,解釋下C++里的聲明(用的是C語言的BNF,足夠解決題主的疑問和說明這些難懂的語法了),特別說明合成類型的說明。以下討論的對C和C++一樣適用。(僅限這些討論中C++和C可以通用,加上這句是防止某些人詰難。)

如果在任何一個點你看不下去了,請直接跳到最後一段。

c語言的聲明的語法是這樣的(看不懂無視就可以)——

& ::= &
| &
| &

& ::= {&}+ {&}*

& ::= &
| & = &

& ::= &
| { & }
| { & , }

& ::= &
| & , &

(不了解的話可以搜索一下BNF,後面盡量寫得不需要這方面的知識)

意思就是說,在一個聲明中,像『constvolatile』(也就是CV限定符,算作type-qualifier),autoregisterstaticexterntypedef(存儲說明符,也就是storage-class-specifier),和各種類型說明符(int啦,你自己寫的typedef啦,struct/union/enum xx之類的),可以寫許多個放在最前面(順序沒有影響,你可以自己去試試,像

int typedef const auto a;

都可以。

上面那些的意思是,聲明以這些東西開頭。

令題主頭痛的東西在這裡:

& ::= {&

}? &
&

::= * {&}* {&

}?
& ::= &
| ( & )
| & [ {&}? ]
| & ( &

)
| & ( {&}* )

直接聲明器,也就是int * a (int);* a (int) 這部分。你會發現 * ,以及 * 緊跟的 const 是和後面部分在一起的,而不是和int在一起,這也解釋了為什麼 int*a,b; 當中b並不是指針。

* 可以有任意多個,而且向右邊結合,int**const ***a; int (*(*(* const (*(*a))))); 等價。

然後你就會發現,

& ::= &
| ( & )
| & [ {&}? ]
| & ( &

)
| & ( {&}* )

int *a[2];int *a(int);里,[2](int)結合的比*緊,相當於int *(a[2]);int (*a(int)); (被捉了bug,這裡可以加括弧,原句:「雖然實際上這裡是不能加括弧的」)。

以上講的都是語法層面上的。從語義上講,語法里結合的越緊密的部分,在類型里的層次越靠上。比如說int*a[2]; [2]結合的更緊,所以就是個array with 2 elements of ... type,中文說就是「XX類型的有兩個元素的數組」,然後去掉了[2]來看剩下的是int *a; ,結合最緊的是指針,所以就是array with 2 elements of pointer to ... type,對應中文「XX類型指針的有兩個元素的數組」,最後把int放進去,就得到最後的結果了——array with 2 elements of pointer to int type,「int類型指針的有兩個元素的數組」。

類型可以看成是一個樹形結構。如果你看得懂這句話,上面那些就很好理解了。

對於題主的問題,指向函數指針的指針,我們把上面的過程反過來想,我們的類型是個指針,所以*和標識符p最緊密,也就是(*p),然後他指向的是個函數指針,也就是指向函數的指針(雖然嚴格來說函數指針不是指針),再貼一層*,也就是(*(*p)),最後他是個函數,加上參數列表(看上面的語法定義你會發現參數的類型語法也是一個類型聲明(沒有分號),得到((*(*p))(int)),最後返回類型是int,我們得到int ((*(*p))(int)),添加上分號就是完整的聲明語句了(後面還可以賦上初值)。

以上這些對於其他複合類型也是一樣可以分析的。

個人的一點吐槽,C語言的類型聲明為什麼不做成類似C++模板語法那樣呢?Array&,5&> p;那樣容易讀懂多了。或者像某些語言一樣,(pointer int)-&>(int,char)-&>int表示int*int(int,char) 的指針。這是我要吐槽C語言(以及C++)的一個地方。

以及你可以用點C++新標準的特性來模擬上面說的東西——

template& using array=T[N];
array& a;

研究這個東西,除了寫編譯器之類的,還真是無聊啊——和回字有幾種寫法差不多。

看了這麼多你肯定有點暈,我也和你一樣暈——所以珍愛生命,多用using(typedef),多寫些代碼(比如class(struct)包裝)來代替單純的複合類型。class給了類型有意義的名字,讓編譯器可以用類型檢查出來更多不好人工查出的錯誤,也讓你節約了這種方面的腦力——這種事情上真的沒必要費腦。用C語言的時候你只有上述的方法可以用,C++還有上面所述的小技巧,還有auto類型推斷,還有std::function,多了解點新鮮工具都是有好處的。

(PS. typedef 的類型作為聲明語句最左邊的類型說明符的時候,相當於把類型整個放在類型架構的最下面,和int放在最左邊一樣)

參考資料:The syntax of C in Backus-Naur form


int f(int i){ cout &<&< i &<&< endl; return 0; } int main(){ int (*p)(int) = f; decltype(p)* p2 = p; (*p2)(1024); system("pause"); return 0; }

C++11簡直無敵


用一次C++11的特性吧。

#include&

#include&

/**

*函數指針指向的函數

**/

template& U doSomeThing(T a, T b)

{

U c = a + b;

return c;

}

/**

*定義std::function與實現類似函數指針功能

**/

typedef std::function& fp;

int main()

{

int a = 2, b = 3;

fp f = doSomeThing&;//定義fp

auto ptf = f;//定義指向fp的指針

std::cout &<&< doSomeThing&(a, b) &<&< std::endl;//調用原函數操作

std::cout &<&< f(a, b) &<&< std::endl;//調用std::function定義的函數指針操作

std::cout &<&< (*ptf)(a,b)&<&}


《C專家編程》...應該是這本吧...


c++11大法好呀

void test()

{

cout &<&< "testing" &<&< endl;

}

auto test_ptr = test;

auto test_ptr_ptr = test_ptr;


在c++11下,可以這麼偷懶獲得類型:

1 #include &
2
3 using namespace std;
4
5 int func(int a)
6 {
7 return a + 1;
8 }
9
10 int main(int argc , char *argv[])
11 {
12 decltype(func) *func_ptr = func; //或者 auto func_ptr = func;
14
15 cout &<&< func_ptr(2015) &<&< endl; 16 }


using func_ptr_ptr = std::add_pointer&::type;


推薦閱讀:

如何有效的練習並且提升寫代碼的能力?
初始化、顯式初始化、隱式初始化。這幾個區別是什麼?
在使用lib時,代碼都被鏈接到exe中去了嗎?
學習C++,應該循序漸進的看哪些書?

TAG:C編程語言 | C | CC | VisualC |