C++之成员初始化列表

时间:2025-09-05 15:45:01来源:互联网

下面小编就为大家分享一篇C++之成员初始化列表,具有很好的参考价值,希望对大家有所帮助。

1. 什么是成员初始化列表?

它是一种特殊的语法,用在构造函数中,专门用于在对象创建时**“初始化”** 其成员变量。

关键区别:初始化 (Initialization) vs. 赋值 (Assignment)

这是理解初始化列表的核心!

  • 初始化:就像婴儿出生时在出生证明上写下名字。这个动作只发生一次,在变量“生命开始”的那一刻。
  • 赋值:就像给一个已经存在的人改名字。变量已经存在了,你只是用一个新的值去覆盖它。

成员初始化列表执行的是初始化,而在构造函数 {} 函数体内部使用 = 执行的是赋值

2. 语法和位置

它位于构造函数参数列表的 ) 和函数体 { 之间,由一个冒号 : 开始,成员之间用逗号 , 分隔。

class MyClass {
    int member1;
    double member2;
    std::string member3;

public:
    // 这就是成员初始化列表
    MyClass(int a, double b, const std::string& c) : member1(a), member2(b), member3(c) {
        // 函数体,现在可以留空,因为初始化都做完了
    }
};
  • : 冒号,表示“接下来是初始化列表”。
  • member1(a) 的意思是:用构造函数的参数 a 来初始化成员变量 member1
  • , 逗号,用于分隔多个成员的初始化。

3. 为什么要用它?(两大好处)

好处一:效率更高

我们来看一个简单的 Box 类的例子,对比两种写法。

写法一:在构造函数体内赋值 (不推荐)

#include <string>

class Box {
private:
    int width;
    int height;
    std::string name;

public:
    // 在函数体内赋值
    Box(int w, int h, const std::string& n) {
        width = w;   // 赋值
        height = h;  // 赋值
        name = n;    // 赋值
    }
};

编译器的工作流程:

  1. 进入构造函数前,先为 widthheightname 分配内存。
  2. 对每个成员执行默认初始化。对于 int 这种内置类型,其值是未定义的(垃圾值)。对于 std::string 这种类类型,会调用它的默认构造函数(创建一个空字符串 "")。
  3. 进入函数体 {}
  4. 执行 width = w;,用 w 的值覆盖掉 width 的垃圾值。
  5. 执行 height = h;,用 h 的值覆盖掉 height 的垃圾值。
  6. 执行 name = n;,调用 std::string 的赋值运算符,将 n 的内容拷贝到 name 中,覆盖掉之前创建的空字符串。

这个过程是两步:先默认构造,再赋值。

写法二:使用成员初始化列表 (推荐)

#include <string>

class Box {
private:
    int width;
    int height;
    std::string name;

public:
    // 使用初始化列表
    Box(int w, int h, const std::string& n) : width(w), height(h), name(n) {
        // 函数体是空的,因为所有工作都做完了
    }
};

编译器的工作流程:

  1. 进入构造函数前,为 widthheightname 分配内存。
  2. 根据初始化列表,直接用 w 来构造 width,用 h 来构造 height
  3. 对于 name,直接调用 std::string 的拷贝构造函数,用 n 来构造 name

这个过程是一步到位:直接用指定的值进行构造。

结论:对于类类型的成员(如 std::string),使用初始化列表可以避免一次不必要的默认构造和一次赋值操作,效率更高。对于所有类型,这都是更地道的 C++ 写法。

好处二:某些情况下必须使用

有些类型的成员变量必须在初始化列表中进行初始化,因为它们一旦创建就不能再被赋值。

1. const (常量) 成员
const 变量必须在声明时或创建时就初始化,之后它的值就不能改变了。

class Book {
private:
    const int ISBN; // 书号是常量,不能更改

public:
    // 错误写法:无法编译!
    // Book(int num) {
    //     ISBN = num; // 错误!不能对 const 成员进行赋值
    // }

    // 正确写法:必须在初始化列表中完成
    Book(int num) : ISBN(num) {}
};

2. 引用 (&) 成员
引用必须在创建时就绑定到一个已存在的对象上,之后不能再改变它引用的对象。

cpp

class Student {
private:
    std::string& teacherName; // 引用老师的名字

public:
    // 错误写法:无法编译!
    // Student(std::string& teacher) {
    //     teacherName = teacher; // 错误!引用必须在创建时初始化
    // }

    // 正确写法:必须在初始化列表中完成
    Student(std::string& teacher) : teacherName(teacher) {}
};

在你的 FunctionRenamer 例子中,Module& wasm_; 就是一个引用成员,所以它必须在成员初始化列表中初始化。

3. 没有默认构造函数的类成员
如果一个成员本身是一个类的对象,而这个类没有提供默认构造函数(即不带参数的构造函数),那么你就必须在初始化列表中明确告诉编译器该如何构造它。

class Pen {
public:
    // 这个类只有一个需要颜色的构造函数,没有默认的
    explicit Pen(std::string color) { /* ... */ }
};

class PencilCase {
private:
    Pen myPen; // 成员是一个 Pen 对象

public:
    // 错误写法:无法编译!
    // 编译器不知道如何创建 myPen,因为它没有默认构造函数
    // PencilCase(std::string penColor) { /* ... */ }

    // 正确写法:必须在初始化列表中告诉编译器如何构造 myPen
    PencilCase(std::string penColor) : myPen(penColor) {}
};

一个重要的陷阱:初始化顺序

成员变量的初始化顺序与它们在初始化列表中的顺序无关,只与它们在类中声明的顺序有关!

class BadExample {
private:
    int b; // b 先声明
    int a; // a 后声明

public:
    // 这是一个有潜在 bug 的写法!
    BadExample(int val) : a(val), b(a) {
        // 程序员的意图是:先用 val 初始化 a,再用 a 的值初始化 b
    }
};

实际执行顺序

  1. 因为 b 在类中先被声明,所以先初始化 b
  2. 初始化 b 时,它使用了 a 的值。但此时 a 还没有被初始化,它的值是垃圾值!
  3. 然后才轮到 a 被初始化为 val
    结果:b 的值是未定义的,程序可能随时崩溃。

最佳实践:为了避免混淆和错误,始终让你的初始化列表的顺序与成员在类中的声明顺序保持一致

class GoodExample {
private:
    int a;
    int b;

public:
    // 好的写法:初始化顺序和声明顺序一致
    GoodExample(int val) : a(val), b(a) {}
};

总结

  • 是什么:构造函数中,用于在对象创建时直接初始化成员变量的特殊语法。

  • 为什么用

    1. 效率更高:避免了“默认构造 + 赋值”的两步操作。
    2. 必须使用:对于 const 成员、引用成员、没有默认构造函数的类成员,这是唯一正确的初始化方式。
  • 如何用:在构造函数参数列表后加冒号 :,然后是 成员(值),用逗号分隔。

  • 黄金法则:始终让初始化列表的顺序与成员在类中的声明顺序保持一致。

本站部分内容转载自互联网,如果有网站内容侵犯了您的权益,可直接联系我们删除,感谢支持!