45

main中的4个语句之间有什么区别吗?我觉得只有 apply2(&func) 有意义。但是,所有 4 个都返回相同的值。

int func(void) 
{
    return 1;
}

int apply1( int f1(void) )
{
    return f1();
}

int apply2( int (*f1) (void) ) 
{
    return f1();
}

int main() 
{
    apply1(func); 
    apply1(&func);
    apply2(func);
    apply2(&func);

    return 0;
}
4

1 回答 1

70

首先,函数指针很难。认为您可以将一个函数作为参数传递给另一个函数需要一些类似于理解递归的思维弯曲。一开始你不会明白,但突然之间,就像理解的闸门在你的大脑中打开,你就开悟了。

但是,您仍然必须了解在 C 和 C++ 中将函数作为参数传递的规则。在这些语言中,函数不是一等公民,所以你可以用它们做什么有很多限制。

句法

函数指针语法有点难看。基本解剖结构是[return type] (*[name])([argument list])。周围的括号对于消除函数指针和返回指针的函数之间的歧义*name必要的:

// not function pointers: * not grouped to function name
int x(); // function that returns an int
int* x(); // function that returns an int*
int *x(); // also a function that returns an int*, spaces don't matter

// function pointers: * grouped to function name
int (*x)(); // pointer to a function that returns an int
int* (*x)(); // pointer to a function that returns an int*

衰变

在作为参数传递方面,函数的行为与数组大致相同。当通过时,它们变成一个指针。相比:

void Foo(int bar[4]); // equivalent to: void Foo(int* bar)
void Bar(int baz()); // equivalent to: void Bar(int (*baz)())

这仅仅是因为函数和数组是不可赋值和不可复制的:

int foo[4];
int bar[4] = foo; // invalid

int foo();
int bar() = foo; // invalid

因此,将它们作为函数参数传递的唯一方法是传递它们的地址而不是复制它们。(这对于数组是有争议的,但它就是这样工作的。)这些“值”在作为参数传递时被转换为指针的事实称为“衰减”。

这两个原型是兼容的(也就是说,它们指的是同一个函数,而不是不同的重载),因此,两者之间没有区别:

int foo(void bar());
int foo(void (*bar)());

除了视觉效果,这两个声明之间绝对没有区别。这两个函数都接受一个函数指针,无论它看起来是否像,因为衰变。虽然,由于衰减通常被认为是一件令人讨厌和令人困惑的事情,大多数开发人员更愿意显式地请求函数指针(许多开发人员甚至不知道函数类型可以衰减)。

隐式转换

现在,关于将函数作为参数传递。这只是衰减的结果:函数必须隐式转换为其函数指针类型。这意味着您可以在需要函数指针的地方传递一个函数,编译器将为您获取它的地址。为此,这些再次相同:

int foo();
int (*bar)() = foo; // the compiler implicitly assigns the address of foo to bar
int (*baz)() = &foo; // you explicitly assign the address of foo to baz

结合这两种解释,你会发现你的四个函数调用都是一样的。apply1并且apply2两者都接受相同类型的参数 ( int (*)(void)),即使对于apply1;来说并不明显。并且当您使用func而不是调用函数时&func,编译器会为您隐式获取地址并使其等效于&func


以下超出了问题的范围,但它详细说明了前面的部分,我认为它有点整洁。

函数引用 [仅限 C++]

这是一个鲜为人知的事实,但也可以传递对数组和函数的引用:在这种情况下,不会发生衰减。像这样:

void Foo(int (&bar)[4]); // NOT equivalent to void Foo(int* bar)
void Bar(int (&baz)()); // NOT equivalent to void Bar(int (*baz)())

在这种情况下,不允许使用 address-of 运算符,因为指针类型和引用类型之间没有隐式转换。击败衰变通常被视为一件好事,因为衰变常常令人困惑。

int baz();
Bar(baz); // valid
Bar(&baz); // INVALID

函数引用遵循与普通引用相同的规则:它们只能在定义时赋值,并且不能为空。

类型定义

您可以使用typedef.

typedef int (*X)();
X func; // func is a pointer to a function that returns an int

如果你去掉这个(*)部分,事情会变得更有趣:

typedef int X();
X* func; // func is a function pointer
X& func; // func is a function reference [C++ only]
X func; // func is a function declaration (!!)

在后一种情况下,X func;相当于声明说int func();。不要在家里这样做,除非你想把每个人都搞糊涂。

decltype有所作为 [仅限 C++]

函数和函数指针之间另一个有趣的区别是使用decltype. decltype“返回”表达式的类型。对于此构造,和之间存在差异:function&function

int bar();
decltype(bar); // type is int ()
decltype(&bar); // type is int (*)()

如果您想将类型作为模板参数传递给std::unique_ptr.

std::unique_ptr<void, decltype(free)> foo; // INVALID
std::unique_ptr<void, decltype(&free)> foo; // valid

第一个是无效的,因为它会尝试创建一个函数作为unique_ptr.

于 2013-09-05T00:55:11.087 回答