传值 vs 传引用

函数的参数传递方式,决定了数据如何从调用者“交到”被调函数手中。选择不当会严重影响性能。

传值 (Pass by Value)

  • 形式:函数参数列表中没有任何特殊符号(如 void func(Complex c))。

  • 本质:调用时,实参对象会被完整地复制一份,生成一个仅在该函数内部有效的副本。函数内所有操作都作用于这个副本,与原实参无关。

  • 性能代价:拷贝整个对象。对于像 double 这样的基本类型(4或8字节),代价尚可接受。但对于包含大量数据的自定义类对象(如一个包含数组的 Complex 类),拷贝的代价(时间与内存)会非常高。

传引用 (Pass by Reference)

  • 形式:函数参数类型后加 & 符号(如 void func(Complex& c))。

  • 本质:传递的是实参对象的别名 (alias)或引用。底层实现上,引用通常通过指针实现,传递的是一个地址(例如4或8字节)。函数内通过这个引用(别名)操作的对象,就是外部的实参对象本身。

  • 性能优势:避免了整个对象的拷贝,无论对象多大,传递的代价都近似于传递一个指针,效率极高。

  • 副作用:因为操作的是原对象,函数内部对参数的修改会直接影响外部的实参。

为什么说引用底层是指针?
语法糖 (Syntactic Sugar):引用是C++提供的一个更安全、更直观的“语法糖”,它隐藏了指针的复杂语法(如*解引用、&取地址)。
底层实现:编译器在生成机器码时,通常会为引用分配一个指针大小的存储空间来存放目标对象的地址。操作引用时,编译器会自动通过这个地址去访问真实对象。
关键区别:虽然底层类似指针,但引用在语法层面必须被初始化且不能重新绑定到其他对象(int& r = a;之后,r永远代表a),这比指针更安全。

结论(良好习惯):

在参数传递时,应优先考虑传引用 (Pass by Reference),以避免不必要的拷贝,提升程序性能。

关于基本类型:对于int, char等极小对象,传值可能比传引用(传指针)更快,因为拷贝本身可能比寻址更快。但作为通用设计准则,“优先传引用”依旧是更好的选择。

const 引用 (const Reference)

当我们为了效率而传递引用,但又不希望函数内部修改我们的数据时,就需要 const 引用。

const 引用

  • 形式:函数参数类型为 const T&(如 double real(const Complex& c))。
  • 本质:传递对象的引用(高效),但通过 const 修饰,使得在该函数内部,这个引用是“只读”的。任何试图通过此引用修改对象成员的操作,都会导致编译错误。
  • 设计意图:兼顾效率与安全。既享受了传引用的高效,又明确了函数“不会修改参数”的契约,同时保护了调用者的数据不被意外修改。
  • 调用灵活性:一个接受 const T& 参数的函数,既可以接受非 const 对象,也可以接受 const 对象、临时对象(右值)作为实参,适用性更广。

image

如何从代码中推断设计意图?

观察函数签名是理解其行为的第一步。例如,在输入材料的例子中:

1
2
Complex& operator+=(const Complex&); // 参数为 `const Complex&`
ostream& operator<<(ostream& os, const Complex& x); // 第一个参数为 `ostream&` (非const)
  • operator+= 的参数是 const 引用,因为它需要读取右操作数的值,但不会修改它;它修改的是左操作数(即调用该函数的对象本身)。
  • operator<< 的第一个参数是非 const 的 ostream&,因为向输出流写入数据(如 os << …)会改变流的状态(如写入位置、错误标志等),所以它必须有权修改这个 ostream 对象。

返回局部变量的引用:“悬空引用”??

返回值也可以选择传值或传引用,原则同样是“在保证正确的前提下,尽量返回引用以提升效率”。但这里有一个致命的陷阱。

核心规则:函数可以安全地返回引用,当且仅当该引用所引用的对象在函数返回后仍然存在。

返回局部变量的引用

  • 场景:在函数内部创建了一个局部对象(在栈上分配),然后尝试返回它的引用。
  • 问题:局部对象在函数结束时,其生命周期就终结了,内存会被释放。此时返回的引用指向了一块已经失效(“悬空”)的内存。通过这个引用访问数据,行为是未定义的 (Undefined Behavior),通常会导致程序崩溃或输出乱码。
  • 示例(错误):
1
2
3
4
5
6
7
8
9
10
int& getBadReference() {
int localVar = 42; // 局部变量
return localVar; // ❌ 大错特错!返回局部变量的引用
}

int main() {
int& ref = getBadReference();
std::cout << ref; // 🚨 未定义行为!localVar 的内存已被回收
return 0;
}

image