C++的六大“天选之子“拷贝构造与与运算符重载

在这里插入图片描述

🎈个人主页:🎈 :✨✨✨初阶牛✨✨✨
🐻推荐专栏1: 🍔🍟🌯C语言初阶
🐻推荐专栏2: 🍔🍟🌯C语言进阶
🔑个人信条: 🌵知行合一
🍉本篇简介:>:讲解C++中有关类和对象的介绍,本篇是中篇的第结尾篇文章,讲解拷贝构造,运算符重载以及取地址重载符.
金句分享:
✨别在最好的年纪,辜负了最好的自己.✨

一、“拷贝构造函数”

拷贝构造函数

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

2.1 自动生成的"拷贝构造函数"

假设哦我们需要创建两个一模一样的对象AB.
在这里插入图片描述
那我们可以先创建一个对象A,再通过将A作为参数,传给B进行初始化,
即一个自定义类型实例化出的对象(B)用另一个该类型实例化出的对象(A)进行初始化.

class Date
{
public:Date(int year = 2020, int month = 1, int day = 1)//全缺省构造函数{_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date A(2023, 7, 20);A.Print();printf("\n");Date B(A);//会调用系统生成的拷贝构造B.Print();return 0;
}

运行结果:

2023-7-20

2023-7-20

其实拷贝构造函数就是构造函数的一种重载形式,他也是六大天选之子之一,没有显式定义时,编译器也会自动生成,但是只会完成"浅拷贝"(下面讲)…

2.2 自定义"拷贝构造函数"

#include <iostream>
using std::cin;
using std::cout;
using std::endl;class Date
{
public:Date(int year = 2020, int month = 1, int day = 1)//全缺省构造函数{_year = year;_month = month;_day = day;}Date(const Date& d)//拷贝构造函数{cout << "拷贝构造" << endl;_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(2023, 7, 20);d1.Print();printf("\n");Date d2(d1);d2.Print();return 0;
}

我们发现Date(const Date& d)这里使用了引用传参,如果直接传参会怎样呢?
在这里插入图片描述
为什么会报错呢?

void test(int a)
{}void test(Date d1)
{}int main()
{Date d1(2023, 7, 20);test(2);test(d1);return 0;
}

这段代码会调用Date 类的拷贝构造.
在这里插入图片描述
对于自定义类型作为参数时,必须调用该类型的拷贝构造函数.
所以可以回答上面的问题了.

在这里插入图片描述
所以拷贝构造函数传参时采用引用传参,这样就避免了传参时调用拷贝构造.

2.3 深浅拷贝?

前面在介绍编译器自动生成的"拷贝构造函数"时,提到了浅拷贝,那什么是浅拷贝呢?

浅拷贝:按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
在这里插入图片描述

深拷贝:
在这里插入图片描述

示例:
栈类中没有显式定义拷贝构造函数,编译器自动生成的拷贝构造是浅拷贝带来的问题.

#include <iostream>
using std::cin;
using std::cout;
using std::endl;typedef int DataType;
class Stack
{
public:Stack(int capacity=5)//全缺省构造函数{cout << "Stack" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data)//压栈操作{CheckCapacity();_array[_size] = data;_size++;}~Stack()//析构函数{cout << "~Stack"<< endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:void CheckCapacity(){if (_size == _capacity){int newcapacity = _capacity * 2;DataType* temp = (DataType*)realloc(_array, newcapacity *sizeof(DataType));if (temp == NULL){perror("realloc申请空间失败!!!");return;}_array = temp;_capacity = newcapacity;}}
private:DataType* _array;int _capacity;int _size;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);//这条语句会报错.return 0;
}

运行结果:
在这里插入图片描述
原因:
因为编译器默认生成的拷贝构造是浅拷贝,这里两个对象的_array也就指向了同一块内存空间,但是两个对象的声生命周期结束时,会调用各自的析构函数,这也就导致对同一块空间进行了释放操作.
解决方法:
显示定义一个拷贝构造函数.

	Stack(const Stack& S)//深拷贝{_array = (int*)malloc(sizeof(int) * S._capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}memcpy(S._array,_array,sizeof(int)*S._size);_capacity = S._capacity;_size = S._size;}

总结:

拷贝构造使用场景:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
  3. 当一个对象作为参数传递给函数时,拷贝构造函数会被调用来创建一个新的对象,该新对象与传递的对象具有相同的属性和属性值,但是它们在内存中是独立的。
  4. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝.

二、赋值运算符重载(“=”)

2.1 运算符重载的介绍

class Date//日期类
{
public:Date(int year = 2023, int month = 10, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
void test1()
{Date d1(2023, 7, 28);Date d2;if (d2 == d1){cout << "d1=d2";}if (d1 < d2){cout << "d1<d2";}
}

在这里插入图片描述

自定义类型是无法像内置类型一样比较大小和使用一些常规运算符的.
为什么呢?
在这里插入图片描述
因为自定义类型是用户自己定义的,编译器不知道该如何进行比较.那编译器太笨了吧,日期按 年-月-日依次比较不就行了?
个人理解:

  1. 格局打开,如果是别的类呢?比如:person是按名字还是按职位,还是按什么?你不告诉编译器如何比较,编译器也很无奈,不敢瞎搞的.
  2. 编译器咋知道你year是年,要是牛牛用nian来命名,他也能识别出来是年吗?

综上,自定义类型如何进行运算比较,只有用户自己知道,所以用户需要自己来设计规则.

在这里插入图片描述
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型.

函数名:关键字operator+需要重载的运算符符号。
operator+ 需要重载的运算符

注意事项:

  • 不能通过连接其他符号来创建新的操作符:
    示例:operator@

  • 重载操作符必须有一个类类型参数
    运算符重载是通过类的成员函数或全局函数来实现的,而这些函数必须具有特定的参数列表。
    对于成员函数的重载操作符,至少需要一个类类型参数,它表示操作符的左操作数。例如,对于二元操作符(如 +、-、* 等),成员函数的参数列表通常还包括一个非常量引用或常量引用,表示操作符的右操作数。

  • 用于内置类型的运算符,其含义不能改变:
    例如:内置的整型*不要实现为了/,害人是不对的.

  • 作为类成员函数重载时,其形参看起来比操作数数目少1一个,因为成员函数的第一个参数为隐藏的this .

  • 注意以下5个运算符不能重载。“.*” (点星) 、" :: " sizeof ? : .

在C++中,有一些操作符是不能被重载的,包括以下几种情况:

  1. ::(作用域解析操作符):作用域解析操作符用于指定命名空间、类或结构的作用域,并访问其成员。它不能被重载,因为它的含义在语言中已经固定不可更改。

  2. .*(指针到成员操作符)和 ->*(指向成员指针的操作符):这些操作符用于访问类的成员指针。它们存储了一个指向类成员的指针,并用于在运行时访问该成员。它们也不能被重载。

  3. sizeof(大小操作符):sizeof操作符用于获取一个对象或类型的大小(以字节为单位)。它是一个编译时的操作符,不能在运行时被重载。因为在编译时就已经确定了对象或类型的大小。

  4. ?:(条件操作符,即三目运算符):条件操作符是一个三元操作符,用于根据条件选择不同的表达式。它不能被重载,因为它的语法和含义已经在语言中定义好了。

  5. .C++中,点操作符(“.”)是用来访问对象的成员的,而它本身是不能被重载的。点操作符的行为在语言中是固定的,无法通过重载来改变。

2.2 赋值运算符重载:

(1)编译器自动生成的 “赋值运算符重载”

class Date//日期类
{
public:Date(int year = 2023, int month = 10, int day = 1){_year = year;_month = month;_day = day;}void print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};void test1()
{Date d1(2023, 7, 28);Date d2;d1.print();d2.print();cout << endl;d2 = d1;d1.print();d2.print();}
int main()
{test1();return 0;
}

在这里插入图片描述

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

赋值运算符如果不显式实现(自己定义),编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
在这里插入图片描述

那编译器会生成一个默认赋值运算符重载会做什么事情呢?

以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

当然对于日期类这种只需要浅拷贝的类来说,编译器默认生成就已经足够了,但是像stack类,同样引发深浅拷贝的问题.

三、最后的两个天选之子

哈哈哈,期待到最后的两个默认成员函数其实没什么要讲解的.

  1. 取地址操作符重载operator&()
  2. const取地址操作符重载operator&()const
    这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{ 
public :Date* operator&(){return this ;}const Date* operator&()const{return this ;}
private :int _year ; // 年int _month ; // 月int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可除非你想搞点特殊的,返回一个特定的特殊地址.

本篇内容到此讲解完了,后续介绍日期类的具体实现,方便大家更好的理解类和对象的知识,实战才能锻炼水平哦.
在这里插入图片描述

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

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

相关文章

MySql(干货)

写这篇博客的目的不是为了将介绍原理&#xff0c;而是为了Sql中的代码操作属实太多了&#xff0c;在这里进行一个汇总&#xff0c;方便查阅&#xff01;&#xff01;&#xff01; Sql分类 分类全称说明 DDL Data Definintion Language数据定义语言&#xff0c;用来定义数据库对…

Docker知识(详细笔记)

概览图 文章目录 概览图docker 知识速查1. 初识 Docker1.1 概念1.2 特点1.3 架构1.4 应用场景1.5 安装 Docker1.6 配置 Docker 镜像 2. Docker 命令2.1 Docker 进程相关命令2.2 Docker 镜像相关命令2.3 Docker 容器相关命令 3. Docker 容器的数据卷3.1 数据卷概念及作用3.1.1 概…

jvm里的内存溢出

目录 堆溢出 虚拟机栈和本地方法栈溢出&#xff08;栈溢出很少出现&#xff09; 方法区和运行时常量池溢出 本机内存直接溢出&#xff08;实际中很少出现、了解即可&#xff09; 堆溢出 堆溢出&#xff1a;最常见的是大list&#xff0c;list里面有很多元素 堆溢出该怎么解决…

修改IDEA的idea.vmoptions参数导致IDEA无法打开(ReservedCodeCacheSize)

事发原因 Maven导依赖的时候OOM&#xff0c;因此怀疑是内存太小&#xff0c;尝试修改idea.vmoptions的参数&#xff0c;然后发现IDEA重启后打不开了&#xff0c;卸载重装后也无法打开。。。 实际上如果导包爆出OOM的话应该调整下图参数&#xff0c;不过这都是后话了 解决思路…

52.Linux学习day02 基础命令详解2

目录 Linux常见的基础命令 1.cp 2.mv 3.rm 4.find 5.grep 6.管道 | 7.wc 8.su 9.关机与重启 10.runleve Linux常见的基础命令 1.cp 用于复制文件或目录 使用 cp 命令的基本格式如下&#xff1a; cp [选项] 源文件 目标文件或目录选项&#xff1a;cp 命令支持一些选…

Spring5新功能

文章目录 前言一、整合日志功能二、Nullable注解三、函数式风格编程四、JUnit5单元测试框架总结 前言 整合日志、Nullable注解、函数式风格编程、整合JUnit5、Webflux 一、整合日志功能 Spring5移除了Log4jConfigListener&#xff0c;官方建议使用Log4j2. 依赖&#xff1a; &…

k8s 滚动更新控制(一)

在传统的应用升级时&#xff0c;通常采用的方式是先停止服务&#xff0c;然后升级部署&#xff0c;最后将新应用启动。这个过程面临一个问题&#xff0c;就是在某段时间内&#xff0c;服务是不可用的&#xff0c;对于用户来说是非常不友好的。而kubernetes滚动更新&#xff0c;…

研发工程师玩转Kubernetes——PVC使用Label和storage选择PV

在《研发工程师玩转Kubernetes——local型PV和PVC绑定过程中的状态变化》和《研发工程师玩转Kubernetes——使用local型PV在不同Pod上共享数据》中&#xff0c;我们介绍了指定VPC的spec.volumeName为PV名称来绑定它们的方法。本文将介绍PVC在创建时&#xff0c;系统自动选择绑定…

什么是DNS欺骗及如何进行DNS欺骗

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、什么是 DNS 欺骗&#xff1f;二、开始1.配置2.Ettercap启动3.操作 总结 前言 我已经离开了一段时间&#xff0c;我现在回来了&#xff0c;我终于在做一个教…

[vscode]vscode运行cmake时候exe不执行而且前面多一些字符

遇到一个奇怪问题,你单独打开cmd去执行vscode编译过程序没问题&#xff0c;但是你在vscode确不会执行&#xff0c;这是因为vscode没有读取到电脑环境变量导致加载DLL失败&#xff0c;但是在vscode终端不会给你提示少DLL&#xff0c;需要你自己把DLL复制到exe目录即可解决问题。…

Vue.js 生命周期详解

Vue.js 是一款流行的 JavaScript 框架&#xff0c;它采用了组件化的开发方式&#xff0c;使得前端开发更加简单和高效。在 Vue.js 的开发过程中&#xff0c;了解和理解 Vue 的生命周期非常重要。本文将详细介绍 Vue 生命周期的四个阶段&#xff1a;创建、挂载、更新和销毁。 …

计算机视觉的应用9-视觉领域中的61个经典数据集【大集合】的应用与实战

大家好,我是微学AI,今天给大家介绍一下计算机视觉的应用9-视觉领域中的61个经典数据集【大集合】的应用与实战,我们都知道计算机视觉是一门研究如何使计算机能够理解和解释数字图像或视频的技术和方法。在计算机视觉领域中,数据集是非常重要的资源,它们可以用于训练和评估…

从源码Debug深入spring事件机制,基于观察者模式仿写spring事件监听骨架

文章目录 1.测试案例2.DEBUG源码分析3. 异步监听4.ApplicationListener子接口5. 注解支持6. 基于观察者模式高仿spring事件监听6.1 先定义自定义一个事件6.2 定义两个监听器6.3 定义一个持有所有监听器的对象&#xff0c;类似spring的SimpleApplicationEventMulticaster6.4 事件…

什么是响应式设计?列举几种实现响应式设计的方法。

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是响应式设计&#xff1f;⭐ 实现响应式设计的方法⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏…

Python实现图片文本支持中文,自定义字体

Python实现图片文本支持中文&#xff0c;自定义字体 # 支持中文 import matplotlib #用下载好的字体文件设置字体&#xff0c;从而正确显示中文 myfont matplotlib.font_manager.FontProperties(fnamer"./simsun.ttc") # 自定义的字体文件 plt.figure(figsize (1…

STM32F429IGT6使用CubeMX配置外部中断按键

1、硬件电路 2、设置RCC&#xff0c;选择高速外部时钟HSE,时钟设置为180MHz 3、配置GPIO引脚 4、NVIC配置 PC13相同 5、生成工程配置 6、部分代码 中断回调函数 /* USER CODE BEGIN 0 */void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {if(GPIO_Pin GPIO_PIN_0){HAL_GPIO…

化工行业案例 | 甄知科技助力万华化学重构IT服务价值,打造信息中心ERP!

随着科技的发展&#xff0c;新材料的应用领域与日俱增&#xff0c;近年来&#xff0c;全球化工新材料产业发展整体步入高技术引领、产品迭代速度快、产业规模和需求不断扩大的阶段。一体化协同与数字化转型策略是实现化工新材料生产原料自给、节能降耗、降低排放和物料成本的重…

QT之UDP通信

QT之UDP通信 UDP不分客户端口服务器,只需要使用一个类QUdpSocket QT += core gui networkgreaterThan(QT_MAJOR_VERSION, 4): QT += widgetsTARGET = udp TEMPLATE = app# The following define makes your compiler emit warnings if you use # any feature of Qt …

编译iOS系统可用的FFmpeg

在进行编译之前&#xff0c;需要做一些准备工作安装必备文件&#xff1a; 1 安装 gas-preprocessor FFmpeg-iOS-build-script 自动编译脚本需要使用到 gas-preprocessor . 执行 sudo git clone https://github.com/bigsen/gas-preprocessor.git /usr/local/bin/gas sudo c…

计算机网络-专业术语

计算机网络-专业术语 实体 实体:任何可发送或接收信息的硬件或软件进程 对等实体:收发双方相同层次中的实体 协议 控制两个对等实体进行逻辑通信的规则的集合 协议三要素 语法 定义所交换的信息的格式 是用户数据与控制信息的结构和格式 语义 定义收发双方所需要完成的操作…