类和对象:运算符重载

news2024/11/25 10:42:14

 本篇文章来介绍一下C++中的运算符重载,以及与运算符重载有关的三个默认默认成员函数:赋值运算符重载普通对象取地址与const对象取地址操作符重载,也就是下面图片中6个默认成员函数的后三个,前三个默认成员函数在之前文章中已经讲过类和对象:构造函数,析构函数与拷贝构造函数_一棵西兰花的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/qq_72916130/article/details/132789343?spm=1001.2014.3001.5501,大家不太清楚的可以去看一下。

本篇文章主要通过一个日期类来进行讲解。我会把完整的日期类放到后面。

class Date
{
public:
    //...
private:
	int _year;
	int _month;
	int _day;
};

1.什么是运算符重载

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

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

注意:

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

光看定义是不行的,我们实现以下 > 和 == 的重载

// >运算符重载
//因为>号有两个操作数,因为有一个是隐藏的this指针,所以这里只有一个参数
bool Date::operator>(const Date& d)
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year == d._year && _month < d._month)
	{
		return false;
	}
	else if (_year == d._year && _month == d._month && _day <= d._day)
	{
		return false;
	}
	else
	{
		return true;
	}
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}

有一个 > 和 == 或< 和 == ,其他比较运算符就可以有这两个推导出来,不用再一个一个写。


2.赋值运算符重载

1.赋值运算符重载格式

  • 参数类型:const T&,传递引用可以提高传参效率,加const是为了防止参数被修改
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要符合连续赋值的含义

代码:

Date& Date::operator=(const Date& d)//可以不用引用,但没有必要
{
	if (this != &d)//自己给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
		
	return *this;
}

对于返回*this,因为我们平时的赋值运算符可以实现 类似与 a = b = c = d 的方式,显示调用就是a.operator(b.operator(c.operator(d))),如果operator=函数没有返回值,就会出现错误。

2.赋值运算符只能重载成类的成员函数不能重载成全局函数

// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
    if (&left != &right)
    {
        left._year = right._year;
        left._month = right._month;
        left._day = right._day;
    }
    return left;
}

// 编译失败:
// error C2801: “operator =”必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

3.用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?当然像日期类这样的类是没必要的。

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。因为他会将地址也进行简单的值拷贝(浅拷贝),在进行析构时回释放两次,会导致程序崩溃。


3.前置++与后置++重载

前置++与后置++的操作数只有一个,而且只有返回值不同,一个前置++返回自己加一,后置++需要在构造一个Date 来存放没有加一时的结果进行返回。只有返回值不同,没有办法区分两个函数,只好在后置++加入一个占位参数,没有什么用途,只是为了区分前置与后置,编译器也是通过这个方式来识别。

C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递

代码: 

// 前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,
//故需在实现时需要先将this保存一份,然后给this+1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
// 后置++ 传入的值不用接收
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

-- 也一样:        

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
// 前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

4.const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,这里的const需要加在形参列表的后面,实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。

我们来看看下面的代码:

这里类成员函数Print() 没有用const修饰。

    const Date d1(2023, 9, 23);
	//const修饰的对象调用不了成员函数,因为权限放大
	d1.Print();//d1.print(&d1) 隐藏的传入this的指针是const Data* 类型

Print() 没有被const 修饰,函数形参this指针是 普通的 Date* 类型,而被const修饰的对象调用Print()函数,传入的this指针是const Data* 类型 ,会发生权限的放大,权限可以缩小,平移,但是不能放大,会报错。

请思考下面的几个问题:

  1. const对象可以调用非const成员函数吗?
  2. 非const对象可以调用const成员函数吗?
  3. const成员函数内可以调用其它的非const成员函数吗?
  4. 非const成员函数内可以调用其它的const成员函数吗?

答案:1,3不可以,权限放大。2,4可以,权限缩小。

我们可以通过函数重载使不同权限的对象调用不同的函数。如下面代码的Print:

class Date
{
public:
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
        cout << "Print()" << endl;
        cout << "year:" << _year << endl;
        cout << "month:" << _month << endl;
        cout << "day:" << _day << endl << endl;
    }
    void Print() const
    {
        cout << "Print()const" << endl;
        cout << "year:" << _year << endl;
        cout << "month:" << _month << endl;
        cout << "day:" << _day << endl << endl;
    }
private:
    int _year; // 年
    int _month; // 月
    int _day; // 日
};
void Test()
{
    Date d1(2022,1,13);
    d1.Print();
    const Date d2(2022,1,13);
    d2.Print();
}

当然Print函数用const 修饰是没有什么意义的。我们看下面的取地址及const取地址操作符重载。


5.取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成

class Date
{
public :
    //这种方式返回指针的内容可以修改,可读可写
    Date* operator&()
    {
        return this ;
    }

    //这种方式返回指针的内容也不能修改,只读
    const Date* operator&()const
    {
        return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容,不返回this,返回nullptr等等。

6. Date 类的实现

Date.h 

class Date
{

public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);

	//全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1);

	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d);

	// 日期+=天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-天数
	Date operator-(int day);
	// 日期-=天数
	Date& operator-=(int day);

	void Print();

	//下面两个是运算符重载,也构成函数重载
	// 前置++ 
	//++d1 -> d1.operator++()
	Date& operator++();
	// 后置++
	//d1++ -> d1.operator++(0)//传一个整形即可,只是用来区分前置++
	//加了一个int参数,进行占位,跟前置++构成函数重载进行区分
	//本质后置++调用,编译器进行了特殊处理
	Date operator++(int);//传入的值不用接收
	// 后置--
	Date operator--(int);
	// 前置--
	Date& operator--();


	// >运算符重载
	bool operator>(const Date& d);
	// ==运算符重载
	bool operator==(const Date& d);
	// >=运算符重载
	bool operator >= (const Date& d);
	// <运算符重载
	bool operator < (const Date& d);
	// <=运算符重载
	bool operator <= (const Date& d);
	// !=运算符重载
	bool operator != (const Date& d);

	// 日期-日期 返回天数
	int operator-(const Date& d);

	const Date* operator&()const;

	Date* operator&();

private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	static int MonthDay[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;
	}
	return MonthDay[month];
}

//全缺省的构造函数
//声明和定义缺省参数的值,不能都给,必须在声明中给,
//因为在编译时头文件中看到的是声明,而声明中没有提供缺省值,会报错。到不了后面的链接,通过函数名修饰规则来找到函数的定义
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	//检查日期日期是否合法
	if (month < 1 || month>12 || day < 1||_day > GetMonthDay(_year, _month))
	{
		cout << "非法日期\n" << endl;
		exit(-1);

	}
}

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)//可以不用引用,但没有必要
{
	if (this != &d)//自己给自己赋值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
		
	return *this;
}

// 日期+=天数
Date& Date::operator+=(int day)//0次拷贝 0次赋值
{
	if (day < 0)
	{
		return *this -= (-day);
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		while (_month > 12)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
// 日期+天数
Date Date::operator+(int day)//两次拷贝
{
	if (day < 0)
	{
		return *this - (-day);
	}

	Date ret(*this);

	ret += day;

	return ret;
}

// 日期-天数
Date Date::operator-(int day)
{
	if (day < 0)
	{
		return *this + (-day);
	}

	Date ret = *this;
	ret -= day;
	return ret;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}

	_day -= day;
	while (_day < 1)
	{
		_month--;
		while (_month < 1)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

//只读函数可以加const,内部不涉及修改成员

//void Date::Print(const Data* this)
//void Date::Print() //可以同时存在,因为this指针类型不同,如果只有const版本也可调用,因为权限可以缩小
//{//不过在这里没有意义
//	cout << _year << "/" << _month << "/" << _day << endl;
//}
void Date::Print() const
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

// 前置++
Date& Date::operator++()
{
	*this += 1;
	return *this;
}

// 后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}
// 前置--
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}



// >运算符重载
bool Date::operator>(const Date& d)
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year == d._year && _month < d._month)
	{
		return false;
	}
	else if (_year == d._year && _month == d._month && _day <= d._day)
	{
		return false;
	}
	else
	{
		return true;
	}
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year && _month == d._month && _day == d._day;
}

// >=运算符重载
bool Date::operator >= (const Date& d)
{
	return *this > d || *this == d;
}

// <运算符重载
bool Date::operator < (const Date& d)
{
	return !(*this >= d);
}

// <=运算符重载
bool Date::operator <= (const Date& d)
{
	return !(*this > d);
}

// !=运算符重载
bool Date::operator != (const Date& d)
{
	return !(*this == d);
}

//跟日期-天数构成函数重载
// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (max < min)
	{
		min = *this;
		max = d;
		flag = -1;
	}
	int count = 0;
	while (max != min)
	{
		--max;
		count++;
	}
	return count * flag;
}


//日常自动生成的就可以
//不要被取到有效地址
//const Date* Date::operator&()const
//{
//	return nullptr;
//}
const Date* Date::operator&()const
{
	return this;
}
//这个接口是读写
Date* Date::operator&()//函数重载,一个给const对象用,一个给非const对象用
{
	return this;
}

本篇结束!

 

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

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

相关文章

Makerbase SimpleFOC MINI 基本测试

第1部分 硬件介绍 1.1 硬件清单 序号品名数量1SimpleFOC MINI V1.0 主板12ARDUINO UNO主板13MKS SF2804电机14杜邦线45DC12V电源16USB 线1 1.2 硬件连接 1.SimpleFOC MINI V1.0 主板主板与Arduino UNO主板叠接。如下图所示&#xff1a; 2.USB 线一端连接 Arduino UNO 主板…

RabbitMQ工作模式——Routing路由模式

1.Routing路由模式 Routing生产者代码 public class Producer_Routing {public static void main(String[] args) throws IOException, TimeoutException {//1.创建连接工厂ConnectionFactory factory new ConnectionFactory();//2.设置参数factory.setHost("172.16.98.…

一文彻底搞懂PN结及其单向导电性(图解说明)

前置知识 首先我们要知道纯净的本征半导体 硅 的导电性是非常差的&#xff0c;所以我们一般都会向纯净硅中添加杂质&#xff0c;也就是P型半导体和N型半导体。P型半导体和N型半导体都是呈电中性的&#xff0c;对于N型半导体而言&#xff0c;它又多数载流子电子和带正电荷的N离子…

【LeetCode-中等题】113. 路径总和 II

文章目录 题目方法一&#xff1a;DFS回溯 题目 方法一&#xff1a;DFS回溯 解题核心 就是要知道递归在哪里结束 &#xff0c;收货结果在哪里收获&#xff0c;哪些变量需要回溯&#xff0c;哪些不需要回溯 class Solution {List<List<Integer>> res new ArrayLis…

BI技巧丨Window应用之累计求和

Window函数除了可以用来计算同环比、移动平均之外&#xff0c;还可以用来处理累计求和问题。 核心在于Window的from和to参数的设定&#xff0c;可以将其设置为绝对位置和相对位置。 先来看看本期的案例数据&#xff1a; 案例数据比较简单&#xff0c;一张销售事实表。 将其导…

C++核心编程——P45-52继承

继承 继承是面向对象三大特性之一 有些类与类之间存在特殊的关系&#xff0c;例如下图中: 我们发现&#xff0c;定义这些类的时候&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这时候我们就可以考虑利用继承的技术&#xff0c;减少重复代码量…

PPPoE配置

实验需求 配置IP地址使用PPPOE拨号上网设置路由让直播业务部和营销部都可以访问外网 实验拓扑 实验步骤 配置 R1地址池 电信链路&#xff1a; [Huawei]undo info-center enable Info: Information center is disabled. [Huawei]sysname r1 [r1]ip pool zhibo  //配置…

多进程编程- POSIX命名信号量(named semaphore)

POSIX命名信号量是POSIX标准下的一个进程间同步原语&#xff0c;允许多个进程共享同一个信号量&#xff0c;从而实现进程间的同步和通信。这与无名信号量不同&#xff0c;无名信号量主要用于线程之间的同步&#xff0c;而不是进程之间。 命名信号量是“命名”的&#xff0c;因…

基于AVR128单片机抢答器控制系统

一、系统方案 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 uchar set_time 0 ; DDRA0XFF; PORTA0xff; DDRB0XFF; PORTB0x00; DDRC0X00; PORTC0xff; DDRD0XFc; PORTD0XFF; DDRE0XFF; PORTE0XFF; DDRF0XFF; PORTF0XF0; beer1(); timer1_i…

Linux-多路转接-select/poll

select/poll 五种IO模型对IO的正确理解何为高效的IO阻塞IO非阻塞IO设置文件描述符为非阻塞模式非阻塞IO例子 信号驱动IO异步IO多路转接 selct认识接口select返回值 基本使用select使用特点缺点 poll认识接口对select的改善缺点 五种IO模型 对IO的正确理解 &#x1f680;IO不仅…

MySQL学习笔记9

MySQL数据表中的数据类型&#xff1a; 在考虑数据类型、长度、标度和精度时&#xff0c;一定要仔细地进行短期和长远的规划&#xff0c;另外&#xff0c;公司制度和希望用户用什么方式访问数据也是要考虑的因素。开发人员应该了解数据的本质&#xff0c;以及数据在数据库里是如…

Sentinel故障转移及实现原理

Sentinel故障转移及实现原理 一、哨兵模式的基本工作流程二、判断实例下线三、选举新主库四、哨兵模式弊端五、哨兵集群判断实例下线六、哨兵集群判断实例下线详细工作过程七、哨兵集群的通信八、哨兵和客户端的通信九、总结 一、哨兵模式的基本工作流程 redis在运行时会开启一…

vue基础知识十五:说说你对slot的理解?slot使用场景有哪些?

一、slot是什么 在HTML中 slot 元素 &#xff0c;作为 Web Components 技术套件的一部分&#xff0c;是Web组件内的一个占位符 该占位符可以在后期使用自己的标记语言填充 举个栗子 <template id"element-details-template"><slot name"element-na…

(2022|ECCV,图像分割,VQ-SEG,AR Transformer)Make-A-Scene:利用人类先验进行基于场景的文本到图像生成

Make-A-Scene: Scene-Based Text-to-Image Generation with Human Priors 公众号&#xff1a;EDPJ&#xff08;添加 VX&#xff1a;CV_EDPJ 或直接进 Q 交流群&#xff1a;922230617 获取资料&#xff09; 目录 0. 摘要 1. 简介 2.相关工作 2.1. 图像生成 2.2. 图像标…

2023年汉字小达人区级自由报名明天开赛,3个新问题和往年真题练一练

明天9月25日&#xff0c;备受关注的2023年第十届上海小学生汉字小达人区级自由报名的比赛就要开始了&#xff0c;最近还是有几个“小迷糊”家长刚听说这个活动&#xff0c;问了几个问题&#xff0c;我觉得挺有普遍性的&#xff0c;所以再次给大家回答一下&#xff0c;希望能够帮…

redis漏洞修复:CVE-2022-35977、CVE-2023-22458、CVE-2023-28856

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、漏洞内容二、现状三、更新redis下载镜像停止已有的容器启动新的容器 四、更新后的版本1. 查看日志2. 查看版本 总结 前言 漏扫发现机器上的redis版本有点低…

图像识别-YOLO V8安装部署-window-CPU-Pycharm

前言 安装过程中发现&#xff0c;YOLO V8一直在更新&#xff0c;现在是2023-9-20的版本&#xff0c;已经和1月份刚发布的不一样了。 eg: 目录已经变了&#xff0c;旧版预测:在ultralytics/yolo/v8/下detect 新版&#xff1a;ultralytics/models/yolo/detect/predict.py 1.安…

九、多项式朴素贝叶斯算法(Multinomial NB,Multinomial Naive Bayes)(有监督学习)

Multinomial Naive Bayes&#xff1a;用于多项式模型的Naive Bayes分类器 一、算法思路 多项式Naive Bayes分类器适用于离散特征分类&#xff08;如文本分类中的字数&#xff09; 多叉分布通常需要整数特征计数 不过&#xff0c;在实际应用中&#xff0c;分数计数&#xff08…

LeetCode刷题

一 【移除元素】 原题链接&#xff1a;27. 移除元素 - 力扣&#xff08;LeetCode&#xff09; 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用…

SLAM从入门到精通(机器人建模和仿真环境)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 很多同学学了ros&#xff0c;以为把publish、subscribe、消息、服务这些接口学好了就行。其实这是很大的误区。因为这些通信机制只是帮我们了解ros…