先看一行代码:
int x[] = {1, 3, 5};
这里声明x为有3个int元素的数组,在C语言中存在大量这样的代码,这太司空见惯了,似乎没有什么可好奇的东西存在,那么在C++中呢?
嗯,C++必须兼容C语言中的这种小把戏,说得再直白些:C++中为了兼容C,对某些特殊的数据类型存在一种特殊的初始化方式,那就是直接用{}来初始化,
而这种特殊的数据类型就称之为Aggregate(有人翻译为聚合体,见<<Imperfect C++>>)
关于Aggregate最全面的论述应该就是C++标准中的8.5.1了,先原样翻译一遍好了
标准1:一个Aggregate是一个数组,或一个无用户自定义构造函数.无private或protected的非静态数据成员.无基类并且无虚函数的类.
不要被这一大段定义吓倒,其实Aggregate就是一个数组或者C语言意义上的struct,普通的结构当然是Aggregate,但是还不全面
Aggregate也可以有些普通的成员函数,根据C++的对象模型它们根本不会影响结构的布局
Aggregate也可以有用户自定义的析构函数,嗯,这不会影响初始化
Aggregate也可以有用户自定义的拷贝赋值函数,嗯,这不会影响初始化
Aggregate也可以有引用类型的成员,嗯,这确实影响初始化,但是只要我们小心的给这个引用初始化,也没有什么大不了的
...
嗯,有些人该晕了,标准中没有这样反向的定义Aggregate确实是对的,所以我们最好按照标准重新理解一下
Aggregate最大的特点就是可以直接使用用{}扩起来的初始化列表进行初始化,从这个意思上说Aggregate的三个特点就清晰了
Aggregate不能有用户自定义构造函数,因为这可能和初始化列表有冲突
Aggregate不能private或protected的权限,因为标准已经规定带有该权限的成员顺序可以是任意的
Aggregate不能有基类和虚函数,用初始化列表初始化时是不能加入调用基类默认构造函数的代码的,同理也不能加入构造虚函数表的代码
POD,有人说到POD了吗?终于遇到高手了:),POD是Aggregate的一个子集,也更像是C语言中的结构,实际上它是沟通C++语言和C语言的唯一桥梁
关于POD,就说这么多好了,我将在另一篇文档中详细描述它
标准2:Aggregate可以直接使用用{}扩起来的初始化列表进行初始化.{}内包含了用','分割的成员初始化列表,列表写的顺序应该是按照递增的顺序(如果Aggregate是数组)
或者成员的顺序,如果Aggregate内部还包含子Aggregate,那么就把该规则递归的应用到子Aggregate上,例如:
struct A
{
int x;
struct B
{
int i;
int j;
} b;
} a = { 1, { 2, 3 } };
初始化a.x=1, a.b.i=2, a.b.j=3.
标准3:结构的Aggregate可以用不带{}的单独表达式初始化
这句话不懂,不带{}是编译不过去的?
标准4:未知大小的数组可以用包含n个元素的初始化列表初始化,当n超过0时,就定义该数组有n个元素,例如:
int x[] = {1, 3, 5};
声明x为一个数组,但是却没有声明数组的大小,因为该数组用包含3个元素的初始化语句初始化,因此也就定义该数组大小为3
不包含任何元素的空初始化列表{}不能用来初始化未知大小的数组
也就是说:
int x1[2] = {};//正确,但是VC6是不支持的
int x1[] = {};//错误
标准5:结构的Aggregate中如果包含了静态成员变量,在初始化列表中将不考虑该变量,例如:
struct A1
{
int i;
static int s;
int j;
} a1 = { 1, 2 };
这里2初始化为a1.j,而不是s
标准6:初始化列表中元素的个数超过了Aggregate成员的个数,这是病态的,例如:
int x2[2] = {1, 3, 5};//错误
标准7:初始化列表中元素的个数少于了Aggregate成员的个数,未明确指定的Aggregate成员将被默认初始化,例如:
struct S { int a; char* b; int c; };
S ss = { 1, "asdf" };
这里ss.c被int()初始化,因此是0
标准8:如果Aggregate不包含任何元素,可以用空初始化列表进行初始化,例如:
struct S { };
struct A
{
S s;
int i;
} a = { { } , 3 };
空初始化列表可以初始化任何Aggregate,未明确指定的Aggregate成员将被T()形式默认初始化
标准9:不完全初始化列表或者空初始化列表如果没有明确指定引用类型的成员,那么程序是病态的,例如:
int n = 10;
struct A2
{
int i;
int& j;
} a2 = { 1, n};
这是成立的
但是如果省略了n,把初始化列表写成了{1}就错误
标准10:初始化多维数组,元素初始化的顺序是多维数组的最后一个维度变化最快(也就是常说的按照行的顺序),例如:
int x[2][2] = { 3, 1, 4, 2 };
初始化后是x[0][0] = 3, x[0][1] = 1, x[1][0] = 4, and x[1][1] = 2
float y[4][3] = {
{ 1 }, { 2 }, { 3 }, { 4 }
};
初始化后是y的第一行第一个元素=1,其余3个元素=0,第二行第一个元素=2,其余3个元素=0...
标准11:初始化多维数组,也可以省略子初始化列表,这时初始化列表的顺序和多维数组元素的顺序是一致的,例如:
float y[4][3] = {
{ 1, 3, 5 },
{ 2, 4, 6 },
{ 3, 5, 7 },
};
初始化y的第一行元素为1,3,5,第二行元素为2,4,6,第三行元素为3,5,7,第四行元素是0,0,0,而省略子初始化列表后
float y[4][3] = {
1, 3, 5, 2, 4, 6, 3, 5, 7
};
和未省略初始化列表的效果是一致的
其实这条标准真是没什么说的,只要清楚数组的元素顺序,还用罗唆这么多吗
标准12:初始化列表支持隐式的类型转换,如果子Aggregate省略了子初始化列表,那么认为只初始化子Aggregate的第一个元素,例如:
struct A {
int i;
int j;
operator int()
{
return i;
}
};
struct B {
A a1, a2;
int z;
};
A a;
B b = { 4, a, a };
这里b.a1被4初始化,因为省略了子初始化列表,认为b.a1.i=4,b.a1.j=int();b.a2被a初始化;b.z被a初始化,但是因为类型不一致,自动进行A中int()的隐式类型转换
标准13:初始化列表支持成员构造函数的调用,例如:
struct A {
A(int i)
{
x=i+2;
}
operator int()
{
return x;
}
int x;
};
struct B {
A a1, a2;
int z;
};
A a(2);
B b = { 4, a, a };
A x[3] = {1, 3, 5};
有人能算清楚这里调用了几次A的构造函数吗?呵呵
A a(2);这里是一次
b.a1用4初始化,因为初始化列表支持成员构造函数的调用,因此这里调用了A的构造函数
b.a2用a初始化,这里要调用一次拷贝构造函数,当然系统默认已经生成了
x[0]用1初始化,因为初始化列表支持成员构造函数的调用,因此这里调用了A的构造函数,x[1]和x[2]是同样的
如果不算拷贝构造函数,一共是5次
标准14:如果Aggregate变量有全局的生存期,而且该类型为POD类型,该变量的初始化列表全部是常量表达式,那么这个初始化将在
全局初始化的静态阶段完成
这里是说,符合要求的Aggregate变量初始化发生在更早的时候,而不是语句所在的位置
全局的初始化分为静态阶段和动态阶段两个阶段
标准15:初始化列表可以初始化union,但是只能初始化union中的第一个元素,例如:
union u { int a; char* b; };
u a = { 1 };
u b = a;
//u c = 1; // error
//u d = { 0, "asdf" }; // error
//u e = { "asdf" }; // error
这里a.a被初始化1,但是不能用初始化列表初始化union中的b
总结一下:
Aggregate类型是指可以通过{}初始化列表直接初始化的类型
初始化列表中的顺序和结构或者数组中的顺序一致,这也没什么希奇的
初始化列表中元素的个数应该和结构或者数组中的元素个数一致,如果少了就用T()补齐
初始化列表支持成员的隐式类型转换和构造函数调用,这才是有点意外的地方
初始化列表到底有多大的用处呢?它最大的作用可能还是在于兼容C,在C++中确实是非常少见的.嗯,我想起了那些全局的数据结构后面跟着
常常的{}的情形,甚至是MFC中的消息机制,Qt中的信号和槽机制,或许是程序员自己对初始化列表的清晰性更独有情钟吧,当然效率也可以算是
一方面,总之,初始化列表还是不会消失的,所以我们最好弄清楚它