跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 类和对象:拷贝构造函数与赋值运算符重载详解

C++ 对象复制机制核心在于拷贝构造函数与赋值运算符重载。前者处理初始化时的对象创建,后者处理已存在对象的赋值。编译器默认生成浅拷贝,涉及资源管理时需自定义深拷贝以避免内存泄漏或重复释放。掌握自引用检查与返回引用优化是编写高效安全类的基础。

山野来信发布于 2026/3/22更新于 2026/10/2182 浏览
C++ 类和对象:拷贝构造函数与赋值运算符重载详解

在 C++ 面向对象编程中,对象的复制操作无处不在。无论是函数传参、返回值传递,还是对象间的赋值,都需要精确控制数据的复制行为。C++ 通过拷贝构造函数和赋值运算符重载两套机制,为开发者提供了对象复制的完整解决方案。

一、拷贝构造函数

如果一个构造函数的第一个参数是自身类型的引用,且其他所有参数都有默认值(如果有),就叫做拷贝构造,它是特殊的构造函数。

1.1 解析:拷贝构造特点

拷贝构造函数遵循一些核心规则,理解这些规则能避免很多陷阱:

  1. 参数必须是引用:拷贝构造函数的第一个参数必须是自身类类型的引用(类名& 或 const 类名&)。如果使用传值的方式,逻辑上会引发无穷递归调用——因为要创建形参对象本身就需要调用拷贝构造。
  2. 默认生成规则:若未显式定义拷贝构造,编译器会生成默认的拷贝构造函数。默认生成的拷贝构造对内置类型成员变量完成值拷贝(浅拷贝),对自定义类型成员变量则调用其拷贝构造。
  3. 深拷贝的必要性:如果类中包含指向资源的指针成员(如动态分配的内存),编译器默认的浅拷贝会导致多个对象共享同一块资源。析构时,同一块空间会被释放两次,引发程序崩溃。因此,一旦类显式实现了析构并释放资源,就必须显式定义深拷贝。
  4. 返回值的处理:传值返回会产生临时对象来调用拷贝构造;而传引用返回虽然避免了拷贝,但如果返回的是局部对象的引用,函数结束后该对象销毁,返回引用就会变成野指针。

为了直观理解浅拷贝的危害,我们可以看一个栈(Stack)的例子。如果只进行浅拷贝,两个对象的 _a 指针将指向同一块堆内存。当其中一个对象析构释放了内存,另一个对象再析构时就会发生重复释放错误。

typedef int STDataType;
class Stack {
public:
    Stack(int n = 4) {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a) {
            perror("malloc 申请空间失败");
            return;
        }
        _capacity = n;
        _top = 0;
    }

    // 浅拷贝示例(危险)
    Stack(const Stack& s) {
        _a = s._a;
        _capacity = s._capacity;
        _top = s._top;
    }

    void Push(STDataType x) {
        if (_top == _capacity) {
            int newcapacity = _capacity * 2;
            STDataType* tmp = (STDataType*)realloc(_a, newcapacity * sizeof(STDataType));
            if (tmp == NULL) {
                perror("realloc fail");
                return;
            }
            _a = tmp;
            _capacity = newcapacity;
        }
        _a[_top++] = x;
    }

    ~Stack() {
        cout << "~Stack()" << endl;
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }
private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};

正确的做法是实现深拷贝,为新对象重新分配内存并复制数据:

Stack(const Stack& s) {
    _a = (STDataType*)malloc(sizeof(STDataType) * s._capacity);
    if (_a == NULL) {
        perror("realloc fail");
        return;
    }
    memcpy(_a, s._a, s._top * sizeof(STDataType));
    _capacity = s._capacity;
    _top = s._top;
}

1.2 关键:拷贝构造的调用场景

拷贝构造函数主要在以下情况被隐式调用:

  • 用一个对象初始化另一个同类的对象:例如 Example b(a); 或 Example c = a;。注意 Example c = a; 虽然语法像赋值,但在 C++ 中这实际上是拷贝构造,而非赋值运算符重载。
  • 函数参数按值传递:当函数参数是该类的对象时,实参会先通过拷贝构造传递给形参。
  • 函数按值返回:函数返回对象时,会创建一个临时对象并通过拷贝构造返回给调用者。

二、赋值运算符重载

赋值运算符重载用于完成两个已存在对象之间的拷贝复制,这与拷贝构造函数有本质区别:拷贝构造发生在对象创建时,而赋值发生在对象已创建后。

2.1 铺垫:运算符重载特点

C++ 允许通过运算符重载指定类类型对象使用运算符时的新含义。重载运算符本质上是一个特殊名字的函数,名称由 operator 和后面的运算符构成。

  • 成员函数 vs 全局函数:如果是成员函数,左侧运算对象是 this 指针,参数少一个。对于 << 和 >> 这种流运算符,通常重载为全局函数,以便让 ostream/istream 作为第一个参数。
  • 限制:不能改变内置类型对象的含义,也不能重载 .*、::、sizeof、? :、. 等五个运算符。

2.2 核心:理解赋值运算符重载

赋值运算符重载必须为成员函数。实现时有几个关键点需要注意:

  1. 自引用检查:赋值前需判断 this != &d,防止自我赋值导致不必要的开销或逻辑错误。
  2. 返回引用:建议返回当前类类型的引用(Date&),这样可以支持连续赋值,如 d1 = d2 = d3;。
  3. 深拷贝一致性:如果类需要深拷贝,赋值运算符同样需要执行深拷贝逻辑,确保资源独立。
#include <iostream>
using namespace std;

class Date {
public:
    Date(int day = 8, int month = 1, int year = 2026) {
        _day = day;
        _month = month;
        _year = year;
    }

    // 拷贝构造
    Date(const Date& d) {
        _day = d._day;
        _month = d._month;
        _year = d._year;
    }

    // 赋值重载
    Date& operator=(const Date& d) {
        if (this != &d) {
            _day = d._day;
            _month = d._month;
            _year = d._year;
        }
        return *this;
    }

    void Print() {
        cout << _year << '/' << _month << '/' << _day << endl;
    }
private:
    int _day;
    int _month;
    int _year;
};

int main() {
    Date d1(1, 1, 1);
    Date d2(d1);      // 调用拷贝构造
    Date d3 = d1;     // 调用拷贝构造
    d2.Print();
    d3.Print();

    Date d4;
    d4 = d1;          // 调用赋值重载
    d4.Print();
    return 0;
}

总结

拷贝构造函数与赋值运算符重载构成了 C++ 对象复制机制的核心支柱。它们分别负责对象初始化和对象赋值两种不同场景的复制需求。掌握这些复制控制机制,不仅能写出更安全的代码,更能深入理解 C++ 对象生命周期的管理哲学。这是从 C++ 使用者迈向 C++ 设计者的重要一步。

目录

  1. 一、拷贝构造函数
  2. 1.1 解析:拷贝构造特点
  3. 1.2 关键:拷贝构造的调用场景
  4. 二、赋值运算符重载
  5. 2.1 铺垫:运算符重载特点
  6. 2.2 核心:理解赋值运算符重载
  7. 总结

更多推荐文章

查看全部
  • 基于 Retinaface 与 CurricularFace 的人脸身份核验方案
  • 基于 PSO-DWA 融合的无人机三维动态避障路径规划及 Matlab 实现
  • C++ 函数重载:核心规则、匹配机制与实战
  • Java 集合框架核心:List、Set 与 Map 的差异解析
  • Python 实战:构建文档总结、代码生成与智能检索助手
  • PentAGI 基于 Docker 的 AI 自动化渗透环境部署指南
  • DeepSeek 深度使用指南:提示词技巧与本地知识库搭建
  • 时序数据库选型指南:工业物联网场景首选 Apache IoTDB
  • MarianCG:受机器翻译启发的代码生成 Transformer 模型
  • CCF-GESP 2025 年 6 月二级 C++ T2 幂和数
  • 35 岁失业建议尝试的 9 个行业方向
  • Flutter 底部导航与 TabBar 多页切换及鸿蒙适配
  • 2025 年 AIGC 六大核心趋势与技术落地分析
  • 解决 VS Code 远程连接时 GitHub Copilot 无法使用的问题
  • JavaScript 进阶:Fetch 请求中断与 Headers 对象详解
  • ComfyUI 新手入门:零基础掌握 AI 绘画工作流
  • 鸿蒙金融理财全栈项目:生态合作与用户运营优化
  • Vue 自定义指令详解
  • .NET 集成 GoView 低代码可视化大屏实战指南
  • 《大模型应用开发极简入门》:GPT-4 与 ChatGPT 应用开发指南

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online