当前位置: 首页 > news >正文

【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

🔥个人主页:Forcible Bug Maker

🔥专栏:C++

目录

前言

拷贝构造函数

概念

拷贝构造函数的特性及用法

赋值运算符重载

运算符重载

赋值运算符重载

结语


前言

本篇主要内容:类的6个默认成员函数中的拷贝构造函数赋值运算符重载

在上篇文章中我们讲到了类的默认成员函数的构造函数和析构函数,这两个默认成员函数在对象的生命周期中起着至关重要的作用。而今天我们要讲的拷贝构造函数和赋值运算符重载,作为类默认成员函数的其中之二,则是在对象间的初始化和拷贝当中起着重要作用。再次强六个默认成员函数的共性,这些函数会在你不提供的情况下由编译器自动生成。接下来开始我们今天的内容。

拷贝构造函数

概念

在创建对象时,你可能需要创建一个与已经存在的对象一模一样的新对象。

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用

拷贝构造函数的特性及用法

拷贝构造函数是一种特殊的构造函数,用于创建一个新对象作为现有对象的副本。

1. 拷贝构造函数是构造函数的一个重载形式

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器报错,因为会引发无穷调用。

拿一个Date类来举例:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d) // 正确写法Date(const Date& d) // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
}

在 Date d2(d1); 这句,将调用拷贝构造函数,其中d1通过传引用传参传递给函数中的d,d2以this指针的形式隐式传递。在运行完拷贝构造函数之后,d2创建好就是和d1相同的一个对象了。接下来详细讲解为什么构造中不加应用会导致无穷调用。

void Fun1(Date d)
{cout << "Fun1" << endl;
}int main()
{Date d1(2024, 4, 16);Fun1(d1);return 0;
}

运行以上代码,会发现,在进入函数Fun1之前,会先调用一次拷贝构造,将d1的值赋给d,下面是调试观察,可以看到调试进入Fun1前先进入了拷贝构造。

说明类的传值传递在调用时会先调用类内部的拷贝构造,如果不写拷贝构造中的引用,拷贝构造的传应用传递就会变成类的传值传递,而类的传值传递又需要先调用拷贝构造,最终逻辑形成了一个闭环,导致无穷调用。

3. 若未显式定义,编译器会生成默认的构造函数。默认构造函数对象按内存存储字节完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;
}

上述代码中Date的拷贝构造函数我们并没有提供,由编译器默认生成。 

注:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造完成拷贝的

4. 编译器生成的默认拷贝构造函数既然已经可以完成字节序的拷贝,那么是否还有自己实现的必要?像日期类这样的类是没什么必要的,但是当类涉及资源的打开和关闭,开辟和释放时,默认生成的浅拷贝构造就无法解决问题了。

看如下的Stack类:

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

在Stack类中,我们在创建s2时使用了编译器默认生成的拷贝构造函数,在main函数代码运行的过程中,似乎并没有什么问题,但是当整个程序运行结束时,程序崩溃了。这是因为Stack类涉及到了堆中空间资源的开辟,由于编译器默认生成的拷贝构造是浅拷贝,s1和s2中的_array指针指向相同的堆空间,在程序运行到结尾会调用析构函数,s1和s2对象各调用一次析构时,会导致_array指向的堆空间被释放两次,最终程序崩溃。

注:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以一旦涉及到资源申请,则拷贝构造函数是一定要写的,否则就是浅拷贝,导致上述问题。

5. 拷贝构造函数典型应用场景

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

代码示例:

class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d) // 第二种
{Date temp(d); // 第一种return temp; // 第三种
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}

为了提高效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能引用尽量使用引用。

6. 拷贝构造函数其实还有一种调用方式

类名  对象名 = 已有对象名

赋值运算符重载

运算符重载

运算符重载是C++中的一个重要特性,它允许我们为自定义的数据类型(如类)重新定义或重载已有的运算符,以便它们能像内置类型(如int、float等)的运算符那样工作。通过运算符重载,我们可以让自定义类型的对象像内置类型一样进行运算和操作提高代码的可读性和易用性
函数名字为:关键字operator后面接重载的运算符符号。
函数原型:返回值类型  operator操作符(参数列表)

简单来说,运算符重载就是给运算符“赋予新的意义”,让它在不同的数据类型上有不同的作用

重载的基本规则:

  1. 不能通过连接其他符号来创建新的操作符,如:operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,如,内置类型的+,不能改变含义
  4. 最为类成员函数重载时,其形参看起来比操作数目少1,因为成员函数的第一个参数为隐藏的this
  5.   .*  ::  sizeof  ?:  .  这五个运算符不能重载。 

上面代码中重载了一个全局的operator==,但是全局要求成员变量是公有的,这就牵扯到一个问题,如何保证封装性?

解决方式有三:

  1. 提供可以获取到成员变量的成员函数
  2. 使用友元(后面将会学习)
  3. 重载成成员函数
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};

上面的代码就成功将==操作符重载成了成员函数,在使用时与全局的==并无区别。

赋值运算符重载

经过上面对操作符重载的简单介绍,赋值重载简单说就是字面意思,重载了=这一符号,使其可以用于对象之间的相互赋值。

1. 赋值运算符重载格式

  • 参数类型:const T&,传引用提高效率
  • 返回值类型:T& 返回引用可以提高返回效率,有返回值的目的是为了支持连续赋值
  • 检测是否给自己赋值
  • 返回*this:要符合连续赋值的含义

代码示例(完整的赋值运算符重载):

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。虽然编译器生成的默认赋值运算符可以完成字节序的值拷贝,但一旦涉及到资源的管理时,编译器生成的依然是不够用的,原因和默认生成的拷贝构造函数类型。

故:如果类中未涉及到资源管理,赋值运算符是否实现都可以一旦涉及到资源管理则必须要实现

结语

本篇博客主要讲了拷贝构造函数和赋值运算符重载,它们在类中扮演着至关重要的角色,是对象复制和赋值操作的基础,确保对象在复制和赋值过程中保持正确的状态和行为。如果没有正确地实现这两个函数,可能会导致数据不一致、内存泄漏或其他严重问题。因此,在编写自定义类时,通常需要仔细考虑是否需要显式定义拷贝构造函数和赋值运算符重载,并根据类的具体需求来实现它们。对于某些类(如包含动态分配内存的类),显式定义这两个函数是必不可少的。下篇博客将会讲到最后两个类的默认成员函数,以及操作符重载更多的使用情境。

本篇博客到此结束,感谢大家的支持!♥

相关文章:

【C++】类和对象③(类的默认成员函数:拷贝构造函数 | 赋值运算符重载)

&#x1f525;个人主页&#xff1a;Forcible Bug Maker &#x1f525;专栏&#xff1a;C 目录 前言 拷贝构造函数 概念 拷贝构造函数的特性及用法 赋值运算符重载 运算符重载 赋值运算符重载 结语 前言 本篇主要内容&#xff1a;类的6个默认成员函数中的拷贝构造函数…...

掀起区块链开发狂潮!Scaffold-eth带你一键打造震撼DApp

文章目录 前言一、Scaffold-eth是什么&#xff1f;二、安装和配置1.准备工作2.安装3.配置开发环境 三、进阶使用1.放入自己的合约2.部署运行 总结 前言 前面的文章传送&#x1f6aa;&#xff1a;hardhat入门 与 hardhat进阶 在之前的文章中&#xff0c;我们已经探讨了使用Har…...

【Qt 学习笔记】Qt常用控件 | 按钮类控件Check Box的使用及说明

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt常用控件 | 按钮类控件Check Box的使用及说明 文章编号&#xff1a;…...

android gradle 配置远程仓库

build.gradle buildscript { ext.kotlin_version "1.6.0" // 使用适合你项目的Kotlin版本 repositories { maven { url http://maven.aliyun.com/nexus/content/groups/public/ } maven { url http://maven.aliyun.com/nexus/content/repos…...

第十二章 OpenGL ES 基础-色温、色调、亮度、对比度、饱和度、高光

第十二章 OpenGL ES 基础-色温、色调、亮度、对比度、饱和度、高光 第一章 OpenGL ES 基础-屏幕、纹理、顶点坐标 第二章 OpenGL ES 基础-GLSL语法简单总结 第三章 OpenGL ES 基础-GLSL渲染纹理 第四章 OpenGL ES 基础-位移、缩放、旋转原理 第五章 OpenGL ES 基础-透视投影…...

力扣经典150题解析之二十八:盛最多水的容器

目录 力扣经典150题解析之二十八&#xff1a;盛最多水的容器1. 介绍2. 问题描述3. 示例4. 解题思路5. 算法实现6. 复杂度分析7. 测试与验证测试用例设计测试结果分析 8. 总结9. 参考文献感谢阅读 力扣经典150题解析之二十八&#xff1a;盛最多水的容器 1. 介绍 在这篇文章中&…...

Rockchip Android13 Vold(二):Framework层

目录 前言 1、接收VolumeInfo状态 2、通知VolumeInfo状态变化 3、创建StorageVolume...

Oracle数据库故障类别及日常运维规划策略

一、故障类别 1、语句故障 单个数据库操作失败&#xff08;select、insert、update或delete&#xff09;&#xff0c;如&#xff1a; 在表中输入无效的数据&#xff0c;解决方法&#xff1a;可与用户合作来验证并更正数据&#xff1b;执行操作&#xff0c;但权限不足&#x…...

电商技术揭秘九:搜索引擎中的SEO数据分析与效果评估

相关系列文章 电商技术揭秘一&#xff1a;电商架构设计与核心技术 电商技术揭秘二&#xff1a;电商平台推荐系统的实现与优化 电商技术揭秘三&#xff1a;电商平台的支付与结算系统 电商技术揭秘四&#xff1a;电商平台的物流管理系统 电商技术揭秘五&#xff1a;电商平台的个性…...

多线程传参以及线程的优缺点

进程是资源分配的基本单位 线程是调度的基本单位 笼统来说&#xff0c;线程有以下优点&#xff1a; 创建一个新线程的代价要比创建一个新进程小得多 与进程之间的切换相比&#xff0c;线程之间的切换需要操作系统做的工作要少很多 线程占用的资源要比进程少很多 能充分利用多…...

keil创建单片机工程

一、创建工程 打开Keil uVision4&#xff0c;依次选择 Project—>New uVision4 Project&#xff0c;选择工程保存路径及填写工程名称&#xff0c;如下图 然后点“保存”。在Select a CPU Data Base File中选择"STC MCU Database"&#xff0c;点 "OK"&am…...

QT 串口助手 学习制作记录

QT 串口助手qt 学习制作记录 参考教程&#xff1a;​​​​​​QT初体验&#xff1a;手把手带你写一个自己的串口助手_qt设计串口助手的流程图-CSDN博客 Qt之串口编程&#xff08;添加QSerialPort模块&#xff09;_如何安装 qt串口模块教程-CSDN博客 串口调试助手&#xff1…...

Github 2024-04-13 Rust开源项目日报Top10

根据Github Trendings的统计,今日(2024-04-13统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10CUE项目1Go项目1Tauri: 构建小型、快速和安全的桌面应用程序 创建周期:1673 天开发语言:Rust协议类型:Apache License 2.0Star数量…...

大模型日报|今日必读的10篇大模型论文

大家好&#xff0c;今日必读的大模型论文来啦&#xff01; 1.谷歌推出新型 Transformer 架构&#xff1a;反馈注意力就是工作记忆 虽然 Transformer 给深度学习带来了革命性的变化&#xff0c;但二次注意复杂性阻碍了其处理无限长输入的能力。 谷歌研究团队提出了一种新型 T…...

深度学习 Lecture 8 决策树

一、决策树模型&#xff08;Decision Tree Model) 椭圆形代表决策节点&#xff08;decison nodes)&#xff0c;矩形节点代表叶节点&#xff08;leaf nodes)&#xff0c;方向上的值代表属性的值&#xff0c; 构建决策树的学习过程&#xff1a; 第一步&#xff1a;决定在根节点…...

打包 docker 容器镜像到另一台电脑

# 提交容器为镜像 <container_id> 容器id my_migration_image 镜像名称 docker commit <container_id> my_migration_image # 保存镜像为tar文件 docker save my_migration_image > my_migration_image.tar 在另一台电脑上导入上面的镜像&#xff0c;请…...

贪心算法--购买股票

给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能获得的 最大 利润 。 示例 1&a…...

在Mac主机上连接Linux虚拟机

前言 最近醉心于研究Linux&#xff0c;于是在PD上安装了一个Debian Linux虚拟机&#xff0c;用来练练手。但是每次在mac和Linux之间切换很是麻烦&#xff0c;有没有一种方法&#xff0c;可以在mac终端直接连接我的虚拟机&#xff0c;这样在mac终端上就可以直接操控我的Linux虚…...

前端如何单独做虚拟奖金池?

公司业务需求要做一个虚拟奖金池&#xff0c;具体是需求是&#xff0c;不需要后端数据支持&#xff0c;但是又需要不同用户看到的奖金池数据每次变动都是一致的&#xff0c;并且要在给定的最小最大值中变动。 一开始看需求&#xff0c;因为需要所有登录/未登录&#xff0c;不同…...

前端md5校验文件

前端获取文件的md5值&#xff0c;与文件一同传到后端&#xff0c;后端同样对md5值进行校验。如果相同&#xff0c;则文件未被损坏&#xff08;其实这种方式优点类似于tcp、ip的差错校验&#xff0c;好像token也是这种方式&#xff09; 项目准备 前端并不可能手写一个算法来实…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

大模型多显卡多服务器并行计算方法与实践指南

一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

Kafka主题运维全指南:从基础配置到故障处理

#作者&#xff1a;张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1&#xff1a;主题删除失败。常见错误2&#xff1a;__consumer_offsets占用太多的磁盘。 主题日常管理 …...

STM32标准库-ADC数模转换器

文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”&#xff1a;输入模块&#xff08;GPIO、温度、V_REFINT&#xff09;1.4.2 信号 “调度站”&#xff1a;多路开关1.4.3 信号 “加工厂”&#xff1a;ADC 转换器&#xff08;规则组 注入…...