C++11 - Initializer_list
前言:
Vue框架:
从项目学Vue
OJ算法系列:
神机百炼 - 算法详解
Linux操作系统:
风后奇门 - linux
列表初始化:
- C++11引入了通过{}初始化变量,包括内置类型和自定义类以及STL。本质其实是通过新增的STL容器initializer_list新增了许多STL的构造函数以及编译器为内置类型赋值。
一,用法:
内置类型:
- 四种选择:=,= {},{},指针初始化
int a = 1; //c++98
int a = {1}; //c++11
int a{1}; //c++11
int *p = new int(5); //98
int *p = new int{5}; //11
内置类型数组:
- 三种选择:= {},{},new
//未规定数组长度:
int arr1[] = {1, 2, 3, 4, 5}; //98
int arr1{1, 2, 3, 4, 5}; //11
//规定数组长度,未初始化者均为0
int arr2[5] = {0};
int arr2[5]{0};
//指针初始化
int *p = new int[5]{1, 2, 3, 4}; //11
结构体/类:
- 三种初始化:构造函数,{}拷贝构造,={}拷贝构造
struct Point{
int x, y;
Point(int _x, int _y){
x = _x;
y = _y;
};
};
Point p1(1, 1);
Point p2 = {1, 1};
Point p3{1, 1};
结构体/类数组:
- 一种构造函数:{{}}
struct Point{
int x, y;
Point(int _x, int _y){
x = _x;
y = _y;
};
};
Point *p = new Point[3]{{1,1}, {2,2}, {3,3}};
//注意,内部小括号不可以省略为{1, 1, 2, 2, 3, 3}
二,隐式类型转化:
内置类型转化:
整型提升:
提升到int:
cpu对于整型的加法器是基于int的32位进行的,
任何大小小于int的类型变量在与int运算时,
会先发生整型提示,先提升到32位,再进入加法器运算
char a = 1;
short b = 2;
int c = 3;
cout<< (a+c) << (b+c) <<endl;
提升到浮点数:
另一方面,当int等整型与精度更高的浮点型数运算时
整型会首先被转换为浮点型的x.000000
再携带小数位与浮点数float / double进行计算
int a = 5;
float b = 2; //1,赋值时临时变量int 2被提升为float型
cout<< (a/b)<< endl; //2,计算时a先被提升为float型,存储在临时变量中
整型截断:
- 整型提升是编译器隐式帮我们处理的,整型截断也是
- 当我们为变量赋的值超过其内存可容纳范围时,发生整型截断:
char a = 190;
//signed char的范围为 -2^7 ~ 2^7-1
/*
整型截断过程:
190源码:0000 0000 0000 0000 0000 0000 1011 1110
190补码:0000 0000 0000 0000 0000 0000 1011 1110
截断后8位赋予变量a
a补码:1011 1110
a源码:1100 0010
a真实值:-66
*/
隐式拷贝构造:
- 上述的整型提升/整型截断属于编译器隐式处理,但是这和cpp中的类/c++11中我们今天将的Initializer_list关系不大,下面来看看类的隐式拷贝构造
- 创建一个自定义了构造函数和拷贝构造的类:
class A{
private:
int a;
public:
A(int _a){a = _a}
void operator=(A _a){
a = _a.a;
}
};
- 分析两种初始化对象写法调用的是哪种构造函数:
A a1(1); //直接构造
A a2 = 2; //先通过值2直接构造一个A对象,再通过a2的拷贝构造复制该对象值
A a3{2}; //先通过值2直接构造一个A对象,再通过a3的拷贝构造复制该对象值
- 也就是说,两种写法= {},都调用了两种构造函数,借助了临时变量完成拷贝构造
- 常见的隐式构造:
string s("hello"); //直接构造
string s = "world"; //隐式:直接构造+拷贝构造
string s{"world"}; //隐式:直接构造+拷贝构造
三,Initializer_list:
背景&定义:
背景:
- 98中的STL缺陷:vector<>为什么不能像数组一样实现不定长初始化?
- c++98中vector初始化的四种方式:
vector<int> v1; //默认初始化
vector<int> v2(v1); //拷贝已有的vector
int a[] = {1, 2, 3, 4, 5};
vector<int> v3(a, a+sizeof(a)); //通过数组地址+数组元素个数实现初始化
vector<int> v4(7, 0); //通过明确元素个数+元素值实现初始化
- c++11中vector<>新增的初始化方式:
vector<int> v5{1, 2, 3, 4, 5}; //类似数组int arr[] = {1,2,3,4,5}初始化
其实从98支持的后两种方式:数组地址+数组元素个数 / 明确元素个数+元素值,
就可以看出来,提供给vector<>元素个数,vector<>就能实现类似数组初始化的效果
c++11中的{}初始化其实也是通过编译器隐式构造了中间临时变量Initializer_list,
告知vector<>元素个数后,实现了类似数组初始化的功能
定义:
- initializer_list:本质亦为STL容器,可以理解为一个标明长度的数组
- STL中的源码:
template<class _E>
class initializer_list
{
public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;
typedef const _E* iterator;
typedef const _E* const_iterator;
private:
iterator _M_array;
size_type _M_len;
// The compiler can call a private constructor.
constexpr initializer_list(const_iterator __a, size_type __l)
: _M_array(__a), _M_len(__l) { }
public:
constexpr initializer_list() noexcept
: _M_array(0), _M_len(0) { }
constexpr size_type size() const noexcept { return _M_len; }
constexpr const_iterator begin() const noexcept { return _M_array; }
constexpr const_iterator end() const noexcept { return begin() + size(); }
};
- 功能:
1. 基本:含有元素值,以及元素个数
2. 进阶功能:由于含有元素个数,所以可以作为中间临时变量为其他STL提供初始化构造函数所需内容
使用列表初始化不定长STL:
- 以vector / map 演示:
//vector:
vector<int> v{1, 2, 3, 4, 5};
//map:
map<string, string> m{{"1", "I"}, {"2", "II"}, {"4", "IV"}};
//pair + map:
pair<string, string> p = {"5", "v"};
m.insert({"6", "VI"}); //map的insert()和erase()
m.insert(makepair("10", "X"));
自定义STL支持initializer_list:
经过编译器隐式类型转化的举例,以及initializer_list的原理和使用过程,
下面我们自己实现一个基于initializer_list实现列表初始化的类
其实就是实现一下基于initializer_list的构造函数:
//迭代器版本:
template <class T>
list(initializer_list<T> ilt){
initializer_list<T>::iterator it = ilt.begin();
while(it != ilt.end()){
push_back(*it);
it++;
}
}
//范围for版本:
template <class T>
list(initializer_list<T> ilt){
for(auto i : ilt){
push_back(i);
}
}