最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
1 内置类型的初始化
C++中内置类型继承于C,而C中是没有初值的,定义变量时一定需要初始化,不然它的初值一般都是个垃圾值。
int x;//未初始化
cout<<x<<endl;//输出的是一个垃圾值
//初始化方式
int a = 10; //对int进行手工初始化
const char* str = "Hello"; //对指针进行手工初始化
double d;
cin>>d; //以读取输入流的方式完成初始化
注:如果定义在全局区的变量没有被用户初始化的话,编译器会自动将其初始化。int 初值为 0,bool 为 false
首先看一下C++中的几个存储区:
2 类的初始化
类的初始化工作落到构造函数的身上,确保每一个构造函数都将对象的每一个成员初始化。重要的是,别混淆了赋值和初始化。
class PhoneNumber{
...
};
class ABEntry{
public:
ABEntry(const string &name, const string &address,const list<PhoneNumber> &phones);
ABEntry();//无参构造
private:
string theName; //自定义类型(不是用户自定义,是std)
string theAddress; //自定义类型
list<PhoneNumber> thePhones; //自定义类型
int numTimesConsulted; //内置类型
};
//第一版:在构造函数执行体内进行赋值操作
ABEntry::ABEntry(const string &name, const string &address,const list<PhoneNumber> &phones)
{
theName = name; //这些都是赋值
theAddress = address; //而非初始化
thePhones = phones;
numTimesConsulted = 0;
}
//第二版:使用初始化列表
ABEntry::ABEntry(const string &name, const string &address,const list<PhoneNumber> &phones):
theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)//这些都是初始化
{
//构造函数本体无需任何动作
}
ABEntry::ABEntry():theName(),theAddress(),thePhones(),numTimesConsulted(0)
{
//对于无参构造函数,也可以使用初始化列表,因为theName(),theAddress(),thePhones()会调用它们的默认构造函数
}
第一版和第二版的最终结果相同,但第二版的效率较高。
第一版:首先调用非内置类型成员变量 theName
、theAddress
、thePhones
的默认构造函数赋初值,然后立刻对它们进行赋新值。这个方法中非内置类型默认构造函数赋初值这动作浪费了。
第二版:初始化列表中为各个成员变量的实参被拿去作为各成员变量的构造函数的实参。即 theName(name)
表示 theName
以 name
作为参数调用了拷贝构造。
注:对于内置类型(如 numTimesConsulted
)在第一版和第二版中的效率是一样的,但为了初始化列表的一致性,一律用初始化列表。加上如果成员变量是 const
或 reference
,它们一定需要初值,不能被赋值。为避免需要思考什么时候用初始化列表,什么时候不用,还不如都用初始化列表来得方便。
3 成员变量初始化的顺序
基类更早于其派生类被初始化,而 class
内的成员变量总是以其声明的顺序被初始化。如在 ABEntry
类中,初始化顺序从前到后为 theName
、theAddress
、thePhones
、numTimesConsulted
,即使它们在初始化列表中的顺序与声明顺序不同,还是按照声明顺序为准,但一般初始化列表的顺序都是和它的声明顺序一致。
class
内、 file
作用域内被声明的 static
对象在两个源代码文件中分别包含至少一个非局部静态对象,当不同源代码文件中的非局部静态对象有所依赖时,直接使用这些对象是由风险的,因为它们的初始化顺序是不确定的。
//a.cpp
class Server{
...
public:
int clientNum;
};
extern Server server;//在全局范围声明外部对象server,供本文件外使用(即在其它文件内知道有server这个全局变量)
//b.cpp
class Client{
Client()
{
m_num = server.clientNum;
}
public:
int m_num;
}
Client client;//定义全局变量client对象,自动调用了Client类的构造函数,而构造函数内需要server的成员变量 clientNum 的值,但我们不能保证server是否已经被初始化了。
解决方法:把非局部静态对象变为局部静态对象
使用一个函数,只用来定义一个局部静态变量并返回它的引用。因为C++规定在本地范围(函数范围)内定义某静态对象时,当此函数被调用,该静态变量一定会被初始化。
//a.cpp
class Server{...};
Server& server(){ //将直接的声明改为一个函数
static Server server;
return server;
}
//b.cpp
class Client{...};
Client::client(){ //客户端构造函数通过函数访问服务器数据
number = server().number;
}
Client& client(){ //同样将客户端的声明改为一个函数
static Client client;
return client;
}
Note:
- 为内置类型进行手工初始化
- 构造函数最好使用初始化列表,而不要在构造函数的实现体内使用赋值操作。初始化列表的顺序最好与成员变量的声明顺序一致
- 当不同源代码文件中的有所依赖时,最好将非局部静态对象变为局部静态对象
条款05:了解C++隐式提供并调用哪些函数