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

C++第七节课 运算符重载

一、运算符重载

并不是所有情况下都需要运算符重载,要看这个运算符对这个类是否有意义!

例如:日期减日期可以求得两个日期之间的天数;但是日期 + 日期没有意义!

#include<iostream>
using namespace std;
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)
{if (d1._year < d2._year){return true;}else if (d1._year == d2._year && d1._month < d2._month){return true;}else if (d1._year == d2._year && d1._month == d2._month && d1._day < d2._day){return true;}elsereturn false;
}
int main()
{Date d1(2023,11,20);Date d2(2023,10, 10);d1 < d2;   //第一种调用形式operator<(d1 , d2); // 两种调用方法都可以(第二种调用形式)return 0;
}

在上述例子中我们对<进行重载,使其可以比较两个对象的日期大小! 

d1<d2 和operator<(d1,d2)两者是等价的!编译器会将第一种调用形式转化为第二种调用形式;

从底层来看,两者执行的汇编指令是一样的!

但是这里有个问题,在类的外面不能访问private和protect!(上面是将private改为public)

因此,我们可以将函数写在类的内部,这样子方便调用!

但是,直接放入类中会报错:

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

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

使用成员函数进行运算符重载如下所示:

#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator<(const Date& x){if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}elsereturn false;}//private:int _year;int _month;int _day;
};int main()
{Date d1(2023,11,20);Date d2(2023,10, 10);d1 < d2;// operator<(d1 , d2); // 两种调用方法都可以d1.operator<(d2);   // 此时通过成员函数的方式调用!(或者直接比较)return 0;
}

返回值类型为bool; 

此时调用函数是采用成员函数的方法调用,且依然可以直接进行比较!(这里依然是两个参数,只是第一个this参数被隐藏了!)

接下来我们看两个操作之间的汇编代码,可以发现两者的操作是等价的!

传入的参数必须有一个自定义类型(因为我们不能对内置类型进行重载!)

二、赋值运算符重载

引入:

如果我们想把d2的值赋值给d1,此时我们就需要用到赋值运算符!(这个等号被称为赋值运算符)

  • 拷贝构造是:用一个对象初始化创建另一个对象;
  • 赋值运算符重载是已经存在的两个对象之间的拷贝!

如下所示:

接下来我们尝试写一下最简单版本的赋值重载运算符函数:

#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator<(const Date& x){if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}elsereturn false;}void operator=(const Date& x){_year = x._year;_month = x._month;_day = x._day;}//private:int _year;int _month;int _day;
};int main()
{Date d1(2023,11,20);Date d2(2023,10, 10);d1 < d2;// operator<(d1 , d2); // 两种调用方法都可以d1.operator<(d2);   // 此时通过成员函数的方式调用!(或者直接比较)// 将d2的值赋值给d1d1 = d2;d1.operator=(d2);cout << d1._year << endl;cout << d1._month << endl;cout << d1._day << endl;return 0;
}

函数的返回值类型为void! 

通过结果发现可以完成对象之间的赋值!(对于日期来说我们需要的就是浅拷贝!)

小插曲:

对C语言来说,支持这样的连续赋值:将0赋值给k(整个返回式的结果为k),k赋值给j(整个返回式的结果为j),j赋值给i(整个返回式的结果为i);

那么两个对象是否可以这样赋值呢?

其实不可以!

自定义类型是转换为对应的两个函数调用,但是这里调用的返回值是void!

正确的操作应该是让右边的表达式返回d4!再将d4的值返回给d5!

因此我们修改下赋值运算符重载函数:

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

例如d4 = d1,这里的返回值就是d4! 

其返回值应该是一个数据,且this指针可以在函数内调用,这也是this指针的一大应用!

但是这里的返回类型是对象,因此会调用拷贝构造函数!(每一次重载赋值都要调用一次拷贝构造函数,效率太低下!)

出了作用域,this指针不在,但是*this还存在!因此这里我们建议使用引用返回!(返回的是*this的别名)

因此,最终我们的赋值运算重载函数就从上面进化到了下面:

注意点:所有的指针类型都是内置类型,哪怕是stack* / queue*!

对于下面的代码:

d1 = d1;

此时对于上面的函数体内,this和d实际上都是d1的地址,因此此时赋值没有意义,我们可以将其做以下的修改:(如果相等直接返回任意一个的地址即可,不需要中间赋值,从而提高效率)

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

如果将if替换为下面的注释行,即比较两个对象是否相等,此时需要调用赋值运算符重载,但是我们实现函数就是为了完成赋值运算符重载!发生报错!

注意:

        用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

注意:

        内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。

 默认生成的赋值重载跟拷贝构造行为一样!

  1. 内置类型成员 -- 值拷贝/浅拷贝;
  2. 自定义类型成员会调用它的赋值重载!

能不能将赋值运算符重载写成一个全局函数?

不能!因为他是默认的成员函数(类中),如果写在外面会与类中的发生冲突! --> 默认成员函数不可以写在全局域中!但是可以类和声明分开写(类中声明,外面定义)!

三、时间类的功能实现

1、获取一个月有多少天的功能实现:

	int GeiMonthDay(int year, int month){int daysArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) && month == 2){return 29;}else{return daysArr[month];}}

函数的优化:

第一个优化: 

采用下面这种代码形式避免了每次都有先判断是不是闰年,判断闰年的代码较长,先判断闰年效率低下;

第二个优化:

因为该函数在之后需要被频繁调用,直接将开辟好的数组放到静态区有助于避免每次调用都要开辟栈帧,从而提高效率!

搞成内联函数的话也可以,但是编译器不一定会将其变为内联;

引用返回也可以,但是每次返回都是一个整型,只有四个字节,没有必要必须引用返回!且因为返回了整数常量,引用前还需要加上const!太过于麻烦!

一般内置类型传值传参不需要引用!

调整后的函数如下:

int Date::GetMonthDay(int year, int month)
{static int daysArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return daysArr[month];}
}

 2、日期 + 天数

我们给出一个初级的函数代码:

Date Date::operator+(int day)
{_day += day;while (_day > GetMonthDay(_year,_month)){_day -= GetMonthDay(_year, _month);++_month;while (_month == 13){++_year;_month = 1;}}return *this;
}

但是这个函数存在一些问题! 

对一个变量来说,例如:

i+100;

i的值没有发生改变,实际上我们如果传入一个时间,要计算当日之后的100天是几月几号,实际上计算的是+=!(返回的date是+过日期后的date,date的值发生改变)(返回的是一个对象,因此我们这里最好用引用!

 对于内置类型来说,连续的+=可是满足语法条件的!

接下来我们尝试使用+实现:(+不能改变自己)

尽管引用返回会调用拷贝构造函数,降低效率,但是tmp出作用域被销毁,因此必须使用传值返回!(实现+=的时候*this没有被销毁,因此可以返回引用!)

整个表达式的返回值为tmp!

实现了+=之后,再实现+可以通过复用! 

 同样实现了+之后,再实现+=可以通过复用! 

但是两种方法的对象的调用情况不一样:

这里的+=没有创建对象,运用的是引用;然后+创建了一次,返回了一次,共调用两次;

这种情况完成了4次的对象的创建,+中创建tmp再返回tmp(2次);

+=中调用+也创建了两次,一共4次! (调用拷贝构造函数!)(*this不用创建)

3、前置++和后置++的重载

日期类也支持++的操作!

且++是一个单运算符,只有一个操作数!

前置++和后置++的区别:

  • 前置++返回++后的对象;
  • 后置++返回++前的对象;

因此不能用一个函数运算符重载!

class默认的operator++就是前置++,实现代码如下:

Date Date::operator++()
{*this += 1;return *this;
}

两个重载的运算符能不能构成函数重载?

可以!依然是根据参数不同调用不同的函数,后置++就是通过函数重载实现的!

规定后置++的参数列表中+上一个int类型用来区分!

可以不加上形参,因为这个参数不是用来接受传递值的,仅仅是为了占位,只是用来区分前置++和后置++的,构成函数重载!

当执行++d1的时候,编译器会自动转化为d1.operator() ;

当执行d1++的时候,编译器会自动转化为d1.operator(0) ;

(用来区分)

为什么说前置的效率比后置好?

后置会调用拷贝构造创建tmp,在返回对象tmp时也会调用,而前置++不会调用;

因此前置的效率比后置的高! 

总的实现代码如下所示:

Date.h

#pragma once
#include<iostream>
using namespace std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1);void Print(){cout << _year << "_" << _month << "_" << _day << endl;}bool operator<(const Date& x);bool operator==(const Date& x);bool operator<=(const Date& x);bool operator>(const Date& x);bool operator>=(const Date& x);bool operator!=(const Date& x);// 日期 + 时间的返回值还是一个日期// 实现获取当前月份的天数int GetMonthDay(int year, int month);// 实现 日期 + 时间 返回日期Date& operator+=(int day);Date operator+(int day);Date operator++();  // 前置++Date operator++(int);  // 后置++private:int _year;int _month;int _day;
};

Date.cpp

#define _CRT_SECURE_NO_WARNINGS
#include"data.h"// 分开写拷贝构造函数
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
bool Date::operator<(const Date& x)
{if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;
}bool Date::operator==(const Date& x)
{return _year == x._year&& _month == x._month&& _day == x._day;
}//   d1<d2
//	 this = d1
//	 x = d2
bool Date::operator<=(const Date& x)
{return *this < x || *this == x;
}// 大于就是小于等于取反!
bool Date::operator>(const Date& x)
{return !(*this <= x);
}bool Date::operator>=(const Date& x)
{return !(*this < x);
}bool Date::operator!=(const Date& x)
{return !(*this == x);
}
int Date::GetMonthDay(int year, int month)
{static int daysArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)){return 29;}else{return daysArr[month];}
}
Date& Date::operator+=(int day)
{// 方法一_day += day;while (_day > GetMonthDay(_year,_month)){_day -= GetMonthDay(_year, _month);++_month;while (_month == 13){++_year;_month = 1;}}return *this;// 方法二// *this = *this + day;// return *this
}Date Date::operator+(int day)
{// 通过拷贝构造创建一个tmpDate tmp(*this);// 方法二:复用+=tmp += day;   // 方法一//tmp._day += day;//while (tmp._day > GetMonthDay(tmp._year, tmp._month))//{//	tmp._day -= GetMonthDay(tmp._year, tmp._month);//	++tmp._month;//	while (tmp._month == 13)//	{//		++tmp._year;//		tmp._month = 1;//	}//}return tmp;
}
Date Date::operator++()   // 前置++
{*this += 1;return *this;
}Date Date::operator++(int)  // 后置++
{Date tmp = Date(*this);*this + -1;return tmp;
}

main.cpp

#define _CRT_SECURE_NO_WARNINGS
// 运算符重载有一个自定义类型即可
// 不用全部类型都是自定义类型,例如:日期(自定义类西) + 天数(int)
#include"data.h"
void test1()
{Date d1(2023, 11, 20);d1 + 100;d1.Print();//Date d2(d1+100);Date d2 = d1 + 100;  // 上面两种调用方法都可以!d2.Print();d1 += 200;d1.Print();}
void test2()
{Date d1(2023, 11, 20);++d1;d1++;
}
int main()
{test1();return 0;
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Math Reference Notes: 微积分概述
  • kettle 数据库迁移 使用分页原理实现 数据库mysql
  • Oracle SQL injection(SQL注入)
  • 4款AI生成PPT工具推荐,提升工作效率
  • 安卓沉浸式状态栏遇到的问题
  • 【已解决】Chrome浏览器被2024年新版流氓软件劫持,总会自动打开hao.360.com和so.com主页
  • 面试常见题之java
  • 【D3.js in Action 3 精译_022】3.2 使用 D3 完成数据准备工作
  • 和笔记相关的页面:编辑笔记和展示笔记 以及相关的viewmodel
  • C++面试模拟01
  • 三维点云处理(C++)学习记录——PDAL
  • 【git】
  • Mysql基础——DML
  • mysql Field ‘ssl_cipher‘ doesn‘t have a default value的解决
  • OpenFeign:Spring Cloud中的声明式HTTP客户端
  • 230. Kth Smallest Element in a BST
  • android 一些 utils
  • Angular 4.x 动态创建组件
  • chrome扩展demo1-小时钟
  • CSS盒模型深入
  • es6
  • JavaScript HTML DOM
  • JavaScript-Array类型
  • JavaScript标准库系列——Math对象和Date对象(二)
  • java中具有继承关系的类及其对象初始化顺序
  • js继承的实现方法
  • vue数据传递--我有特殊的实现技巧
  • 订阅Forge Viewer所有的事件
  • 对JS继承的一点思考
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 前嗅ForeSpider教程:创建模板
  • 前嗅ForeSpider中数据浏览界面介绍
  • 日剧·日综资源集合(建议收藏)
  • 如何使用 JavaScript 解析 URL
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 学习笔记:对象,原型和继承(1)
  • 与 ConTeXt MkIV 官方文档的接驳
  • kubernetes资源对象--ingress
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • ​ 无限可能性的探索:Amazon Lightsail轻量应用服务器引领数字化时代创新发展
  • #define、const、typedef的差别
  • #传输# #传输数据判断#
  • ${ }的特别功能
  • (1)SpringCloud 整合Python
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (C++17) std算法之执行策略 execution
  • (Qt) 默认QtWidget应用包含什么?
  • (动手学习深度学习)第13章 计算机视觉---图像增广与微调
  • (经验分享)作为一名普通本科计算机专业学生,我大学四年到底走了多少弯路
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第3章 信息系统治理(一)
  • (四)模仿学习-完成后台管理页面查询
  • (一)认识微服务