在C++学习中容易出现变量引用不恰当导致编译错误,先来看一段代码:
#include <bits/stdc++.h>
using namespace std;
void printMessage(string& message) {
cout << message << endl; }
int main() {
// 常规字符串赋值给变量
string msg = "Hello, World!";
printMessage(msg);//输出正确
// 临时对象(字符串字面值)
printMessage("Hello, World!");
//出现错误:[Error] cannot bind non-const lvalue reference
// of type 'std::string&' to an rvalue of type 'std::string'
return 0;
}
上面的第二个引用构造函数会出现错误,意思是一个非 const 的左值引用绑定到一个右值的表达式或对象上,这里重点谈谈左值与右值的引用及注意事项。
左值引用与右值引用
左值(lvalue)和右值(rvalue)是C++11 及之后的版本中表达式分类的术语,它们描述了表达式在内存中的位置或状态。
左值代表“locator value”,通常指的是具有明确存储位置(即内存地址)的对象。左值可以出现在赋值语句的左侧,因为它们可以被定位,并且它们的值可以被修改。例如,变量、数组元素和通过解引用得到的指针等都是左值。
右值代表“pure value”,通常指的是临时对象,即没有固定存储位置的值。右值不能出现在赋值语句的左侧,因为它们没有内存地址,不能被修改。字面量、临时对象和表达式的结果等通常都是右值。
认识了左值与右值,再来看看它们的引用:
- 非const左值引用(如
int&、int*
)必须绑定到一个左值上,因为左值可以被修改,而引用本质上是对其绑定对象的别名,可以通过引用来修改对象的值。int a = 100; // a 是左值,因为它有明确的内存地址,并且可以被赋值 int* p = &a; // p 是左值,因为它指向一个有明确内存地址的对象 *p = 200; // 通过指针解引用p(即a),我们可以修改a的值
- 右值引用(如
int&&
)则可以绑定到右值上,从C++11开始,它们被用于实现移动语义和完美转发等特性,允许对象资源的转移而不是复制。int a = 100; // 100 是右值,它是一个字面量,没有内存地址 int b= a + 1; // a + 1 的结果是右值,因为它是一个临时计算出来的值 void fuc(int&& i) { i = 100; // 函数内部可以修改 i 的值,因为它是右值引用 }
- const左值引用(如
const int&
),即常量引用。可以绑定到左值或右值上,但对常量引用的任何修改都是不允许的,一旦赋值初始化后就不能修改,只能读取。由于这个特性,在函数参数中使用const左值引用可以提高效率,因为它允许传递临时对象而无需复制,同时保证函数不会修改传入的值。这通常用于优化性能,特别是在处理大型对象或资源密集型对象时。int a = 10; const int& b = a; // b 是一个const左值引用,绑定到a const int& c = 20; // c 是一个const左值引用,可以赋值一个右值 a=c; // 正确:可以通过const引用读取所引用的值,运行后同时会改变b的值 b = 30; // 错误:不能修改const引用的值
通过分析,文章开始的构造函数据修改为如下,变为常量引用即可。
void printMessage(const string& message) { cout << message << endl; } int main() { // 常规字符串 string msg = "Hello, World!"; printMessage(msg);//输出正确 // 临时对象(字符串字面值) printMessage("Hello, World!");//输出正确
总之,我们需要看懂编译错误提示,确保引用的类型与要绑定的对象的类型兼容或匹配。