Re:从零开始的 C++ 入門篇(七)类和对象·第四篇:拷贝构造函数&赋值运算符重载

Re:从零开始的 C++ 入門篇(七)类和对象·第四篇:拷贝构造函数&赋值运算符重载

◆ 博主名称: 晓此方-ZEEKLOG博客

大家好,欢迎来到晓此方的博客。

⭐️C++系列个人专栏:

Re:从零开始的C++_晓此方的博客-ZEEKLOG博客


目录

0.1概要&序論

一,拷贝构造函数

1.1拷贝构造函数的定义

1.2拷贝构造函数的创建

1.2.1采用const引用的原因

1.3拷贝构造函数的调用

1.3.1情形一:拷贝构造初始化

1.3.2情形二传值调用拷贝构造

1.3.3情形三

1.3.4总结

1.3.5补充

1.4传值拷贝构造与无穷递归

1.4.1浅拷贝与深拷贝

1.4.2无穷递归原理

1.5深究浅拷贝与深拷贝

1..5.1自动生成拷贝构造函数

1.5.2需要自己实现深拷贝的场景

1.5.3C语言在此的致命缺陷

二,赋值运算符重载

2.1赋值运算符重载的定义

2.2赋值运算符重载的特点

2.3赋值运算符重载是否需要手动实现

2.3.1不需要手动实现的情况

2.3.2必须手动实现的情况

2.3.3实用技巧:一个简单的判断法则


0.1概要&序論

      这里是此方,久しぶりです!今天,为大家带来的是类和对象中的两位超级容易混淆的概念:拷贝构造和赋值重载,相信很多小伙伴都了解过构造函数和运算符重载,相信大家看完本文后能对他们有更新的认识。「此方」です。それでは、始めましょう!


一,拷贝构造函数

1.1拷贝构造函数的定义

拷贝构造函数是构造函数的重载

        如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数(又名复制构造函数),也就是说拷贝构造是一个特殊的构造函数

1.2拷贝构造函数的创建

Date(const Date& d, int x = 0){ _year = d._year; _month = d._month; _day = d._day; }

逐层分析:

  1. 函数和构造函数基本一致,无返回值(包括void)
  2. 第一个参数必须是自身类类型的引用。(Date&d)
  3. 后面的参数(如果有)都必须加上缺省值。(int x = 0)
  4. 引用前面最好加上const限制

1.2.1采用const引用的原因

       可以保护形式参数不被改变。传值传参,拷贝构造函数只是一个中转。我不希望它在函数体内对我的原来的对象造成修改。

Date(const Date& d){ if (d._year = _year){ } }

此外,常见的错误:在if语句种将==写成=,const也可以提醒你避免这些错误。

1.3拷贝构造函数的调用

注意:拷贝构造函数的调用必须以现有的自身类类型对象为第一个参数。

int main() { Date d1(2024,2,23); Date d2(d1); func (d1); }

1.3.1情形一:拷贝构造初始化

Date(const Date& d){ this->_year = d._year; this->_month = d._month; this->_day = d._day; }

        采用 Date d2(d1) 调用拷贝构造函数完成初始化,d2调用拷贝构造函数所以this指针就是d2的指针d就是d1的别名,d将d1的值传递给this指针指向的内容,完成拷贝初始化。

1.3.2情形二传值调用拷贝构造

        传值拷贝的时候,别名d任然指的是传递的形参。但是此时的this指针是函数形式参数的指针

1.3.3情形三

        传值返回的时候会调用拷贝构造函数完成拷贝:传值返回会产生一个临时对象调用拷贝构造。

1.3.4总结

        C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。

        对于内置类型,传值调用实际上影响不大。对于自定义类型,传值传参还要到用拷贝构造函数,费劲所以一般建议传引用传参,跳过拷贝构造函数这一步。(不可避免传值传参的时候传值)

但是:

         传引用返回,返回的是返回对象的别名(引用),没有产生拷贝。如果返回对象是一个当前函数局部域的局部对象函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回。

1.3.5补充

  1. 未定义行为:对多个参数进行传值调用的时候,C++没有规定这些参数传值调用拷贝构造函数的顺序。由编译器自动决定
  2. 调用拷贝构造函数的另外一种方法:第二种调用方法实质上等价于第一种。(你以后会喜欢第二种的)
Stack st2(st1); Stack st3 = st1;

1.4传值拷贝构造与无穷递归

        拷贝构造函数的参数:必须是类类型对象的引用,使用传值方式编译器必定报错,因为语法逻辑上会引发无穷递归调用

1.4.1浅拷贝与深拷贝

要理清这个问题,首先要明白C语言和C++在传值拷贝上的区别。

语言拷贝方式解释
C语言浅拷贝(值拷贝)将每个字节一次拷贝给目标
C++深拷贝调用拷贝构造函数完成拷贝
class Date{ public: Date(const Date& d){ _year=d._year; _month=d._month; _day=d._day; } void Func(Date d); private: int _year; int _month; int _day; } void Func(Date d){ cout<<d._year<<d._month<<d._day<<endl; } int main(){ Date d1; Func(d1); return 0; }

如上代码:

  1. 如果是C语言环境:调用Func()函数传值d1会直接把实参d1一个字节一个字节的拷贝给形参d,这就是值拷贝(浅拷贝)。
  2. 如果是C++的环境:调用Func()函数会先调用拷贝构造函数Date(),此时的d就是d1的别名,this指针是Func()函数的形参d的指针。完成深拷贝。

        补充内容:不管是深拷贝还是浅拷贝,都会生成一个临时对象,只是这个临时对象的生成方式不同,浅拷贝生成的方式是一个字节一个字节的拷贝过去,深拷贝在于复制语义:不仅复制对象的值,还重新分配资源并复制资源的内容,因此临时对象内部持有独立资源。

摘要来自ChatGPT:

浅拷贝(shallow copy)

拷贝策略是 member-wise copy

  • 遇到 int、double、bool 等 POD 类型 → 直接复制值
  • 遇到 指针 → 复制指针本身的值(即地址),不复制指向的内容

结果是:

两个对象共用同一块堆资源

深拷贝(deep copy)

拷贝策略是 member-wise copy + resource reallocation

  • 遇到 int、double、bool → 复制值
  • 遇到 指针
    1. 根据源对象的资源大小,重新申请一块新内存
    2. 将源对象指向的内容复制到新申请的内存
    3. 将新的地址赋给目标对象的指针成员

用更直白的话说:

深拷贝复制的是「资源」,不是「指针的地址」。

(注意:值拷贝和浅拷贝不是一回事,但是这里讲不清楚,放在后面的章节,)

1.4.2无穷递归原理

        能否发生无穷递归的关键是Date拷贝构造函数能否结束调用,如图,传引用调用,Date拷贝构造函数执行完成拷贝后自动转到Func()函数。避免了无穷递归。

 Date(const Date d){ _year=d._year; _month=d._month; _day=d._day; }

       假设我们把拷贝构造函数的传引用改成传值灾难性的问题就发生了:

过程分析:

  • 调用拷贝构造函数(第一个)采用传值调用。传值调用需要调用拷贝构造函数(第二个)。
  • 调用拷贝构造函数(第二个)采用传值调用,传值调用需要调用拷贝构造函数(第三个)。
  • 调用拷贝构造函数(第三个)采用传值调用,传值调用需要调用拷贝构造函数(第四个)。
  • 调用拷贝构造函数(第四个)采用传值调用,传值调用需要调用拷贝构造函数(第五个)。
  • ...........以此类推,无穷无尽,永远无法结束调用拷贝构造函数(第n个),也就永远无法调用到拷贝构造函数(第n-1个)

1.5深究浅拷贝与深拷贝

1..5.1自动生成拷贝构造函数

与构造函数与析构函数不同,自动生成的拷贝构造函数会管内置类型。

        若未显示定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝,对自定义类型成员变量会调用他的拷贝构造。
  1. 情况一: 类成员均为内置类型,且不管理资源
    例如Date类。编译器生成的拷贝构造函数会完成值拷贝,因此不需要我们显式实现拷贝构造。
  2. 情况二: 类成员包含自定义类型,且该类型已正确实现拷贝构造函数
    例如Myqueue类,其内部成员主要是自定义类型Stack的对象。编译器自动生成的拷贝构造函数会调用Stack的拷贝构造函数,因此也不需要我们显式实现Myqueue的拷贝构造。
  3. 情况三:类管理动态资源
    例如Stack 类,其成员_a指向了资源。编译器自动生成的拷贝构造函数仅完成值拷贝 / 浅拷贝(仅复制指针值),并不符合我们的需求,因此必须手动实现深拷贝,即对指针所指向的资源也进行完整拷贝。
设计者的小巧思:

        这种设计某种意义上是为了兼容C语言
,浅拷贝——类似于memcpy函数,也就是C语言传值调用的方式,C语言结构体传参会完成值拷贝。

1.5.2需要自己实现深拷贝的场景

如图:对栈类型调用拷贝构造函数初始化。

        但是你发现:程序崩溃原因:同一个空间被析构两次。调试查找问题你发现:_a存放的地址在两个栈中一致。

        原理解释:Stack不显示实现拷贝构造,用自动生成的拷贝构造完成浅拷贝,会导致st1和st2里面的_a指针指向同一块资源,析构时会析构两次,程序崩溃

于是,对于栈这种向内存申请资源的类,不建议使用编译器自带的拷贝方式。必须手动实现拷贝构造函数。

Stack(const Stack& st){ _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity); if (nullptr == _a){ perror("malloc申请空间失败!!!"); return; } memcpy(_a, st._a, sizeof(STDataType) * st._top); _top = st._top; _capacity = st._capacity; } 

1.5.3C语言在此的致命缺陷

void func(Stack st){ } int main(){ Stack st1; func(st1); return 0; }

        C语言传值传参,形式参数确实不会改变实际参数的值,但是传值传参把指针变量的指针一个字节一个字节的拷贝过去,让形式参数的对象的指针和实际参数的指针指向同一块空间会引发连锁改变效应。

二,赋值运算符重载

2.1赋值运算符重载的定义

        赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。
Date d1(2024, 7, 5); Date d2(2024, 7, 6); // 赋值重载 d1 = d2; // 拷贝构造 Date d3(d2); Date d4 = d2;

尤其注意这个不是赋值运算符重载:Date d4 = d2;这里的d4也是要创建的对象(优先是构造再是拷贝。),所以不是赋值重载,一定要强调是两个已经存在的对象。这里非常容易混淆。

2.2赋值运算符重载的特点

        1, 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算符重载的参数建议写成 const 当前类类型引用,否则会传值传参会有拷贝。

operator=(const Date& d) { } 

        2,有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。

Date& operator=(const Date& d) { _year=d._year; _month=d._month; _day=d._day; return *this: }

        赋值运算符的结合性是从左向右:k=1运算符结果是左操作数k,k=j运算符结果是左操作数j,以此类推。如图:

2.3赋值运算符重载是否需要手动实现

        没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的 赋值重载

2.3.1不需要手动实现的情况

  • 情况一:类成员均为内置类型,且不管理资源
    例如Date类。编译器生成的赋值运算符会进行值拷贝。
  • 情况二:类成员包含自定义类型,且该类型已正确实现赋值运算符
    例如Myqueue类,其成员为Stack对象。编译器会递归调用Stack的赋值运算符,只要Stack已正确实现深拷贝,Myqueue就无需手动实现。

2.3.2必须手动实现的情况

  • 类管理动态资源(如堆内存、文件句柄等)
    例如Stack类,其成员_a指向动态数组。编译器生成的赋值运算符仅进行浅拷贝(复制指针值),会导致:内存泄漏,重复释放,悬空指针等一系列问题。

2.3.3实用技巧:一个简单的判断法则

如果类显式实现了析构函数(因为需要释放资源),那么它通常也需要显式实现:拷贝构造函数赋值运算符重载

这就是著名的 “Rule of Three”(C++98/03)或 “Rule of Five”(C++11 后,包括移动语义)。反之,如果不需要写析构函数,通常也不必写拷贝控制函数。

好了,本期内容到此结束,我是此方,我们下期再见。バイバイ!

Read more

用 10% GPU 跑通万亿参数 RL!马骁腾拆解万亿参数大模型的后训练实战

用 10% GPU 跑通万亿参数 RL!马骁腾拆解万亿参数大模型的后训练实战

整理 | 梦依丹 出品 | ZEEKLOG(ID:ZEEKLOGnews) 左手是提示词的工程化约束,右手是 Context Learning 的自我进化。 在 OpenAI 新发布的《Prompt guidance for GPT-5.4》中,反复提到了 Prompt Contracts(提示词合约)。要求开发者像编写代码一样,严谨地定义 Agent 的输入边界、输出格式与工具调用逻辑,进而换取 AI 行为的确定性。 但在现实操作中,谁又能日复一日地去维护那些冗长、脆弱的“提示词代码”? 真正的 Agent,不应只靠阅读 Context Engineering,更应该具备 Context Learning 的能力。 为此,在 4 月 17-18

By Ne0inhk
当OpenClaw引爆全网,谁来解决企业AI Agent的“落地焦虑”?

当OpenClaw引爆全网,谁来解决企业AI Agent的“落地焦虑”?

2026 年 3 月,开源 AI Agent 框架 OpenClaw 在 GitHub 上的星标突破28万,并一度超越 React,成为 GitHub 最受关注的软件项目之一。短时间内,开发者利用它构建了大量实验性应用:从全栈开发辅助,到自动化营销脚本,再到桌面操作自动化,AI Agent 的能力边界正在迅速被拓展。 这股热潮也带动了另一个趋势——本地部署与算力硬件需求的快速增长。越来越多开发者尝试在个人设备或企业服务器上运行 Agent 系统,以获得更高的控制权和数据安全性。 从表面上看,AI Agent 似乎正从“概念验证”走向更广泛的开发实践。但在企业环境中,情况却没有想象中乐观。当企业负责人开始追问—— “它能直接解决我的业务问题吗?” 很多演示级产品仍难以给出令人满意的答案。 如何让 Agent 真正融入企业既有系统、适配复杂业务流程,正成为大模型产业落地必须跨越的一道门槛。 与此同时,中国不同城市的产业结构差异明显:互联网、

By Ne0inhk
二手平台出现OpenClaw卸载服务,299元可上门“帮卸”;2026年春招AI人才身价暴涨:平均月薪超6万;Meta辟谣亚历山大·王离职 | 极客头条

二手平台出现OpenClaw卸载服务,299元可上门“帮卸”;2026年春招AI人才身价暴涨:平均月薪超6万;Meta辟谣亚历山大·王离职 | 极客头条

「极客头条」—— 技术人员的新闻圈! ZEEKLOG 的读者朋友们好,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧。(投稿或寻求报道:[email protected]) 整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 一分钟速览新闻点! * 微信员工辟谣“小龙虾可自动发红包”:不要以讹传讹 * 蚂蚁集团启动春招,超 70% 为 AI 相关岗位 * 受贿 208 万!拼多多一员工被抓 * 2026 年春招 AI 人才身价暴涨: 平均月薪超 6 万元 * 二手平台出现 OpenClaw 上门卸载服务 * 权限太高,国家互联网应急中心发布 OpenClaw 安全应用的风险提示 * 字节豆包内测 AI 电商功能:无需跳转抖音,日活用户数超

By Ne0inhk
遭“美国政府封杀”后,Anthropic正式提起诉讼!

遭“美国政府封杀”后,Anthropic正式提起诉讼!

整理 | 苏宓 出品 | ZEEKLOG(ID:ZEEKLOGnews) 据路透社报道,当地时间周一,AI 初创公司 Anthropic 正式对美国国防部及特朗普政府提起诉讼,抗议五角大楼将其列为“国家安全供应链风险”主体的决定。 Anthropic 在向美国加州北区地方法院提交的诉讼文件中表示,这一认定“史无前例且非法”,已对公司造成“不可挽回的损害”。公司希望法院撤销该决定,并指示联邦机构停止执行相关认定。 划定 AI 应用红线,双方观点不一 正如我们此前报道,这场争端的核心在于 Anthropic 为其核心 AI 模型 Claude 设定的两条技术使用红线,与美国国防部的使用需求发生根本冲突。 此前,Anthropic 曾与五角大楼签署一份价值最高可达 2 亿美元的合作合同,Claude 也成为少数被纳入美国机密网络环境进行测试的 AI 系统之一。 对此,Anthropic 一直坚持两条底线: * Claude 等技术不得被用于对美国民众的大规模国内监控;

By Ne0inhk