简体   繁体   English

C ++函数指针,再次。关于语法的困惑

[英]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 just f ? 那么,为什么在语法上使用(*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 is f 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 as f ? 是符号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 symbol add 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 just f ? 那么,为什么在语法上使用(*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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM