int i_arr[3]={1,2,3}; //数组
struct A{
int x;
struct B{
int i,j;
}b;
}a={1,{2,3}}; //POD类型
//拷贝初始化
int i=0;
class Foo{
public:
Foo(int){}
}foo=123;
//直接初始化
int i(0) ;
Foo bar(123);
这些不同的初始化方法,都有各自的适用范围和作用。最关键的是,这些种类繁多的初始化方法,没有一种可以通用所有情况。
为了统一初始化方式,并且让初始化行为具有确定的效果,c++11里提出了列表初始化的概念。
class Foo{
public:
Foo(int){}
private:
Foo(const Foo&){}
};
int main(){
Foo a1(123);
Foo a2=123; //error: 'Foo::Foo(const Foo&)' is private
Foo a3={123};
Foo a4{123};
int a5={3};
int a6{3};
}
上例中,a3、a4 使用了新的初始化方式来初始化对象,效果如同a1的直接初始化;其实这在c98/03里也有使用,但是不支持所有类型,只支持POD类型和数组类型。
在初始化时,{ }前面的等于号是否书写对初始化行为没有影响。
上面并没有 new操作符的影子,我们也可以试一试
int *c = new int (23); //指针c 初始化为 23;
int *a = new int { 123}; //指针a使用初始化列表的方式在内存初始化时指定了值为123;
double b = double {12.12}; //指针 b 是对匿名对象使用列表初始化后,再进行拷贝初始化。
int *arr = new int[3]{1,2,3}; //初始化一个数组,并用初始化列表的方式进行初始化。
列表初始化还可以用在函数的返回值上:
struct Foo{
Foo(int , double ){}
};
Foo fun(void){ return { 123, 321.0} };
这里return就如同返回了一个Foo( 123, 321.0)。
其实,上述类和结构体的例子能使用初始化列表,是因为它们是聚合类型。什么的类型C++会认为它是一个聚合体呢?
下面来看看定义:
(1)类型是一个普通数组。
(2)类型是一个类,且
例如:
struct Foo{
int x;
int y;
private:
double z;
} a= {1,2,3.4}; //error 这有私有函数
struct ST{
int x1;
double y1;
virtual void f(){}
}s={1,2.5}; //error virtual函数
struct Base{};
struct Foo: public Base{
int x;
double y;
} foo = {1,2.5}; //error 继承
struct Foo{
int x;
double y;
int z;
Foo(int ,int ){}
}foo{1,2.3,4}; //error 自定义构造函数
struct Foo{
int x;
double y=0.0
}foo{1,2.3}; //error y 已经直接初始化
当不是聚合类型时,那么就需要自定义一个构造函数。
列表初始化还有一个很重要的作用,那就是防止类型收窄。
何为类型收窄,类型收窄指的是导致数据内容发生变化或精度丢失的隐式类型转换。如:
1、从一个浮点数隐式转换为一个整数,如 int a = 2.2;
2、从高精度浮点数转换为低精度浮点数,如从long double 隐式转换成 double 或 float 类型。
3、从一个整型数隐式转换成一个浮点数,并且超出了浮点数范围,如 float x = ( unsigned long long) - 1;
4、从一个整型数隐式转换为一个长度较短的整型数,并且超出了长度较短的整形术的表示范围,如char x=65536。
在c++中,上面所示的类型收窄并不会报错。这往往会导致一些隐藏的错误。在c++11中,可以通过列表初始化来检查即防止类型收窄。(部分编译器可能不会报错,但会有警告)int a= 1.1;
int b = { 1.11}; //error
float fa = 1e40;
float fb = { 2e40}; //error