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

news/2024/9/19 19:04:27 标签: 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;
	}
	else
		return 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;
		}
		else
			return 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;
		}
		else
			return 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的值赋值给d1
	d1 = 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)
{
	// 通过拷贝构造创建一个tmp
	Date 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.niftyadmin.cn/n/5666003.html

相关文章

光伏发电量估算有多重要?如何分析?

光伏发电量的准确估算不仅是项目规划、投资决策的关键依据&#xff0c;也是后续运维管理、效益评估的基础。 一、光伏发电量估算的重要性 1、项目规划与投资决策&#xff1a;准确的发电量预测能够帮助投资者评估项目的经济可行性&#xff0c;包括投资回报率、成本回收期等关键…

Unity携程Coroutine用法

一.携程概述 官方的解释是&#xff0c;携程允许你可以在多个帧中执行任务。在Unity中&#xff0c;携程是一个可以暂停并在后续帧中从暂停处继续执行的方法。 二.携程写法 下面示例使用携程和Update打印前5帧的时间间隔&#xff0c;展示了携程的基础写法 using System.Colle…

webgl入门(003):技术优势和性能优势

还是大剑师兰特&#xff1a;曾是美国某知名大学计算机专业研究生&#xff0c;现为航空航海领域高级前端工程师&#xff1b;CSDN知名博主&#xff0c;GIS领域优质创作者&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;canvas&#xff0c;webgl&#xff0c;ech…

MATLAB系列08:输入/输入函数

MATLAB系列08&#xff1a;输入/输入函数 8. 输入/输入函数8.1 函数textread8.2 关于load和save命令的进一步说明8.3 MATLAB文件过程简介8.4 文件的打开和关闭8.4.1 fopen函数8.4.2 fclose函数 8.5 二进制 I/O 函数8.5.1 fwrite 函数8.5.2 fread函数 8.6 格式化 I/O 函数8.6.1 f…

自定义Spring Security认证处理的完整解决方案

文章目录 1. 自定义认证成功处理器2. 自定义认证失败处理器3. 自定义登出成功处理器4. 自定义未认证用户访问处理器5. 完整的Web安全配置总结 在今天的开发过程中&#xff0c;安全性是不可或缺的一部分&#xff0c;而Spring Security作为Java开发中的一站式解决方案&#xff0c…

Vm软件安装_链接相机

工业相机的驱动连接 下载安装MVS MVS 客户端支持安装在 Windows XP/7/10 32/64bit&#xff0c;Linux 32/64bits 以及MacOS64bits操作系统上。本文以 Windows 系统为例进行介绍。 具体操作步骤如下&#xff1a; 请从海康机器人官网&#xff08;www.hikrobotics.com&#xff0…

重修设计模式-结构型-适配器模式

重修设计模式-结构型-适配器模式 将不兼容的接口转换为可兼容的接口&#xff0c;让原本由于接口不兼容而不能一起工作的类可以一起工作 适配器模式&#xff08;Adapter Pattern&#xff09;允许将一个类的接口变换成客户端所期待的另一种接口&#xff0c;从而使原本因接口不匹配…

【machine learning-六-supervise learning之线性回归模型】

监督学习之线性回归模型 线性回归模型线性模型回归模型 如何使用线性模型实现智能化预测呢寻找数据训练模型输入、特征、目标、预测值、模型代价函数 线性模型是人工智能监督学习中最广泛的应用&#xff0c;所以有必要先学习一下这个基础模型&#xff0c;做好基石。 线性回归模…