仅一行点到点
14.34 MB · 2025-10-31
它是一种特殊的语法,用在构造函数中,专门用于在对象创建时**“初始化”** 其成员变量。
关键区别:初始化 (Initialization) vs. 赋值 (Assignment)
这是理解初始化列表的核心!
成员初始化列表执行的是初始化,而在构造函数 {} 函数体内部使用 = 执行的是赋值。
它位于构造函数参数列表的 ) 和函数体 { 之间,由一个冒号 : 开始,成员之间用逗号 , 分隔。
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。, 逗号,用于分隔多个成员的初始化。我们来看一个简单的 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;    // 赋值
    }
};
编译器的工作流程:
width, height, name 分配内存。int 这种内置类型,其值是未定义的(垃圾值)。对于 std::string 这种类类型,会调用它的默认构造函数(创建一个空字符串 "")。{}。width = w;,用 w 的值覆盖掉 width 的垃圾值。height = h;,用 h 的值覆盖掉 height 的垃圾值。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) {
        // 函数体是空的,因为所有工作都做完了
    }
};
编译器的工作流程:
width, height, name 分配内存。w 来构造 width,用 h 来构造 height。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
    }
};
实际执行顺序:
b 在类中先被声明,所以先初始化 b。b 时,它使用了 a 的值。但此时 a 还没有被初始化,它的值是垃圾值!a 被初始化为 val。b 的值是未定义的,程序可能随时崩溃。最佳实践:为了避免混淆和错误,始终让你的初始化列表的顺序与成员在类中的声明顺序保持一致。
class GoodExample {
private:
    int a;
    int b;
public:
    // 好的写法:初始化顺序和声明顺序一致
    GoodExample(int val) : a(val), b(a) {}
};
是什么:构造函数中,用于在对象创建时直接初始化成员变量的特殊语法。
为什么用:
const 成员、引用成员、没有默认构造函数的类成员,这是唯一正确的初始化方式。如何用:在构造函数参数列表后加冒号 :,然后是 成员(值),用逗号分隔。
黄金法则:始终让初始化列表的顺序与成员在类中的声明顺序保持一致。
 
                    