class Club
{
struct Date
{
int year;
int month;
int day;
Date(int y, int m, int d);
}
string name;
vector<string> members;
vector<string> officers;
Date founded;
public:
Club(const string& n, Date fd);
};
Club的构造函数接收两个参数。在构造函数的定义中,通过成员初始化列表
给出成员的构造函数的参数:
Club::Club(const string& n, Date fd)
:name{n},founded{fd}
{
// ...
}
如果一个成员的构造函数不需要参数,就不必在成员初始化列表中提及此成员。
类自身的构造函数在其构造函数体执行之前会先调用成员的构造函数。成员的构造函数按成员在类中的声明顺序调用,而不是按成员在初始化列表中出现的顺序。成员之间存在初始化依赖的时候需要注意成员的声明顺序
内置类型成员需要显式初始化,隐式初始化的内置类型成员实际上是未初始化的
一个构造函数可以初始化其类的成员和基类,但不会初始化 其成员或基类 的 成员或基类
。也就是说类的成员初始化过程只会初始化一层成员(调用自己成员的构造函数),不会初始化成员的成员(不会调用自己成员的成员的构造函数)
引用成员或者const成员必须初始化
如果基类要求一个初始化器,就必须在构造函数中提供相应的基类的初始化器。如果希望进行默认构造,可以显式指出。
class B1{ B1(); }; // 具有默认构造函数
class B2{ B2(int); }; // 无默认构造函数
struct D1 : B1,B2
{
D1(int i) : B2{i}{} // 隐式使用B1{}
};
struct D2 : B1,B2
{
D2(int i){} // 错误:B2没有默认构造,要求有一个int初始化器
};
基类按声明顺序进行初始化,建议按此顺序指定基类的初始化器。
基类的初始化在成员之前,销毁在成员之后。
class X
{
int a;
public:
X(int x){ if (0else throw Bad_X(x); }
X() : X{42} {}
X(string s) : X{to<int>(s)} {}
// ......
};
使用一个成员风格的初始化器,但用的是类自身的名字(也是构造函数名),它会调用另一个构造函数,作为这个构造过程的一部分。
如果一个构造函数在初始化列表中委托了其他构造函数,则该函数不能在初始化列表中出现任何成员初始化器
可以在类声明中为非static数据成员指定初始化器:
class A
{
public:
int a{7};
int b=77;
};
{}和=语法能用于类内成员初始化器,但()语法就不行
默认情况下,构造函数会使用这种类内初始化器。
与上面的代码效果等价如下:
class A
{
public:
int a;
int b;
A() : a{7},b{77} {}
};
类内初始化器的这种用法可以节省一些输入工作量,但真正的收益是在用于具有多个构造函数的复杂的类时,对同一个成员,多个构造函通常使用相同的初始化器。
如果一个成员既被类内初始化器初始化,又被构造函数初始化,则只执行只执行后者的初始化操作
一般来说,static成员声明充当类外定义的声明:
class Node
{
//......
static int node_count; // 声明
}
int Node::node_count = 0; // 定义
但是,在少数简单的特殊情况下,在类内声明中初始化static成员也是可能的。条件是static成员必须是整型或枚举类型的const,或字面值类型的constexpr,且初始化器必须是一个常量表达式:
class Curious
{
public:
static const int c1 = 7; // 正确
static int c2 = 11; // 错误:非const
const int c3 = 13; // 正确,但非static
static const int c4 = sqrt(9); // 错误:类内初始化器不是常量
static const float c5 = 7.0; // 错误:类内初始化成员不是整型(应使用constexpr而非const)
}
当(且仅当)使用一个已初始化成员的方式要求它像对象一样在内存中存储时,该成员必须在某处(唯一)定义,初始化器不能重复。