为什么C语言中函数不能有引用参数?

6
例如:void foo( int& i );是不允许的。这是有原因的吗,还是只是规范的一部分?据我所知,引用通常被实现为指针。在C++中,void foo( int* i )void foo( int& i )之间是否有任何功能上的区别(而非语法/语义上的区别)?
6个回答

31

因为引用是C++的一个特性。


3
C程序员通常很乐意没有C语言中的引用,这其中有一些原因。 - R.. GitHub STOP HELPING ICE
2
@Paul:还有一个事实就是C++是在C之后发明的。 - Oliver Charlesworth
4
理想情况下,C++程序员只应在两种情况下使用引用,但在现实情况下,我经常看到他们推荐使用引用(甚至在明确标记为C的问题中),而指针才是正确的处理方式... - R.. GitHub STOP HELPING ICE
2
@R.. : "像自动转换为/来自任何指针类型的空指针之类的东西,C++破坏了它。" 作为一名C++开发人员,我非常高兴编译器拒绝将void*强制转换为任何指针类型。在C ++中,这些转换的需求很少,错误也很少发生。 当发生错误时,我希望使用static_cast进行更正。 这些转换并不是无用的,因为它们标志着程序的弱点。 这一切都关乎强类型安全。 - paercebal
2
@meagar:我写了很多 C++ 代码,指针深藏不露。在任何主要逻辑中都可能没有指针。原因是,我们在C++中发明了这个叫做“所有权语义”的概念。这就是动态分配对象具有显式所有者的地方。然后所有者执行所有内存清理并去除所有C代码(初学者C代码)标志性的那些恶心的内存泄漏。指针对于“所有权语义”无效,因为没有指示谁拥有指针,它只是一个指针。 - Martin York
显示剩余12条评论

8

引用仅仅是指针的语法糖。它们的实现是相同的,但它们隐藏了被调用函数可能修改变量的事实。它们唯一真正起重要作用的时间是使其他C++特性成为可能 - 操作符重载就是其中之一 - 根据您的观点,这些也可能是语法糖。


1
我同意,不过C++也有const。声明接受常量引用对象的函数比使用指针更加安全(引用不能为null),而且更容易阅读,因为你不需要使用*或->对引用进行解引用。 - Orion Edwards
1
const 引用是可以的,因为它就像传值一样高效,但允许 swap(a, b) 而不是 swap(&a, &b) 明显是一个设计上的失误。C# 使用 refout 关键字解决了这个问题。 - dan04
7
引用未定义行为来阐述观点是没有意义的,这样反而偏离了重点。 - GManNickG
2
@R:UB(未定义行为)的位置在您取消引用 null 的点上。通过使用引用而不是指针,我保证我的函数仅对有效对象进行操作。如果调用者取消引用 null,则无论其他任何事情如何,UB 都不是我的函数的错误。相反,当您接受指针而不是引用时,您需要负责正确检查指针是否为 null 并报告错误。对于引用,管理指针(正确地)的责任在于使用指针的代码,而不是函数。 - GManNickG
1
@GMan:那是彻头彻尾的胡说八道。为什么一个接受指针的函数应该接受无效的指针值?为什么foo必须检查它是否被调用为foo((int*)0),当它不能检查它是否被调用为foo((int*)1)或其他数十亿个可能的无效指针值?除非函数的文档明确说明它接受某些无效的指针值并赋予它们特殊含义,否则调用此类函数并传递空指针参数的代码编写者是负责引发UB的人。 - R.. GitHub STOP HELPING ICE
显示剩余6条评论

4
例如:void foo(int& i);是不允许的。这是有原因的吗,还是只是没有规定中的一部分?
这不是规范的一部分。引用的语法“type&”是在C++中引入的。
我理解引用通常被实现为指针。在C++中,void foo(int* i)和void foo(int& i)之间是否有任何功能上的区别(而不是语法/语义)?
我不确定它是否符合语义上的区别,但引用提供了更好的保护,防止对空指针进行解引用。

0
因为在C语言中,&运算符只有两个含义:
  1. 操作数的地址(一元运算符)

  2. 按位与运算符(二元运算符)

所以int &i;不是C语言中有效的声明。


在C++中,int &i;也不是一个有效的“声明”! - André Caron
@Andre:如果给定一个初始化器,那就没问题。 - GManNickG

0

对于函数参数而言,指针和引用之间的区别并不是很大的问题。但在许多情况下(例如成员变量),使用引用会严重限制您可以做的事情,因为它不能重新绑定。


0

C语言中没有引用(References)的概念。但是,C语言有类似于通过引用传递可变参数的机制。例如:
int foo(int in, int *out) { return (*out)++ + in; }
// ...
int x = 1; int y = 2;
x = foo(x, &y);
// x == y == 3.
然而,在更复杂的foo()函数中,很容易忘记在每次使用时对"out"进行解引用。C++中的引用允许更平滑的语法来表示闭包的可变成员。在两种语言中,这可能会使编译器优化受到干扰,因为有多个符号引用同一存储空间。(考虑"foo(x,x)"。现在不确定"++"是仅作用于"*out"还是同时作用于"in",因为两个使用之间没有序列点,并且增量只需要在左表达式的值被获取后的某个时间发生。)

但是,显式引用还可以消除C++编译器中的两种情况。传递到C函数中的指针可能是可变参数或指向数组的指针(或许还有其他很多情况,但这两种情况足以说明不明确性)。对比“char *x”和“char *y”。(...或者像预期的那样失败)。通过引用传递到C++函数中的变量无疑是闭包的可变成员。例如,如果我们有
//在类baz的作用域内
private: int bar(int &x, int &y) {return x - y};
public : int foo(int &x, int &y) {return x + bar(x,y);}
//退出作用域并漫游...
int a = 1; int b = 2; baz c;
a = c.foo(a,b);
我们知道几件事:
bar()仅从foo()调用。这意味着bar()可以被编译,使其两个参数在foo()的堆栈帧中找到,而不是在自己的堆栈帧中找到。这被称为复制省略,它是一件好事。

当一个函数的形式为"T &foo(T &)"时,复制省略会变得更加令人兴奋,编译器知道一个临时对象正在进入和离开,并且编译器可以推断出结果可以直接在参数的位置上构造。然后,在编译中不需要编译任何关于临时对象的复制或结果的输出。foo()可以被编译以从某个封闭的堆栈帧获取其参数,并将其结果直接写入某个封闭的堆栈帧。

最近有一篇关于复制省略的文章(惊喜),在现代编译器中,如果你通过值传递,它甚至可以工作得更好(以及C++0x中的右值引用将如何帮助编译器跳过更多无意义的复制),请参见http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/


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