[英]C++ function pointers, again. Confusion regarding syntax
在這個頁面上,我找到了一個很好的C ++函數指針示例(以及仿函數,但這個問題與仿函數無關)。 下面是該頁面的一些copypasta。
#include <iostream>
double add(double left, double right) {
return left + right;
}
double multiply(double left, double right) {
return left * right;
}
double binary_op(double left, double right, double (*f)(double, double)) {
return (*f)(left, right);
}
int main( ) {
double a = 5.0;
double b = 10.0;
std::cout << "Add: " << binary_op(a, b, add) << std::endl;
std::cout << "Multiply: " << binary_op(a, b, multiply) << std::endl;
return 0;
}
我總體上理解了代碼,但有一些事情我總是感到困惑。 函數binary_op()
接受函數指針*f
,但是當它被使用時,例如在第19行binary_op(a, b, add)
,函數符號add
被傳入,而不是人們會想到它的指針&add
。 現在你可能會說這是因為符號add
是一個指針; 它是與add()
函數對應的代碼位的地址。 很好,但是這里似乎仍然存在類型差異。 函數binary_op()
接受*f
,這意味着f
是指向某事物的指針。 我傳入add
,它本身就是代碼的指針。 (對吧?)那么f
賦值為add
,這使得f
成為代碼的指針,這意味着f
就像add
一樣,這意味着f
應該被調成f(left, right)
,究竟如何應該調用add
,但在第12行,它被調用為(*f)(left, right)
,這對我來說似乎不對,因為它就像寫(*add)(left, right)
和*add
不是函數,它是add
點的代碼的第一個字符。 (對?)
我知道用以下代替binary_op()
的原始定義也有效。
double binary_op(double left, double right, double f(double, double)) {
return f(left, right);
}
事實上,這對我來說更有意義,但正如我上面解釋的那樣,原始語法沒有意義。
那么,為什么在語法上使用(*f)
而不僅僅是f
? 如果符號func
本身就是一個指針,那么短語“函數指針”或“指向函數的指針”究竟是什么意思? 由於原來的代碼目前維持,當我們寫double (*f)(double, double)
,是什么樣的事情f
呢? 指向指針的指針(因為(*f)
本身就是指向一些代碼的指針)? 是符號add
同一類的事情(*f)
或同一類的事情f
?
現在,如果所有這些的答案是“是的C ++語法很奇怪,只需記住函數指針語法,不要質疑它。” 那么我會不情願地接受它,但我真的想要正確解釋我在這里想錯了什么。
我已經讀過這個問題了 ,我想我明白這一點,但是沒有發現它有助於解決我的困惑。 我也讀過這個問題 ,這也沒有幫助,因為它沒有直接解決我的類型差異問題。 我可以繼續閱讀互聯網上的信息海洋以找到我的答案,但是嘿,這就是Stack Overflow的用途嗎?
這是因為C函數指針是特殊的。
首先,表達式add
將衰減為指針。 就像引用數組會衰減成指針一樣,對函數的引用會衰減為指向函數的指針。
然后,那里有奇怪的東西:
return (*f)(left, right);
那么,為什么在語法上使用
(*f)
而不僅僅是f
?
兩者都有效,你可以像這樣重寫代碼:
return f(left, right);
這是因為解除引用操作符將返回對函數的引用,並且對函數或函數指針的引用都被認為是可調用的。
有趣的是,函數引用很容易衰減,在調用dereference運算符時會衰減回指針,允許根據需要多次取消引用函數:
return (*******f)(left, right); // ah! still works
由於原來的代碼目前維持,當我們寫
double (*f)(double, double)
,是什么樣的事情f
呢?
f
的類型是double (*)(double, double)
即它是指向double(double,double)
類型函數的指針。
因為
(*f)
本身就是一個指針
它不是。
問:通過指針(例如*f
)間接得到什么? 答:你得到左值參考。 例如,給定一個對象指針int* ptr
,表達式*ptr
的類型是int&
即lvalue對int
引用。
對於函數指針也是如此:當您通過函數指針間接時,您將獲得指向該函數的左值引用。 在*f
的情況下,類型是double (&)(double, double)
即對double(double,double)
類型函數的引用。
是符號
add
同一類的事情(*f)
或同一類的事情f
?
不合格的id表達式add
與*f
相同,即它是一個左值:
標准草案[expr.prim.id.unqual]
...如果實體是一個函數,表達式是一個左值...
函數符號
add
是傳入的,而不是人們會想到的指針&add
。 現在你可能會說這是因為符號add
是一個指針;
不,那不是原因。
add
不是指針。 這是一個左值。 但是函數類型的左值隱式轉換為指針(這稱為衰減):
標准草案[conv.func]
函數類型T的左值可以轉換為“指向T的指針”的prvalue。 結果是指向函數的指針。
因此,以下在語義上是等效的:
binary_op(a, b, add); // implicit function-to-pointer conversion
binary_op(a, b, &add); // explicit use of addressof operator
那么,為什么在語法上使用
(*f)
而不僅僅是f
?
事實證明,調用函數lvalue與調用函數指針具有相同的語法:
標准草案[expr.call]
函數調用是后綴表達式,后跟括號,其中包含可能為空的逗號分隔的initializer-clause列表,這些子句構成函數的參數。 后綴表達式應具有函數類型或函數指針類型。 對於非成員函數或靜態成員函數的調用,后綴表達式應該是引用函數的左值 (在這種情況下,函數到指針標准轉換([conv.func])被抑制在postfix表達式上), 或者有函數指針類型 。
這些都是相同的函數調用:
add(parameter_list); // lvalue
(*f)(parameter_list); // lvalue
(&add)(parameter_list); // pointer
f(parameter_list); // pointer
PS這兩個聲明是等價的:
double binary_op(double, double, double (*)(double, double))
double binary_op(double, double, double (double, double))
這是因為以下規則,它與函數指針的隱式衰減互補:
標准草案[dcl.fct]
使用以下規則確定函數的類型。 每個參數的類型(包括函數參數包)由其自己的decl-specifier-seq和聲明符確定。 在確定每個參數的類型之后,將“T數組”或函數類型T的 任何參數 調整為“指向T的指針” ......
首先,當編譯器確定參數的類型時,將指定為函數聲明的函數參數調整為指向函數的指針。 例如,遵循函數聲明
void f( void h() );
void f( void ( *h )() );
等價並聲明相同的一個函數。
考慮以下示范程序
#include <iostream>
void f( void h() );
void f( void ( *h )() );
void h() { std::cout << "Hello Ray\n"; }
void f( void h() ) { h(); }
int main()
{
f( h );
}
從c ++ 17標准(11.3.5函數):
5使用以下規則確定函數的類型。 每個參數的類型(包括函數參數包)由其自己的decl-specifier-seq和聲明符確定。 在確定每個參數的類型之后,將“T數組”或函數類型T的任何參數調整為“指向T的指針 ”。
另一方面,根據C ++ 17標准
9當給定參數沒有參數時,參數的傳遞方式使得接收函數可以通過調用va_arg(21.11)來獲取參數的值。 [注意:此段落不適用於傳遞給函數參數包的參數。 函數參數包在模板實例化期間(17.6.3)進行了擴展,因此當實際調用函數模板特化時,每個這樣的參數都有一個相應的參數。 - 結束注釋]對參數表達式執行左值到右值(7.1),數組到指針(7.2)和函數到指針(7.3)標准轉換
那么這兩個聲明之間有什么區別呢
void f( void h() );
void f( void ( *h )() );
對於第一個聲明,您可以將函數體中的參數h
視為函數指針的typedef。
typedef void ( *H )();
例如
#include <iostream>
void f( void h() );
void f( void ( *h )() );
void h() { std::cout << "Hello Ray\n"; }
typedef void ( *H )();
void f( H h ) { h(); }
int main()
{
f( h );
}
根據C ++ 17標准(8.5.1.2函數調用)
1函數調用是一個后綴表達式,后跟括號,其中包含一個可能為空的逗號分隔的initializer-clause列表,這些子句構成函數的參數。 后綴表達式應具有函數類型或函數指針類型 。
所以你也可以定義像這樣的函數
void f( void h() ) { ( *h )(); }
甚至喜歡
void f( void h() ) { ( ******h )(); }
因為當operator *應用於函數名時,函數名被隱含地賦予函數pijnter。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.