1. 左值
定义:
一句话概括,左值是指可以获取地址且具有持久性的对象或表达式,通常出现在'='的左边
#include <iostream>
int main(void) {
int a = 66; // 变量 a 是一个左值
int b = a; // a 是左值,可以赋值给另一个左值 b
int* p = &a; // 可以获取 a 的地址
std::cout << "Value of a: " << a << std::endl; // 输出 42
std::cout << "Address of a: " << p << std::endl; // 输出 a 的地址
*p = 88; // 修改 p 指向的地址的值,即修改 a 的值
std::cout << "New value of a: " << a << std::endl; // 输出 50
}
a
是一个左值,它有一个固定的内存地址,可以通过&a
获取。- 我们可以将
a
的值赋给b
,并且可以修改a
的值。
2. 右值
定义:
一句话概括,右值是指那些临时性、无法被取地址的对象或表达式,只能出现在'='的右边
#include <iostream>
int main(void) {
int x = 10;
int y = x + 5; // 表达式 x + 5 是一个右值
// 它的结果只能短暂存在于 y 被赋值的瞬间
std::cout << "Value of x: " << x << std::endl; // 输出 10
std::cout << "Value of y: " << y << std::endl; // 输出 15
// 尝试获取右值的地址是非法的
// int* p = &(x + 5); // 错误:不能获取右值的地址
// 右值只能在其生命周期内使用,之后就被销毁
}
- 表达式
x + 5
是一个右值,因为它是一个临时计算的结果。一旦y
被赋值,该右值就不存在了。 - 右值不能获取地址,所以
int* p = &(x + 5);
是非法的。
3. 左值引用
定义:
一句话概括,左值引用是对已存在变量或对象取的别名,允许我们对这个对象进行操作而不需要复制 用&来声明
左值引用的具体用法如下:
1.传递函数参数时避免复制:void printValue(const std::string& str);
2.返回内部对象的引用:std::string& getValue();
3.延长对象生命周期:在函数内部使用局部变量的引用。
#include <iostream>
#include <string>
// 函数声明:避免复制,通过引用传递参数
void printValue(const std::string& str) {
std::cout << "String value: " << str << std::endl;
}
// 类声明:返回内部对象的引用,避免不必要的复制
class Myclass {
public:
Myclass(const std::string& s) : value(s) {}
const std::string& getValue() const { return value; } // 返回内部对象的引用
private:
std::string value;
};
int main(void) {
int a=66;
int &b=a;//给a取别名
b=88;
std::cout<<b<<std::endl;//打印88
std::string myString = "Hello, World!";
// 传递参数时避免复制
printValue(myString);
Myclass myclass("test");
const std::string& ref = mycalss.getValue(); // 返回内部对象的引用
std::cout<< ref << std::endl; // 使用对象的引用延长生命周期
}
4. 右值引用
定义:
一句话概括,右值引用就是对一个右值的引用,通常用于移动语义和优化临时对象的资源管理,用&&来声明
右值引用的引用场景如下:
1.实现移动语义,通过转移资源来提高性能
2.重载函数,通过区分左值和右值处理提高性能
#include <iostream>
#include <string>
#include <utility> // for std::move
// 实现移动语义
class Myclass {
public:
// 移动构造函数
Myclass(Myclass&& other) noexcept : data(std::move(other.data)) {
other.data.clear(); // 迁移资源后清空源对象
}
// 移动赋值操作符
Myclass& operator=(Myclass&& other) noexcept {
if (this != &other) {//避免自赋值
std::cout << data << other.data << std::endl;
data = std::move(other.data);
other.data.clear();
}
return *this;
}
private:
std::string data;
};
// 重载函数,区分左值和右值处理
void printMyclass(Myclass& obj) {
std::cout << "lvalue: " << obj.data << std::endl;
}
void printMyclass(Myclass&& obj) {
std::cout << "rvalue: " << obj.data << std::endl;
}
int main() {
int &&a=666;//可以捕获右值
std::cout<<a<<std::endl;
Myclass obj1("Hello peng_ge!");
// 通过左值引用调用
printMyclass(obj1); // 处理左值
// 通过右值引用调用
printMyclass(std::move(obj1)); // 处理右值,资源被转移
Myclass obj2("Hello xuan_mei!");
Myclass obj3 = std::move(obj2); // 使用移动构造函数,避免复制
Myclass obj4("Hello ccsu!");
obj4 = std::move(obj3); // 使用移动赋值操作符,避免复制
return 0;
}
5.i++和++i的深度刨析
关于i++和++i谁是左值谁是右值是在面试中经常会问到的问题 如果你还分辨不清 那就太对辣!直接往下看,使你收获颇丰!
i++和++i的返回值性质
i++执行过程:
1.保存当前的 i
值到一个临时变量。
2.对 i
进行递增操作(即 i = i + 1
)。
3.返回保存的临时变量的值。
i++返回值:
1.返回的是一个临时值,也就是 i
递增前的值。
2.这个临时值是一个右值,它存在于表达式的上下文中,只在该表达式的生命周期内有效。
3.由于它是临时的右值,不能作为左值来使用(即不能直接给它赋值)。
++i执行过程:
1.对 i
进行递增操作(即 i = i + 1
)。
2.返回递增后的 i
本身(即递增后的 i
的引用)。
++i的返回值:
1.返回的是 i
递增后的新值,实际上是 i
这个变量本身。
2.由于返回的是 i
本身(一个左值),它可以在后续的操作中继续使用。
综上所述:
i++为右值
++i为左值
i++和++i的内存管理方面的差异
用类来模拟
class Counter {
public:
Counter() : value(0) {}
Counter& operator++() { // 前置递增++i
++value;
return *this;
}
Counter operator++(int) { // 后置递增i++
Counter temp = *this; // 创建临时变量
++value;
return temp; // 返回临时变量
}
private:
int value;
};
在上面的类定义中:
前置递增 ++i
直接递增 value
并返回对象的引用,没有额外的开销。
后置递增 i++
创建了一个 Counter
的副本,递增原对象的值,然后返回副本,涉及额外的内存分配和复制操作
i++和++i总结
1.i++
返回的是临时值(右值),涉及创建和管理临时变量。
2.++i
返回的是变量 i
本身(左值),直接操作和返回变量,没有额外的临时变量开销。
3.在实际使用中,++i
通常比 i++
更高效,所以能用++i就尽量不用i++。
总结
1.左值是具有持久性且可以获取地址的对象或表达式,通常出现在赋值操作符的左边,例如变量或具名对象。
2右值是临时性的、无法获取地址的对象或表达式,通常出现在赋值操作符的右边,例如临时计算的结果或字面量。
3.左值引用使用 &
声明,用于操作现有对象而避免复制,如传递参数和返回内部对象引用。
4.右值引用使用 &&
声明,用于实现移动语义和优化临时对象的资源管理。