C++类与对象(3)—拷贝构造函数运算符重载

目录

一、拷贝构造函数

1、定义

2、特征

3、内置与自定义类型 

4、const修饰参数

5、默认生成

浅拷贝

深拷贝

6、总结

二、运算符重载

1、定义 

2、判断是否相等

3、比较大小

4、赋值

5、总结


一、拷贝构造函数

1、定义

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存

在的类类型对象创建新对象时由编译器自动调用。

2、特征

  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 拷贝构造Date(const Date& d){d._year = _year;d._month = _month;d._day = _day;}
private:int _year;int _month;int _day;
};
int main()
{date d1(2023, 2, 3);date d2(d1);return 0;
}

如果不加&引用符号,编译器会报错。 

 使用拷贝构造也可以用这种方式:

Date d3 = d1;

3、内置与自定义类型 

  • 内置类型的拷贝和传参,编译器可以直接拷贝,
  • 自定义类型的拷贝和传参,需要调用拷贝构造。

为什么需要拷贝构造呢?


用栈实现队列的时候,可能一会对st1进行析构一会对st2进行析构,成员变量指针_a指向的空间不能析构两次,而且如果分别对st1和st2赋初值,后赋值的会覆盖先赋值的数据。所以就要求它们不能指向同一块空间,各自要有各自的空间,所以C++规定:自定义类型的拷贝和传参,需要调用拷贝构造,对于栈这种需要深拷贝的拷贝构造(后续学习),现阶段只需要知道需要拷贝构造即可。

自定义类型传参:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 拷贝构造// Date d2(d1);Date(Date& d){d._year = _year;d._month = _month;d._day = _day;}
private:int _year;int _month;int _day;
};// 传值传参
void Func1(Date d)
{}
// 传引用传参
void Func2(Date& d)
{}
// 传指针
void Func3(Date* d)
{}
int main()
{Date d1(2023, 2, 3);Func1(d1);//Func2(d1);//Func3(&d1);return 0;
}

使用Func1传值传参 :

在Func1(d1)处按F11会直接跳转到拷贝构造。 

拷贝构造结束会进行Func1函数。 

如果用Func2引用传参,就不需要拷贝构造了。 

直接跳转Func2函数。 

此外还可以使用以前C语言中常用的指针传参,但这种方法有点啰嗦。

Func3(&d1);

4、const修饰参数

拷贝构造一般会加const,如果不加,比如下面的情况,赋值方向反了,会导致原数据变成随机值。

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

加上const可以避免拷贝方向错误时,原数据被修改,所以一般习惯参数前加上const。

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

此外,如果被拷贝的变量是被const修饰,如果拷贝构造的参数不被const修饰,会造成引用的权限扩大,所以一定要用const修饰参数。

5、默认生成

浅拷贝

若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按

字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

class Date
{
public:Date(int year = 1, 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 d1(2023, 11, 20);d1.Print();Date d2(d1);d2.Print();return 0;
}

通过输出结果我们可以发现,没写拷贝函数,编译器会自动对内置类型拷贝。

编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?

当然像日期类这样的类是没必要的。

如果是自定义类型呢?程序会怎么处理?

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){cout << "Stack(size_t capacity = 10)" << endl;_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");exit(-1);}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){cout << "~Stack()" << endl;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;
}

 输出后程序会报错:

如果按照内置类型的方式对自定义类型进行拷贝, 两个Stack类的变量st1和st2的成员变量*array都指向同一块空间,这样会导致两个问题:

  1. 插入和删除数据会互相影响,比如st1先插入三个数据,st1的size为3,这时st2要插入数据,但st2的size为0,如果插入数据则会造成st1插入的数据被覆盖;同时,在析构st1时,对st1的空间进行释放,由于st1和st2的成员变量*array都指向同一块空间,这时st2再插入数据会造成对野指针的访问。
  2. 程序销毁*_array的空间时,会析构两次,程序会崩溃。

深拷贝

这时只有深拷贝才能解决自定义类型的拷贝构造。

	Stack(const Stack& st){_array = (DataType*)malloc(sizeof(DataType) * st._capacity);if (_array == nullptr){perror("malloc fail");exit(-1);}memcpy(_array, st._array, sizeof(DataType) * st._size);_size = st._size;_capacity = st._capacity;}

st2成功实现了拷贝构造,st2与st1的地址不同,它们不在指向同一空间了。

什么时候需要自己实现拷贝构造? 

  • 当自己实现了析构函数释放空间,就需要实现拷贝构造。
class MyQueue
{
public:// 默认生成构造// 默认生成析构// 默认生成拷贝构造private:Stack _pushST;Stack _popST;int _size = 0;//缺省值处理
};
  •  两个Stack类型的成员变量初始化调用默认生成构造,销毁调用它的析构函数,拷贝使用它的拷贝构造。
  • 整型变量_size初始化通过缺省值处理,内置类型出磊之后会自动销毁,不需要析构处理,可以使用默认生成拷贝构造。

6、总结

拷贝构造函数典型调用场景:

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

默认生成拷贝构造:

  • 内置类型完成浅拷贝/值拷贝(按字节一个一个拷贝)
  • 自定义类型去调用这个成员的拷贝构造。

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

尽量使用引用。

二、运算符重载

1、定义 

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

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

注意:

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

我们通过代码一点一点理解:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};
//运算符重载
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
int main()
{Date d1(2023, 1, 1);Date d2(2023, 1, 1);//下面两种使用方式均可operator==(d1, d2);d1 == d2;//编译器会转换成去调用operator==(d1,d2);return 0;
}

如果要输出函数返回值,方式如下:

cout << (d1 == d2) << endl;//必须加括号,保证优先级
cout << operator==(d1, d2) << endl;

这里会发现,如果运算符重载成全局的就需要成员变量是公有的,否则会报错。

那么问题来了,封装性如何保证?

我们可以选择后面学习的友元处理,现阶段直接把运算符重载函数重载成成员函数即可。

2、判断是否相等

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& d)
// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023, 1, 1);Date d2(2023, 1, 1);
//operator内置只能使用这种形式(d1 == d2,cout << (d1 == d2) << endl;return 0;
}

3、比较大小

	bool operator<(const Date& d){if (_year < d._year){return true;}else if (_year == d._year && _month < d._month){return true;}else if (_year == d._year && _month == d._month && _day < d._day){return true;}else{return false;}}

还可以写成这种简便形式,但上述较长的方式更直观。

        return _year < d._year|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day);

判断小于等于我们可以借助隐藏的this指针,使用运算符重载嵌套的方式,在判断小于等于中分别调用判断小于和判断等于的运算符重载。

// d1 <= d2
bool operator<=(const Date& d)
{return *this < d || *this == d;
}

同理,判断大于、大于等于和不等于,我们也对上面的运算符重载进行复用。 

// d1 > d2
bool operator>(const Date& d)
{return !(*this <= d);
}bool operator>=(const Date& d)
{return !(*this < d);
}bool operator!=(const Date& d)
{return !(*this == d);
}

 4、赋值

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

 operator=就是我们的赋值运算符重载,如果我们不使用引用传值,那自定义类型会调用拷贝构造,我们使用引用传值,就避免了这些拷贝操作。

 

如果三个数赋值呢?

d3 = d2 = d1;

 根据赋值操作符的右结合性,d1赋值给d2需要一个返回值才能对d3赋值。

  返回值使用引用返回,避免传值返回过程中,创建临时变量和不必要的拷贝。

 

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

如果自己给自己赋值呢?那就可以不进行赋值操作了,我们添加一个判断即可。 

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

上面就是赋值运算符的完整版了。

5、总结

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
  • 注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值,所以日期类就不需要写运算符重载函数,而类似用栈实现队列则需要。
  • 注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。

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

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

相关文章

EL-input添加双击或者单击事件

#El-lement UI # 这个框架确实给我们带来了很多好处&#xff0c;最近一直忙于项目&#xff0c;没时间来写文章&#xff0c;最近有个问题困扰我很久&#xff0c;最终却很简单的解决了&#xff0c;记下来希望能帮助更多的人。 大家都知道el-input是无法直接添加click或者dblcli…

day61

今日内容概要 前后端数据传输的编码格式 Ajax提交json格式的数据 Ajax提交文件数据 Ajax实现弹窗的二次确认 批量增加数据 分页的原理及推导 分页类的使用 cookie和session的介绍 Django操作cookie、session 前后端数据传输的编码格式 我们只研究post请求方式的编码格…

axios的封装之axios是基于什么封装的?

axios的封装_axios是基于什么封装的 axios是基于JavaScript的XMLHttpRequest 和 Promise 对象进行封装的使用axios发送GET请求的示例axios 拦截器 axios的封装_axios是基于什么封装的 axios是基于JavaScript的XMLHttpRequest 和 Promise 对象进行封装的 在浏览器中&#xff…

利用 React 和 Bootstrap 进行强大的前端开发

文章目录 介绍React 和 Bootstrap设置环境使用 Bootstrap 创建 React 组件React-Bootstrap 组件结论 介绍 创建响应式、交互式和外观引人入胜的 Web 界面是现代前端开发人员的基本技能。幸运的是&#xff0c;借助 React 和 Bootstrap 等工具的出现&#xff0c;制作这些 UI 变得…

Tomcat 基线安全加固操作

目录 账号管理、认证授权 日志配置 通信协议 设备其他安全要求 账号管理、认证授权 ELK-tomcat-01-01-01 编号 ELK-Tomcat-01-01-01 名称 为不同的管理员分配不同的账号 实施目的 应按照用户分配账号&#xff0c;避免不同用户间共享账号,提高安全性。 问题影响 …

conda创建pytorch环境报错

昨天训练数据的时候&#xff0c;发现Anaconda占用C盘达到了20G&#xff08;暑假在cmd状态下安装的&#xff0c;默认下载到了C盘&#xff09;&#xff0c;心道再创建几个环境&#xff0c;C盘就要爆红了&#xff0c;于是重装Anaconda到了D盘&#xff0c;不过之后的初始化并不顺利…

Jtti:windows中apache怎么实现负载均衡

Jtti&#xff1a;windows中apache怎么实现负载均衡 在Windows环境下&#xff0c;你可以使用Apache HTTP Server搭建负载均衡集群。Apache提供了一个模块叫做mod_proxy&#xff0c;它可以用来实现反向代理和负载均衡。以下是一个简单的步骤来配置Apache负载均衡&#xff1a; 步骤…

Codesys数据类型(2.7):扩展数据类型之 别名 详解

Codesys代码代写&#xff0c;程序开发&#xff0c;软件定制&#xff0c;bug修改&#xff0c;问题咨询&#xff1a; T宝搜索店铺【林磊教育】 定义及声明 别名的目的是声明出 基本数据类型&#xff0c;结构体&#xff0c;枚举、共用体(UNION)、功能块、指针备用名字&#xff0c…

django及DRF流程源码分析

Django执行流程: 1.nginx作为接入层,通过反向代理&#xff0c;监听80端口获取请求连接 2.将请求交给wsgi server 3.wsgi server调用django的wsgi.py 处理请求 4.WSGIHandler的__call__函数就是整个逻辑处理流程 5.WSGIHandler __init__中的加载中间件&#xff0c;对request对象…

【ARM AMBA AXI 入门 16 - AXI 写响应通道 BVALID | BREADY | BRESP 详细介绍】

请阅读【ARM AMBA AXI 总线 文章专栏导读】 文章目录 AXI 写响应通道BVALIDBREADYBRESP举例BRESP[2:0] 编码AXI 写响应通道 在 ARM AMBA AXI 协议中,写响应通道包括以下三个信号,用来完成写事务的确认和状态传递: BVALID 这是一个从设备(Slave)发出的信号,表明与当前…

生态系统NPP及碳源、碳汇模拟实践技术应用

由于全球变暖、大气中温室气体浓度逐年增加等问题的出现&#xff0c;“双碳”行动特别是碳中和已经在世界范围形成广泛影响。碳中和可以从碳排放&#xff08;碳源&#xff09;和碳固定&#xff08;碳汇&#xff09;这两个侧面来理解。陆地生态系统在全球碳循环过程中有着重要作…

【HarmonyOS】低代码平台组件拖拽使用技巧之常用基础组件(上)

【关键字】 HarmonyOS、低代码平台、组件拖拽、常用基础组件、基础容器 1、写在前面 之前是花了一些时间介绍了在低代码平台中滚动容器、网格布局、页签容器、列表这几种容器的拖拽技巧及使用方法&#xff0c;今天我会继续来介绍咱们在应用开发中可能会经常用到的一些基础容器…

Flutter dio Http请求之Cookie管理

在应用开发过程中&#xff0c;我们进行Http通讯时会使用Cookie进行验证&#xff0c;今天我们就着重讲解Flutter 网络请求插件dio的cookie使用。 首先&#xff0c;我们要进行插件引用 # HTTP 请求 dio: ^5.1.1 cookie_jar: ^4.0.8 dio_cookie_manager: ^3.0.0# 获取沙盒路径 p…

CSS 属性计算过程

CSS 属性计算过程 首先&#xff0c;不知道你有没有考虑过这样的一个问题&#xff0c;假设在 HTML 中有这么一段代码&#xff1a; <body><h1>这是一个h1标题</h1> </body>上面的代码也非常简单&#xff0c;就是在 body 中有一个 h1 标题而已&#xff…

CSDN每日一练 |『多边形的面积』『硬币的面值』『开学趣闻之美食诱惑』2023-11-21

CSDN每日一练 |『多边形的面积』『硬币的面值』『开学趣闻之美食诱惑』2023-11-21 一、题目名称:多边形的面积二、题目名称:硬币的面值三、题目名称:开学趣闻之美食诱惑一、题目名称:多边形的面积 时间限制:1000ms内存限制:256M 题目描述: 给出一个简单多边形(没有缺口)…

Docker快速安装Mariadb11.1

MariaDB数据库管理系统是MySQL的一个分支&#xff0c;主要由开源社区在维护&#xff0c;采用GPL授权许可 MariaDB的目的是完全兼容MySQL&#xff0c;包括API和命令行&#xff0c;使之能轻松成为MySQL的代替品。在存储引擎方面&#xff0c;使用XtraDB来代替MySQL的InnoDB。 Mari…

AIX 系统基线安全加固操作

目录 账号管理 ELK-AIX-01-01-01 口令 ELK-AIX-01-02-01 授权 ELK-AIX-01-03-01 通信协议 IP协议安全 ELK-AIX-03-01-01 ELK-AIX-03-02-01 路由协议安全 补丁管理 ELK-AIX-04-01-01 服务进程和启动 ELK-AIX-05-01-01 设备其他安全要求 登陆超时策略 ELK-AIX-06-01-01 …

Clickhouse初认识

技术主题-clickhouse 一什么是clickHouse 1&#xff09;本质上就是一款数据库管理系统&#xff0c;能提供海量数据的存储和检索 2&#xff09;基于列存储&#xff0c;数据是按照列进行存储的&#xff08;数据格式一样&#xff0c;方便进行压缩&#xff09; 3&#xff09;具备…

基于Cortex®-M4F的TM4C123GH6NMRT7R 32位MCU,LM74900QRGERQ1、LM74930QRGERQ1汽车类理想二极管

一、TM4C123GH6NMRT7R IC MCU 32BIT 256KB FLASH 157BGA Tiva™C系列微控制器为设计人员提供了基于ARMCortex™-M的高性能架构&#xff0c;该架构具有广泛的集成功能以及强大的软件和开发工具生态系统。以性能和灵活性为目标&#xff0c;Tiva™C系列架构提供了一个具有FPU的80…

JS中Cookie的基本使用

JavaScript 创建Cookie&#xff1a; JavaScript 读取 Cookie &#xff1a; JavaScript 修改 Cookie&#xff1a; JavaScript 删除 Cookie &#xff1a; JavaScript Cookie&#xff08;JavaScript 的 Cookie&#xff09;是一种在Web浏览器中存储和检索用户信息的机制。它允许…