目录
一.为什么要初始化
二.初始化与赋值,善用初始化列表
三.不同编译单元内定义的非局部静态对象的初始化顺序
初始化可以让我们避免很多不可预知的问题。
比如下列伪代码:
class Human
{
...
public:
int* arr;
...
}
在这里我们并没有对arr指针进行初始化。如果我的编译器不会自行初始化,那么虽然我自己知道arr并没有初始化,但是如果其他人调用了我这个arr指针又恰巧不知道这一点,那就糟糕了。
比如他可能写出下列代码:
while(arr != nullptr)
{
std::cout<< *arr << endl;
++arr;
}
当我们在vs2019上运行时就会报错:
这其实还好,但假如编译器比较笨并没有进行相关语法检查那就会出大问题。
我们不能妄想每个人都能记得创建的对象是否初始化了,因此对于所创建的每个对象都初始化就是非常高效且有益的事。
正如原文所说:永远在使用对象之前先将它初始化。
首先来看一个代码:
class Human
{
public:
Human()
{
name = "me";//赋值
age = 20;
}
private:
string name;
int age;
}
当我们创建Human对象调用构造函数时,会先对name和age初始化,之后才将"me"、20赋予这两个成员。
对于name成员而言需要先调用string的构造函数,然后再调用string的赋值重载函数,这太冗余了!假如我们在name初始化时就赋予了它应得的值,那么就只需要调用string的构造函数即可。
class Human
{
public:
Human()
:name("me")//使用初始化列表对成员初始化
,age(20)
{ }
private:
string name;
int age;
}
虽然结果是一样的,但是这样合二为一,效率更高。
因此我们应该在构造函数还未调用——还未初始化成员时,调用初始化列表对成员进行初始化。
另外,使用初始化列表时最好全部成员都进行初始化。防止因为遗忘而出现纰漏。
同时,我们也需要注意初始化列表是按照成员声明顺序进行初始化顺序。
//这样的代码并不能为arr正确分配空间
class Human
{
public:
Human()
:size(16)
,arr(new int[size])//编译器会先给arr初始化
{ }
private:
int* arr;//按照声明顺序会先初始化arr,后size
int size;
...
}
//这是正确的次序
class Human
{
public:
Human()
:size(16)
,arr(new int[size])
/* :arr(new int[size])
,size(16)
这样也是可以的
*/
{ }
private:
int size;//按照声明顺序会先初始化size,后arr
int* arr
...
}
不同编译单元(源文件)内的对象如果其中一方要使用另一方,那么一定要注意初始化的次序,倘如被借用的一方尚未初始化,那么会发生意想不到的错误。
小编将《Effective C++》中代码示例做了调整,以便讲解展示:
下面有两个类Par、Child分别位于不同的编译单元内。
我们所期待的结果是当Child对象B定义时,调用自己构造函数打印Par对象A中的Hello world。
//头文件
#include
#include
using namespace std;
class Par
{
public:
Par()
:str("Hello world")
{}
string str;
};
extern Par A;
//源文件
#include"Par.h"
Par A;
//源文件
#include"Par.h"
class Child
{
public:
Child()
{
cout << A.str << endl;
}
};
Child B;
//源文件
#include"Par.h"
int main()
{
return 0;
}
这个程序中我们无法确定child对象B的初始化结果。
当Child.cpp先编译时,是段错误
当Par.cpp先编译时,打印Hello world,是预期结果。
这就是因为Par、Child两个类在不同的编译单元内,造成如果Child先编译了那么Par对象A就没有初始化,从而产生错误。
解决方法自然是有的:
C++中规定,函数中的局部static对象会在函数使用时,首次遇上其定义式时初始化。意思就是说当使用函数时,函数中的static对象一定是已经初始化过的了。
因此,我们可以将对象A声明为static封装进一个函数中,函数返回A的引用。
在对象B的构造函数中调用这个函数,又因为上述特性,函数返回的引用A一定是经过初始化的。
//头文件
#include
#include
using namespace std;
class Par
{
public:
Par()
:str("Hello world")
{}
string str;
};
Par& GetPar();//函数声明
//源文件
#include"Par.h"
Par& GetPar()//函数定义
{
static Par A;
return A;
}
/*
当调用GetPar函数时,
A对象是已经初始化的。
*/
//源文件
#include"Par.h"
class Child
{
public:
Child()
{
//构造时直接调用函数间接获取Par对象。
cout << GetPar().str << endl;
}
};
Child B;
Main.cpp不变。
现在即便Child.cpp先编译也无所谓。
当然,要尽量避免不同编译单元的类出现相互调用的情况,这会使问题变得麻烦且复杂。
一个好的程序员应该是那种过单行线都要往两边看的人。— Doug Linder
如有错误,敬请斧正