背景:值传递的代价
在 《函数形参与实参简介》中,我们介绍了值传递(pass by value):实参被拷贝到形参。
#include <iostream>
void printValue(int y) { std::cout << y << '\n'; }
int main() {
int x{ 2 };
printValue(x); // x 被拷贝到 y
}
对基本类型而言,拷贝成本极低;但对标准库中的类类型(如 std::string
)则往往昂贵:
#include <iostream>
#include <string>
void printValue(std::string y) { std::cout << y << '\n'; }
int main() {
std::string x{ "Hello, world!" };
printValue(x); // 每次调用都拷贝一个 std::string
}
为避免不必要的昂贵拷贝,我们采用引用传递(pass by reference)。
引用传递
将形参声明为引用类型(或常量引用类型)。调用时,引用形参直接绑定到实参,无需拷贝:
#include <iostream>
#include <string>
void printValue(std::string& y) { std::cout << y << '\n'; }
int main() {
std::string x{ "Hello, world!" };
printValue(x); // y 绑定到 x,无拷贝
}
关键要点
引用传递让我们在不拷贝实参的情况下把数据传入函数。
证明:引用即别名
#include <iostream>
void printAddresses(int val, int& ref) {
std::cout << "值形参地址: " << &val << '\n';
std::cout << "引用形参地址: " << &ref << '\n';
}
int main() {
int x{ 5 };
std::cout << "x 地址: " << &x << '\n';
printAddresses(x, x);
}
输出示例:
x 地址: 0x7ffd16574de0
值形参地址: 0x7ffd16574de4
引用形参地址: 0x7ffd16574de0
- 值形参与
x
地址不同,说明是独立对象。 - 引用形参与
x
地址相同,证明引用即别名。
通过引用修改实参
值传递时,函数内对形参的修改不影响实参:
#include <iostream>
void addOne(int y) { ++y; } // 修改的是拷贝
int main() {
int x{ 5 };
addOne(x);
std::cout << x; // 5
}
引用传递时,对形参的修改直接作用于实参:
#include <iostream>
void addOne(int& y) { ++y; } // 修改原对象
int main() {
int x{ 5 };
addOne(x);
std::cout << x; // 6
}
关键要点
非常量左值引用允许函数修改实参,可用于“在函数内更新外部对象”的场景。
非常量左值引用的限制
非常量左值引用只能绑定到可修改的左值,因此:
#include <iostream>
void printValue(int& y) { std::cout << y << '\n'; }
int main() {
int x{ 5 };
printValue(x); // OK
const int z{ 5 };
printValue(z); // 错误:z 是 const
printValue(5); // 错误:5 是右值
}
下一课将介绍常量左值引用来解决此限制,并讨论何时用值传递、何时用引用传递。