再次C++函数指针.关于语法的混淆
C++ function pointers, again. Confusion regarding syntax
在这个页面上,我发现了一个很好的函数指针示例 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
是一个像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++语法很奇怪,只需记住函数指针语法,不要质疑它">,那么我会勉强接受它,但我真的很想对我在这里的想法有一个适当的解释。
我已经阅读了这个问题,我想我理解了这一点,但发现它对解决我的困惑没有帮助。我也读过这个问题,这个问题也没有帮助,因为它没有直接解决我的类型差异问题。我可以继续阅读互联网上的信息海洋来找到我的答案,但是嘿,这就是堆栈溢出的目的,对吗?
这是因为 C 函数指针很特殊。
首先,表达式add
将衰减为指针。就像对数组的引用将衰减为指针一样,对函数的引用将衰减为指向函数的指针。
然后,奇怪的东西在那里:
return (*f)(left, right);
那么,为什么使用
(*f)
而不仅仅是f
在语法上是正确的呢?
两者都有效,您可以像这样重写代码:
return f(left, right);
这是因为取消引用运算符将返回对函数的引用,并且对函数的引用或函数指针都被视为可调用。
有趣的是,函数引用很容易衰减,以至于在调用取消引用运算符时它会衰减回指针,从而允许根据需要多次取消引用函数:
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&
即对int
的左值引用。
函数指针也是如此:当您间接通过函数指针时,您将获得对指向函数的左值引用。在*f
的情况下,类型是double (&)(double, double)
即引用类型double(double,double)
的函数。
符号
add
和(*f)
是同一种东西,还是和f
是同一类东西?
非限定 id 表达式add
与*f
是同一种东西,即它是一个左值:
标准草案 [expr.prim.id.unqual]
。如果实体是一个函数,则表达式为左值...
函数符号
add
被传入,而不是人们认为的指针,&add
。现在你可能会说这是因为符号add
是一个指针;
不。这不是原因。
add
不是指针。这是一个左值。但是函数类型的 lvalues 隐式转换为指针(这称为衰减):
标准草案 [会议功能]
函数类型 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
在语法上是正确的?
事实证明,调用函数左值与调用函数指针具有相同的语法:
标准草案 [expr.call]
函数调用是一个后缀表达式,后跟括号,其中包含可能为空的、以逗号分隔的初始值设定项子句列表,这些初始值设定项子句构成函数的参数。 后缀表达式应具有函数类型或函数指针类型。 对于对非成员函数或静态成员函数的调用,后缀表达式应为引用函数的左值(在这种情况下,后缀表达式上禁止显示函数到指针的标准转换 ([conv.func]),或者具有函数指针类型。
这些都是相同的函数调用:
add(parameter_list); // lvalue
(*f)(parameter_list); // lvalue
(&add)(parameter_list); // pointer
f(parameter_list); // pointer
附言这两个声明是等效的:
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 Rayn"; }
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 Rayn"; }
typedef void ( *H )();
void f( H h ) { h(); }
int main()
{
f( h );
}
根据 C++ 17 标准(8.5.1.2 函数调用)
1 函数调用是后缀表达式,后跟括号 包含一个可能为空的逗号分隔列表 构成函数参数的初始值设定项子句。 后缀表达式应具有函数类型或函数指针 类型。
所以你也可以定义这样的函数
void f( void h() ) { ( *h )(); }
甚至喜欢
void f( void h() ) { ( ******h )(); }
因为当运算符 * 应用于函数名称时,函数名称将隐式耦合到函数的 pijnter。
- QMetaObject invokeMethod的基于函数指针的语法
- 使用基类指针调用基类的值构造函数的语法是什么?
- C++使用 rand 定义函数语法
- C++中未命名函数指针的语法
- 将值传递给构造函数 c++ 的差异语法
- C++语法中的函数指针
- 存储函数指针的正确语法
- QObject::连接不起作用 - 使用函数语法找不到信号
- 对函数库中的语法感到困惑 std::bind
- 解释通过从函数引用返回数组的语法
- 函数错误 C2059:语法错误:'>'不起作用
- 当C++类函数参数之一是结构时,它们的语法有什么不同
- 将显式指定的函数模板重载作为模板参数传递的正确语法是什么?
- Qt的新信号/时隙语法问题 - 连接到一个简单的函数
- 需要"模板<>"语法 --> 通过函数调用类模板
- C++ std::函数语法问题
- 备用函数语法/函数原型
- 奇怪的c++语法:函数调用之前的类型定义
- c++数组语法(函数返回数组)
- 实现[B,C]=f(A)语法(函数f作用于具有两个或多个输出数组的数组)