C++ 核心语法笔记:拷贝构造、深浅拷贝与运算符重载

这篇博客会帮你把图里的知识点梳理清楚,从拷贝构造到深浅拷贝,再到运算符重载,一次性吃透!


一、6 个默认成员函数总览

C++ 中,一个类如果不写任何成员函数,编译器会自动生成 6 个默认成员函数:

  1. 构造函数:初始化对象

  2. 析构函数:清理资源

  3. 拷贝构造函数:用同类对象初始化新对象

  4. 赋值运算符重载:对象之间赋值

  5. 取地址重载:普通对象取地址(很少手动实现)

  6. const 取地址重载:const 对象取地址(很少手动实现)


二、拷贝构造函数:从定义到底层原理

1. 定义与特点

  • 定义:构造函数的第一个参数是自身类型的引用,且额外参数都有默认值,就是拷贝构造函数。它是一种特殊的构造函数。

  • 特点

    • 必须是构造函数的重载,参数必须是自身类型的引用(不能传值,否则会无限递归调用)。

    • C++ 规定:自定义类型的传值传参、传值返回,都会调用拷贝构造。

    • 若未显式实现,编译器会自动生成默认拷贝构造:

      1. 内置类型:直接按字节浅拷贝。

      2. 自定义类型成员:调用其拷贝构造函数。

2. 为什么参数必须是引用?

如果参数是值传递:

复制代码
Date(Date d) { /* ... */ } // 错误写法

调用时 Date d2(d1); 会先调用拷贝构造,而值传递又会再次调用拷贝构造,形成无限递归,直接导致栈溢出。


三、深浅拷贝:你必须踩过的坑

1. 浅拷贝(默认拷贝构造的行为)

编译器自动生成的拷贝构造,只会按字节复制成员变量:

  • 对于 Date 类这种无资源管理的类,浅拷贝完全够用。

  • 对于 Stack 这种动态申请内存的类,浅拷贝会导致严重问题:

    • 两个对象的 _a 指向同一块内存。

    • 修改一个对象的数据,另一个对象也会被修改。

    • 析构时,同一块内存会被释放两次,程序直接崩溃。

2. 深拷贝(手动实现的拷贝构造)

为每个对象独立申请资源,复制数据到新空间,让两个对象的资源完全独立:

复制代码
Stack(const Stack& st)
{
    _a = new int[st._capacity]; // 申请新内存
    memcpy(_a, st._a, sizeof(int) * st._top); // 复制数据
    _top = st._top;
    _capacity = st._capacity;
}
  • 这样两个对象的 _a 指向不同的内存,修改互不影响,析构也不会重复释放。

3. 什么时候需要手动实现拷贝构造?

当类中存在需要手动管理的资源(如动态内存、文件句柄、网络连接)时,必须手动实现深拷贝。如果类的成员都是内置类型或无资源管理的自定义类型,默认拷贝构造就足够了。


四、传值返回与引用返回的区别

1. 传值返回

函数返回时会生成一个临时对象,调用拷贝构造。

复制代码
Stack f2()
{
    Stack st;
    st.Push(1);
    return st; // 调用拷贝构造生成临时对象
}
  • 临时对象会在表达式结束后销毁,调用 f2().Top() 是安全的。

2. 引用返回

直接返回对象的别名,不生成临时对象。

  • 若返回的是函数局部对象的引用,会导致野引用:函数结束后局部对象已销毁,引用指向无效内存。

  • 引用返回的前提:返回的对象在函数结束后依然存在(如成员变量、全局变量)。


五、运算符重载:让自定义类型像内置类型一样工作

1. 核心规则

  • 运算符重载是特殊的函数,格式为 operator+运算符

  • 不能改变运算符的操作数个数、优先级和结合性。

  • 不能创建新运算符,也不能重载 . .* :: sizeof ?: 这 5 个运算符。

  • 重载的运算符必须至少有一个自定义类型参数,不能重载内置类型的运算符(如 int+int)。

2. 成员函数 vs 全局函数

  • 成员函数重载 :第一个参数是隐藏的 this 指针,操作数个数比运算符少 1 个(如二元运算符作为成员函数时,参数只有 1 个)。

  • 全局函数重载:参数个数和运算符操作数个数一致,通常需要声明为友元,才能访问类的私有成员。

3. 示例:Date 类的 operator+ 重载

实现 Date + int,支持日期加天数:

复制代码
Date Date::operator+(int day)
{
    Date tmp = *this;
    tmp._day += day;
    while (tmp._day > GetMonthDay(tmp._year, tmp._month))
    {
        tmp._day -= GetMonthDay(tmp._year, tmp._month);
        tmp._month++;
        if (tmp._month == 13)
        {
            tmp._month = 1;
            tmp._year++;
        }
    }
    return tmp;
}
  • 实现时要处理月份进位、年份进位,确保日期合法。

  • 支持链式调用,也可以重载 operator+= 实现更高效的原地修改。


六、写在最后:拷贝构造与运算符重载的核心要点

  1. 拷贝构造:引用传参是必须的,深拷贝只在有资源管理时才需要。

  2. 深浅拷贝:本质区别是是否复制资源本身,而不是仅复制资源的地址。

  3. 运算符重载:让自定义类型更易用,但不要滥用,保持语义清晰。

  4. 传值返回会生成临时对象,引用返回要警惕野引用问题。

这些知识点是 C++ 面向对象的核心,理解它们才能写出安全、高效的自定义类!

相关推荐
之歆1 小时前
Ajax 进阶:跨域、CORS、JSONP 与请求封装实战
前端·javascript·ajax
jieyucx1 小时前
Go MongoDB 实战完全指南|从连接、CRUD、BSON结构体映射到高并发避坑全解
开发语言·mongodb·golang
Shadow(⊙o⊙)1 小时前
信号2.0,深入信号三张表block pending handlers,core文件的使用,信号执行逻辑:CPU虚拟内存物理内存,时钟源,软中断。
linux·运维·服务器·开发语言·c++
极创信息1 小时前
信创产品适配测试认证,域名和SSL是必须的吗?
java·开发语言·网络·python·网络协议·ruby·ssl
humcomm1 小时前
Go语言在AI领域的最新进展(2026年上半年)
开发语言·人工智能·golang
sugar__salt1 小时前
前端Ajax核心原理与实战:从异步机制到接口请求全解析
前端·javascript·ajax
難釋懷1 小时前
Nginx缓冲区
前端·javascript·nginx
码云骑士1 小时前
11-GIL不是性能杀手(上)-CPU密集vsIO密集的实测对比
开发语言·python
程序猿小泓1 小时前
2026 前端面试全攻略:手写题、算法与计网/TS 高频考点
前端·javascript·css