读取未初始化的值会导致不明确的行为。在某些平台上,仅仅只是读取未初始化的值,就可能让你的程序终止运行。更可能的情况是读入一些"半随机"bits ,污染了正在进行读取动作的那个对象,最终导致不可测知的程序行为,以及许多令人不愉快的调试过程。
永远在使用对前先将它初始化。
一、对于无任何成员的内置类型,你必须手工完成此事。例如:
int x = 0; //对int 进行手工初始化
const char* text = "A C-style string"; //对指针进行手工初始化
double d;
std::cin>> d; // 以读取input s出am 的方式完成初始化
二、至于内置类型以外的任何其他东西,初始化责任落在构造函数( constructors)身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。这个规则很容易奉行,重要的是别混淆了赋值(assignment) 和初始化( initialization)。
考虑一个用来表现通讯簿的class ,其构造函数如下:
class PhoneNumber { ... };
class ABEntry { //ABEntry ="Address Book Entry"
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=narne; //这些都是赋值(assignments) ,
theAddress = address; //而非初始化(initializations)。
thePhones = phones;
numTimesConsulted = 0;}
这会导致ABEntry 对象带有你期望(你指定)的值,但不是最佳做法。C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
ABEntry 构造函数的一个较佳写法是,使用所谓的member initialization list (成员初值列〉替换赋值动作:
ABEntry: :ABEntry(const std: :string& n缸ne , const std: :string& address,
const std::1工st<PhoneNumber>& phones)
:theNarne(narne) ,
theAddress(address) , //现在,这些都是初始化(initializations )
thePhones(phones) ,
numTimesConsu1ted(0){ } //I现在,构造函数本体不必有任何动作
这个构造函数和上一个的最终结果相同,但通常效率较高。
如果成员变量是const 或references,它们就一定需要初值,不能被赋值。避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:总是使用成员初值列。这样做有时候绝对必要,且又往往比赋值更高效。
而class 的成员变量总是以其声明次序被初始化。回头看看ABEntry. 其theName 成员永远最先被初始化,然后是theAddress. 再来是thePhones. 最后是numTimesConsulted 。即使它们在成员初值列中以不同的次序出现(很不幸那是合法的) ,也不会有任何影响。
三、不同编译单元内定义之non-local static对象"的初始化次序。
函数内的static 对象称为local static 对象(因为它们对函数而言是local) ,其他static对象称为non-local static 对象。程序结束时static 对象会被自动销毁,也就是它们析构函数会在main ()结束时被自动调用。
所谓编译单元<translation unit)是指产出单一目标文件(single object file) 的那些源码。基本上它是单一源码文件加上其所含入的头文(#includefiles) .
现在,我们关心的问题涉及至少两个源码文件,每一个内含至少一个non-local static 对象(也就是说该对象是global 或位于namespace 作用域内,抑或在class 内或file 作用域内被声明为static)。真正的问题是:如果某编译单元内的某个non-local static 对象的初始化动作使用了另一编译单元内的某个non-local static 对象,它所用到的这个对象可能尚未被初始化,因为C++ 对"定义于不同编译单元内的non-local static 对象"的初始化次序并无明确定义。
例子:
假设你有一个FileSystem class ,它让互联网上的文件看起来好像位于本机(local)。由于这个class 使世界看起来像个单一文件系统,你
可能会产出一个特殊对象,位于global 或namespace 作用域内,象征单一文件系统:
class FileSystem { //来自你的程序库
public:
std::size t numDisks() canst; //众多成员函数之一};
extern FileSystem tfs; //预备给客户使用的对象; //tfs 代表"the file system"现在假设某些客户建立了一个class 用以处理文件系统内的目录(directories) 。恨自然他们的class 会用上theFileSystem 对象:
class Directory { //由程序库客户建立
public:
Directory ( params );
} ;
Directory::Directory( params )
std::size t disks = tfs.numDisks(); //使用tfs 对象
进一步假设,这些客户决定创建一个Directory对象,用来放置临时文件:Directory tempDir ( params ); //为临时文件而做出的目录
现在,初始化改序的重要性显现出来了:除非tfs 在tempDir 之前先被初始化,否则tempDir 的构造函数会用到尚未初始化的tfs 。但tfs 和ternpDir 是不同的人在不同的时间于不同的源码文件建立起来的,它们是定义于不同编译单元内的non-local static 对象。而你无法确定其初始化顺序。
一个小小的设计便可完全消除这个问题, 唯一需要做的是:将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static) 。这个手法的基础在于: C++ 保证,函数内的local static 对象会在"该函数被调用期间" "首次遇上该对象之定义式"时被初始化。
以此技术施行于tfs 和tempDir 身上,结果如下:
class FileSystem { ... ); // 同前
FieSystem& tfs (){
static FileSystem fs; //这个函数用来替换tfs 对象:它在FileSystemclass 中可能是个static 。return fs; //定义并初始化一个local static 对象,返回一个reference 指向上述对象}
class Directory { ... ); // 同前
Directory::Directory( params)
{// 同前,但原本的reference to tfs 现在改为tfs ()
std::size t disks = tfs() .numDisks( );
Directory& tempDir() //这个函数用来替换tempDir对象,它在Directoryclass 中可能是个static
static Directory td; //定义并初始化local static 对象,
return td; //返回→个reference指向上述对象。}
这么修改之后,这个系统程序的客户完全像以前一样地用它,唯一不同的是他们现在使用tfs ()和tempDir()而不再是tfs 和tempDir。也就是说他们使用函数返回的"指向static 对象"的reference,而不再使用static 对象自身。
请记住:
1、为内置型对象进行手工初始化,因为C++不保证初始化它们。
2、构造函数最好使用成员初值列(member initialization list) ,而不要在构造函数本体内使用赋值操作(assignment) 。初值列列出的成员变 量,其排列次序应该和它们在class 中的声明次序相同。
3、为免除"跨编译单元之初始化次序"问题,请以local static 对象替换non-local static 对象。