在之前的成员函数一文中谈到过构造函数,在创建对象时,编译器会调用构造函数,为成员变量进行赋值。但这个操作并不是初始化,初始化只能有一次,而在构造函数里面是可以进行多次赋值的。初始化的工作通常可以交由初始化列表来执行。

📕 什么是初始化列表?

C++的初始化列表是一种在创建对象时初始化成员变量的特殊语法。它的存在可以显著的提高代码的可读性和性能。
在C++中,当我们创建一个对象时,我们需要为其成员变量赋值。通常情况下,我们会在构造函数中对成员变量赋值。但是,当对象中有大量成员变量时,这种方法可能会让代码变得冗长。
C++的初始化列表提供了一种更简洁、更清晰的方法来初始化成员变量。它使用冒号分隔符来定义一个初始化列表,并在其中列出成员变量及其初始值。
下面是一个类:

1
2
3
4
5
6
7
8
9
class A{
private:
int a;
double b;
public:
A(int a1,int b1)
: a(a1),b(b1)
{}
};

在上述的代码里,使用了初始化列表来为a与b赋值而没有经过构造函数体内。这就是初始化列表的基础用法。

使用初始化列表有一些需要注意的地方:

  1. 成员变量在初始化列表里只可以有一次。
  2. 当类中包含引用成员变量,const成员变量,自定义类型成员变量时,必须放在初始化列表里进行初始化。

📗 初始化列表的使用

在需要注意的第二点中我们提到了一旦包含上面的三个成员,就要放入初始化列表中进行初始化。

1
2
3
4
5
6
7
8
9
10
11
class A{
public:
A(int a,int b)
: eternity(a),reference(b)
{}
private:
int& reference;
const int eternity;
B b1; // B是另一个自定义类型

};

如果B类不存在默认构造函数,那么就需要在A的初始化列表中对b1进行一个显式的调用。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class B {
public:
B(int x) : data(x) {}
private:
int data;
};
class A {
public:
A(int a, int b, int x) : eternity(a), reference(b), b1(x) {}
private:
int& reference;
const int eternity;
B b1;
};

因为在创建A对象时,编译器需要为其成员变量分配内存并初始化,如果B没有默认构造函数,那么编译器不知道如何为b1分配内存并初始化,从而产生编译错误。

并且请注意:成员变量在类中的声明次序就是其在初始化列表中的初始化顺序,而与其在初始化列表的顺序是无关的!

一个很经典的例子:

1
2
3
4
5
6
7
class A{
public:
A(int a): a1(a),a2(a1){}
private:
int a2;
int a1;
}

虽然在初始化列表中的顺序是a1,a2,但真正的执行顺序是a2,a1,也就是a2的初始化是一个随机值(因为此时a1就是随机的),在a2初始化完成之后a1才会被a赋值。

📙为什么引用,const或者类变量必须在初始化列表中?

const,引用类型或者类的成员变量必须要放在初始化列表里面,因为它们在创建对象时需要被初始化,而且只能在初始化时被赋值,不能在构造函数中再次被赋值。

1
2
3
4
private:
int& reference;
const int eternity;
B b1; //这里的都是声明!!!

上面三个变量的特征就是在定义时就必须被初始化

在C++中,对象的创建分为两个步骤:分配内存和初始化。在分配内存的过程中,编译器会为对象中的每一个成员变量分配内存空间;在初始化的过程中,编译器会调用构造函数来初始化每一个成员变量。对于const或者引用类型的成员变量,其值必须在初始化时被赋值,因此必须在构造函数的初始化列表中进行初始化,否则会导致编译错误。
另外,const或者引用类型的成员变量在对象的整个生命周期中都不能被修改,因此也不能在构造函数中再次被赋值。因此,将其放在初始化列表中进行初始化,可以确保它们的值只被赋值一次,并且在对象的整个生命周期中保持不变。