[英]C++ function pointers, again. Confusion regarding syntax
On this page I found a good example of function pointers in C++ (as well as of functors, but this question isn't about functors). 在这个页面上,我找到了一个很好的C ++函数指针示例(以及仿函数,但这个问题与仿函数无关)。 Below is some copypasta from that page.
下面是该页面的一些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;
}
I understand the code in general terms, but there are a couple of things that I've always found confusing. 我总体上理解了代码,但有一些事情我总是感到困惑。 Function
binary_op()
takes a function pointer *f
, but when it's used, for example on line 19 binary_op(a, b, add)
, the function symbol add
is passed in, not what one would think of as its pointer, &add
. 函数
binary_op()
接受函数指针*f
,但是当它被使用时,例如在第19行binary_op(a, b, add)
,函数符号add
被传入,而不是人们会想到它的指针&add
。 Now you may say that this is because the symbol add
is a pointer; 现在你可能会说这是因为符号
add
是一个指针; it's the address of the bit of code corresponding to the function add()
. 它是与
add()
函数对应的代码位的地址。 Very well, but then there still seems to be a type discrepancy here. 很好,但是这里似乎仍然存在类型差异。 The function
binary_op()
takes *f
, which means f
is a pointer to something. 函数
binary_op()
接受*f
,这意味着f
是指向某事物的指针。 I pass in add
, which itself is a pointer to code. 我传入
add
,它本身就是代码的指针。 (Right?) So then f
is assigned the value of add
, which makes f
a pointer to code, which means that f
is a function just like add
, which means that f
should be called like f(left, right)
, exactly how add
should be called, but on line 12, it's called like (*f)(left, right)
, which doesn't seem right to me because it would be like writing (*add)(left, right)
, and *add
isn't the function, it's the first character of the code that add
points to. (对吧?)那么
f
赋值为add
,这使得f
成为代码的指针,这意味着f
就像add
一样,这意味着f
应该被调成f(left, right)
,究竟如何应该调用add
,但在第12行,它被调用为(*f)(left, right)
,这对我来说似乎不对,因为它就像写(*add)(left, right)
和*add
不是函数,它是add
点的代码的第一个字符。 (Right?) (对?)
I know that replacing the original definition of binary_op()
with the following also works. 我知道用以下代替
binary_op()
的原始定义也有效。
double binary_op(double left, double right, double f(double, double)) {
return f(left, right);
}
And in fact, this makes much more sense to me, but the original syntax doesn't make sense as I explained above. 事实上,这对我来说更有意义,但正如我上面解释的那样,原始语法没有意义。
So, why is it syntactically correct to use (*f)
instead of just f
? 那么,为什么在语法上使用
(*f)
而不仅仅是f
? If the symbol func
is itself a pointer, then what precisely does the phrase "function pointer" or "pointer to a function" mean? 如果符号
func
本身就是一个指针,那么短语“函数指针”或“指向函数的指针”究竟是什么意思? As the original code currently stands, when we write double (*f)(double, double)
, what kind of thing is f
then? 由于原来的代码目前维持,当我们写
double (*f)(double, double)
,是什么样的事情f
呢? A pointer to a pointer (because (*f)
is itself a pointer to a bit of code)? 指向指针的指针(因为
(*f)
本身就是指向一些代码的指针)? Is the symbol add
the same sort of thing as (*f)
, or the same sort of thing as f
? 是符号
add
同一类的事情(*f)
或同一类的事情f
?
Now, if the answer to all of this is "Yeah C++ syntax is weird, just memorise function pointer syntax and don't question it." 现在,如果所有这些的答案是“是的C ++语法很奇怪,只需记住函数指针语法,不要质疑它。” , then I'll reluctantly accept it, but I would really like a proper explanation of what I'm thinking wrong here.
那么我会不情愿地接受它,但我真的想要正确解释我在这里想错了什么。
I've read this question and I think I understand that, but haven't found it helpful in addressing my confusion. 我已经读过这个问题了 ,我想我明白这一点,但是没有发现它有助于解决我的困惑。 I've also read this question , which also didn't help because it doesn't directly address my type discrepancy problem.
我也读过这个问题 ,这也没有帮助,因为它没有直接解决我的类型差异问题。 I could keep reading the sea of information on the internet to find my answer but hey, that's what Stack Overflow is for right?
我可以继续阅读互联网上的信息海洋以找到我的答案,但是嘿,这就是Stack Overflow的用途吗?
This is because C function pointer are special. 这是因为C函数指针是特殊的。
First of, the expression add
will decay into a pointer. 首先,表达式
add
将衰减为指针。 Just like reference to array will decay into a pointer, reference to function will decay into a pointer to function. 就像引用数组会衰减成指针一样,对函数的引用会衰减为指向函数的指针。
Then, the weird stuff it there: 然后,那里有奇怪的东西:
return (*f)(left, right);
So, why is it syntactically correct to use
(*f)
instead of justf
?那么,为什么在语法上使用
(*f)
而不仅仅是f
?
Both are valid, you can rewrite the code like this: 两者都有效,你可以像这样重写代码:
return f(left, right);
This is because the dereference operator will return the reference to the function, and both a reference to a function or a function pointer are considered callable. 这是因为解除引用操作符将返回对函数的引用,并且对函数或函数指针的引用都被认为是可调用的。
The funny thing is that a function reference decay so easily that it will decay back into a pointer when calling the dereference operator, allowing to dereference the function as many time as you want: 有趣的是,函数引用很容易衰减,在调用dereference运算符时会衰减回指针,允许根据需要多次取消引用函数:
return (*******f)(left, right); // ah! still works
As the original code currently stands, when we write
double (*f)(double, double)
, what kind of thing isf
then?由于原来的代码目前维持,当我们写
double (*f)(double, double)
,是什么样的事情f
呢?
The type of f
is double (*)(double, double)
ie it is a pointer to a function of type double(double,double)
. f
的类型是double (*)(double, double)
即它是指向double(double,double)
类型函数的指针。
because
(*f)
is itself a pointer因为
(*f)
本身就是一个指针
It is not. 它不是。
Q: What do you get when you indirect through a pointer (such as in *f
)? 问:通过指针(例如
*f
)间接得到什么? A: You get an lvalue reference. 答:你得到左值参考。 For example, given an object pointer
int* ptr
, the type of the expression *ptr
is int&
ie lvalue reference to int
. 例如,给定一个对象指针
int* ptr
,表达式*ptr
的类型是int&
即lvalue对int
引用。
The same is true for function pointers: When you indirect through a function pointer, you get an lvalue reference to the pointed function. 对于函数指针也是如此:当您通过函数指针间接时,您将获得指向该函数的左值引用。 In the case of
*f
, the type is double (&)(double, double)
ie reference to function of type double(double,double)
. 在
*f
的情况下,类型是double (&)(double, double)
即对double(double,double)
类型函数的引用。
Is the symbol
add
the same sort of thing as(*f)
, or the same sort of thing asf
?是符号
add
同一类的事情(*f)
或同一类的事情f
?
The unqualified id expression add
is the same sort of thing as *f
ie it is an lvalue: 不合格的id表达式
add
与*f
相同,即它是一个左值:
Standard draft [expr.prim.id.unqual]
标准草案[expr.prim.id.unqual]
... The expression is an lvalue if the entity is a function ...
...如果实体是一个函数,表达式是一个左值...
the function symbol
add
is passed in, not what one would think of as its pointer,&add
.函数符号
add
是传入的,而不是人们会想到的指针&add
。 Now you may say that this is because the symboladd
is a pointer;现在你可能会说这是因为符号
add
是一个指针;
No. That's not the reason. 不,那不是原因。
add
is not a pointer. add
不是指针。 It is an lvalue. 这是一个左值。 But lvalues of function type implicitly convert to a pointer (this is called decaying):
但是函数类型的左值隐式转换为指针(这称为衰减):
Standard draft [conv.func]
标准草案[conv.func]
An lvalue of function type T can be converted to a prvalue of type “pointer to T”.
函数类型T的左值可以转换为“指向T的指针”的prvalue。 The result is a pointer to the function.
结果是指向函数的指针。
As such, the following are semantically equivalent: 因此,以下在语义上是等效的:
binary_op(a, b, add); // implicit function-to-pointer conversion
binary_op(a, b, &add); // explicit use of addressof operator
So, why is it syntactically correct to use
(*f)
instead of justf
?那么,为什么在语法上使用
(*f)
而不仅仅是f
?
Turns out that calling a function lvalue has the same syntax as calling a function pointer: 事实证明,调用函数lvalue与调用函数指针具有相同的语法:
Standard draft [expr.call]
标准草案[expr.call]
A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function.
函数调用是后缀表达式,后跟括号,其中包含可能为空的逗号分隔的initializer-clause列表,这些子句构成函数的参数。 The postfix expression shall have function type or function pointer type.
后缀表达式应具有函数类型或函数指针类型。 For a call to a non-member function or to a static member function, the postfix expression shall either be an lvalue that refers to a function (in which case the function-to-pointer standard conversion ([conv.func]) is suppressed on the postfix expression), or have function pointer type .
对于非成员函数或静态成员函数的调用,后缀表达式应该是引用函数的左值 (在这种情况下,函数到指针标准转换([conv.func])被抑制在postfix表达式上), 或者有函数指针类型 。
These are all the same function call: 这些都是相同的函数调用:
add(parameter_list); // lvalue
(*f)(parameter_list); // lvalue
(&add)(parameter_list); // pointer
f(parameter_list); // pointer
PS These two declarations are equivalent: PS这两个声明是等价的:
double binary_op(double, double, double (*)(double, double))
double binary_op(double, double, double (double, double))
This is because of the following rule, which is complementary to the implicit decay into function pointer: 这是因为以下规则,它与函数指针的隐式衰减互补:
Standard draft [dcl.fct]
标准草案[dcl.fct]
The type of a function is determined using the following rules.
使用以下规则确定函数的类型。 The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator.
每个参数的类型(包括函数参数包)由其自己的decl-specifier-seq和声明符确定。 After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T” ...
在确定每个参数的类型之后,将“T数组”或函数类型T的 任何参数 调整为“指向T的指针” ......
First of all a function parameter specified as a function declaration is adjusted to pointer to the function when the compiler determinates the type of the parameter. 首先,当编译器确定参数的类型时,将指定为函数声明的函数参数调整为指向函数的指针。 So for example following function declarations
例如,遵循函数声明
void f( void h() );
void f( void ( *h )() );
are equivalent and declare the same one function. 等价并声明相同的一个函数。
Consider the following demonstrative program 考虑以下示范程序
#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 );
}
From the c++ 17 Standard (11.3.5 Functions): 从c ++ 17标准(11.3.5函数):
5 The type of a function is determined using the following rules.
5使用以下规则确定函数的类型。 The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator.
每个参数的类型(包括函数参数包)由其自己的decl-specifier-seq和声明符确定。 After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T ”.
在确定每个参数的类型之后,将“T数组”或函数类型T的任何参数调整为“指向T的指针 ”。
On the other hand, according to the C++ 17 Standard 另一方面,根据C ++ 17标准
9 When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (21.11).
9当给定参数没有参数时,参数的传递方式使得接收函数可以通过调用va_arg(21.11)来获取参数的值。 [ Note: This paragraph does not apply to arguments passed to a function parameter pack.
[注意:此段落不适用于传递给函数参数包的参数。 Function parameter packs are expanded during template instantiation (17.6.3), thus each such argument has a corresponding parameter when a function template specialization is actually called.
函数参数包在模板实例化期间(17.6.3)进行了扩展,因此当实际调用函数模板特化时,每个这样的参数都有一个相应的参数。 — end note ] The lvalue-to-rvalue (7.1), array-to-pointer (7.2), and function-to-pointer (7.3) standard conversions are performed on the argument expression
- 结束注释]对参数表达式执行左值到右值(7.1),数组到指针(7.2)和函数到指针(7.3)标准转换
So what is the difference between these two declarations 那么这两个声明之间有什么区别呢
void f( void h() );
void f( void ( *h )() );
For the first declaration you may consider the parameter h
within the function body like a typedef for a function pointer. 对于第一个声明,您可以将函数体中的参数
h
视为函数指针的typedef。
typedef void ( *H )();
For example 例如
#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 );
}
According to the C++ 17 Standard (8.5.1.2 Function call) 根据C ++ 17标准(8.5.1.2函数调用)
1 A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function.
1函数调用是一个后缀表达式,后跟括号,其中包含一个可能为空的逗号分隔的initializer-clause列表,这些子句构成函数的参数。 The postfix expression shall have function type or function pointer type .
后缀表达式应具有函数类型或函数指针类型 。
So you may also define the function like 所以你也可以定义像这样的函数
void f( void h() ) { ( *h )(); }
Or even like 甚至喜欢
void f( void h() ) { ( ******h )(); }
because when the operator * is applied to a function name then the function name is implicitly convereted to pijnter to the function. 因为当operator *应用于函数名时,函数名被隐含地赋予函数pijnter。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.