一、列表初始化
c++11想实现的效果是一切皆用列表初始化。
1、内置类型
我们先用内置类型来理解一下列表初始化。
首先需要明白,为了适应泛型编程,C++
中的内置类型(比如 int
、double
等)就已经写了构造函数,方便在进行模板传参时,传递默认构造值。所以是支持( )初始化。
int main()
{
int a(10);
char b('x');
return 0;
}
新的特性列表初始化是可以让内置类型也可以支持{ }初始化。
int main()
{
int arr[] = { 1, 2, 3 };
int a = { 10 };
char b = { 'x' };
return 0;
}
解读
与( )的构造函数不同,{ }初始化是构造+赋值,编译器优化成直接构造
效果在内置类型可能不明显,自定义类型会体现的更明显
2、自定义类型
class A
{
public:
A(int x, int y)
:_x(x)
, _y(y)
{}
A(int x)
:_x(x)
, _y(x)
{}
private:
int _x;
int _y;
};
int main()
{
// 单参数的隐式类型转换
A aa2 = 1;
A aa4 = { 1 };
// 多参数的隐式类型转换
A aa1 = { 2,2 };
//隐式类型转换产生临时对象具有常性
const A& aa3 = { 2,2 };
//一切皆列表初始化
A aa5{ 1 };
A aa6{ 2,2 };
int i{ 1 };
return 0;
}
对于 aa1 , aa4 就是列表初始化,先编译器识别用构造函数初始化一个临时对象,用临时对象赋值构造给目标对象。
aa3 就是印证构造函数产生临时对象具有常性,要加const(这里又会牵扯出右值问题后面会解决,为什么拷贝构造的参数是 const T& x,只有这样才既能接受临时对象,又能接受普通对象,这里的临时对象就是右值中的将亡值)
aa5 , aa6 相当于调用拷贝构造不用 =
3、总结
多参数构造函数支持隐式类型转换,要用 { }初始化(构造)并且赋值
二、initialize_list
本质就是指向常量数组的容器,但是未开空间,只有两个指针_First, _Last,类似于begin(), end()
两个指针指向常量数组的开始和结尾用于构造。
initializer_list支持迭代器,意味着它可以用范围for
举例了解底层
int main()
{
vector<int> v1({ 1, 2, 3, 4 });
vector<int> v2 = { 2, 3 , 4, 5 };
return 0;
}
v1是构造函数直接构造
v2是隐式类型转换产生临时对象再构造函数
练习
map<string, string> dict2 = { {"sort", "排序"}, {"insert", "插入"} };
当然3,4步可以被优化成直接构造。
三、auto&
int i = 1;
int& j = i;
auto x = j;
auto& y = j;
四、decltype 与 typeid
typeid用于打印变量的数据类型,不能用推出的字符串来定义变量。
decltype用于定义对象初始化,模板传参。
it2报错,it3正确
五、范围for
范围for的本质其实就是迭代器遍历,所以只要是有迭代器的容器就能用范围for
int main()
{
map<string, string> dict2 = { {"sort", "排序"}, {"insert", "插入"} };
for (auto& [x,y] : dict2)
{
cout << x << ":" << y << endl;
x += '1';
y += '2';
}
return 0;
}
如果不加引用首先就没法对容器中原来的数据进行改变,其次会造成深拷贝导致代价太大。