您的位置: 首页> C++教程> C++之成员初始化列表

C++之成员初始化列表

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

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) {
        // 函数体,现在可以留空,因为初始化都做完了
    }
};

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) {}
};

总结

上一篇:数据运营DataOps扩展实时数据系统 下一篇:C++之头文件 (.h)

相关文章

相关应用

最近更新