【C++】类和对象(四)拷贝构造、赋值运算符重载

文章目录

  • 四、拷贝构造函数
    • 干嘛的?
    • 写拷贝构造函数的注意事项
      • 正确写法
    • 不显示定义拷贝构造函数的情况
      • 浅拷贝
        • :one:示例:内置类型
        • :two:示例:自定义类型
        • 一个提问
      • 深拷贝
  • 五、赋值运算符重载
    • 运算符重载
      • 函数原型
      • 注意
      • 调用时的两种书写方式
      • 完整实现代码
    • 赋值运算符重载
      • 干嘛的?
      • 连续赋值
      • 总结赋值运算符重载格式
      • 默认生成的复制重载函数的行为
      • 默认生成的函数行为总结
      • 赋值运算符是否可以重载为全局函数

书接上回: 【C++】类和对象(三)构造与析构

四、拷贝构造函数

干嘛的?

拷贝构造函数:用同类型的其他对象 构造一个(初始化 )新的对象
代码演示:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date d2(d1);Date(Date& d){_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 1, 28);Date d2(d1);d1.Print();d2.Print();return 0;
}

写拷贝构造函数的注意事项

注意:拷贝构造函数 参数位置必须要传引用

为什么参数位置必须要传引用?
传值做拷贝构造函数的参数 🆚 传引用做拷贝构造函数的参数

C++规定 调用拷贝构造函数时,
1️⃣自定义类型本身 作为实参传递过去(传值传参),都会先调用拷贝构造。
🌰例如:
在这里插入图片描述
如果传值传参 会发生无穷递归的问题:
在这里插入图片描述
2️⃣自定义类型的引用 作为实参传递过去(传引用传参),就不会先调用拷贝构造。

除此之外,为了 防止写反方向的类似问题,我们一般给 引用前面加const修饰
例如:可能出现赋值方向写反的问题

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date d2(d1);Date(Date& d){//赋值方向写反的问题d._year = this->_year;d._month = this->_month;d._day = this->_day;//_year = d._year;//_month = d._month;//_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 1, 28);Date d2(d1);d1.Print();d2.Print();return 0;
}

写反方向的情况如下:
在这里插入图片描述

正确写法

加入const修饰 保护要赋值给别人的对象

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

不显示定义拷贝构造函数的情况

浅拷贝

程序员不显示定义拷贝构造函数 ,则编译器会自动生成拷贝构造函数。并且

1️⃣ 对内置类型的成员变量进行值拷贝(浅拷贝)。
2️⃣对自定义类型的成员变量 调用它的拷贝构造

1️⃣示例:内置类型

在这里插入图片描述

2️⃣示例:自定义类型
class Time
{
public:~Time(){cout << "~Time()" << endl;}// 注意:拷贝构造函数 也属于构造函数 编译器就不会自动生成 构造函数// 但是下面一句代码 可以强制编译器生成默认构造Time() = default;Time(const Time& t){cout << "Time(const Time& t)" << endl;_hour = t._hour;_minute = t._minute;_second = t._second;}private:int _hour;int _minute;int _second;
};
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date d2(d1);/*Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:// 内置类型int _year;int _month;int _day;// 自定义类型Time _t;
};int main()
{Date d1(2024, 1, 28);Date d2(d1);d1.Print();d2.Print();return 0;
}

代码演示:对自定义类型的成员变量 调用它的拷贝构造
在这里插入图片描述

一个提问

❓对于内置类型、自定义类型的成员变量,即使程序员不提供拷贝构造函数,编译器都会自动进行拷贝,那么拷贝构造函数是不是就可以不用我们写了呢?

答案当然不是的。在有些情况,编译器默认生成的拷贝构造会出现问题,我们可以看看如下的问题情况。
问题代码:

#include<iostream>
using namespace std;
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 st1;Stack st2(st1);return 0;
}

在这里插入图片描述
问题描述:经过调试,我们发现 _array的值是一个地址也被拷贝过来 ,这意味着两个对象指向同一块空间。
在这里插入图片描述

出了函数作用域,会对他们析构,把st2对象的 _array空间释放。但st2置空并不影响st1,st1依然指向那块空间,导致st1成为野指针,析构st1时,_array空间再次被释放,相当于同一块空间被释放了两次。

所以,对于动态开辟的内存空间 都要使用深拷贝。深拷贝就是,为要拷贝st1的st2再开辟一块同样大小的新空间。

深拷贝

对于动态开辟的内存空间,必须程序员自己实现深拷贝!
代码如下:

#include<iostream>
using namespace std;
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;}Stack(const Stack& s){DataType* tmp = malloc(sizeof(s._capacity * sizeof(DataType)));if(tmp == nullptr){perror("malloc fail");exit(-1);}memcpy(tmp , a._array,sizeof(DataType)*s._size);_array = tmp;_size = s._size;_capacity = s._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 st1;Stack st2(st1);return 0;
}

在这里插入图片描述
可以看到经过深度拷贝的两个指针分别开辟了两块不同的空间。

五、赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型、函数名字、参数列表。其返回值类型、参数列表与普通的函数类似。

函数原型

函数名:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

🌰

bool operator==(const Date& y)
bool operator<(const Date& y)

注意

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .* (点星运算符) :: (域作用限定符) sizeof (计算变量大小的运算符) ?: (三目运算符) . (点操作符) 注意以上5个运算符不能重载。这个经常在笔试选择题中出 现。

注意:区分 运算符重载 跟 函数重载,二者没有关系。

运算符重载:让自定义类型的对象可以使用运算符。通过函数定义了该运算符的行为

函数重载:允许函数名相同 参数不同的函数存在,通过函数名修饰规则可以找到对应的函数

代码示例🌰
这里展示两个​运算符重载函数 他们是用来 比较自定义对象 日期年月日的大小

调用时的两种书写方式

d1.operator==(d2)
d1 == d2

完整实现代码

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this// operator运算符 做函数名bool operator==(const Date& y){return _year == y._year&& _month == y._month&& _day == y._day;}// operator运算符 做函数名bool operator<(const Date& y){//如果 年小就小if (_year < y._year){return true;}//如果年相等else if (_year == y._year){//如果月小就小if (_month < y._month){return true;}else if (_month == y._month){return _day < y._day;}}return false;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 1, 28);Date d2(2024, 2, 27);//通过对象调用成员函数的方式 调用运算符重载函数 比较日期对象d1和d2的大小cout << d1.operator==(d2) << endl;cout << d1.operator<(d2) << endl;//另一种调用方式 加括号的原因:流插入运算符的优先级高于等于号cout << (d1 == d2) << endl; // cout << (d1.operator==(d2)) << endl;cout << (d1 < d2) << endl;  // cout << (d1.operator==(d2)) << endl;bool ret1 = d1 < d2;bool ret2 = d1.operator<(d2);int i = 0;int j = 1;bool ret3 =i<j;return 0;
}

在这里插入图片描述
通过反汇编窗口,我们可以看到

对于自定义类型的运算符重载函数的两种调用方式底层的汇编代码都是一样的。
而对于内置类型的运算符比较,是直接有cmp指令支持的,不用程序员自己规定大小比较方式。

赋值运算符重载

干嘛的?

赋值运算符重载:对已经存在的同类型对象,一个拷贝赋值给另一个,就用到了赋值运算符重载

代码示例

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//d2 = d1;Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 1, 28);Date d2(1990, 2, 1);Date d3(d2);  // 拷贝构造,同类型一个存在的对象进行初始化要创建的对象d2 = d1;// 已经存在的对象,一个拷贝赋值给另一个d2.Print();d3.Print();return 0;
}

栗子结果
在这里插入图片描述

连续赋值

int  i = 0, j = 1;
i = j = 10;

注意:

  1. 对于内置类型,10先赋值给j ,表达式的返回值为j ,j再作为下一次赋值的右操作数 以此支持连续赋值。
    同理,对于自定义类型为了支持连续赋值,我们需要拿到表达式的结果,所以在写赋值重载函数时,我们要返回被赋值好的对象 *this。
  2. 函数传值返回会调用拷贝构造函数,为了避免浪费,赋值运算符函数的返回值使用引用返回
  3. 为了防止自己给自己赋值,我们需要加一个判断。

总结赋值运算符重载格式

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

默认生成的复制重载函数的行为

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

默认生成的函数行为总结

在这里插入图片描述

赋值运算符是否可以重载为全局函数

❓运算符重载可以在全局重载,那赋值运算符重载可以在全局重载嘛?

注意: 赋值运算符只能重载成类的成员函数不能重载成全局函数

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/856753.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SAFEnet加密机的加密算法和技术

SAFEnet加密机是一款功能强大、安全可靠的加密设备&#xff0c;它在网络安全领域发挥着不可替代的作用。下面将从特点、功能、应用及优势等方面对SAFEnet加密机进行详细介绍。 一、特点 先进的加密算法和技术&#xff1a;SAFEnet加密机采用了最先进的加密算法和技术&#xff0c…

12 物理层解析

物理层解析 一、物理层功能 ​ 物理层主要功能 功能一&#xff1a;为数据端设备提供传送数据的通路 功能二&#xff1a;传输数据 二、物理层关心的问题 &#xff08;一&#xff09;信号 ​ 信息是人对现实世界事物存在方式或运动状态的某种认识 ​ 数据是用于描述事物的…

网络安全:什么是SQL注入

文章目录 网络安全&#xff1a;什么是SQL注入引言SQL注入简介工作原理示例代码 攻击类型为什么SQL注入危险结语 网络安全&#xff1a;什么是SQL注入 引言 在数字化时代&#xff0c;数据安全成为了企业和个人最关心的问题之一。SQL注入&#xff08;SQL Injection&#xff09;是…

【面试干货】Java的基础类型和字节大小

【面试干货】Java的基础类型和字节大小 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java编程语言中&#xff0c;有八种基本数据类型&#xff0c;它们分别是&#xff1a;布尔型&#xff08;boolean&#xff09;、字节型&#xff08;byt…

厚膜电阻电路丝网印刷

厚膜丝网印刷 该技术用于需要长寿命、热耐久性、机械强度、导热性、高密度电气互连、低介电损耗等的苛刻应用 特征&#xff1a; 陶瓷标准工艺从前到后的通孔连接 正面和背面的丝网印刷电阻器是标准工艺 金导体可以用金线和/或氧化铝线进行线键合 可焊接金属化&#xff0c;…

oracle 数据库导入dmp文件

荆轲刺秦王 从线上正式环境导出的 dmp 文件&#xff0c;导入到本地 oracle 数据库。 1. 创建用户: CREATE USER hf_chip IDENTIFIED BY hf_chip; 2. 授予 CONNECT 和 RESOURCE 基本权限给新用户。 GRANT CONNECT, RESOURCE TO hf_chip; 3. 创建表空间 CREATE TABLESPACE…

【C++高阶】探索STL的瑰宝 map与set:高效数据结构的奥秘与技巧

&#x1f4dd;个人主页&#x1f339;&#xff1a;Eternity._ ⏩收录专栏⏪&#xff1a;C “ 登神长阶 ” &#x1f921;往期回顾&#x1f921;&#xff1a;初步了解 二叉搜索树 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; ❀map与set &#x1f4d2;1.…

开发uniapp插件包aar文件,使uniapp可以调用jar包

背景 使用 uniapp 开发应用时&#xff0c;很多时候都需要调用第三方的sdk&#xff0c;一般以 jar 为主。为了应对这个问题&#xff0c;官方提供了插件方案&#xff0c;可以将第三方 jar 包进行封装为 aar 包后&#xff0c;再集成到 uniapp 中使用。 一、环境安装工具 1、jdk…

C语言练习02-数组

一、求最值 已知数组元素为{33,5,22,44,55}&#xff0c;找出数组中的最大值并打印在控制台 #include<stdio.h> #include<math.h>int main() {int arr[] {33,5,22,44,55};int max arr[0];int len sizeof(arr) / sizeof(int); //注意&#xff1a;sizeof(arr)计算…

【无线传感网】分簇路由算法介绍

目录 1、LEACH路由算法 2、PEGASIS 算法 3、TEEN 算法 5、APTEEN 5、LEACH-C 算法 无线传感网中的路由协议就是寻找一条路径让网络中节点沿着这条路径将数据信息传输出去。路由协议的两大关键要点就是路径的优化和数据的分组,在传统计算机网络中,是将网络的拓扑…

Linux系统安装Dify结合内网穿透实现远程访问本地LLM开发平台

文章目录 前言1. Docker部署Dify2. 本地访问Dify3. Ubuntu安装Cpolar4. 配置公网地址5. 远程访问6. 固定Cpolar公网地址7. 固定地址访问 前言 本文主要介绍如何在Linux Ubuntu系统使用Docker快速部署大语言模型应用开发平台Dify,并结合cpolar内网穿透工具实现公网环境远程访问…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] API集群访问频次统计(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

Python酷库之旅-第三方库openpyxl(01)

目录 一、 openpyxl库的由来 1、背景 2、起源 3、发展 4、特点 4-1、支持.xlsx格式 4-2、读写Excel文件 4-3、操作单元格 4-4、创建和修改工作表 4-5、样式设置 4-6、图表和公式 4-7、支持数字和日期格式 二、openpyxl库的优缺点 1、优点 1-1、支持现代Excel格式…

前端基础操作1——利用nvm任意切换(管理)node版本

在实际前端项目开发过程中&#xff0c;同时开发多个项目或者切换新项目时&#xff0c;因为node版本问题造成项目无法运行的问题比比皆是&#xff0c;这时候通过nvm管理切换不同版本的node&#xff0c;就能很快进入开发模式&#xff0c;避免因为环境问题浪费大量精力&#xff0c…

安卓系统安装linux搭建随手服务器termux平替软件介绍

引言 旧手机丢可惜&#xff0c;可以用ZeroTermux&#xff08;一款代替termux&#xff09;的超级终端&#xff0c;来模拟Linux&#xff08;甚至你可以模拟Win&#xff0c;只要性能够用&#xff09; ps&#xff1a;此软件只是termux的增强版&#xff0c;相当于增加右边菜单&…

校园任务平台系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;论坛管理&#xff0c;任务咨询管理&#xff0c;用户管理&#xff0c;基础数据管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;任务资讯公告&#…

AGI的多模态融合

在人工智能的宏伟蓝图中&#xff0c;人工通用智能&#xff08;AGI&#xff09;代表着一个集大成者&#xff0c;一个能够理解、学习、适应并执行任何智能任务的系统。随着我们对AGI的探索愈发深入&#xff0c;尤其是在视觉、语言和其他模态的融合上&#xff0c;关于AGI的讨论愈发…

图像分割(三)-RGB转HSV后图像分割方法

常用彩色模型有RGB和HSV模型&#xff0c;有时候在RGB颜色空间进行背景分割比较困难的问题&#xff0c;转换为HSV模型然后对色调和饱和度图像进行处理会得到比较理想的处理结果,下面通过一个实例讲解该方法的MATLAB实现&#xff0c;该方法对其他图像检测也具有一定的参考价值。 …

React路由笔记(函数组件,自用)

配置 npm i react-router-dom基本使用 目录结构 在src中创建page文件夹放置各页面组件&#xff0c;router中放置路由 1、router中配置路由 在/router/index.js中&#xff0c;使用createBrowserRouter配置路由。 import { createBrowserRouter } from "react-router…

Pure Nature 2 : Mountains

3D样式化的自然环境资源 所有东西都配有预制件,随时可以放在现场。 包含URP版本! 此包包含: 植被 -云杉 -松树 -冷杉 -布什 -蘑菇 -草地 草,树枝,蕨类植物,各种… -鲜花 浆果,胡萝卜,雏菊,浓香,薰衣草,羽扇豆,各种… -蘑菇 岩石 -悬崖 -巨石 -岩石和卵石 -山脉 材料…