本文详细讲解C++的初始化列表和构造函数初始化区别
目录
一、先看代码理解一下
//构造函数初始化
class MyClass
{
public:
int m_a, m_b;
MyClass(int a, int b) {
m_a = a;
m_b = b;
}
};
//初始化列表
class MyClass2
{
public:
int m_a, m_b;
MyClass2(int a, int b) : m_a(a), m_b(b) {
}
};
二、再看定义
初始化列表:与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
关系:从定义中我们可以知道初始化列表也是配合构造函数使用的,也就是说,构造函数可以使用初始化列表,也可以不使用(简单理解为初始化列表是构造函数的配件,可以有,也可以没有,但是有些场合必须使用初始化列表,第三部分会说)。
区别:我们以前都认为构造函数是给对象初始化,其实严格来说构造函数体中的语句只能将其称为赋初值,而初始化列表才是真正的初始化。因为初始化是在一个变量(或对象)定义的时候赋予初始值才叫初始化。
三、初始值列表的必要性
因为类中有下面三种成员变量时,必须使用初始化列表来初始化
1、引用成员变量,因为引用必须在定义的时候初始化,而构造函数体中的语句是赋值,所以应该使用初始化列表初始化
2、const修饰的成员变量,因为const修饰的成员变量也必须在定义的时候初始化,所以必须使用初始化列表
3、没有默认构造的自定义类型成员变量,因为使用构造函数一定不可避免的会调用自定义类型成员变量的默认构造,然而使用初始化列表可以避免使用默认构造函数,直接调用该成员变量的拷贝构造
解析:对于第一个和第二个条件比较好理解,因为const本身在初始化后是无法再赋值的,所以必须使用初始化列表来对其初始化。
对于第二种情况,因为根据构造函数的执行顺序,在构造类B时必须先构造类A的构造函数,但类A并没有提供默认的构造函数,此时导致编译器找不到合适的构造函数,所以对象构造失败。因此这里必须在初始值列表中初始化。
四、需要初始化列表实例
const的情况
如果把MyClass中的变量int m_a, m_b改为const,会报错。
错误:
class MyClass
{
public:
const int m_a, m_b;
MyClass(int a, int b) {
m_a = a;
m_b = b;
}
};
正确:
class MyClass
{
public:
const int m_a, m_b;
MyClass(int a, int b) : m_a(a), m_b(b) {
}
};
引用类型的情况
错误
class MyClass3
{
public:
int& m_a, m_b;
MyClass3(int a, int b)
{
m_a = a;
m_b = b;
}
};
正确
class MyClass3
{
public:
int& m_a, m_b;
MyClass3(int a, int b) : m_a(a), m_b(b) {
}
};
没有默认构造的自定义类型成员变量
错误
class MyClass4
{
public:
MyClass4(MyClass& c)
{
cls = c;
}
private:
MyClass cls;没有默认构造的自定义类型
};
正确
class MyClass4
{
public:
MyClass4(MyClass& c):cls(c)
{
}
private:
MyClass cls;
};
五、初始值列表的效率
使用普通方式初始化时编译器会先执行一次对象的默认构造函数,然后才会对其赋值,初始化时执行了两个步骤。
#include <iostream>
class CAnimal
{
public:
CAnimal() { std::cout << "default" << std::endl; } // 添加默认初始化函数
CAnimal(int weight) :m_weight(weight) {
}
int m_weight;
};
class CDog : public CAnimal {
public:
//采用普通的会执行CAnimal默认的构造函数
CDog(int weight) {
m_weight = weight;
}
// 初始值列表,不会执行CAnimal默认的构造函数
//(和上面的构造函数不可共存)
/*CDog(int weight) : CAnimal(weight) {
}*/
};
执行CDog构造函数时会先执行CAnimal的默认构造函数,输出defualt 然后后才会执行m_weight = weight。但是对于初始值列表方式来说并不会执行两步,直接通过相应的构造函数初始化就完成了,相对来说简化了一个过程,效率肯定也会高一些。
六、始值列表初始化时顺序
使用初始值列表初始化时,初始化的顺序是根据成员变量定义的顺序来的,并不是初始值列表的顺序。
class CAnimal
{
public:
CAnimal(int weight) :m_weight(weight) {
std::cout << weight << std::endl;
}
int m_weight;
};
class CDog {
public:
CAnimal m_a, m_b;
CDog(int a, int b) : m_b(b), m_a(a){
}
};
七、使用初始化列表的好处
当你了解了没有默认构造的自定义类型成员变量必须使用初始化列表的时候,初始化列表的好处也就出来了:可以减少一次默认构造的调用,当有大量对象的时候,使用初始化列表的好处还是很明显的,可以大大降低函数压栈带来的开销。所以我们能使用初始化列表尽量使用初始化列表。