对于C++:类和对象的解析—中

【C++ 笔记中篇】类与对象(中级核心概念)

上一期我们聊了类的基本定义、访问控制、成员变量/函数的初步使用。这一期重点进入中级阶段最常考、最实用、也最容易出错的几个核心机制:

  • this 指针
  • 构造函数的6大默认成员函数(尤其是生成规则)
  • 拷贝构造 & 移动构造(深浅拷贝、三五零法则)
  • 析构函数的调用时机 & RAII思想
  • 静态成员 & const 成员
  • 初始化列表 vs 赋值初始化
  • explicit / mutable 等修饰符

1. this 指针(最基础却最容易混淆的概念)

class Point {
    int x, y;
public:
    void set(int x, int y) {          // 形参x,y 遮蔽了成员x,y
        this->x = x;                  // 必须写this->才能访问成员
        this->y = y;
    }

    Point& move(int dx, int dy) {
        x += dx;
        y += dy;
        return *this;                 // 返回自身引用,支持链式调用
    }
};

this 的本质

  • 类型:类类型* const(指针常量,不能改变指向)
  • 每个非静态成员函数隐式第一个参数就是 this
  • 只有非静态成员函数才有 this
  • 静态成员函数没有 this(因为不依赖具体对象)

经典面试题

Point& Point::operator=(const Point& rhs) {
    if (this == &rhs) return *this;   // 防止自赋值
    // ...
}

2. 类的6大默认特殊成员函数(C++11/14/17/20 视角)

编译器可能自动生成以下函数(但有生成条件):

序号函数名默认生成条件(什么时候编译器会帮你写)是否 trivial(平凡的,可 memcpy)
1默认构造函数用户没写任何构造函数时生成(C++11前)
用户没写任何构造函数且没声明 =default/=delete 时
是(成员都是 trivial 的情况下)
2拷贝构造函数用户没声明时生成是(同上)
3拷贝赋值运算符用户没声明时生成
4析构函数用户没声明时生成(总是生成,除非用户 =delete)
5移动构造函数(C++11)用户没声明拷贝/移动构造/析构/拷贝赋值时,且有移动语义成员才生成否(通常)
6移动赋值运算符(C++11)同上

现代 C++ 重要规则(2025-2026 面试最爱问)

  • 规则0(零法则):尽量使用 RAII 类管理资源(如 std::unique_ptrstd::shared_ptrstd::stringstd::vector),自己什么都不写。
  • 规则3(三法则):如果你需要自定义析构函数、拷贝构造或拷贝赋值中的任意一个,通常需要同时定义全部三个(经典指针成员场景)。
  • 规则5(五法则):如果你需要自定义析构、拷贝构造、拷贝赋值,通常也应该同时定义移动构造和移动赋值(C++11+ 推荐)。
  • 规则5 的简化版:要么全用编译器默认(零法则),要么全自己写/禁用(五法则)。

3. 拷贝构造 vs 移动构造(最容易写错的地方)

class Widget {
    std::string name;
    int* data;           // 拥有资源
public:
    // 拷贝构造(深拷贝)
    Widget(const Widget& other)
        : name(other.name), data(new int(*other.data)) {}

    // 移动构造(窃取资源)
    Widget(Widget&& other) noexcept
        : name(std::move(other.name)), 
          data(other.data) {
        other.data = nullptr;   // 重要!置空防止双重释放
    }

    ~Widget() { delete data; }
};

移动语义关键点

  • 参数必须是 T&&(右值引用)
  • 函数要标记 noexcept(否则容器可能不使用移动)
  • 移动后要把源对象置于“合法但未指定状态”(通常把指针置 nullptr)

4. 成员初始化列表(必须掌握)

class Person {
    std::string name;
    const int id;           // const 成员
    int& ref_age;           // 引用成员
public:
    Person(std::string n, int i, int& a)
        : name(std::move(n))   // 效率更高
        , id(i)                // const 必须在这里初始化
        , ref_age(a)           // 引用必须在这里绑定
    {
        // 这里不能初始化 const / 引用成员
    }
};

必须用初始化列表的场景(面试必考):

  1. const 成员
  2. 引用成员
  3. 没有默认构造函数的成员对象
  4. 基类构造函数需要参数
  5. 想避免先构造再赋值的两步开销(性能)

5. 静态成员 & const 成员

class Singleton {
private:
    static Singleton* instance;           // 声明
    Singleton() = default;
public:
    static Singleton& getInstance() {
        if (!instance) instance = new Singleton;
        return *instance;
    }

    static int count;                     // 所有对象共享
};

Singleton* Singleton::instance = nullptr;   // 定义(必须类外)
int Singleton::count = 0;

// const 静态成员可以在类内初始化(C++11+)
class Config {
    static const int MAX = 100;         // 整型 const static 可类内初始化
    static constexpr double PI = 3.14159;  // C++11 constexpr 更强
};

6. const 成员函数 & mutable

class TextBlock {
    std::string text;
    mutable size_t textLength;     // mutable 允许 const 函数修改
    mutable bool lengthValid = false;
public:
    size_t length() const {        // const 成员函数
        if (!lengthValid) {
            textLength = text.size();
            lengthValid = true;
        }
        return textLength;
    }
};

小结:中级阶段最常踩的 10 个坑

  1. 忘记写移动构造/赋值 → 容器退化成拷贝
  2. 移动后没把源对象指针置空 → 双重 delete
  3. const 成员 / 引用成员没用初始化列表
  4. 自赋值没判 if(this == &rhs)
  5. 析构函数里 delete 了 nullptr(其实是安全的,但很多人紧张)
  6. 写了析构就忘了写拷贝构造 → 浅拷贝灾难
  7. 没把移动函数标记 noexcept
  8. static 成员忘记类外定义
  9. 在 const 函数里改了非 mutable 成员
  10. 滥用友元(friend)破坏封装

下一期可以继续聊继承与多态(虚函数、虚继承、override/final、纯虚函数、抽象类等),或者深入运算符重载类型转换(explicit/single-argument ctor)、RAII+智能指针实战。

你现在更想先深入哪个方向?

  • 继承 & 多态 & 虚函数表
  • 运算符重载全攻略
  • 现代 C++ 资源管理(智能指针 + RAII)
  • 模板初步(类模板 / 函数模板)

或者你项目里最近在用哪些类设计模式 / 痛点,也可以直接抛出来,我们针对性拆解~

文章已创建 4206

发表回复

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

相关文章

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

返回顶部