【C++笔记】构造函数初始化列表

C++ 构造函数初始化列表 是 C++ 中非常重要且经常被误解/低估的特性之一。

它不只是“写法不同”,而是直接影响:

  • 成员初始化的顺序
  • const 成员能否被赋值
  • 引用成员能否被绑定
  • 基类/成员对象是否能高效构造
  • 是否会产生一次多余的默认构造+赋值

下面用最清晰的方式把核心知识点和实际使用场景整理出来。

1. 基本写法对比(最容易理解的差异)

class Person {
private:
    std::string name;
    int age;
    const std::string id;      // const 成员
    int& ref;                  // 引用成员
    Address addr;              // 成员对象

public:
    // 错误写法1:const/引用成员必须在初始化列表中初始化
    Person(std::string n, int a, std::string i, int& r)
    {
        name = n;     // 赋值
        age  = a;
        id   = i;     // 编译错误!const 不能赋值
        ref  = r;     // 编译错误!引用必须初始化,不能赋值
        addr = Address("beijing");   // 多构造一次
    }

    // 正确写法(推荐)
    Person(std::string n, int a, std::string i, int& r)
        : name(std::move(n))           // 移动构造(最优)
        , age(a)
        , id(std::move(i))             // const 只能在这里初始化
        , ref(r)                       // 引用只能在这里绑定
        , addr("shanghai")             // 直接用参数构造,避免默认构造+赋值
    {
        // 这里可以写一些有逻辑的代码
        if (age < 0) age = 0;
    }
};

2. 必须使用初始化列表的几种情况(面试/写代码必考)

情况能否在函数体内赋值?是否必须用初始化列表?原因
const 成员const 只能初始化,不能赋值
引用成员引用必须在定义时绑定对象
没有默认构造函数的成员对象否(会编译错误)必须显式调用有参构造
基类(父类)否(特殊情况)通常是基类构造必须先于派生类
希望避免一次默认构造+赋值可以,但效率低推荐性能优化

3. 初始化顺序(非常重要!决定性的规则)

C++ 标准规定:成员的初始化顺序与它们在类中声明的顺序一致,与初始化列表的书写顺序无关。

class Test {
    int a;      // 先声明
    int b;      // 后声明
    int c;

public:
    Test(int x, int y, int z)
        : c(x), a(y), b(z)     // 你写成这个顺序也没用
    {
        std::cout << a << " " << b << " " << c << "\n";
    }
};

int main() {
    Test t(1,2,3);   // 输出的是 ? ? ?
    // 实际输出:2 3 1
    // 因为初始化顺序永远是:a → b → c
}

口诀
“声明顺序决定初始化顺序,列表顺序只决定谁先写代码”

4. 常见错误写法 & 隐患

// 错误示范1:依赖初始化列表顺序(非常危险)
class Dangerous {
    int* ptr;
    int value;

public:
    Dangerous(int v) : value(v), ptr(&value) {}   // UB! ptr 可能指向已销毁的临时对象
    // 正确:先初始化 ptr 指向的 value
    // Dangerous(int v) : ptr(&value), value(v) {}  // 仍然错!因为 value 先初始化
    // 正确写法:
    // 先声明 value,再声明 ptr
};
// 错误示范2:基类初始化写在成员后面(逻辑错误但语法允许)
class Base { ... };
class Derived : public Base {
    int x;
public:
    Derived() : x(10), Base() {}   // 虽然能编译,但 Base 先构造(不符合直觉)
};

5. 现代 C++ 推荐写法(C++11 之后)

class Modern {
private:
    std::string name;
    int         age       {18};           // 就地初始化(C++11)
    const int   MAX       {100};
    std::vector<int> scores;

public:
    // 委托构造 + 初始化列表
    Modern(std::string n) 
        : Modern(std::move(n), 18) {}     // 委托给另一个构造函数

    Modern(std::string n, int a)
        : name(std::move(n))
        , age(a)
        , scores{1,2,3,4,5}               // 直接列表初始化
    {}
};

6. 面试/笔试高频问题总结

  1. const 成员和引用成员为什么必须在初始化列表中初始化?
  2. 成员初始化顺序是由什么决定的?
  3. 下面代码有什么问题?(考察顺序依赖)
class X {
    A a;
    B b;
public:
    X() : b(a) {}   // 大概率是错的!
};
  1. 成员有默认成员初始化和初始化列表同时存在,谁生效?
    初始化列表优先(覆盖默认成员初始化)
  2. 什么时候用 = default 构造函数和初始化列表结合最好?

希望这些内容能帮你把构造函数初始化列表从“会用”提升到“理解底层+熟练避坑”。

有哪一部分还想再深入一点(比如委托构造、继承中的初始化列表顺序、与就地初始化的交互、性能对比等),可以直接告诉我~

文章已创建 4791

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部