内置类型(built-in type)的初始化——C part of C++
int a; //未初始化int
double b; //未初始化double
char* text; //未初始化字符指针
需改为:
int a = 0; //对 int 进行手动初始化
const char* text = "A C-style string"; //对指针进行手动初始化
double b;
std::cin >> b; //以读取 intput stream 的方式完成初始化
类的初始化(构造函数)
自定义类型参数的初始化依靠构造函数(constructors)完成,其原则为:确保每一个构造函数都将对象的每一个成员初始化
其难点主要在于区分 赋值(assignment) 和 初始化(initialization)。
class PhoneNumber { ... };
class ABEntry {
public:
ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name; // 这些操作均是赋值,并不是初始化
theAddress = address; / /初始化发生在成员的默认构造函数被自动调用时
thePhones = phones; // 比进入构造函数本体时间早
numTimesConsulted = 0; // 该参数属于内置类型,与上述不同
}
该方法本质是在默认构造函数完成后,再次对参数进行赋值,则默认构造函数的行为浪费了
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
:theName(name), // 这些操作均是初始化操作
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{ } // 构造函数本体不需要做任何动作
该方法,初值列表中针对各个成员变量而设的实参,被拿去作为成员变量构造函数的实参
本例中 theName 以 name 为初值进行 copy构造,后两者同理,单调用一次拷贝构造函数效率更高
其中,numTimesConsulted 初始化和赋值成本相同,但推荐一直通过成员初值列表完成初始化
无参构造函数也可通过,指定无物作为初始化实参,如:
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
:theName(), // 调用 theName、theAddress、thePhones 的默认构造函数
theAddress(),
thePhones(),
numTimesConsulted(0) // 内置 int 需要显示初始化为 0
{ }
如果成员变量是 const 或 references,就一定需要初值,不能被赋值。因此为避免错误发生,必须对所有成员使用成员初值列表完成初始化。
例如:
const int a; //报错,需要初始化
int& b; //报错,需要初始化
//现在对其进行初始化:
const int a = 3; //编译通过
int c = 3;
int& b = c; //编译通过
在继承关系中,基类(base class)总是先被初始化。
在同一类中,成员数据的初始化顺序与其声明顺序是一致的,而不是初始化列表的顺序。
因此,为了代码一致性,要保证初始化列表的顺序与成员数据声明的顺序是一样的。
初始化非本地静态对象
在两个编译单元中,分别包含至少一个非本地静态对象,当这些对象发生互动时,它们的初始化顺序是不确定的,所以直接使用这些变量,就会给程序的运行带来风险。
编译单元(translation unit): 可以让编译器生成代码的基本单元,一般一个源代码文件就是一个编译单元。
非本地静态对象(non-local static object): 静态对象可以是在全局范围定义的变量,在名空间范围定义的变量,函数范围内定义为 static 的变量,类的范围内定义为 static 的变量,而除了函数中的静态对象是本地的,其他都是非本地的。
注意:静态对象存在于程序的开始到结束,所以它不是基于堆(heap)或者栈(stack)的。初始化的静态对象存在于 .data 中,未初始化的则存在于 .bss 中。
class Server{...};
extern Server server; //在全局范围声明外部对象server,供外部使用
class Client{...};
Client::Client(...){
number = server.number;
}
Client client; //在全局范围定义client对象,自动调用了Client类的构造函数
class Server{...};
Server& server(){ //将直接的声明改为一个函数
static Server server;
return server;
}
class Client{...};
Client::client(){ //客户端构造函数通过函数访问服务器数据
number = server().number;
}
Client& client(){ //同样将客户端的声明改为一个函数
static Client client;
return client;
}
总结
1 内置型对象进行手动初始化,C++代码不保证初始化参数;
2 构造函数使用成员初值列表,不要再构造函数内使用赋值操作。初值列表列出的成员变量,其排列次序应该和 class 中的声明次序相同;
3 为免除“跨编译单元的初始化次序”问题,请以 local static 对象替换 non-local static 对象。