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++ 面向对象的核心,理解它们才能写出安全、高效的自定义类!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

影视飓风TIM

无限进步

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值