当前位置: 首页 > 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;
}


http://www.mrgr.cn/news/29108.html

相关文章:

  • 将Docker中nginx静态资源目录映射到宿主机的某个目录及配置文件映射到宿主机
  • 大数据新视界 -- 大数据大厂之 Impala 存储格式转换:从原理到实践,开启大数据性能优化星际之旅(下)(20/30)
  • 云原生学习
  • MySQL数据库专栏(四)MySQL数据库链接操作C#篇
  • 计算机代码python代做matlab编程c语言编写接单matlabqt开发java
  • 通俗易懂的正则表达式
  • Linux:用户账号管理和组账号管理
  • stm32开发之串口空闲中断和环形数组的最简单的组合使用
  • 【ShuQiHere】算法分析:揭开效率与复杂度的神秘面纱
  • goctl安装失败
  • oracle 11g SYSAUX表空间清理
  • 408算法题leetcode--第七天
  • Java中的OOM与SOF:详解内存溢出与栈溢出
  • 计算机视觉中的图像ROI区域提取与应用
  • 25届校招IQCAT思维能力自适应测验智鼎测评指南:题库获取、刷题策略与真题解析!
  • Errorresponsefromdaemon:toomanyrequests:Youhavereachedyourpullratelimit.
  • 掌握文本分割:使用CharacterTextSplitter进行有效的文档处理
  • Java零基础-继承详解!
  • 网络流之最大流(dinic算法模板+模板题)
  • 2024重生之回溯数据结构与算法系列学习【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
  • C++第六节课 - 拷贝构造函数
  • C++核心编程和桌面应用开发 第四天(构造/析构函数)
  • 【python设计模式2】创建型模式1
  • (185)时序收敛--->(35)时序收敛三五
  • C++ 科目二 [dynamic_cast]
  • 企业开发时,会使用sqlalchedmy来构建数据库 结构吗? 还是说直接写SQL 语句比较多?