C++11新特性 | 欢迎来到现代C++的世界!

左值与右值

左值与右值的概念

可以被取地址的值为左值(left value,简称lvalue),否则为右值(right value,简称rvalue)。

常见的左值、右值例子:

c++ 复制代码
// >>>>>>> 左值
// a, p, str, str[0]等均为左值
int a = 10;
int* p = &a;
string str("hello");
str[0]

// >>>>>>> 右值
// 匿名对象,临时表达式,常量10、"world",均为右值
string("world")
x + y
10

初步认识左值和右值时,可以理解记忆为左值存储在内存中,具有"身份",生命周期长。右值存储在寄存器中,大多为临时变量,生命周期短。

但实际上,不完全是这样:可能存在被编译器优化为存储于寄存器的左值(临时变量),也有占用空间较大,需要存储于内存的临时副本(如复杂匿名对象)。初步认识时可以帮助理解move()将左值转换为右值的行为,类似汇编中的mov命令。

右值引用

语法为使用&&,例如:

cpp 复制代码
int&& a;
char&& b;

特性:

  1. 左值引用不能直接引用右值,除非加const限定;右值引用也不能直接引用左值,需要使用move()处理左值。
  2. 右值引用本身是一个左值。

移动构造

语法规则

cpp 复制代码
class class_name {
public:
	class_name(class_name&& t) {
		::swap(t, t.array);    // 交换资源
	}

private:
	int* array;
}

意义

右值引用的概念与移动构造等特性密切相关,C++11提供了一种提高程序效率的方式,通过交换临时资源的指针代替拷贝。

对于下面这一段程序:

cpp 复制代码
string func() {
	string tmp("1111111111111");
	return tmp;
}

int main() {
	string str = func();
	...
}

取决于编译器的不同优化方式,C++98标准下,实际的程序会有三种行为:

  1. 在func内调用构造函数得到tmp,传值返回时调用构造,得到临时对象,然后拷贝赋值给str;
  2. 省略临时对象的构造,直接将tmp拷贝赋值给str;
  3. 省略tmp的构造,直接用"1111111111111"构造str;

移动构造出现后就能够替代拷贝构造,降低拷贝时的资源消耗。因为对于整个程序来说,tmp的存在只是暂时的【实际会被视为将亡值】,其资源被转移给str也无所谓,所以不用进行拷贝,直接交换资源,就能达成相同的效果。

引用折叠

基本规则

类似"&"与运算,要右值引用的右值引用最终才是右值引用。

复制代码
typedef int& lref
typedef int&& rref
lref&  a  -> a的类型为int&
lref&& b  -> b的类型为int&
rref&  c  -> c的类型为int&
rref&& d  -> d的类型为int&&

TIP: 不能直接写int& &&int& &,即不能直接对引用再做引用,必须通过typedef自定义类型才可以,否则会语法报错。

完美引用

借用"引用折叠"的规则,可以统一模版的写法,使得最终的类型完全由实例化时的类型决定。万能模版写法:

cpp 复制代码
tempate<class T>
void func(T&& a) {
	...
}

func<int&> f1(num1)  // 最终a要求为一个左值,因此num1需要是左值
func<int&&> f2(num2) // 最终a要求为一个右值,因此num2需要是右值

完美转发

cpp 复制代码
forward<typename>(arg);

为避免右值被右值引用后变为左值,调用该函数,可以保持参数arg的原始属性。

例子:

cpp 复制代码
void overload(int&& rv) { std::cout << "rv " std::endl; }
void overload(int& lv) { std::cout << "lv" std::endl; }

void func(int&& v) {
	overload(v);

	overload(std::forwad<int>(v));
}
  • 当调用func时输入左值,将会得到输出:lv lv
  • 当调用func时输入右值,将会输出:lv rv

意义

完美引用结合完美转发,可以运用在模版的嵌套场景,可以有效避免代码冗余,同时提高代码效率。

Lambda表达式

本质

lambda表达式的本质是一个匿名函数对象,可以直接定义在函数内部。

语法

cpp 复制代码
// [capture list] (parameters) -> retrun type (function body)

int main() {
	auto test1 = [](int a) -> void {
		cout << a << endl;
		return 0;
	}

	test1(1);
	return 0;
}
  1. 捕捉列表(capture list)不能省略
  2. 如果参数为空,则可以省略
  3. 即使有返回值,返回值类型也可以省略,将会由返回对象自动推导
  4. 函数体不能省略(必然的)
相关推荐
要做朋鱼燕3 小时前
【C++】迭代器详解与失效机制
开发语言·c++·算法
曼巴UE54 小时前
UE5 C++ 第三方动态库的使用
开发语言·c++
C语言小火车4 小时前
【C++八股文】数据结构篇
数据结构·数据库·c++·c++八股文
良木林4 小时前
JS函数进阶
开发语言·前端·javascript
利以檀本人(梦泽不忙)5 小时前
#T1359. 围成面积
c++·程序人生·算法
汉克老师6 小时前
第十四届蓝桥杯青少组C++国赛[2023.5.28]第二部分编程题(4、 数独填数)
c++·蓝桥杯·蓝桥杯c++·c++蓝桥杯
闻缺陷则喜何志丹6 小时前
【线段树 懒删除堆】P12372 [蓝桥杯 2022 省 Python B] 最优清零方案|普及+
数据结构·c++·线段树·懒删除堆
闻缺陷则喜何志丹6 小时前
【 线段树】P12347 [蓝桥杯 2025 省 A 第二场] 栈与乘积|普及+
数据结构·c++·蓝桥杯·线段树·洛谷
徐归阳6 小时前
数组本身的深入解析
数据结构·c++·算法