一.列表初始化
1.在C++98/03中,只有普通数组和POD类型(plain old data类型,可以直接使用memcpy复制的对象)可以用初始化列表来进行初始化。
int i_arr[3] = { 1, 2, 3 };
long l_arr[3] = { 1, 3, 2, 4 };
struct A
{
int x;
int y;
} a = { 1, 2 };
2.在C++11中,初始化列表可以用于任何类型对象的初始化列表,它统一了各种对象的初始化方法,使得我们书写更加简单清晰。格式:变量名+初始化列表(相比之前少了“=”)。
class Foo
{
public:
Foo(int) {}
private:
Foo(const Foo &);
};
int main(){
Foo a1(123);
Foo a2 = 123; //error
Foo a3 = { 123 };
Foo a4 { 123 };
int a5 = { 3 };
int a6 { 3};
return 0;
}
a3虽然使用了等号,但它仍然是初始化列表。
a3,a4使用了新的初始化方式来初始化对象。
a5,a6是基本数据类型的列表寝始化方法。
a4,a6的写法在C++98/03中不存在,在C++11中,可以直接在变量名后跟上始初化列表来进行对象的初始化。
1).初始化时,{}前面的等于号是否书写对初始化行为没有影响。
int a = { 123 };
int b { 123 };
2). new操作符等可以用圆括号进行初始化的地方,也可以使用初始化列表。
int* a = new int ( 123 );
int* b = new int { 123 };
3).堆上分配的动态数组也可以使用初始化 列表来进行初始化。
int* arr = new int[3] { 1, 2, 3 };
4).列表初始化还可以作用在函数的返回值上。
struct Foo
{
Foo(int, double){}
};
Foo fun(){
return { 123, 321.0 };
}
这里的return语句如同返回一个Foo(123,321.0)。
二.列表初始化的使用细节
1.
struct A
{
int x;
int y;
} a = { 123, 321 };
struct B
{
int x;
int y;
B(int, int) : x(0), y(0) {}
} b = { 123, 321 }; //b.x = 0, b.y = 0
a的初始化过程是C++98/03中就有的聚合类型的初始化。它将以拷贝的形式,用初始化列表的值来初始化struct A中的成员。
B由于定义了一个自定义的构造函数,因此,是以B的构造函数进行初始化的。
什么情况下,C++会认为它是一个集合体?
1).类型是一个普通数组。
int x[] = { 1, 3, 5 };
2).类型是一个类,且
1)).无用户自定义的构造函数。
struct Foo
{
int x,
double y;
int z;
Foo(int, int) {}
};
Foo foo{1, 2.5, 1} //error
2)).无私有或保护的非静态数据成员。
struct ST
{
int x;
double y;
protected:
int z;
};
ST s { 1, 2.5, 1} //error
3)).无基类。
struct ST
{
int x;
double y;
virtual void F(){}
};
ST s { 1, 2.5 };
4)).无虚函数。
struct Base {};
struct Foo : public Base
{
int x;
double y;
};
Foo foo { 1, 2.5 };
5)).不能有{}和=直接初始化的非静态数据成员。
struct ST
{
int x;
double y = 0.0;
};
ST s { 1, 2.5 }; //error
C++98/03中不能在声明时进行初始化工作。但是在C++11放宽了这限制。但对一个类来说,如果对非静态数据成员进行了初始化,那么它就不再是一个聚合类型,就i 能使用初始化列表。
总之,对于聚合类型,使用初始化列对于非聚合类型,使用初始化列表相当于对其中的每个元素分别赋值,而对于非聚合类型,想要使用初始化列表的方法就是定义一个构造函数。
struct ST
{
int x;
double y;
virtual void F() {}
private:
int z;
public:
ST(int i, double j, int k) : x(i), y(j), z(k) {}
};
ST s {1, 2.5, 2 };
2.
struct Foo
{
int x;
double y;
};
Foo foo { 1, 2.5 };
int arr[] { 1, 2, 3 };
std::map<std::string, int> mn = { {"1", 1 }, { "2", 2 ), {"3", 3 } };
std::set<int> ss = { 1, 2, 3 };
std::vector<int> arr = { 1, 2, 3, 4, 5 };
迄今为止,我们所讨论的都是按步就班的按照构造函数指定的参数列表进行初化,而像数组,std::map,std::set,std::vector可以在初始化时,使用任意长度的初始化列表。
实际上,stl中的容器是通过使用std::initializer_list这个轻量级的类模板来完成上述功能的支持。我们只需要在Foo添加一个std::initiallzer_list构造函数,它也将有这种任意长度的初始化能力。
class Foo
{
public:
Foo(std::initializer_list<int>) {}
};
Foo foo = { 1, 2, 3, 4, 5 }; //OK
class FooVector
{
std::vector<int> content_;
public:
FooVector(std::initializer_list<int> list){
for (auto it = list.begin(); it!= list.end();++it){
content_.push_back(*it);
}
}
};
class FooMap
{
std::map<int, int> content_;
using pair_t = std::map<int, int>::value_type;
public:
FooMap(std::initializer_list<pair_t> list)
{
for(auto it = list.begin();it!=list.end(); ++it){
content_.insert(*it);
}
}
};
FooVector foo_1 = { 1, 2, 3, 4, 5 };
FooMap foo_2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };
3.
初始化列表(std::initializer_list)的一些特点:
1).它是一个轻量级的容器类型,内部定义了iterator等容器必需的概念。
2).对于std::initializer_list<T>而言,它可以接收任意长度的初始化列表,但要求元素必须是同种类型T(或可转化为T)。
3).它有3个成员接口:size(),begin(),end()。
4).它只能被整体初始化或赋值。
5).std::initializer_list拥有一个无参数的构造函数,因此,可以直接定义实例,得到一个空的std::initializer_list。
6).对std::initializer_list的访问只能通过begin()和end()进行循环遍历,遍历时取得的迭代器是只读的。要修改,只能通过列表初始化表的赋值对std::initializer_list做整体修改。
std::initializer_list<int> list;
size_t n = list.size(); // n== 0
list = { 1, 2, 3, 4, 5 };
n = list.size(); //n == 5
list = { 3, 1, 2, 4 };;
n = list.size(); // n == 4
7).std::initializer_list在传递或赋值的时候跟vector容器不一样,vector把每个元素都复制一遍,std::initializer_list则是非常高效的,它的内部并不负责保存初始化列表中元素的拷贝,仅仅存储了列表中元素的引用而已。
std::initializer_list<int> fund(){
int a=1;
int b=2;
return { a, b }; //error a,b在返回时并没有被拷贝
}
三.防止类型收窄
类型收窄包括以下几种情况:
1).从一个浮点数隐式转换为一个整型数。
2).从高精度浮点数隐式转换为低精度浮点数。
3).从一个整型数隐式转换为一个长度较短的整型数,并且超出了长度较短的整型数的表示范围。
4).从一个整型数隐式转换为一个浮点数,,并且超出了浮点数的表示范围。
上述情况,编译器并不会报错,而是给出一些警告。
使用列表初始化来检查及防止类型收窄。
int a =1.1 //OK
int b = { 1.1 }; //error
float fa = 1e40; //OK
float fb = { 1e40 }; //error
float fc = (unsigned long long)-1; //OK
float fd = {(unsigned long long)-1 }; //OK
float fe = (unsigned long long)1; //OK
float ff = { (unsigned long long)1 }; //OK
const int x = 1024, y = 1;
char c = x; //OK
char d { x }; //error
char e = y; //OK
char f = { y }; //OK
唯一要注意的是,x,y被定义为const int,如果去掉const限定符,那么最后一个变量f也会因为类型收窄而报错。