【问题标题】:Why can't a function have a reference argument in C?为什么函数不能在 C 中具有引用参数?
【发布时间】:2026-02-17 20:15:02
【问题描述】:

例如:void foo( int& i ); 是不允许的。这是有原因的,还是不是规范的一部分?据我了解,引用通常以指针的形式实现。在 C++ 中,void foo( int* i )void foo( int& i ) 之间是否存在任何功能差异(不是语法/语义)?

【问题讨论】:

    标签: c++ c function reference


    【解决方案1】:

    因为引用是 C++ 特性。

    【讨论】:

    • C 程序员倾向于很高兴没有 C 中的引用是有原因的。
    • @Paul:嗯,那个,还有 C++ 是在 C 之后发明的。
    • @dan04:理想情况下,C++ 程序员只会将引用用于这两个目的,但实际上我看到他们一直在推荐引用(即使是明确标记为 C 的问题),而指针是正确的方法对于任何一种语言...
    • @R.. :“诸如 void 指针之类的东西会自动转换为/从任何指针类型转换,这被 C++ 破坏了。”作为 C++ 开发人员,我非常高兴编译器拒绝编译从 void * 到任何指针的转换。在 C++ 中,对这些类型转换的需求很少,无论如何很少会发生错误,当它发生时,我希望用static_cast 纠正它们。这些演员表并不是没有用的,因为它们标志着你的程序中的一个弱点。这都是关于强类型安全的。
    • @meagar:我写了很多 C++ 代码,并且指针被隐藏得很深。任何主要逻辑中都可能没有指针。原因是在 C++ 中我们发明了这个称为Ownership Symantics 的概念。这是动态分配的对象具有明确所有者的地方。然后所有者清理所有内存并删除所有那些作为 C 代码(初级 C 代码)标志的令人讨厌的内存泄漏。指针不适用于Ownership Symantics,因为没有迹象表明谁拥有指针,它只是一个指针。
    【解决方案2】:

    引用只是指针的语法醋。它们的实现是相同的,但是它们隐藏了被调用函数可能会修改变量的事实。它们真正发挥重要作用的唯一一次是使其他 C++ 特性成为可能——想到了运算符重载——根据你的观点,这些也可能是句法醋。

    【讨论】:

    • 我同意,但是 C++ 也有 const。声明对对象进行 const 引用的函数 1. 比使用指针更安全(引用不能为空)和 2. 更易于阅读,因为您不必使用 * 或 ->取消引用引用>
    • const 引用是可以的,因为它就像按值传递,只是效率更高,但允许 swap(a, b) 而不是 swap(&a, &b) 绝对是一个错误功能。 C# 的 refout 关键字做到了这一点。
    • @R: 调用未定义的行为来说明问题没有抓住重点。
    • @R:UB 的位置在您取消引用 null 的位置。通过使用引用而不是指针,我保证我的函数只对有效对象起作用。如果调用者取消引用 null,那么无论如何,UB not 都是我的函数的错。相反,当您接受指针而不是引用时,您有责任正确检查指针是否为空并报告错误。对于引用,管理指针(正确)的责任在于使用指针的代码,而不是函数。
    • @GMan:这完全是垃圾。为什么需要一个指针的函数应该接受无效的指针值?为什么foo 必须检查它是否被称为foo((int *)0),而它也不能检查它是否未被称为foo((int *)1) 或任何其他数十亿个可能的无效指针值?除非函数的文档明确声明它接受某些无效指针值并赋予它们特殊含义,否则编写代码以空指针参数调用此类函数的白痴是一个责任 用于调用 UB。
    【解决方案3】:

    例如:void foo(int& i);不允许。这是有原因的,还是不是规范的一部分?

    这不是规范的一部分。引用的语法“type&”是在 C++ 中引入的。

    据我了解,引用通常以指针的形式实现。在 C++ 中,void foo( int* i ) 和 void foo( int& i ) 之间是否存在任何功能差异(不是语法/语义)?

    我不确定它是否属于语义差异,但引用可以更好地防止取消引用空值。

    【讨论】:

      【解决方案4】:

      因为& 运算符在 C 中只有 2 个含义:

      1. 其操作数的地址(一元),

      2. 和,按位与运算符(二进制)。

      int &i; 不是 C 中的有效声明。

      【讨论】:

      • int &i; 在 C++ 中也不是有效的“声明”!
      • @Andre:给定一个初始化器就可以了。
      【解决方案5】:

      对于函数参数,指针和引用之间的区别并没有那么大,但在许多情况下(例如成员变量),引用大大限制了你可以做的事情,因为它不能被反弹。

      【讨论】:

        【解决方案6】:

        C 中不存在引用。但是,C 确实具有通过引用传递的可变参数。示例:
        int foo(int in, int *out) { return (*out)++ + in; }
        // ...
        诠释 x = 1;整数 y = 2;
        x = foo(x, &y);
        // x == y == 3.
        然而,在更复杂的 foo()s 中,忘记在每次使用中取消引用“out”是一个常见的错误。 C++ 引用允许更平滑的语法来表示闭包的可变成员。在这两种语言中,这可能会因为多个符号引用相同的存储而混淆编译器优化。 (考虑“foo(x,x)”。现在不确定“++”是仅出现在“*out”之后还是出现在“in”之后,因为两次使用之间没有序列点,增量只需要在取左表达式的值之后的某个时间发生。)

        此外,显式引用消除了 C++ 编译器的两种情况。传递给 C 函数的指针可以是可变参数或指向数组的指针(或许多其他东西,但这两个充分说明了歧义)。对比“char *x”和“char *y”。 (...或未能如预期那样这样做。)通过引用传递给 C++ 函数的变量无疑是闭包的可变成员。例如,如果我们有
        // 在类 baz 的范围内
        私人:int bar(int &x, int &y) {return x - y};
        公共 : int foo(int &x, int &y) {return x + bar(x,y);}
        // 退出作用域并继续徘徊...
        整数a = 1;诠释 b = 2;巴兹 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/

        【讨论】: