与非常量左值引用的区别
非常量左值引用只能绑定到可修改的左值;而常量左值引用(const T&
)能够绑定:
- 可修改的左值
- 不可修改的(
const
)左值 - 右值
因此,把形参声明为 const
引用后,可接受任意类别的实参:
#include <iostream>
void printRef(const int& y) // y 为常量引用
{
std::cout << y << '\n';
}
int main()
{
int x{ 5 };
printRef(x); // OK:可修改左值
const int z{ 5 };
printRef(z); // OK:不可修改左值
printRef(5); // OK:右值字面量,绑定到临时 int
}
优势:避免拷贝且禁止修改
常量引用传递带来的主要好处与非常量引用相同——避免拷贝;同时保证函数内部不能修改被引用对象:
void addOne(const int& ref)
{
++ref; // 错误:ref 为 const,不能修改
}
最佳实践
除非需要修改实参,否则请优先使用常量左值引用作为形参。
另外,正因为允许常量引用绑定右值,我们才能把字面量或其他右值传给引用形参的函数。
类型不同时的绑定规则
在 12.4 课中已指出:常量左值引用可绑定到可隐式转换的值,此时会生成一个临时对象供引用绑定。
示例:
#include <iostream>
void printVal(double d)
{
std::cout << d << '\n';
}
void printRef(const double& d)
{
std::cout << d << '\n';
}
int main()
{
printVal(5); // 5 → 临时 double → 拷贝给 d
printRef(5); // 5 → 临时 double → 直接绑定给 d
}
值传递时,转换产生的额外拷贝通常可忽略(编译器常优化掉)。
引用传递若发生转换,则可能带来一次(可能昂贵的)拷贝,需留意。
警告
若实参类型与引用类型不符,编译器会生成临时对象;引用实际指向该临时对象,而非原对象,后续对原对象的修改不会影响引用。
混合传参方式
函数的多个形参可分别采用值传递或引用传递:
#include <string>
void foo(int a, int& b, const std::string& c) {}
int main()
{
int x{ 5 };
const std::string s{ "Hello" };
foo(5, x, s); // 第 1 个值传递,第 2 个非常量引用,第 3 个常量引用
}
何时用值传递,何时用引用传递
简明规则:
- 基本类型 / 枚举:拷贝代价低,通常值传递。
- 类类型:拷贝可能昂贵,通常常量引用传递。
若拿不准,用常量引用更稳妥。
其他常见情况
值传递(高效)
‑ 枚举(含限定与非限定)
‑ 视图类std::string_view
、std::span
‑ 轻量级引用类(迭代器、std::reference_wrapper
)
‑ 小型值语义类(std::pair<int,int>
、std::optional
等)引用传递
‑ 需在函数内修改实参
‑ 不可拷贝类型(std::ostream
)
‑ 拷贝涉及所有权转移(std::unique_ptr
、std::shared_ptr
)
‑ 多态基类或可能派生类(防对象切片)
值传递与引用传递的成本对比(进阶)
参数初始化
- 值传递:拷贝成本 ≈ 对象大小 + 额外构造开销
- 引用传递:绑定成本 ≈ 拷贝一个指针大小
参数使用
- 值参数:一次寄存器/内存访问
- 引用参数:先访问引用本身,再访问被引用对象(多一次间接寻址)
优化机会
- 值传递无别名风险,优化器可更激进
- 引用传递需保守处理别名
“廉价拷贝”经验阈值
若对象大小 ≤ 2 × sizeof(void*)
且无额外构造开销,可视为廉价;否则倾向引用传递。
检测宏示例:
#define isSmall(T) (sizeof(T) <= 2 * sizeof(void*))
字符串形参:const std::string& vs std::string_view
现代 C++ 中,若函数只需读取字符串,应优先使用 std::string_view
:
void doSomething(std::string_view); // 推荐
void doSomething(const std::string&); // 次选
std::string_view
可无缝接受std::string
、std::string_view
、C 风格字符串/字面量,且转换代价低。- 仅当需要 C 风格空终止符或必须调用只接受
std::string
/char*
的 API 时,才用const std::string&
。
性能对比表(略)已在前文详细列出。