前言:C++构造函数的初始化列表,你真的懂它吗?

很多C++初学者,一般都会将构造函数的参数列表与初始化列表混为一谈。今天,小栈帮大家真正读懂构造函数的初始化列表。

目录

一、先看两个例子

二、初始化列表的优势:

三、初始化列表存在的必要性:

四、初始化顺序


一、先看两个例子

例1

class Box {
public:
    Box(int l, int w, int h) { // 这是参数列表
        // 这是在函数体内进行“赋值”操作
        length = l;
        width = w;
        height = h;
    }
private:
    int length;
    int width;
    int height;
};

在这个示例中,我们是在函数体内为成员赋值的,它实际上分为两步:

1.在进入函数体{}之前,成员已经默认初始化了

2.进入函数体之后,再通过赋值操作符"="进行赋值操作

例2

class Box {
public:
    //                 参数列表         成员初始化列表
    Box(int l, int w, int h) : length(l), width(w), height(h) {
        // 函数体可以为空,或者执行其他逻辑
    }
private:
    int length;
    int width;
    int height;
};

在这个示例中,我是在函数体外,通过初始化列表对成员直接进行初始化的。这种初始化列表的方式是一步到位的。

二、初始化列表的优势:

从上述两个示例中,不难发现,构造函数的初始化列表更加高效,它将原先函数体内赋值的两步操作默认初始+赋值),变成直接初始化。

三、初始化列表存在的必要性:

初始化列表的存在不仅仅是为了提高效率,有些情况下,我们必须使用初始化列表的方式。例如,以下几种case:

Case1: const成员变量

const类型的变量不能做赋值操作。

class AA {
    const int a;
public:
    // ❌ 错误:const 不能先默认构造再赋值
    AA(int val) {
        a = val;   // 编译错误:const 成员不可赋值
    }

    // ✅ 正确:直接初始化
   AA(int val) : a(val) {}
};

Case2: 引用成员变量(&)

引用类型的变量不能做赋值操作。

class AA {
public:
    AA(int& age_ref) {
        age = age_ref;  // ❌ 编译错误:引用不是可修改的左值
    }

    // ✅ 正确:直接初始化
    AA(int& age_ref) : age(age_ref) {}
private:
    int& age;  // 引用成员
};

Case3: 在子类构造函数中,调用父类的构造函数

class Base {
public:
    Base(int x);  // 带参构造,无默认构造
};

class Derived : public Base {
public:
    // 必须通过初始化列表调用 Base(10)
    Derived() : Base(10) {}
};

四、初始化顺序

需要注意一个陷阱,构造函数的初始化顺序是由类中成员的声明顺序决定,与初始化列表书写顺序无关。

典型的错误示例:

class AA {
    int a;
    int b;
public:
    AA(int x) : b(x), a(b + 1) {}  // 危险!! !
};

上述代码的问题在于,成员a先于成员b进行初始化,此时b的值是垃圾值。