C++11中的{}初始化,initializer_list,右值引用全解

 更新时间:2026年06月24日 09:22:19   作者:望舒329  
本文详细解析了C++1初始化初始化方式及列表初始化,介绍了C++中左值与右值引用的概念及其用途,并探讨了移动语义、引用折叠和完美转发等特性,强调了这些特性在提高代码效率上的重要作用,感兴趣的朋友一起看看吧

1.C++11中的{}

1.C++11以后想统⼀初始化⽅式,试图实现⼀切对象皆可⽤{}初始化,{}初始化也叫做列表初始化。

2.内置类型⽀持,⾃定义类型也⽀持,⾃定义类型本质是类型转换,中间会产⽣临时对象,最后优化 了以后变成直接构造。 

3.{}初始化的过程中,可以省略掉=

4.C++11列表初始化的本意是想实现⼀个⼤统⼀的初始化⽅式,其次他在有些场景下带来的不少便 利,如容器push/inset多参数构造的对象时,{}初始化会很⽅便

#include<iostream>
#include<vector>
using namespace std;
struct Point
{
	int _x;
	int _y;
};
class Date
{
public:
	Date(int month, int day, int year)
		:_month(month),
		_year(year),
		_day(day)
	{};
private:
	int _month=0;
	int _year=0;
	int _day=0;
};
int main()
{
	int x{ 2 };
	Date d1{ 2,3,4 };
	const Date& d2 = { 2024, 7, 25 };
	Point p1{ 1, 2 };
	return 0;
}

2.initializer_list

initializer_list是一个方便容器初始化创造的类,这个类的本质是底层开⼀个数组,将数据拷⻉ 过来,std::initializer_list内部有两个指针分别指向数组的开始和结束。支持迭代器遍历,同时容器支持initializer_list构造也就支持多个参数的构造STL中的容器⽀持任意多个值构成的 {x1,x2,x3...} 进⾏初始化,就是通过 std::initializer_list的构造函数⽀持的。

int main()
{
	std::initializer_list<int> mylist;
	mylist = { 10, 20, 30 };
	cout << sizeof(mylist) << endl;
    // 这⾥begin和end返回的值initializer_list对象中存的两个指针 
    // 这两个指针的值跟i的地址跟接近,说明数组存在栈上 
	cout << mylist.begin() << " " << mylist.end() << endl;
	vector<int> v1(mylist);
	vector<int> v2 = { 1,2,3,4,5 };
	vector<int> v3({ 1,2,3,4 });
	return 0;
}

3.右值引用和移动语义

3.1 左值和右值

左值是⼀个表⽰数据的表达式(如变量名或解引⽤的指针),⼀般是有持久状态,存储在内存中,我 们可以获取它的地址,左值可以出现赋值符号的左边,也可以出现在赋值符号右边。定义时const 修饰符后的左值,不能给他赋值,但是可以取它的地址。

右值也是⼀个表⽰数据的表达式,要么是字⾯值常量、要么是表达式求值过程中创建的临时对象 等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。

值得⼀提的是,左值的英⽂简写为lvalue,右值的英⽂简写为rvalue。传统认为它们分别是left value、rightvalue的缩写。现代C++中,lvalue被解释为loactorvalue的缩写,可意为存储在内 存中、有明确存储地址可以取地址的对象,⽽rvalue被解释为readvalue,指的是那些可以提供 数据值,但是不可以寻址,例如:临时变量,字⾯量常量,存储于寄存器中的变量等,也就是说左 值和右值的核⼼区别就是能否取地址。

3.2 左值引⽤和右值引⽤

1.Type& r1 = x; Type&& rr1 = y; 第⼀个语句就是左值引⽤,左值引⽤就是给左值取别 名,第⼆个就是右值引⽤,同样的道理,右值引⽤就是给右值取别名。

2.左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值

3.右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值)

move是一个函数,底层类似于对左值进行强转:(string&&)s,内部具体先不讲述

4.需要注意的是变量表达式都是左值属性,也就意味着⼀个右值被右值引⽤绑定后,右值引⽤变量变 量表达式的属性是左值,底层都是指针实现的,左值右值引用目的都是为了提高效率。

int main()
{
	int* p = new int(0);
	int b = 1;
	const int c = b;
	*p = 10;
	string s("111111");
	s[0] = 'x';
	double x = 1.1, y = 2.2;
	// 左值引⽤给左值取别名 
	int& r1 = b;
	int*& r2 = p;
	int& r3 = *p;
	string& r4 = s;
	char& r5 = s[0];
	// 右值引⽤给右值取别名 
	int&& rr1 = 10;
	double&& rr2 = x + y;
	double&& rr3 = fmin(x, y);
	string&& rr4 = string("11111");
	// 左值引⽤不能直接引⽤右值,但是const左值引⽤可以引⽤右值 
	const int& rx1 = 10;
	const double& rx2 = x + y;
	const double& rx3 = fmin(x, y);
	const string& rx4 = string("11111");
	// 右值引⽤不能直接引⽤左值,但是右值引⽤可以引⽤move(左值) 
	int&& rrx1 = move(b);
	int*&& rrx2 = move(p);
	int&& rrx3 = move(*p);
	string&& rrx4 = move(s);
	string&& rrx5 = (string&&)s;
	return 0;
}

3.3引⽤延⻓⽣命周期

右值引用可以延长临时对象的生命周期,const 左值引用也可以延长,但是这些对象无法被修改

int main()
{
	string&& s1 = "test";
	const string& s2 = s1 + s1;//cosnt左值引用无法改变
	string&& s3 = s1 + s1;
	s3 += "abg";//右值引用是左值可以改变
	cout << s3;
	return 0;
}

3.4 左值和右值的参数匹配

C++98中,我们实现⼀个const左值引⽤作为参数的函数,那么实参传递左值和右值都可以匹配。 C++11以后,分别重载左值引⽤、const左值引⽤、右值引⽤作为形参的f函数,那么实参是左值会匹配f(左值引⽤),实参是const左值会匹配f(const 左值引⽤),实参是右值会匹配f(右值引⽤),会优先匹配最适合的。

3.5 移动构造和移动赋值

1.移动构造函数是⼀种构造函数,类似拷⻉构造函数,移动构造函数要求第⼀个参数是该类类型的引 ⽤,但是不同的是要求这个参数是右值引⽤,如果还有其他参数,额外的参数必须有缺省值。

2.移动赋值是⼀个赋值运算符的重载,他跟拷⻉赋值构成函数重载,类似拷⻉赋值函数,移动赋值函 数要求第⼀个参数是该类类型的引⽤,但是不同的是要求这个参数是右值引⽤。

3.对于像string/vector这样的深拷⻉的类或者包含深拷⻉的成员变量的类,移动构造和移动赋值才有意义,因为移动构造和移动赋值的第⼀个参数都是右值引⽤的类型,他的本质是要“窃取”引⽤的右值对象的资源,⽽不是像拷⻉构造和拷⻉赋值那样去拷⻉资源,从提⾼效率,直接转移资源,在传值返回时可以提效。

3.6引用折叠和完美转发

引用折叠

C++中不能直接定义引⽤的引⽤,这样写会直接报错,通过模板或typedef 中的类型操作可以构成引⽤的引⽤。产生这种情况时C++引入了引用折叠的规则:右值引用的右值引用是右值引用,其他都是左值引用

// 由于引⽤折叠限定,f1实例化以后总是⼀个左值引⽤ 
template<class T>
void f1(T& x)
{}
// 由于引⽤折叠限定,f2实例化后可以是左值引⽤,也可以是右值引⽤ 
template<class T>
void f2(T&& x)
{}
int main()
{
	typedef int& lref;
	typedef int&& rref;
	int n = 0;
	lref& r1 = n; // r1 的类型是 int& 
	lref&& r2 = n; // r2 的类型是 int& 
	rref& r3 = n; // r3 的类型是 int& 
	rref&& r4 = 1; // r4 的类型是 int&&
	// 没有折叠->实例化为void f1(int& x) 左值引用的左值引用是左值引用
	f1<int>(n);
	//f1<int>(0); // 报错 左值引用不能引用临时对象(右值)
	// 折叠->实例化为void f1(int& x) 
	f1<int&>(n);
	//f1<int&>(0); // 报错 
	// 折叠->实例化为void f1(int& x) 
	f1<int&&>(n);
	//f1<int&&>(0); // 报错
	// 折叠->实例化为void f1(const int& x) 
	f1<const int&>(n);
	f1<const int&>(0);//const 左值引用可以引用右值
	// 折叠->实例化为void f1(const int& x) 
	f1<const int&&>(n);
	f1<const int&&>(0);
	// 没有折叠->实例化为void f2(int&& x) 
	//f2<int>(n); // 报错 右值引用不能引用左值 
	f2<int>(0);
	// 折叠->实例化为void f2(int& x) 
	f2<int&>(n);
	//f2<int&>(0); // 报错 
	// 折叠->实例化为void f2(int&& x) 
	//f2<int&&>(n); // 报错 
	f2<int&&>(0);
	return 0;
}

同时f2也被称为万能引用,传左值是左值引用,传右值是右值引用

template<class T>
void Function(T&& t)
{
	int a = 0;
	T x = a;
	x++;
	cout << x;
}
int main()
{
	Function(10);//传右值T被推到为int,int&&是右值引用
	int a = 0;
	Function(a);//传左值T被推到为int& ,int& &&是int&
	const int b = 8;
	Function(b);// b是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int& t)
				// 所以Function内部会编译报错,x不能++ 
	Function(move(b));//b是右值,推到出T为const int,x不能++;
	return 0;
}

完美转发

Function(T&& t)函数模板程序中,传左值实例化以后是左值引⽤的Function函数,传右值实例化 以后是右值引⽤的Function函数。但是右值引用是左值,如果下一层函数使用t,t的属性就会改变,在这样的情况应该使用forward<T> 完美转发实现

完美转发forward本质是⼀个函数模板,他主要还是通过引⽤折叠的⽅式实现,下⾯⽰例中传递给 Function的实参是右值,T被推导为int,没有折叠,forward内部t被强转为右值引⽤返回;传递给 Function的实参是左值,T被推导为int&,引⽤折叠为左值引⽤,forward内部t被强转为左值引⽤返回。

using namespace std;
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<class T>
void Function(T&& t)
{
	Fun(t);
}
template<class T>
void Function1(T&& t)
{
	Fun(forward<T>(t));
}
int main()
{
	Function(10);
	Function1(10);
	const int b = 8;
	Function(b);
	Function1(b);
	Function(move(b));
	Function1(move(b));
	return 0;
}

到此这篇关于C++11中的{}初始化,initializer_list,右值引用全解的文章就介绍到这了,更多相关C++ {}初始化,initializer_list,右值引用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C语言用easyx实现消砖块游戏

    C语言用easyx实现消砖块游戏

    这篇文章主要为大家详细介绍了C语言消砖块游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-05-05
  • C++合并二叉树的思路与示例代码

    C++合并二叉树的思路与示例代码

    二叉树大家应该都不陌生,但是合并二叉树呢?这篇文章主要给大家介绍了关于C++合并二叉树的相关资料,文中给出了两种解决的方法,大家可以根据需要选择对应的方法,需要的朋友可以参考下
    2021-08-08
  • 在C++中实现高效的数组原地轮转的方法总结

    在C++中实现高效的数组原地轮转的方法总结

    在 C++ 中,可以通过多种方式实现数组的轮转操作,以下是几种常见的实现方法及其对应的代码示例,文中通过代码示例介绍的非常详细,具有一定的参考价值,需要的朋友可以参考下
    2025-04-04
  • c语言冒泡排序和选择排序的使用代码

    c语言冒泡排序和选择排序的使用代码

    算法中排序是十分重要的,而每一个学习计算机的都会在初期的时候接触到这种排序,下面这篇文章主要给大家介绍了关于c语言冒泡排序和选择排序使用的相关资料,需要的朋友可以参考下
    2022-04-04
  • 一波C语言字符数组实用技巧集锦

    一波C语言字符数组实用技巧集锦

    这篇文章主要介绍了一波C语言字符数组实用技巧集锦,包括许多字符的转换与提取等基本操作示例,需要的朋友可以参考下
    2016-04-04
  • C++ 如何使用栈求解中缀、后缀表达式的值

    C++ 如何使用栈求解中缀、后缀表达式的值

    这篇文章主要介绍了C++ 使用栈求解中缀、后缀表达式的值,本文讲解了中缀、后缀表达式的求值过程以及如何将一个中缀表达式转换成后缀表达式,需要的朋友可以参考下
    2022-10-10
  • C语言实现动态顺序表的实现代码

    C语言实现动态顺序表的实现代码

    这篇文章主要介绍了C语言实现动态顺序表的实现代码的相关资料,动态顺序表在内存中开辟一块空间,可以随我们数据数量的增多来扩容,需要的朋友可以参考下
    2017-08-08
  • C++变量,常量,数组和字符串详解

    C++变量,常量,数组和字符串详解

    这篇文章主要介绍了C++变量,常量,数组和字符串,是C++入门学习中的基础知识,需要的朋友可以参考下,希望能够给你带来帮助
    2021-10-10
  • C++实现图书管理系统课程设计(面向对象)

    C++实现图书管理系统课程设计(面向对象)

    这篇文章主要为大家详细介绍了C++实现图书管理系统课程设计,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • C语言超详细讲解猜数字游戏的实现

    C语言超详细讲解猜数字游戏的实现

    现在很多游戏都有抽奖抽卡的功能,其实这个就类似于猜数字,生成一个随机数,然后你去猜,猜对了就得奖。猜到一定次数就会保底。要实现猜数字的小游戏,首先是要让程序生成随机数,这就要用到rand、srand和time这三个函数,其次要了解时间戳
    2022-07-07

最新评论