网络公司的名字,山东济南网站制作优化,wordpress版权图片,宝安中心医院是什么级别大家好#xff01;上一篇我们学会了对象的 “出生”#xff08;构造函数#xff09;和 “死亡”#xff08;析构函数#xff09;#xff0c;今天咱们聚焦对象的 “日常互动”—— 怎么用已有对象 “克隆” 新对象#xff1f;怎么让自定义类型像int一样用、做运算#x…大家好上一篇我们学会了对象的 “出生”构造函数和 “死亡”析构函数今天咱们聚焦对象的 “日常互动”—— 怎么用已有对象 “克隆” 新对象怎么让自定义类型像int一样用、做运算这篇就带大家吃透拷贝构造函数和运算符重载全程用代码 图解拆解新手也能轻松上手一、拷贝构造函数用 “旧对象” 造 “新对象”先想个场景你已经有了一个日期对象d1(2024, 6, 1)现在想创建一个和d1完全一样的d2总不能再写一遍2024,6,1吧C 里这种 “用已存在对象初始化新对象” 的操作就需要拷贝构造函数来帮我们自动完成。1.1 先搞懂什么时候会触发拷贝构造拷贝构造只负责 “新对象的初始化”常见场景有 3 种记牢这 3 个例子就够了直接用旧对象初始化新对象Date d2 d1;或Date d2(d1);最直观的场景函数参数是类类型传值调用比如void Func(Date d) {}调用Func(d1)时会拷贝d1给参数d函数返回值是类类型传值返回比如Date Func() { Date d; return d; }返回时会拷贝d给临时对象1.2 拷贝构造的 “致命坑”参数必须是引用这是初学者最容易踩的雷拷贝构造的参数必须是 “类类型的引用”比如const Date d如果用传值Date d编译器会直接报错因为会触发无穷递归。咱们用代码拆解这个坑错误示例参数用传值触发死循环cppclass Date { public: Date(int year2000, int month1, int day1) { // 普通构造 _year year; _month month; _day day; } // 错误参数d是传值 Date(Date d) { _year d._year; _month d._month; _day d._day; } private: int _year, _month, _day; }; int main() { Date d1(2024,6,1); Date d2(d1); // 试图用d1初始化d2触发拷贝构造 return 0; }为什么会递归调用Date d2(d1)时需要把d1传给拷贝构造的参数d—— 而 “传值参数” 本身需要先拷贝d1生成d这又要调用拷贝构造函数…… 相当于 “要调用函数得先完成参数拷贝要完成拷贝又得调用函数”陷入死循环正确示例参数用const 引用cppclass Date { public: Date(int year2000, int month1, int day1) { // 普通构造 _year year; _month month; _day day; cout 普通构造调用 endl; } // 正确参数是const Date避免额外拷贝 Date(const Date d) { _year d._year; // 直接访问d的成员引用无拷贝 _month d._month; _day d._day; cout 拷贝构造调用 endl; } private: int _year, _month, _day; }; int main() { Date d1(2024,6,1); // 输出普通构造调用 Date d2(d1); // 输出拷贝构造调用用d1初始化d2 return 0; }加const的原因防止在拷贝构造里修改原对象比如误写d._year 2025符合 “拷贝不修改原对象” 的逻辑。1.3 浅拷贝的 “隐形炸弹” 与深拷贝的救赎如果我们没写拷贝构造编译器会自动生成一个 “默认拷贝构造”但它有个大问题只做浅拷贝字节级拷贝。什么是浅拷贝默认拷贝构造对成员的处理规则内置类型int、指针等直接拷贝值比如把d1._year复制给d2._year指针也只复制地址自定义类型比如其他类对象调用该类型的拷贝构造。对Date这种无资源的类浅拷贝够用但对Stack这种需要申请堆内存的类浅拷贝会直接导致程序崩溃实战浅拷贝导致的 “双重析构”cppclass Stack { public: // 构造函数申请堆内存存数据 Stack(int capacity4) { _arr new int[capacity]; // 堆内存地址存在_arr里 _top 0; _capacity capacity; cout Stack构造 _arr endl; } // 析构函数释放堆内存自己写的因为有资源申请 ~Stack() { delete[] _arr; // 释放_arr指向的堆内存 _arr nullptr; cout Stack析构 _arr endl; } private: int* _arr; // 指向堆内存的指针关键 int _top; // 栈顶 int _capacity; // 容量 }; int main() { Stack st1; // 构造st1_arr指向堆地址A Stack st2(st1); // 用默认拷贝构造st2._arr也指向A return 0; // 析构时先析st2释放A再析st1再释放A→崩溃 }运行结果程序崩溃提示 “双重释放内存”。内存图解建议配图 { // 普通构造 _arr new int[capacity]; _top 0; _capacity capacity; cout Stack构造 _arr endl; } // 自己写的深拷贝构造 Stack(const Stack st) { // 1. 给st2的_arr重新申请堆内存和st1容量一样 _arr new int[st._capacity]; // 2. 把st1._arr里的数据拷贝到新内存 memcpy(_arr, st._arr, sizeof(int) * st._top); // 3. 拷贝其他成员 _top st._top; _capacity st._capacity; cout Stack拷贝构造 _arr endl; } ~Stack() { // 析构 delete[] _arr; _arr nullptr; cout Stack析构 _arr endl; } private: int* _arr; int _top; int _capacity; }; int main() { Stack st1; // 输出Stack构造0x00123456假设地址 Stack st2(st1); // 输出Stack拷贝构造0x00789abc新地址 return 0; // 析构先释st2的0x00789abc再释st1的0x00123456→正常 }内存图解建议配图函数很麻烦。C 的运算符重载解决了这个问题让自定义类型也能像内置类型一样用运算符2.1 运算符重载的 “黄金规则”先记住这些核心规则避免踩坑函数名固定operator 运算符比如operator、operator参数个数和运算对象数量一致一元运算符 1 个参数二元运算符 2 个参数成员函数特殊处理如果运算符重载是类的成员函数第一个参数会被默认作为this指针指向左侧运算对象所以参数个数会少 1 个不能重载的运算符5 个特殊运算符绝对不能重载记牢选择题常考.*成员指针访问、::作用域解析、sizeof大小计算、?:三目运算符、.成员访问必须有类类型参数不能重载内置类型的运算比如int operator(int a,int b)是不允许的。2.2 实战给 Date 类重载运算符需求判断两个Date对象的年、月、日是否完全相同。实现作为成员函数cppclass Date { public: Date(int year2000, int month1, int day1) { _year year; _month month; _day day; } // 重载运算符成员函数 bool operator(const Date d) { // this指向左侧对象比如d1d2中this就是d1 return _year d._year _month d._month _day d._day; } private: int _year, _month, _day; }; int main() { Date d1(2024,6,1); Date d2(2024,6,1); Date d3(2024,6,2); // 直接用编译器会转成d1.operator(d2) cout (d1 d2) endl; // 输出1真 cout (d1 d3) endl; // 输出0假 return 0; }调用逻辑图解建议配图this指针指向d1参数d是d2的引用对比两者的成员变量三、赋值运算符重载“旧对象” 给 “旧对象” 赋值很多人会把 “拷贝构造” 和 “赋值重载” 搞混其实核心区别就一个拷贝构造新对象初始化用旧对象造新对象比如Date d2 d1赋值重载两个已存在对象赋值比如d1和d2都已创建执行d1 d2。3.1 赋值重载的 “必记特点”赋值运算符重载是默认成员函数没写的话编译器会自动生成但它有 3 个关键特点必须是成员函数这是 C 规定的不能写成全局函数参数建议用const 引用避免传值导致的拷贝和拷贝构造一样返回值建议用类类型引用支持 “连续赋值”比如d3 d2 d1。3.2 默认赋值重载的 “浅拷贝坑”和默认拷贝构造一样默认赋值重载也做浅拷贝。对Stack这种有资源的类同样会导致双重析构。错误示例默认赋值重载cppint main() { Stack st1(4); // 构造st1_arr指向地址A Stack st2(8); // 构造st2_arr指向地址B st2 st1; // 用默认赋值重载st2._arr st1._arr指向A return 0; // 析构st2先释Ast1再释A→崩溃且地址B的内存泄漏了 }额外问题st2 原来的_arr指向地址 B赋值后被覆盖再也无法释放 B→内存泄漏解决自己写深拷贝赋值重载实现要点先判断 “是否给自己赋值”比如st1 st1避免不必要的操作释放当前对象的旧资源避免内存泄漏深拷贝新资源返回*this支持连续赋值。cppclass Stack { public: // 省略构造、拷贝构造、析构... // 自己写的深拷贝赋值重载 Stack operator(const Stack st) { // 1. 防止给自己赋值比如st1 st1 if (this ! st) { // 2. 释放当前对象的旧资源避免泄漏 delete[] _arr; // 3. 深拷贝申请新资源拷贝数据 _arr new int[st._capacity]; memcpy(_arr, st._arr, sizeof(int) * st._top); _top st._top; _capacity st._capacity; } // 4. 返回*this支持连续赋值比如st3 st2 st1 return *this; } private: int* _arr; int _top; int _capacity; }; int main() { Stack st1(4); Stack st2(8); Stack st3(10); st3 st2 st1; // 连续赋值先st2st1再st3st2返回的st2 return 0; // 析构正常无泄漏 }3.3 又一个小技巧和拷贝构造类似自己写析构的类一定要自己写赋值运算符重载三者析构、拷贝构造、赋值重载往往是 “捆绑出现” 的。总结核心知识点速记拷贝构造用旧对象初始化新对象参数必须是const 引用有资源要深拷贝运算符重载函数名operator运算符成员函数少一个参数this5 个运算符不能重载赋值重载处理两个已存在对象的赋值要先释旧资源、再深拷贝返回*this支持连续赋值黄金规律自己写析构 → 必须自己写拷贝构造 赋值重载。下一篇我们会讲 “初始化列表” 和 “static 成员”帮大家进一步完善类的知识体系 现在不妨动手敲一敲今天的Stack代码感受一下浅拷贝和深拷贝的区别有问题欢迎在评论区交流