又是 C++ 函数指针。语法混乱
C++ function pointers, again. Confusion regarding syntax
在 this page 上,我发现了一个很好的 C++ 函数指针示例(以及仿函数,但这个问题与仿函数无关)。下面是该页面的一些副本。
#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
是一个函数,就像 [=15] =],这意味着 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
本身就是一个指针,那么短语 "function pointer" 或 "pointer to a function" 究竟意味着什么?按照目前的原码,写double (*f)(double, double)
,那么f
是什么东西呢?指向指针的指针(因为 (*f)
本身就是指向一段代码的指针)?符号 add
是和 (*f)
一样的东西,还是和 f
一样的东西?
现在,如果所有这一切的答案是"Yeah C++ syntax is weird, just memorise function pointer syntax and don't question it.",那我勉强接受,但我真的很想对我的事情有一个适当的解释我想错了。
我读过 and I think I understand that, but haven't found it helpful in addressing my confusion. I've also read this question,这也没有帮助,因为它没有直接解决我的类型差异问题。我可以继续阅读互联网上的海量信息来找到我的答案,但是嘿,这就是 Stack Overflow 的用武之地吗?
这是因为C函数指针比较特殊
首先,表达式 add
会衰减为一个指针。就像对数组的引用会衰减为指针一样,对函数的引用也会衰减为指向函数的指针。
然后,那里有奇怪的东西:
return (*f)(left, right);
So, why is it syntactically correct to use (*f)
instead of just f
?
两者都有效,你可以这样重写代码:
return f(left, right);
这是因为取消引用运算符将 return 对函数的引用,并且对函数的引用或函数指针都被认为是可调用的。
有趣的是,函数引用很容易衰减,以至于在调用取消引用运算符时它会退化回指针,从而可以根据需要多次取消引用函数:
return (*******f)(left, right); // ah! still works
As the original code currently stands, when we write double (*f)(double, double)
, what kind of thing is f
then?
f
的类型是 double (*)(double, double)
即它是一个指向类型 double(double,double)
的函数的指针。
because (*f)
is itself a pointer
不是。
问:通过指针间接访问会得到什么(例如 *f
)? A:你得到一个左值引用。例如,给定一个对象指针 int* ptr
,表达式 *ptr
的类型是 int&
即 int
.
的左值引用
函数指针也是如此:当您间接通过函数指针时,您将获得指向函数的左值引用。在 *f
的情况下,类型是 double (&)(double, double)
即引用类型 double(double,double)
.
的函数
Is the symbol add
the same sort of thing as (*f)
, or the same sort of thing as f
?
非限定 id 表达式 add
与 *f
是同一类东西,即它是一个左值:
Standard draft [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
. Now you may say that this is because the symbol add
is a pointer;
没有。不是这个原因。
add
不是指针。它是一个左值。但是函数类型的左值隐式转换为指针(这称为衰减):
Standard draft [conv.func]
An lvalue of function type T can be converted to a prvalue of type “pointer to T”. The result is a pointer to the function.
因此,以下内容在语义上是等价的:
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 just f
?
事实证明,调用函数左值与调用函数指针具有相同的语法:
Standard draft [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.
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.
这些都是相同的函数调用:
add(parameter_list); // lvalue
(*f)(parameter_list); // lvalue
(&add)(parameter_list); // pointer
f(parameter_list); // pointer
P.S。这两个声明是等价的:
double binary_op(double, double, double (*)(double, double))
double binary_op(double, double, double (double, double))
这是因为以下规则,它是对函数指针隐式衰减的补充:
Standard draft [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.
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” ...
首先,当编译器确定参数的类型时,将指定为函数声明的函数参数调整为指向函数的指针。因此,例如以下函数声明
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 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. 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”.
另一方面,根据 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). [ 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. — 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
那么这两个声明有什么区别
void f( void h() );
void f( void ( *h )() );
对于第一个声明,您可以将函数体内的参数 h
视为函数指针的类型定义。
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 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.
The postfix expression shall have function type or function pointer
type.
所以你也可以这样定义函数
void f( void h() ) { ( *h )(); }
甚至喜欢
void f( void h() ) { ( ******h )(); }
因为当运算符 * 应用于函数名称时,函数名称会隐式转换为函数的指针。
在 this page 上,我发现了一个很好的 C++ 函数指针示例(以及仿函数,但这个问题与仿函数无关)。下面是该页面的一些副本。
#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
是一个函数,就像 [=15] =],这意味着 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
本身就是一个指针,那么短语 "function pointer" 或 "pointer to a function" 究竟意味着什么?按照目前的原码,写double (*f)(double, double)
,那么f
是什么东西呢?指向指针的指针(因为 (*f)
本身就是指向一段代码的指针)?符号 add
是和 (*f)
一样的东西,还是和 f
一样的东西?
现在,如果所有这一切的答案是"Yeah C++ syntax is weird, just memorise function pointer syntax and don't question it.",那我勉强接受,但我真的很想对我的事情有一个适当的解释我想错了。
我读过
这是因为C函数指针比较特殊
首先,表达式 add
会衰减为一个指针。就像对数组的引用会衰减为指针一样,对函数的引用也会衰减为指向函数的指针。
然后,那里有奇怪的东西:
return (*f)(left, right);
So, why is it syntactically correct to use
(*f)
instead of justf
?
两者都有效,你可以这样重写代码:
return f(left, right);
这是因为取消引用运算符将 return 对函数的引用,并且对函数的引用或函数指针都被认为是可调用的。
有趣的是,函数引用很容易衰减,以至于在调用取消引用运算符时它会退化回指针,从而可以根据需要多次取消引用函数:
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?
f
的类型是 double (*)(double, double)
即它是一个指向类型 double(double,double)
的函数的指针。
because
(*f)
is itself a pointer
不是。
问:通过指针间接访问会得到什么(例如 *f
)? A:你得到一个左值引用。例如,给定一个对象指针 int* ptr
,表达式 *ptr
的类型是 int&
即 int
.
函数指针也是如此:当您间接通过函数指针时,您将获得指向函数的左值引用。在 *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
?
非限定 id 表达式 add
与 *f
是同一类东西,即它是一个左值:
Standard draft [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
. Now you may say that this is because the symboladd
is a pointer;
没有。不是这个原因。
add
不是指针。它是一个左值。但是函数类型的左值隐式转换为指针(这称为衰减):
Standard draft [conv.func]
An lvalue of function type T can be converted to a prvalue of type “pointer to T”. The result is a pointer to the function.
因此,以下内容在语义上是等价的:
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
?
事实证明,调用函数左值与调用函数指针具有相同的语法:
Standard draft [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. 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.
这些都是相同的函数调用:
add(parameter_list); // lvalue
(*f)(parameter_list); // lvalue
(&add)(parameter_list); // pointer
f(parameter_list); // pointer
P.S。这两个声明是等价的:
double binary_op(double, double, double (*)(double, double))
double binary_op(double, double, double (double, double))
这是因为以下规则,它是对函数指针隐式衰减的补充:
Standard draft [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. 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” ...
首先,当编译器确定参数的类型时,将指定为函数声明的函数参数调整为指向函数的指针。因此,例如以下函数声明
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 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. 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”.
另一方面,根据 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). [ 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. — 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
那么这两个声明有什么区别
void f( void h() );
void f( void ( *h )() );
对于第一个声明,您可以将函数体内的参数 h
视为函数指针的类型定义。
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 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. The postfix expression shall have function type or function pointer type.
所以你也可以这样定义函数
void f( void h() ) { ( *h )(); }
甚至喜欢
void f( void h() ) { ( ******h )(); }
因为当运算符 * 应用于函数名称时,函数名称会隐式转换为函数的指针。