类与对象(中)——运算符重载

当想让运算符对于类对象实现一些功能时,C++允许我们对这些运算符进行重载定义,从而实现对类的操作。同时C++也规定,对类对象实现运算符操作时,必须调用相应的运算符重载,否则会产生编译报错。

一.运算符重载

运算符重载本质是创建了一个函数,实现了相应的功能。其基本语法格式为:

返回值类型 operator 要重载的符号(形式参数)。

运算符重载参数的个数要与运算符的操作对象个数有关:一元操作符一个形式参数;二元操作符两个形式参数。同时注意二元操作符的两个形式参数顺序不能改变,二元运算符左侧对象为第一个形式参数,右侧对象为第二个形式参数。

下面将结合日期类,来具体实现几个运算符重载的操作:

cpp 复制代码
class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}


	int _year;
	int _month;
	int _day;
};

bool operator==(const Date& d1, const Date& d2)
{
	//这样写,由于类内成员是private限制
	//有以下几种解决方法:
	//1.成员改为共有
	//2.Date提供get响应数据的函数
	//3.友元函数
	//4.将重载放到类中
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2026,2,22);
	Date d2(2026,2,23);

	cout << operator==(d1, d2) << endl;;
	cout << (d1 == d2) << endl;;

	return 0;
}

这里实现了一个基本的日期类,内部存在一个构造函数。在类外定义了一个判断相等的运算符重载,返回值类型为布尔类型,如果两个对象相等则返回true,不同则返回false。其内部函数具体实现为判断两个对象每个成员变量是否值相同。

如果将运算符重载函数定义在类外,那么就无法访问到私有的成员变量,这里提供四种解决方法的思路:1.提供一个get函数,用来获取类内成员变量的数据。2.成员变量改为共有public类型;3.友元函数;4.将重载放到类中。

这里为了示例,先将成员变量公有化,之后会实现重载放到类中。

实现判等重载之后,调用方式就会变成类似于两个数值判等的方法,直接判断两个实例化类对象是否相等d1 == d2,如果相等返回true。

同时也可以利用函数的调用方法,直接调用operator==函数,传递两个类对象,即可比较大小。

下面这段代码,将运算符重载放到了类中:

cpp 复制代码
class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//由于会默认传一个this指针,所以只需要传另一个比较的参数即可
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && d._day == d._day;
	}

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

int main()
{
	Date d1(2026, 2, 22);
	Date d2(2026, 2, 23);

	//相当于是类内函数,所以需要下面这样写
	//二元操作符,左侧数据为this,右侧数据为另一个。
	cout << d1.operator==(d2) << endl;;
	cout << (d1 == d2) << endl;;
	return 0;
}

由于c++编译器的特性,会在成员函数调用时,自动传一个Date* this指针,所以将重载函数实现到类中,只需要传另一个元素即可。

在调用时,同样有两种方法:直接进行比较和通过一个实例化对象调用函数传参来进行比较。

下述几个需要注意的点:

  1. 在这里实现了判断相等的运算符重载,在之后的代码中会实现日期+-天数、日期-日期、判断两个日期大小的运算符重载。需要注意的是,在语法上,运算符可以实现重载,但在实际意义上,有些运算符是不需要进行重载,比如日期+日期的运算符就不需要进行重载,其没有实际意义。

2.运算符重载,必须要包含一个类类型的对象。

3.C++规定,不允许使用原本语法中不存在的运算符来重载,一个错误示例:operator@

4.C++规定,以下5个运算符不可以重载:sizeof、三目操作符(?:)、 .* (对于类中函数指针的解引用)、::(类域限定符)、. (对象访问)。

5.对于重载前置++和后置++(或--),C++专门对其有特殊处理规定:前置++的参数不需要有任何标识、后置++的参数需要放置一个int用作标识。

cpp 复制代码
Date& Date::operator++()
{
	*this += 1;
	return *this;
}
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}
Date& Date::operator--()
{
	*this -= 1;
	return *this;
}
Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

如上面代码所示,四个运算符重载函数,分别实现了前置++、后置++、前置--、后置--。前置++ -- 函数中,参数位置不需要放置任何东西。调用时将++放到实例化对象之前即可;后置++ --需要在参数位置放置一个int即可(C++规定,只需要int即可),调用时在对象后面++ --即可。

下面介绍一个较为重要且形式和上面的运算符重载类型不太相同的提取插入流运算符的重载:

提取插入流运算符<< >>:

插入流运算符<<

提取流运算符>> 这里继续利用日期类为例:

cpp 复制代码
class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    
	ostream& operator<<(ostream& out)
	{
		out << _year << " " << _month << " " << _day << endl;	
		return out;
	}
	istream& operator>>(istream& in)
	{
		in >> _year >> _month >> _day;
		return in;
	}

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

上面这段代码,在类内重载了<< 和 >> 操作符,其语法格式为:返回值类型为ostream 和istream的引用,传参为cout和cin的引用类型。在函数内分别定义输出和输入相应的成员变量即可。

但是由于二元运算符重载的规则,左侧的操作数对应第一个参数,右侧操作数对应第二个参数,而第一个参数为默认的this,所以如果将其定义在类内,那么调用方法应该为:

cpp 复制代码
int main()
{
    Date d1;
        
    d1.operator<<(cout);
	
    d1 << cout;
}

调用方法和正常的输入正好相反。

那么就需要在类外进行定义,而成员变量为私有,所以就需要将该重载函数定义为友元函数friend,就可以正常调用成员变量。类内进行函数声明,类外进行函数实现:

cpp 复制代码
class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << " " << d._month << " " << d._day << endl;
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	cout << "cin the year month and day" << endl;
	in >> d._year >> d._month >> d._day;
	while (!d.Cheak())
	{
		cout << "Try again:" << endl;
		in >> d._year >> d._month >> d._day;
	}
	return in;
}

类外实现基本格式如下所示,类内进行友元函数的声明,类外进行函数的定义,注意这个时候的参数顺序,cout和cin放在第一个参数的位置

二.赋值运算符重载

赋值运算符重载也是类中六个默认成员函数中的一个,其作用和拷贝构造函数类似,是实现对一个类对象的拷贝,但最大的不同是:赋值运算符重载函数,是对已有类型对象的拷贝;而拷贝构造函数是在创建类对象时,利用已有对象进行创建。

赋值运算符规定必须重载为成员函数。

如下面这段代码所示:

cpp 复制代码
class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//由于会默认传一个this指针,所以只需要传另一个比较的参数即可
	bool operator==(const Date& d)
	{
		return _year == d._year && _month == d._month && d._day == d._day;
	}

	
	//没有返回值的赋值运算符重载,不能连续赋值
	//void operator=(const Date& d)
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}

	//有运算符的赋值重载

	//不写的话,也会默认生成,但和拷贝构造类似,只能进行浅拷贝。
	//所以如果一个类需要手动实现析构函数,那么其也需要手动实现拷贝构造和赋值运算符重载
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return (*this);
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;//此处调用全缺省构造函数进行创建对象。
	Date d2(d1);//此处调用拷贝构造函数进行创建对象
	Date d3(2026, 2, 25);

	Date d4;

	//这里就是调用赋值运算符重载
	d4 = d3 = d1;//对于已有对象的拷贝,需要利用赋值运算符的重载函数
	
	//赋值运算符的重载函数也是一个默认成员函数,不写会自动生成
	return 0;
}

这段函数在类内实现了两个赋值运算符的重载,其中返回值为void类型的注释掉了。其具体实现的内容为给this所指向的对象进行数据的拷贝,最后返回*this对象。

在主函数的示例中,d1的创建调用了构造函数;d2的创建是利用d1的数据并调用拷贝构造函数进行创建;d3对构造函数进行传值调用;d4和d1相同,调用了全缺省的构造函数。

之后d4 = d3 = d1,连续赋值,运行程序会发现三者的值最后相同。这就是赋值运算符的重载,主要应用是对已有对象的拷贝赋值。返回值设置为Date类型的引用,一是为了连续赋值,二是节省空间,不需要再额外调用一次拷贝构造。

由于赋值运算符重载是默认成员函数,没有手动实现的情况下,编译器也会默认生成一份,类似于拷贝构造函数,默认生成的只会对对象进行浅拷贝。所以在这里也能发现,如果类中存在动态开辟内存的资源,就需要手动实现赋值运算符重载。这里也可以总结出一份规律:如果一个类需要手动实现析构函数,那么也需要手动实现拷贝构造函数和赋值运算符重载。

三.const成员函数

在对成员函数传参时,会默认存在一个Date* const this的成员函数(以日期类为例),这个成员函数的const修饰this指针,标识this指针不可以改变,而*this的对象可以被改变。

而如果存在有一个const修饰了的实例化对象,就表示不应该通过*this改变,那么就需要在成员函数中实现const Date* const this,但是this指针不可以传参,所以出现了const成员函数。就能做到对*this实现const的功能。

其基本用法是在函数的后面加上const即可。

cpp 复制代码
class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print() const
	{
		cout << _year << " " << _month << " " << _day << endl;;
	}

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

int main()
{
	Date d1;
	const Date d2;


	return 0;
}

例如在这段程序中,创建了一个实例化对象d1和一个const修饰的实例化对象d2,那么d2的内容就不能被修改。调用const成员函数print时,d2和d1都可以传参,对d2来说相当于权限平移、对d1来说相当于权限缩小,均可以达到传参的目的,同时还可以保证其内容不会被修改。

四.取地址运算符的重载

顾名思义为在实现一个对实例化对象取地址的运算符的重载函数,这是6个默认成员函数中的最后一个(组)成员函数。取地址操作符的重载有两个函数,分为普通取地址操作符的重载和const修饰取地址操作符的重载。

cpp 复制代码
class Date
{
public:
	Date(int year = 2000, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		return this;
        //return nullptr;
	}

	const Date* operator&() const
	{
		return this;
        //return nullptr;
	}

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

int main()
{

	Date d1;
	const Date d2;
	cout << &d1 <<'\n' << &d2 << endl;
	return 0;
}

如上面这段程序,手动实现了两个取地址运算符的重载,正常情况下不需要进行取地址运算符重载函数的手动编写,编译器默认生成的就足够满足要求。

特殊情况下,如果不想让该对象的正确地址被显示出来,那么就可以手动编写取地址运算符重载函数,让其返回一个空指针即可。

下一篇博客带来一个日期类对象功能的具体实现。

相关推荐
郝学胜-神的一滴1 小时前
深入理解链表:从基础到实践
开发语言·数据结构·c++·算法·链表·架构
敲敲了个代码1 小时前
vue文件自动生成路由会成为主流
开发语言·前端·javascript·vue.js·前端框架
你住过的屋檐1 小时前
【Java】虚拟线程详解
java·开发语言
霍理迪1 小时前
JS—事件高级
开发语言·javascript·ecmascript
范特西.i1 小时前
QT聊天项目(8)
开发语言·qt
烟花落o2 小时前
栈和队列的知识点及代码
开发语言·数据结构·笔记·栈和队列·编程学习
crescent_悦2 小时前
C++:Have Fun with Numbers
开发语言·c++
mjhcsp2 小时前
C++轮廓线 DP:从原理到实战的深度解析
开发语言·c++·动态规划
ArturiaZ2 小时前
【day36】
数据结构·c++·算法