C++之成员初始化列表

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

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

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

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

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

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

2. 语法和位置

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

cpp 复制代码
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 类的例子,对比两种写法。

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

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

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

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

cpp 复制代码
#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. 进入构造函数前,为 width, height, name 分配内存。
  2. 根据初始化列表,直接用 w构造 width,用 h构造 height
  3. 对于 name,直接调用 std::string拷贝构造函数 ,用 n构造 name

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

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

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

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

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

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

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

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

2. 引用 (&) 成员

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

cpp

c 复制代码
class Student {
private:
    std::string& teacherName; // 引用老师的名字

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

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

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

3. 没有默认构造函数的类成员

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

cpp 复制代码
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) {}
};

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

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

cpp 复制代码
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 的值是未定义的,程序可能随时崩溃。

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

cpp 复制代码
class GoodExample {
private:
    int a;
    int b;

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

总结

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

  • 为什么用

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

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

相关推荐
Natsume17107 小时前
音视频开发入门:FFmpeg vs GStreamer,新手该如何选择?
c语言·c++·ffmpeg·音视频·webrtc·实时音视频·视频编解码
linux开发之路7 小时前
C++精选面试题集合(100份大厂面经提取的200+道真题)
linux·c++·网络编程·数据结构与算法·c++面试题
long_run7 小时前
C++之头文件 (.h)
c++
重启的码农7 小时前
云游戏技术之高速截屏和GPU硬编码 (1) 捕获-预处理-编码流水线
c++·云计算·音视频开发
重启的码农7 小时前
云游戏技术之高速截屏和GPU硬编码 (2) 应用程序主控
c++·云计算·音视频开发
沐怡旸8 小时前
【C++基础知识】深入剖析C和C++在内存分配上的区别
c++
studytosky8 小时前
C语言数据结构之双向链表
c语言·数据结构·c++·算法·链表·c
沐怡旸8 小时前
【底层机制】malloc 在实现时为什么要对大小内存采取不同策略?
c++
HABuo9 小时前
【C++进阶篇】学习C++就看这篇--->多态超详解
c语言·开发语言·c++·后端·学习