[c++]string赋值运算符重载:从“砸碗“到优雅的“换碗仪式“

[c++]string赋值运算符重载:从“砸碗“到优雅的“换碗仪式“

一、为什么需要赋值运算符重载?

        使用编译器默认生成的赋值运算符重载,而这个对象有申请动态分配的资源时,使用编译器默认生成的赋值运算符重载会带来两个问题。

二、浅拷贝的两个致命问题

1.1、同一个空间析构释放两次

        赋值运算符重载中深拷贝用来解决浅拷贝带来的危害的,如果对象有在堆区申请内存,那么赋值运算符重载浅拷贝会造成同一块内存会被释放两次。

1.2、内存泄漏

        内存泄漏,被赋值的那个对象中的_str指向了赋值对象_str指向的空间,而被赋值的对象_str原先的空间没有指针指向了,造成内存泄漏,因此赋值运算符重载对于有动态分配资源的对象进行赋值操作时,需要深拷贝来解决问题。

        形象比喻:在C++中,当类包含动态分配的资源时,默认的赋值操作(浅拷贝)会带来严重问题。想象一下两个人共用一个碗吃饭,当其中一个人决定换碗时,如果处理不当,可能会把另一个人的饭碗也砸了。这就是我们需要自定义赋值运算符重载的原因。

三、赋值运算符重载传统写法:粗暴的"砸碗"操作

        传统写法是自己完成开空间,拷贝内容到新空间,释放旧空间,然后_str指向新空间,并且更新成员变量,也就是_size和_capacity。

1、"砸碗"比喻解析

场景:A = B(赋值操作)

买新碗new char[]创建新内存盛饭memcpy复制B的数据到新内存砸旧碗delete[] _str释放A的旧内存用新碗_str = temp让A指向新内存

自赋值的危险:A = A

A和B其实是同一个人,共用同一个碗买新碗、盛饭都正常砸旧碗时把唯一的碗砸了!后续操作无法进行
string& operator=(const string& s) { if (this != &s) { // 自赋值检查:确保不是自己给自己赋值 char* temp = new char[s._capacity + 1]; // 1. 买新碗 memcpy(temp, s._str, s._size + 1); // 2. 把旧碗的饭盛到新碗 delete[] _str; // 3. 砸旧碗 _str = temp; // 4. 用新碗 _size = s._size; _capacity = s._capacity; } return *this; }

2、赋值运算符重载传统写法-自赋值问题

        我们来看传统写法中自赋值的问题。假设我们有一个字符串对象,我们将其赋值给自己,即 str = str;。在传统写法中,如果没有自赋值检查,代码会这样执行:

分配新内存:char* temp = new char[s._capacity + 1];复制数据:memcpy(temp, s._str, s._size + 1);释放旧内存:delete[] _str;更新指针:_str = temp;

        现在,如果发生自赋值,那么 s 和 *this 是同一个对象。因此,在第3步释放 _str 时,实际上也释放了 s._str,因为它们指向同一块内存。然后,在第2步中,我们试图从已经被释放的内存中复制数据(s._str 现在是一个悬空指针),这会导致未定义行为(通常程序崩溃)。

所以,自赋值检查 if (this != &s) 是为了避免在自赋值时出现先释放再使用的问题。

string& operator=(const string& s) { // 假设没有 if (this != &s) 检查 // 1. 分配新内存 char* temp = new char[s._capacity + 1]; // 分配新空间 // 2. 复制数据:memcpy(temp, s._str, s._size + 1); // 此时 s._str 和 this->_str 指向同一块内存 // 3. 释放旧内存:delete[] _str; // 这里释放了 _str,但 s._str 也指向同一块内存! // 现在 s._str 变成了悬空指针 // 4. 更新指针:_str = temp; return *this; }

四、赋值运算符重载传统写法:优雅的"换碗仪式"

         现代写法是把自己完成开空间,拷贝内容到新空间,释放旧空间,然后_str指向新空间,并且更新_size和_capacity,这些操作交给拷贝构造去做,拷贝构造出一个临时对象,然后再与这个临时对象交换内容,并且让临时对象生命周期结束的时候调用析构函数来清理自己原本的空间,而自己完成了赋值的深拷贝

场景:A = B(赋值操作)

准备阶段(传值参数):厨师用B的配方重新做一份菜(深拷贝临时对象)新菜和原菜食材独立,味道相同交换阶段(swap操作):A说:"我把我的剩菜给你,你把新菜给我"双方优雅交换碗,不破坏任何食物清理阶段(析构临时对象):服务员收走A的剩菜剩饭(临时对象析构)新菜完好留在A的桌上

自赋值安全:A = A

厨师用A的配方做新菜(创建副本)A与新菜交换服务员收走A的旧菜结果:A的菜品保持不变

阶段一:引入拷贝构造辅助

string& operator=(const string& s) { if (this != &s) { string temp(s); // 拷贝构造创建临时对象 std::swap(_str, temp._str); // 交换资源 std::swap(_size, temp._size); std::swap(_capacity, temp._capacity); } return *this; }

阶段二:封装Swap操作

 void swap(string& temp) { std::swap(_str, temp._str); std::swap(_size, temp._size); std::swap(_capacity, temp._capacity); } string& operator=(const string& s) { if (this != &s) { string temp(s);// 厨师做新菜 swap(temp);// 优雅换碗 } return *this; }

阶段三:终极版Copy-and-Swap

        在这个版本中,我们利用了一个事实:参数s是一个临时对象,它是传入对象的副本。我们直接交换当前对象和s,这样当前对象就拥有了传入对象的副本,而s则拥有了当前对象原来的资源。当函数返回时,s超出作用域,析构函数被调用,释放掉原对象资源。

        自赋值的情况:当发生自赋值时,比如 str = str,传值会调用拷贝构造函数,创建一个与str相同的临时对象,然后交换,临时对象被销毁,最终str还是原来的值。虽然效率低一些,但是正确。最终版本中不需要显式检查自赋值,因为自赋值的情况也能正确工作。

        总结:最终版本的赋值运算符重载代码简洁、安全(异常安全、自赋值安全),是推荐的写法。需要注意的是,这种写法依赖于拷贝构造函数的正确实现和析构函数的正确实现。

 void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } string& operator=(string s)// 传值参数:自动创建副本 { swap(s);// 交换资源 return *this;// 临时对象析构清理旧资源 }

Read more

【粉丝福利社】构建自主AI深入A2A协议的智能体开发

【粉丝福利社】构建自主AI深入A2A协议的智能体开发

💎【行业认证·权威头衔】 ✔ 华为云天团核心成员:特约编辑/云享专家/开发者专家/产品云测专家 ✔ 开发者社区全满贯:ZEEKLOG博客&商业化双料专家/阿里云签约作者/腾讯云内容共创官/掘金&亚马逊&51CTO顶级博主 ✔ 技术生态共建先锋:横跨鸿蒙、云计算、AI等前沿领域的技术布道者 🏆【荣誉殿堂】 🎖 连续三年蝉联"华为云十佳博主"(2022-2024) 🎖 双冠加冕ZEEKLOG"年度博客之星TOP2"(2022&2023) 🎖 十余个技术社区年度杰出贡献奖得主 📚【知识宝库】 覆盖全栈技术矩阵: ◾ 编程语言:.NET/Java/Python/Go/Node… ◾ 移动生态:HarmonyOS/iOS/Android/小程序 ◾ 前沿领域:

By Ne0inhk
《Claude Code 落地实战:本地搭建 + 智谱 GLM-4.7 强强联手,打造最强 AI 编程助手》

《Claude Code 落地实战:本地搭建 + 智谱 GLM-4.7 强强联手,打造最强 AI 编程助手》

前言: 💡 为什么选择 Claude Code + 国产模型? * Claude Code:Anthropic 官方出品的命令行编程智能体(Agent),它拥有直接操作文件、执行终端命令、分析 git 提交记录的极高权限,比传统的 Chat 工具更贴近开发者。 * GLM-4.7:智谱 AI 的明星模型,国内适配度极高,响应速度快且性价比极高,实测在代码生成和逻辑理解上表现优异。 * CC-Switch:解决协议适配的“瑞士军刀”,让我们能以极低的成本在本地跑起这套顶级工具。 一、 环境准备:Windows 平台极速搭建 在开始之前,请确保你的系统已安装 Node.js (v18+)。 1. 提升 PowerShell 权限 为了顺利安装全局包,我们需要调整执行策略。在开始菜单搜索 PowerShell,以管理员身份运行,如图(1)

By Ne0inhk
与AI沟通的正确方式——AI提示词:原理、策略与精通之道

与AI沟通的正确方式——AI提示词:原理、策略与精通之道

文章目录 * 第一章:提示词革命——AI时代的新语言 * 1.1 从命令行到自然语言:人机交互的范式转变 * 1.1.1 历史脉络中的交互演进 * 1.1.2 提示词的本质:思维的结构化投射 * 1.2 提示词为何如此重要:放大人类智能的杠杆 * 1.2.1 提示词作为“思维乘数” * 1.2.2 经济性价值:降低AI使用成本 * 1.2.3 协作性价值:标准化智能协作协议 * 1.3 提示词的认知科学基础:人类如何思考AI如何“思考” * 1.3.1 人类思维的特点与提示词设计 * 1.3.2 AI的“思维”

By Ne0inhk