赋值运算符重载-从无到有与从有到有六大函数收官

赋值运算符重载:从无到有与从有到有,六大默认函数收官

我的github:(https://github.com/xcx55/ubuntu-linux-project)

感谢各位大佬参观我的github!!

运算符重载里有一个地位特殊的:赋值运算符 =。它是六大默认成员函数之一,跟拷贝构造长得像却干的是两码事。这篇把它写完,六大默认成员函数就收官了。

一、从无到有 vs 从有到有

先看两段代码,区分清楚了这篇就赢了一半:

cpp 复制代码
Date d1(2024, 4, 26);

Date d2 = d1;   // 拷贝构造!d2 还不存在,用 d1 初始化它------从无到有
Date d3(2025, 4, 28);
d1 = d3;        // 赋值重载!d1、d3 都已存在------从有到有

笔记里那句原话值得单独放大:

用一个对象去创建另一个对象就是拷贝构造,是从无到有!而赋值运算符重载是两个都已存在。

最容易骗人的是 Date d2 = d1;------长得像赋值,实际是拷贝构造 (d2 正在诞生,不存在"两个对象")。判断标准就一条:看左边的对象是不是已经存在。

d1 = d3 则是 d1.operator=(d3) 的一次调用,等于号是一种特殊的 operator 函数。而且它有个特权:其他 operator 函数都可以是全局函数,= 必须是成员函数------它是六大默认函数之一,规定写在类里面(不写编译器自动生成,也无法用全局函数代替)。

二、赋值重载的标准写法

cpp 复制代码
Date& operator=(const Date& d)
{
    if (this != &d)        // 防自赋值
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    return *this;          // 返回自身,支持连续赋值
}

三个要点:

  1. 参数建议 const 引用------传值传参会触发拷贝构造,白亏一次;
  2. 有返回值,且建议返回当前类类型的引用 ------返回 *this 是为了支持 d1 = d2 = d3 连续赋值(右边的先算完返回自身,继续给左边赋);
  3. 防自赋值 ------d1 = d1 在有资源的类里不判一下会出事故(自己 free 自己再拷贝,数据没了)。

三、默认生成的赋值重载:跟拷贝构造一个毛病

不写的话编译器自动生成,行为跟默认拷贝构造类似:内置类型成员完成值拷贝(一个字节一个字节的浅拷贝),自定义类型成员调用它自己的赋值重载。

所以对 Date 这种纯 int 的类,不写,编译器生成的就够了------两个已存在的对象互相拷成员,浅拷贝没毛病。

但对 Stack 这种成员里有指针的类,赋值重载和拷贝构造是同一个坑的两面:有资源(动态开辟的空间)就必须自己写 ------先释放自己的旧空间,再 new 出新空间把对方的数据深拷贝进来,最后返回 *this。拷贝构造是"出生时深拷贝",赋值重载是"生存期中深拷贝",一个都不能省。

至此六大函数的行为规律凑齐了:

函数 不写会怎样 什么时候必须自己写
构造函数 内置类型成员不初始化 基本都要写(重载适配)
析构函数 不清理资源 成员有动态资源
拷贝构造 浅拷贝 成员有动态资源(深拷贝)
赋值重载 浅拷贝 成员有动态资源(深拷贝)
取地址重载 够用 几乎不用写
const取地址重载 够用 几乎不用写

思维导图总结就是三大块:初始化和清理 (构造+析构)、拷贝复制 (拷贝构造+赋值重载)、取地址重载(普通对象和 const 对象取地址,很少自己实现)。

四、前置++ / 后置++:一个 int 参数的约定

++ 有前置后置两种,重载函数名都叫 operator++,怎么区分?C++ 的约定:后置版本多一个 int 形参,跟前置构成重载:

cpp 复制代码
Date& operator++()        // 前置:先加再返回
Date  operator++(int)     // 后置:int 只是标记,先保存旧值再自增,返回旧值

那个 int 不用来传值,纯粹是个"后置"的标记位------语言设计的巧思,也解释了为什么后置 ++ 对自定义类型更贵(要拷贝一份旧值)。

五、收官前补一块底:编译器到底怎么找符号

六篇下来,"编译器找名字"的规则散落各处,借收官把 3-14 晚上那篇《域--链接--编译》的账算总:

  • 域四级:局部域 → 全局域 → 命名空间域 → 类域 ;编译器遇到符号先在当前局部域找声明/定义,找不到先保留,去当前文件全局域找;
  • 对函数,先有声明才会去大符号表找定义------没有声明,编译器根本不会去查表;声明可以在局部、全局、命名空间、类任何域里;
  • 类域和命名空间域编译器不会主动进 ,除非 :: 或对象 . 触发:以类创建对象后,访问它的成员函数就去类域找声明,声明有了再去找定义;
  • 声明和定义都没有空间------只有链接完成后,才在代码段为定义分配空间。声明只是描述,定义才分配空间;
  • 回收旧账:inline 函数在编译期完成替换,决定它只能在当前 .cpp 里找 (不进符号表);static 函数不进大表,别的文件永远找不到。

这套规则就是前面 namespace、inline、this、类域所有细节的总开关------查找规则只有一套,各种语法都在它上面跳舞。


六大默认成员函数到此收官:构造/析构管初始化和清理,拷贝构造/赋值重载管拷贝复制(从无到有 vs 从有到有),两个取地址重载几乎不用管。下一篇进"下":初始化列表、explicit、static 成员与友元------类和对象的最后一批语法。

相关推荐
晴天的雨.9921 小时前
【C++】模板进阶
开发语言·c++·算法
Shadow(⊙o⊙)2 小时前
仿muduo实现高并发服务器——时间轮timewheel.cpp
c语言·c++
WL学习笔记14 小时前
C & C++ 内存管理
c++·c++内存管理
宛 禾16 小时前
Qt 控件入门:QLabel 从拖拽到对象树,一篇讲透
c++·qt·学习
hetao173383716 小时前
联盟场-提高组-R7 & ZYZSC-S-Round 11
c++·算法
程序喵大人16 小时前
【C++入门】编译链接模型 - 07 链接器怎样把符号对上号
开发语言·c++·编译器·编译链接·链接器
局部有解17 小时前
Hot100 - 三数之和
数据结构·c++·算法
longlongzihan17 小时前
C++ 常用数据结构速查:vector、stack、queue、list、树、图、哈希表等声明与核心操作
数据结构·c++·list
别动我齐刘海17 小时前
实时控制学习2
linux·c++·人工智能·学习·算法