函数指针 vs 函数引用

33
在下面的代码中,函数指针和我认为的“函数引用”似乎具有相同的语义:
#include <iostream>
using std::cout;

void func(int a) {
    cout << "Hello" << a << '\n';
}
void func2(int a) {
    cout << "Hi" << a << '\n';
}

int main() {
    void (& f_ref)(int) = func;
    void (* f_ptr)(int) = func;

    // what i expected to be, and is, correct:
    f_ref(1);
    (*f_ptr)(2);

    // what i expected to be, and is not, wrong:
    (*f_ref)(4); // i even added more stars here like (****f_ref)(4)
    f_ptr(3);    // everything just works!

    // all 4 statements above works just fine

    // the only difference i found, as one would expect:
//  f_ref = func2; // ERROR: read-only reference
    f_ptr = func2; // works fine!
    f_ptr(5);

    return 0;
}

我在Fedora/Linux中使用gcc版本4.7.2。

更新

我的问题是:

  1. 为什么函数指针不需要解引用?
  2. 为什么解引用函数引用不会导致错误?
  3. 有没有情况必须选择其中之一?
  4. 为什么f_ptr = &func;有效?因为func应该被衰减成一个指针吗?
    f_ptr = &&func;无效(来自void *的隐式转换)
4个回答

20

函数和函数引用(即这些类型的id表达式)几乎立即退化为函数指针,因此在您的情况下,表达式funcf_ref实际上变成了函数指针。如果您喜欢,也可以调用(***func)(5)(******f_ref)(6)

在需要&运算符像应用于函数本身一样工作的情况下,使用函数引用可能更可取,例如&func&f_ref相同,但是&f_ptr则不同。


这是实现上的错误吗?因为这违背了我们对“类型”的直觉概念,对吧?我也见过像 void (*&f)(void) 这样的东西作为函数参数 - *& 一起使用.. 怎么回事? - John
1
这不是一个错误,而是语言定义的方式。在表达式中,基本上没有函数,只有函数指针;退化处理可以使“明显”的语法起作用(即您可以输入f(1),而不是更繁琐的(&f)(1))。 - Kerrek SB
@John:与数组处理方式相比,它们无法通过值传递。因此,在函数原型中,int x[](以及int x[10])等同于int *x;由于按值接收C数组是不合法的,所以他们允许使用数组声明语法作为接收指针的语法糖。 - ShadowRanger
@KerrekSB &f_ptr 返回函数指针的地址吗? - ConventionalProgrammer

16

“为什么函数指针不需要解引用?”

因为函数标识符本身已经是指向该函数的指针:

4.3 函数指针转换
§1 函数类型的左值可以转换为类型为“指向T的指针”的右值。结果是指向该函数的指针。

“为什么对函数引用进行解引用不会导致错误?”

基本上,您可以将定义引用视为定义别名(替代名称)。即使在标准中的8.3.2 引用中,部分地涉及创建对象的引用时,也会发现:
“引用可以被视为对象的名称。”

所以,当您定义一个引用时:

void (& f_ref)(int) = func;

使用f_ref几乎可以在任何可以使用func的地方使用,这就是为什么:

f_ref(1);
(*f_ref)(4);

使用 func 与直接使用相比,其工作方式完全相同:

func(1);
(*func)(4);

4

请参见这里

取地址运算符的作用与您预期的一样,它指向一个函数但不能被赋值。当将函数用作右值时,它们会转换为函数指针,这意味着您可以对函数指针进行任意次数的解引用,并且仍然得到相同的函数指针。


2
由于其他人已经给出了好的答案,所以没有解释为什么f_ptr = &&func;不起作用。当您对变量/函数应用地址运算符&时,您会得到它的地址。地址本身是一个r-value /临时变量。您不能获取临时变量的地址。
但似乎存在类型错误。消息implicit conversion from void*对于此代码非常特定于编译器。我猜你正在使用GCC / Clang。GCC / Clang提供了获取标签地址&&label的能力。结果值的类型为void*。其他编译器将输出类似于cannot take address of temporaryinvalid syntax的内容。在使用这些编译器时,在特殊情况下,这种错误可能会被隐藏而没有任何警告。
int main() {
    int foo = 42;
    foo:;
    void* a = &foo; // take the address of a variable/function
    void* b = &&foo; // take the address of a label

    std::cout << *(int*)a << '\n';
    goto *b;
};

但是谁会把每个东西都取相同的名字呢?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接