10

在 C 中使用 qsort 我们传入一个比较函数,例如

int cmp(const void*, const void*);

qsort 的原型需要 aint (* )(const void* , const void*)所以我们称之为:

qsort(..., cmp);

但调用同样有效:

qsort(..., &cmp);

如果我们在 C++ 中传入一个静态成员函数,这就是我们必须要做的。Kernighan & Ritchie (2nd Edition, 5.11 "Pointers To Functions" p119) 指出“由于 [cmp] 已知是一个函数,因此 & 运算符不是必需的,就像在数组名称之前不需要它一样。 "

有没有其他人对此感到有点不舒服(特别是关于类型安全)?

4

6 回答 6

6

嗯,答案是按值传递函数会产生函数指针,就像按值传递数组会产生指向其第一个元素的指针一样。有人说数组和函数“衰减”。只有少数情况不会发生这种衰减。例如 sizeof(array) 产生数组的大小,而不是它的第一个元素指针。sizeof(function) 无效(函数不是对象),你必须做 sizeof(&function)。其他场合对引用具有约束力:

void baz();

void foo(void (&bar)()) {
    bar();
}

// doesnt work, since a reference to a function is requested. 
// you have to pass 'bar' itself, without taking its address 
// explicitely.
foo(&baz);  

这,顺便说一句,这就是你能做到的原因

template<typename T, int N>
void ByRef(T (&foo)[N]) { 
    ...
}

因为在考虑参考参数时数组还没有衰减。

于 2008-11-05T01:42:13.497 回答
6

无论您是否感到不舒服,都不会改变 C 不是一种类型安全的语言这一事实​​。一个例子:

int main()
{
   int integer = 0xFFFFFF; 
   void (*functionPointer)() = (void(*)())integer; 

   functionPointer(); 

   return 0; 
}

这在编译时是完全有效的,但显然不安全。

于 2008-11-03T11:58:21.757 回答
5

我怀疑你只是觉得不舒服,因为你不习惯像 C 允许的那样“接近金属”编码。C 不需要在函数上使用地址运算符的原因是,除了调用或传递函数之外,您实际上无法对函数做任何事情。

换句话说,没有合理的定义来操纵函数的“值”。

而使用整数,您可以添加或减去值,这对函数没有意义。

无论如何,C 早于 C++ 和它自己的标准化——最初的 K&R C 甚至没有原型,而 ANSI 必须确保它们的标准化不会尽可能多地破坏现有代码。

于 2008-11-03T12:12:03.117 回答
3

这是一个语法细节。如果您不喜欢不使用 & 来指示函数指针,请始终使用它。如果您喜欢节省额外的按键并且在编码中不那么明确,那么请利用它。

至于类型安全,我看不出它有什么不同。编译器被赋予一个函数作为参数,它可以完全从名称中计算出函数签名是什么。您没有调用它(使用 () 语法)这一事实表明编译器需要您在此位置有一个函数指针。& 只是一种语法上的精巧,向人们表明他们正在查看某个描述的指针。

如果您使用的是 C++,那么我建议您将 boost 函子视为一种更好的传递函数的方法。这些可爱的实体为 C++ 中的所有函数提供了统一且相当清晰的语法:)

于 2008-11-03T12:10:30.000 回答
1

请注意,“取消引用”(即调用)函数指针也是如此;你不必

(*funcptr)(arg1, arg2);

作为

funcptr(arg1, arg2);

就足够了。

于 2008-11-03T22:20:56.163 回答
1

这与其说是一种不适感,不如说是对有效但冲突的语法选项的厌恶。要么cmp是指针,并且应该始终如一地对待,要么是其他类型——恕我直言,这在语法上具有误导性。

更进一步,我还需要调用以取消引用指针(以突出显示它是指针的事实)或不取消引用(因为函数名指针)......但不允许两者。

函数指针可能非常强大,但对于新手和有经验的程序员来说,它们似乎都是一个困惑的根源。部分困难可以通过要求一个单一的、有意义的语法来澄清它们的使用而不是模糊它来缓解。

于 2008-11-03T12:11:04.053 回答