众所周知,对于做题中常见的结构体类型可以使用如下初始化方式
struct Test
{
int a, b;
};
Test t1 = {};
Test t2 = { 1 };
Test t3 = { 1, 2 };
这种情况下,大括号中的值会按照结构体中变量的声明顺序进行一对一的赋值
可是并不是所有结构体都能用这种方式进行初始化,能这样操作的结构体必须是一个聚合类
对聚合的定义,在每个C版本中有少许的区别,这里简单总结下C17中定义:一个class或struct类型,当它满足以下条件时,称为一个聚合:
1. 没有private或protected数据成员;
2. 没有用户提供的构造函数(但是显式使用=default或=delete声明的构造函数除外);
3. 没有virtual、private或者protected基类;
4. 没有虚函数
这些条件中,对于竞赛的C++代码来说最重要的就是第二条:不能有自定义的构造函数(= default
除外)
也就是说,只要有自定义的构造函数,哪怕是函数体为空的无参构造函数也不行
struct Test
{
int a, b;
Test() {};
};
Test t1 = {}; //正确
Test t2 = { 1 }; //报错
Test t3 = { 1, 2 }; //报错
那既然不能进行大括号初始化,那为何t1
能正确
这是因为如果一个结构体不是聚合类,那么所有的大括号初始化会转为调用相应的构造函数来进行初始化
struct Test
{
int a, b;
Test() {}
Test(int a, int b) {}
};
Test t1 = {}; //正确
Test t2 = { 1 }; //报错
Test t3 = { 1, 2 }; //正确
不仅是初始化,赋值也遵循同样的规则
另外有一点不同的是,由于聚合类是大括号初始化,所以未被赋值的成员同全局变量的初始化一样(基本类型都初始化为0),而非聚合类会按照调用构造函数初始化的规则一样
struct Test1
{
int a, b;
};
struct Test2
{
int a, b;
Test2() { }
Test2(int a, int b) { }
};
int main()
{
Test1 t1 = {};
Test2 t2 = {};
cout << t1.a << " " << t1.b << endl; // 0 0
cout << t2.a << " " << t2.b << endl; // -1764354864 571
}
关于构造函数未被初始化的成员的值会怎样,可以分为两类
- 自定义类型(结构体和类):调用相应的无参构造方法
- 内置类型(基本数据类型和对应的数组):局部变量不会初始化,也就是乱码,全局变量初始化为0,其实就相当于普通变量的初始化
但是这样和显式地调用构造函数还是有区别
struct Test
{
int a, b;
Test() { }
Test(int a, int b) { }
}t[10];
int main()
{
lower_bound(t, t + 10, {}); //报错
lower_bound(t, t + 10, { 1, 2 }); //报错
lower_bound(t, t + 10, Test()); //正确
lower_bound(t, t + 10, Test(1, 2)); //正确
}
这里由于lower_bound
是一个模板函数,想要通过参数来判断类型却失败了,因为三个参数的类型不完全相等,也就是说这种初始化并不能算作真正地调用了构造函数,编译器也就无法识别类型
想要详细了解大括号初始化更多的特性以及初始化列表可以浏览大括号之谜:C++的列表初始化语法解析