在C++11之前,初始化的形式多样,包括小括号、大括号以及赋值运算符,例子如下:
class Base
{
public:
Base(int a, int b, int(*area)(int, int)) { cout<<area(a, b)<<endl; }
};
int area(int a, int b)
{
return a*b;
}
......
void test()
{
int a=1;//使用赋值运算符初始化
int b(1);//使用小括号初始化
int c[]{1,2,3};//使用大括号初始化
Base base={a,b,&area};//使用大括号完成初始化(这里调用大构造函数,输出2)
return 0
}
在C++11之后,为了方便,各种数据类型可以统一使用大括号完成初始化。只是虽然形式上一样,但底层的实现原理有所不同。
这里基于一个事实:当编译器看到大括号——{t1,t2…tn},会自动做出一个初始化列表——initializer_list< T>,该列表关联到标准库提供的容器——array<T,n>,T为大括号中元素类型,n为元素个数。
调用函数(通常为构造函数)完成初始化时,array中的元素会被编译器被逐一传递给函数。
若用于初始化的构造函数有一个重载版本的函数参数是initializer_list<T>,那么{}中的数值就不会逐一传递,而是整包一次性传递过去。
vector<string> city{"Beijing","Nanjing","Shanghai"};//这里会形成一个initializer_list<string>,背后关联一个array<string,3>,在调用vector<string>的构造函数完成初始化时,会找到一个接受initializer_list<string>的构造函数(C++所有容器都有这样一个构造函数),整包数据一起传递过去
complex<double> com(4.0,3.0);//在复数类complex<double>中并没有接受initializer_list<string>的构造函数,因此调用方式为:形成一个initializer_list<double>,背后关联一个array<double,2>,array中的两个元素会被分解逐一传递给构造函数
class Base {
public:
Base(int a, int b) { cout << "①不接受initializer_list:" << a + b << endl; }
Base(initializer_list<int> i) { cout << "②接受initializer_list:" << *(i.begin())+ *(i.begin() + 1) << endl; }
Base(int a, int b, int(*area)(int, int)) { cout<<"③不接受initializer_list:"<<area(a, b)<<endl; }
Base(initializer_list<int> i, int(*area)(int, int)) { cout <<"④接受initializer_list:"<< area(*(i.begin()), *(i.begin()+1)) << endl; }
};
int area(int a, int b) {
return a*b;
}
void main()
{
int a = 1;
int b( 2 );
Base base1{ a,b,&area };
Base base5{ initializer_list<int>{a,b},&area };//或者Base base5{ {a,b},&area };也可以
Base base2( 1, 2 );
Base base3{ initializer_list<int>{1, 2} };
Base base4{ 1, 2 };//这里假如没有重载版本②的构造函数,那么编译器会调用版本①的构造函数(编译器会将初始化列表中的两个参数拆开传给版本①的构造函数)
}
使用{}进行初始化需要注意两点:
①编译器自动赋初值:
int a;//未初始化,在没有赋值前无法使用
int b{};//同样未初始化,但编译器会自动赋初值为0,可以直接使用
int* c;//未初始化,指向非法内存(野指针,不安全)
int* d{};//编译器自动赋初值为nullptr,是安全的
2、无法实现精度收缩窄化转换
int x1(1.1);//编译可以通过,但x1变为5
int x2=1.1;//编译可以通过,但x1变为5
int x3{1.1};//在VS(VC编译器)下编译报错,无法实现收缩窄化转换,但在GCC编译器下仅仅是警告
int x4=1.1;//在VS(VC编译器)下编译报错,无法实现收缩窄化转换,但在GCC编译器下仅仅是警告
使用{}实现初始化背后是靠initializer_list<>做支撑,initializer_list<>背后是array在做支撑
initializer_list的源代码如下,其构造函数通过浅拷贝初始化头尾指针,指向编译器通过标准库提供的array定义的数组的首和尾。
template<class _Elem>
class initializer_list
{ // list of pointers to elements
public:
/*
typedef _Elem value_type;
typedef const _Elem& reference;
typedef const _Elem& const_reference;
typedef size_t size_type;
typedef const _Elem* iterator;
typedef const _Elem* const_iterator;
constexpr initializer_list() _NOEXCEPT
: _First(0), _Last(0)
{ // empty list
}
*/
//这里使用浅拷贝获取数组的头与尾指针
constexpr initializer_list(const _Elem *_First_arg,
const _Elem *_Last_arg) _NOEXCEPT
: _First(_First_arg), _Last(_Last_arg)
{ // construct with pointers
}
/*
constexpr const _Elem *begin() const _NOEXCEPT
{ // get beginning of list
return (_First);
}
constexpr const _Elem *end() const _NOEXCEPT
{ // get end of list
return (_Last);
}
constexpr size_t size() const _NOEXCEPT
{ // get length of list
return ((size_t)(_Last - _First));
}
*/
private:
const _Elem *_First;//指向数组的头
const _Elem *_Last;//指向数组的尾
};
/*
// TEMPLATE FUNCTION begin
template<class _Elem> inline
constexpr const _Elem *begin(initializer_list<_Elem> _Ilist) _NOEXCEPT
{ // get beginning of sequence
return (_Ilist.begin());
}
// TEMPLATE FUNCTION end
template<class _Elem> inline
constexpr const _Elem *end(initializer_list<_Elem> _Ilist) _NOEXCEPT
{ // get end of sequence
return (_Ilist.end());
}
*/
在C++的STL中大量使用到了initializer_list,主要是其可以接受任意数目的参数给用户提供了很大的方便。以vector为例:
class vector{
...
public:
//构造函数
vector(initializer_list<value_type> _l,const allocator_type& _a=allocator_type()):_Base(_a){
_M_range_initialize(_l.begin(),_l.end(),random_access_iterator_tag());
}
//赋值运算符重载
vector& operator=(initializer_list<value_type> _l){
this->assign(_l.begin(),_l.end());
return *this;
}
//插入操作
void insert(iterator _position,initializer_list<value_type> _l){
this->insert(_position,_l.begin(),_l.end());
}
...
};
void test()
{
vector<int> v1{1,2,3,4,5};//调用构造函数
vector<int> v2({1,2,3,4,5});//调用构造函数
vector<int> v3;
v3={1,2,3,4,5};//调用赋值运算符
v3.insert(v3.begin()+2,{6,7,8});//插入操作
}
以算法min和max为例,旧版本的min和max只接受两个参数,而新版本(C++2.0以后)可以通过initializer_list接受任意多参数:
template<typename _Tp>
inline _Tp min(initializer_list<_Tp> _l)
{
return *std::min_element(_l.begin(),_l.end());
}
template<typename _Tp>
inline _Tp max(initializer_list<_Tp> _l)
{
return *std::max_element(_l.begin(),_l.end());
}
...
void test()
{
cout<<min({string("1","2","3","4")})<<endl;
cout<<max({1,2,3,4})<<endl;
}
这里多提一嘴,VS2015默认支持C++11的部分功能,但上面的**max({1,2,3,4})**却不支持,而且也无法配置。如果是VS2017就完全支持C++11,具体的配置方法参考博客。