C++11中的{}初始化,initializer_list,右值引用全解
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,右值引用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!


最新评论