)
系列文章目录后续会将类与对象中重要的六个默认成员函数构造、析构、拷贝构造、赋值重载、普通对象和const对象的取地址重载链接放在这目录里有学习需求的可以先点个收藏~文章目录系列文章目录前言一、什么是赋值运算符重载1. 核心定义2. 核心区分拷贝构造 VS 赋值重载重点二、标准赋值重载完整规范写法三、带资源类默认赋值需要深拷贝Stack 案例1. 浅拷贝带来的问题2. 解决方案手写深拷贝赋值重载四、为什么赋值重载不能写成全局函数五、自赋值判断到底有什么用六、连续赋值原理为什么返回引用七、类设计黄金三法则八、完整的Date Stack示例1. Date无资源使用默认赋值2. Stack动态内存手写深拷贝赋值总结前言很多新手分不清拷贝构造和赋值重载写带动态内存的类Stack栈时直接使用编译器默认赋值会导致程序崩溃。本篇文章将为大家详细讲解赋值重载的知识内容。一、什么是赋值运算符重载1. 核心定义operator赋值运算符针对自定义类对象时编译器会调用赋值重载成员函数完成对象之间的数据拷贝。语法类名 operator(const 类名 源对象)2. 核心区分拷贝构造 VS 赋值重载重点二者区分拷贝构造新对象创建阶段用已有对象初始化如Date d2 d1;/Date d2(d1);此时d2还不存在赋值重载两个都已经存在的对象把右边对象数据覆盖给左边对象如d2 d1;d1、d2提前都定义好了。代码// Date日期类已提前创建好Dated1(2025,1,1);Date d2d1;// 拷贝构造d2刚创建Dated3(2000,1,1);d3d1;// 赋值重载d3、d1均已存在覆盖赋值关键规则operator只能重载为类成员函数不能写成全局函数若不显式写赋值重载编译器会自动生成默认赋值重载默认赋值重载行为内置类型逐字节浅拷贝自定义成员自动调用自身赋值重载类持有堆内存、文件句柄等资源时默认浅拷贝会崩溃必须手写深拷贝赋值。二、标准赋值重载完整规范写法标准格式类名operator(const类名src){// 1. 判断自赋值防止自己赋值自己if(this!src){// 2 释放当前对象旧资源指针类必须写// 3 开辟新资源拷贝src数据// 4 拷贝普通内置成员}// 5 返回*this支持连续赋值return*this;}解释 4 个强制规范返回值是类引用类名目的支持连续赋值d3 d2 d1;如果返回值是void无法链式赋值返回值不加引用会产生临时对象效率低。参数为const 类名引用避免传值触发拷贝构造const保证不会修改源对象安全通用。必须判断this ! src自赋值检测防止d1 d1这种场景下提前释放自身资源后续无数据可拷贝。结尾return *this返回当前对象本体满足连续赋值左操作数返回规则。无资源类示例Date 日期类Date只有int _year/_month/_day内置成员没有指针、堆资源编译器默认赋值即可#includeiostreamusingnamespacestd;classDate{public:Date(inty1900,intm1,intd1){_yeary;_monthm;_dayd;}voidPrint()const{cout_year-_month-_dayendl;}private:int_year;int_month;int_day;};intmain(){Dated1(2025,5,1);Dated2(1999,12,12);d2d1;// 调用编译器默认赋值重载d2.Print();// 输出2025-5-1return0;}此时编译器生成的默认赋值会逐字节拷贝三个int就不需要手写operator了。三、带资源类默认赋值需要深拷贝Stack 案例1. 浅拷贝带来的问题Stack类中有_a指针指向堆开辟的数组只用编译器默认赋值浅拷贝会程序崩溃#includeiostreamtypedefintSTDataType;classStack{public:Stack(intn4){_a(STDataType*)malloc(sizeof(STDataType)*n);_capacityn;_top0;}~Stack(){free(_a);// 析构释放堆内存_anullptr;}voidPush(intx){_a[_top]x;}private:STDataType*_a;size_t _capacity,_top;};intmain(){Stack st1;st1.Push(1);st1.Push(2);Stack st2;st2st1;// 默认浅拷贝return0;}崩溃原理默认赋值只拷贝指针变量的值也就是st2._a st1._a两个对象指针指向同一块堆内存。如下图监视窗口所示函数结束析构顺序先析构st2执行free(_a)释放内存再析构st1再次free同一块已释放内存导致内存重复释放程序崩溃。2. 解决方案手写深拷贝赋值重载Stackoperator(constStackst){// 自赋值判断if(this!st){// 1 释放自身旧内存free(_a);// 2 开辟和源对象同等大小新空间_a(STDataType*)malloc(sizeof(STDataType)*st._capacity);if(_anullptr){perror(malloc fail);exit(1);// 分配失败直接退出防止后续memcpy空指针}// 3 拷贝所有数据memcpy(_a,st._a,sizeof(STDataType)*st._top);// 4 拷贝普通成员_capacityst._capacity;_topst._top;}return*this;}st2 st1会创建独立堆数组两个对象析构互不冲突不会崩溃。四、为什么赋值重载不能写成全局函数operator只能是成员函数禁止全局重载原因编译器会为所有类默认生成成员版赋值重载如果写全局operator(Stack left, const Stack right)会出现两套同名赋值函数重载冲突编译报错。像、、等运算符可以全局唯独、()、[]、只能成员重载。错误示范// 非法赋值不能全局Stackoperator(Stackleft,constStackright){// ...}五、自赋值判断到底有什么用很多新手觉得d1d1不会有人写没必要判断实际存在间接自赋值场景Stack*pst1;st1*p;// 等价于 st1 st1属于自赋值如果没有if(this ! st)判断先执行free(_a)把自己的堆内存释放再去拷贝st的数据此时st._a已经是空指针memcpy访问空指针直接会程序崩溃如果你的程序没有直接崩溃那只是侥幸代码还是存在严重未定义行为。加上判断后识别是自身赋值直接return *this跳过释放、拷贝逻辑更加安全。六、连续赋值原理为什么返回引用内置类型支持链式赋值inta,b,c;cb10;自定义类同样依靠return *this实现Dated1(2024,1,1),d2,d3;d3d2d1;执行顺序先执行d2 d1operator返回d2自身引用等价于d3 (d2 d1)把返回的d2赋值给d3。如果返回值是Date值而非引用每次赋值都会创建临时对象大量拷贝效率低。七、类设计黄金三法则总结如果一个类显式实现析构释放资源那么必须同时手写拷贝构造 赋值重载。Stack有free析构 → 必须深拷贝拷贝构造 深拷贝赋值重载Date无动态资源编译器默认析构 → 编译器默认拷贝、默认赋值MyQueue两个栈实现队列内部是Stack成员默认赋值会调用Stack自定义赋值析构也是调用Stack的析构也不需要手写拷贝构造和赋值重载。八、完整的Date Stack示例1. Date无资源使用默认赋值#includeiostreamusingnamespacestd;classDate{public:Date(inty1,intm1,intd1){_yeary;_monthm;_dayd;}voidPrint()const{cout_year/_month/_dayendl;}private:int_year,_month,_day;};intmain(){Dated1(2026,7,5);Date d2;d2d1;// 使用默认赋值重载d2.Print();Date d3d2;// 拷贝构造d3.Print();return0;}2. Stack动态内存手写深拷贝赋值#includeiostreamusingnamespacestd;typedefintSTDataType;classStack{public:Stack(intn4){_a(STDataType*)malloc(sizeof(STDataType)*n);_capacityn;_top0;}// 拷贝构造深拷贝Stack(constStackst){_a(STDataType*)malloc(sizeof(STDataType)*st._capacity);if(_anullptr){perror(malloc fail);exit(1);// 分配失败直接退出防止后续memcpy空指针}memcpy(_a,st._a,sizeof(STDataType)*st._top);_capacityst._top;_topst._top;}// 赋值重载深拷贝Stackoperator(constStackst){if(this!st){free(_a);_a(STDataType*)malloc(sizeof(STDataType)*st._capacity);if(_anullptr){perror(malloc fail);exit(1);// 分配失败直接退出防止后续memcpy空指针}memcpy(_a,st._a,sizeof(STDataType)*st._top);_capacityst._capacity;_topst._top;}return*this;}~Stack(){free(_a);_anullptr;}voidPush(intx){_a[_top]x;}voidprint(){for(size_t i0;i_top;i){cout_a[i] ;}coutendl;}private:STDataType*_a;size_t _capacity,_top;};intmain(){Stack st1;st1.Push(10);st1.Push(20);Stack st2st1;// 拷贝构造深拷贝st2.print();Stack st3;st3st2;// 赋值重载深拷贝st3.print();return0;}总结下一篇博客讲解 const 成员函数与取地址运算符重载欢迎点赞收藏关注