引言
用c++的人都知道,c++的构造函数具有初始化列表,初始化列表有什么作用?什么情况下必须使用初始化列表?结合网上的相关博客,这里给出自己的一些理解。
构造函数的两个阶段
首先给出一个结论:初始化操作直接初始化成员,赋值操作则先初始化再赋值。
构造函数的执行分为两个阶段:初始化阶段和计算阶段。
1. 初始化阶段:在类中,所有的类成员都需要进行初始化。初始化阶段可以显式地对成员变量进行初始化。
2. 计算阶段:计算阶段主要处理赋值操作以及一些其他的语句,计算阶段一定在初始化阶段完成后实现。
初始化列表处于初始化阶段,其作用是在计算阶段之前,指定类成员变量的初始化方式。
以下面的代码为例:
Base类中有一个成员变量a,分别拥有默认构造函数、拷贝构造函数以及赋值运算符。
class Base
{
public:
Base()
{
cout<<"Constructor for Base"<<endl;
}
Base(const Base& t)
{
cout<<"Copy Constructor for Base"<<endl;
a=t.a;
}
Base& operator=(const Base& t)
{
cout<<"Assignment for Base"<<endl;
a=t.a;
return *this;
}
private:
int a;
}
接下来,我们构造一个Test类来进行测试,其有个类型为Base的成员 变量b。
-
不使用初始化列表
class Test
{
public:
Test(Base& t)
{
b=t;
}
private:
Base b;
};
调用代码
Base b;
cout<<"Test is running!"<<endl;
Test t(b);
输出结果如下:
Constructor for Base
Test is running!
Constructor for Base
Assignment for Base
输出结果表明,在构造Test类时,由于其构造函数没有初始化列表(意味着没有进行显性的变量初始化),在初始化阶段,编译器首先用Base的默认构造函数初始化Test的成员变量b,接着在计算阶段,执行赋值操作,将入参b赋值给了t.b。
构造Test的过程中,调用了一次默认构造函数和一次赋值操作。
接下来我们改写Test的构造函数来测试初始化列表的作用。
-
使用初始化列表
class Test
{
public:
Test(Base& t):b(t){}
private:
Base b;
};
输出结果如下:
Constructor for Base
Test is running!
Copy Constructor for Base
这一次由于使用了初始化列表,在初始化阶段我们显性地利用拷贝构造函数进行了成员变量的初始化,在计算阶段,不需要进行其他操作。
构造Test的过程中,仅仅使用了一次拷贝构造函数,少调用了一次默认构造函数,节省了构造的效率。
为什么使用初始化列表
经过上述测试我们不难发现,使用初始化列表减少了一次默认构造函数的调用。对于内置类型,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型,尤其是对于数据密集型的类来说,是非常高效的。因此,建议尽量使用初始化列表的方式。
哪些东西必须放在初始化列表中?
由于在构造函数体内进行成员变量初始化需要调用默认构造函数并执行赋值操作,因此对于没有默认构造函数或者不能进行赋值操作的变量,利用这种方式显然是错误的!
- 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
- 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
初始化列表的成员初始化顺序
C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
补充
一个小小的试验。
试验代码如下
class Base
{
public:
Base()
{
cout<<"Constructor for Base"<<endl;
}
Base(const Base& t)
{
cout<<"Copy Constructor for Base"<<endl;
a=t.a;
}
Base& operator=(const Base& t)
{
cout<<"Assignment for Base"<<endl;
a=t.a;
return *this;
}
~Base()
{
cout<<"Destructor for Base"<<endl;
}
private:
int a;
};
class Test
{
public:
Test(Base& t)
{
b=t;
}
~Test()
{
cout<<"Destructor for Test"<<endl;
}
private:
Base b;
};
int main()
{
Base b;
cout<<"Test is running!"<<endl;
{
Test t(b);
}
cout<<"running!"<<endl;
return 0;
}
试验结果
Constructor for Base
Test is running!
Constructor for Base
Assignment for Base
Destructor for Test
Destructor for Base
running!
Destructor for Base
更新一下:为何在main函数中直接些MyClass a = XXX调用的是拷贝构造函数,因为在那里没有额外的声明,定义和声明在一起,所以调用拷贝构造函数;而在类中,已经声明过了,所以写这种方式,会先调用构造函数,再调用operator=操作符。