当初始化一个引用的成员 当初始化一个const的成员 当调用一个基类的对象(该类继承于基类),且基类有含有参数的构造函数时 当调用一个类的对象成员时,且该对象成员所在的类有含有参数的构造函数时
通过下面的代码,可以更详细的说明这四种情况:
#include <iostream>
using namespace std;
class B {
public:
B(int tmp){}
};
class Base {
public:
Base(int tmp){}
};
class A : public Base{ // 第三种情况
public:
int &m_iX; // 第一种情况
const int m_iY; // 第二种情况
B m_mX; // 第四种情况
A(int& tmp) :m_iX(tmp), m_iY(10), m_mX(10), Base(10) {
}
};
int main()
{
int x = 10;
A a(x);
return 0;
}
那么以上这四种情况是必须要用初始化列表的方式去初始化的,如果在函数体中去初始化会报错。
最主要的优势就是可以提高运行效率,尤其是对含有类对象的成员时,效率会有很大的提升,下面也用一个示例来证明一下,首先我们先将初始化写在函数体内,观察一下结果:
#include <iostream>
using namespace std;
class B {
public:
int m_iX;
B(int tmp = 0) : m_iX(tmp){
cout << "this : " << this << " ";
cout << "构造函数" << endl;
}
B& operator=(const B& tmp) {
cout << "this : " << this << " ";
cout << "赋值构造函数" << endl;
return *this;
}
~B() {
cout << "this : " << this << " ";
cout << "析构函数" << endl;
}
};
class A {
public:
B m_mX;
A(int tmp) {
m_mX = 100;
}
};
int main()
{
A a(10);
return 0;
}
上面的代码构建了一个类B,并在类A中定义了一个类B的对象,然后并在函数体中对m_mX进行了初始化,运行结果如下图所示:
我们暂且先不判断放在函数体中的初始化的好坏,我们先来使用初始化列表的方式对其初始化一下看看结果是什么样的,初始化列表方法运行结果:
可以发现少了三行代码,那么我们站在编译器的角度来分析一下具体原因,其实通过多的那三行输出,就可以发现编译器为我们实例化了一个临时对象,并对其进行了初始化,然后再将结果通过赋值构造函数给了m_mX对象,然后再析构掉临时对象,那么编译器在背后实现的代码如下:
class A {
public:
B m_mX;
A(int tmp){
//m_mX = 100;
B m_mX; // 不会调用默认构造函数
m_mX.B::B(); // 调用第一个构造函数
B tmpobj; // 不会调用默认构造函数
tmpobj.B::B(100); // 调用第二个构造函数
m_mX = tmpobj; // 调用赋值构造函数
tmpobj.B::~B(); // 调用第一个析构函数
}
};
所输出的5行语句就是这么来的,其中有两行不会调用默认构造函数这里就不解释了,不清楚的可以看这篇博客:传送门,由此可见,m_mX=100这一行代码被转换成了这么多行,我们再来看看使用初始化列表的方法来初始化的话,编译器是怎么做的:
class A {
public:
B m_mX;
A(int tmp){
// 如果使用初始化列表:
B m_mX; // 不会调用构造函数
m_mX.B::B(100); // 调用第一个构造函数
// m_mX = 100;
}
};
显然这里编译器直接调用构造函数进行初始化就可以了,免去了多余的实例化临时对象,因此效率上会有大的提升,那么对于一些简单的成员进行初始化的时候(比如int类型),其实二者没有太大的区别,但是还是建议都是用初始化列表进行初始化。
2.当自己在函数体中已经写了一部分的代码,那么初始化列表的代码是优先于自己写的代码执行的。
3.对于对象成员的初始化顺序,是按照对象成员的定义顺序执行的,而不是按照初始化列表的顺序执行的。