函数重载和运算符重载

本文详细介绍了C++中函数重载和运算符重载的概念,包括函数重载的规则、不同类型的重载示例,如不同参数类型、数量和顺序,以及运算符重载的规则和常见运算符如==、+=、<<的实现。还讨论了重载运算符时的类成员函数和友元函数选择,以及标准输出重载的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重载分为两种情况,一种是函数重载,一种是运算符重载,它们常用来处理实现功能类似但是数据类型不同的问题。

一、函数重载

函数重载:在C++中,允许在同一作用域中声明多个同名函数,但是这些同名函数的形参列表(形参列表指的是:参数个数或类型或类型顺序)不同。满足这样的同名函数就构成重载。
注意
1、返回值不作为函数重载的判断条件,所以返回值可同可不同。
2、当调用重载的函数时,编译器会根据形参和实参,优先调用最匹配的函数、其次次之,如果没有匹配的函数会报错(下面例子有讲解)。
下面是函数重载的例子:

#include <iostream>
using namespace std;
// 1、参数类型不同
int Add(int x, int y)
{
	cout << "Add(int x, int y)" << endl;
 	return x + y;
}

double Add(double x, double y)
{
	cout << "Add(double x, double y)" << endl;
 	return x + y;
}

double Add(int x, double y)
{
	cout << "Add(int x, double y)" << endl;
}

// 2、参数个数不同
void f()
{
 	cout << "f()" << endl;
}
void f(int a)
{
 	cout << "f(int a)" << endl;
}

// 3、参数类型顺序不同
void f(int a, char b)
{
 	cout << "f(int a,char b)" << endl;
}

void f(char b, int a)
{
 	cout << "f(char b, int a)" << endl;
}

int main()
{	
	//下面三次Add函数调用,编译器会优先查找参数最匹配的函数,显然这里都有最匹配的,则各自调用最匹配的。如果这里只是实现了三个Add中的任意一个,那么下面三次调用都会调用实现的那一个,虽然没有最匹配的,但是可以调用(可能会报警告)。
 	Add(10, 20); //调用第一个Add函数
 	Add(10.1, 20.2); //调用第二个Add函数
 	Add(10, 20.5); //调用第三个Add函数
 	
 	f();
 	f(10);
 	f(10, 'a');
	f('a', 10);
	return 0;
}

二、运算符重载

运算符重载和函数重载类似,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号:比如:operator+
函数原型:返回值类型 operator操作符(参数列表)
注意:
1、不能重载没有的运算符,比如:operator#
2、用于内置类型的运算符,不能改变其含义,比如:+号,不能将它改变成减法
3、运算符函数的参数类型必须有一个是类类型
4、运算符函数的参数个数和运算符的操作数个数是相等的,比如:==号的操作数是两个,重载的时候参数个数也必须是两个
5、当有两个操作数的运算符时,运算符函数第一个参数是左操作数,第二个参数是右操作数
6、当运算符重载是类成员函数的时候,形参看起来比实际参数少一个,因为类成员函数的第一个参数为隐藏的this
7、有五个运算符不能重载,分别为:.*,::,sizeof, ?:, .
下面通过一个例子实现常见的几种运算符重载

(一)==运算符重载

假设我们有下面这样一个日期类,我现在的需求是用日期类实例化两个对象,然后比较两个对象是否相等。此时需要重载运算符==,如果不重载,无法比较,因为运算符==不能比较两个自定义类型。

#include <iostream>
using namespace std;

class Date //日期类
{
public:
	Date(int year = 2000, 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)
{
	//当运算符重载成全局函数的时候,在函数体内访问类成员变量的时候,类成员变量权限必须是公有的,否则函数体内无法访问
	//但是这也带来了一个问题,类成员的安全性,一旦公有,谁都可以访问篡改类成员变量
	//解决方法有两种:1、通常将运算符重载成类成员函数(通常采用的方法) 2、将运算符重载(全局函数)声明成类的友元函数
	return (d1._year == d2._year && d1._month == d2._month && d1._day == d2._day);
}

int main()
{
	Date d1(2024, 2, 29);
	Date d2(2023, 2, 29);
	//当重载成全局函数的时候有两种调用方式
	//第一种:跟普通函数的调用一样
	if (operator==(d1, d2))
		cout << "相等" << endl;
	else
		cout << "不相等" << endl;
		
	//第二种:跟内置类型判断是否相等写法一样,这样写的话看起来更符合运算符的写法
	//你可以理解为编译器会将d1==d2处理成第一种写法
	if (d1 == d2)
		cout << "相等" << endl;
	else
		cout << "不相等" << endl;
		
	return 0;
}

为了解决上述类的成员变量安全性问题,我们将运算符重载成类的成员函数

#include <iostream>
using namespace std;

class Date //日期类
{
public:
	Date(int year = 2000, int month = 1, int day = 1) //默认构造
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//这里看起来少了一个参数,实际上是第一个参数(左操作数)隐藏起来了
	//函数原型可以看成:bool operator==(Date* this, const Date& d2)
	bool operator==(const Date& d2)
	{
		return (this->_year == d2._year && this->_month == d2._month && this->_day == d2._day);
	}

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

int main()
{
	Date d1(2024, 2, 29);
	Date d2(2023, 2, 29);
	//当重载成类的成员函数的时候有两种调用方式
	//第一种:跟类的成员函数调用一样
	cout << d1.operator==(d2) << endl;

	//第二种:跟内置类型使用一样,可以理解为编译器会将是d1==d2处理成第一种
	cout << (d1 == d2) << endl;
	
	return 0;
}

(二)=运算符重载

#include <iostream>
using namespace std;

class Date //日期类
{
public:
	Date(int year = 2000, int month = 1, int day = 1) //默认构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	//赋值运算符返回值应是Date类型,因为要构成链式反应,支持连续赋值
	//比如int类型,a = b = c,可看成:a = (b == c)),其次返回引用是为了提高效率
	Date& operator=(const Date& d2)
	{
		if (this != &d2) //防止自己给自己赋值
		{
			this->_year = d2._year;
			this->_month = d2._month;
			this->_day = d2._day;
		}
		return *this;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 2, 29);
	Date d2(2023, 2, 29);
	Date d3;
	d3 = d1;

	return 0;
}

注意(下面都是类中知识的注意事项):
1、赋值运算符必须重载成类的成员函数,因为赋值运算符如果不显式在类中实现,编译器会生成一个默认的,此时用户再在类外自己重载一个全局的赋值运算符,在调用的时候会出现二义性,故赋值运算符重载只能是类的成员函数。
2、默认提供的赋值运算符函数,只是简单的值拷贝(将一个对象中成员变量按字节为单位拷贝给另一个成员的变量),如果成员变量中涉及动态开辟的资源,那么用默认的赋值运算符函数可能会出现浅拷贝问题。如果类中未涉及到资源管理,赋值运算符是否实现都可以,一旦涉及到资源管理则必须要实现。

(三)++运算符重载(前置和后置)

前置++和后置++都是一元运算符,重载时怎么区分它们呢?为了能正确重载,c++规定,后置++运算符函数多一个int类型的参数,调用时,由编译器自动传递。

#include <iostream>
using namespace std;

class Date //日期类
{
public:
	Date(int year = 2000, int month = 1, int day = 1) //默认构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	//前置++
	//返回值必须返回引用,因为要构成链式反应,支持连续++
	//比如:++(++a),这里的结果是a增加了两次,括号外面的++也是对a自身的++,所有要返回引用,也就是返回它的自身
	//不然返回临时对象的话,临时对象增加之后销毁了,而它本身没有增加
	Date& operator++() 
	{
		this->_day += 1; //这里只是简单的加1,没有做每个月天数处理
		return *this;
	}

	//后置++
	//返回值不是引用,因为返回的是临时变量,所有不能使用引用,并且后置++不支持连续++
	Date operator++(int) 
	{
		Date tmp = *this;
		this->_day += 1;
		return tmp;
	}

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

int main()
{
	Date d1(2024, 2, 29);
	Date d2(2023, 2, 29);
	Date d3 = d1++; //d3是(2024,2,29),d1变成了(2024,2,30)
	Date d4 = ++(++d2); //d4是(2023,2,31),d2变成了(2023,2,31)
	return 0;
}

前置–和后置–,跟前置++和后置++处理方法一样

(四)+、+=运算符重载

#include <iostream>
using namespace std;

class Date //日期类
{
public:
	Date(int year = 2000, int month = 1, int day = 1) //默认构造
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//返回值是Date,因为要返回它们计算的和
	//其次返回值不是引用,因为返回值是临时对象,加法的左操作数是不会改变的,所以返回临时对象
	Date operator+(const int day) 
	{
		Date tmp = *this;
		tmp._day += day;
		return tmp;
	}

	//同上
	Date operator+(const Date& d2)
	{
		Date tmp = *this;
		tmp._year += d2._year;
		tmp._month += d2._month;
		tmp._day += d2._day;
		return tmp;
	}

	//返回值是Date,因为要返回它们相加的和
	//返回引用,因为要和其他运算符构成链式反应,比如:++(a += 1),括号计算结束之后,前置++还会导致a增加一次,所以必须返回引用
	Date& operator+=(const int day)
	{
		this->_day += day;
		return *this;
	}

	//同上
	Date& operator+=(const Date& d2)
	{
		this->_year += d2._year;
		this->_month += d2._month;
		this->_day += d2._day;
		return *this;
	}

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

int main()
{
	Date d1(2024, 2, 29);
	Date d2(2023, 2, 29);
	Date d3 = d2 + 1; //(2023, 2, 30)
	Date d4 = d2 + d3; //(4046, 4, 59)
	d3 += 1; //(2023, 2, 31)
	d4 += d3; //(6069, 6, 90)
	return 0;
}

-、-=运算符重载和+、+=运算符重载类似

(五)<<运算符重载

如果我们直接打印Date类型的数据(如:cout<<d1),是无法打印的,因为Date类型是自定义类型。根本原因还是ostream类中没有重载能处理Date类型的<<运算符函数,因为你是自定义类型,我不知道你要怎样打印,所以我无法重载这样的运算符函数。ostream中只重载了内置数据类型,平时使用的cout是ostream的一个对象,所以可以通过cout<<来打印数据。

下图是ostream类中<<运算符的重载和cout的声明

在这里插入图片描述
在这里插入图片描述

标准输出运算符必须重载成全局函数,并声明为类的友元函数,如果重载在类中,也能使用,但是用法和正常用法会不一样,所以为了统一,定义在类外。
首先看一下重载成类成员函数,会是怎样的情况:

#include <iostream>
using namespace std;

class Date //日期类
{
public:
	Date(int year = 2000, int month = 1, int day = 1) //默认构造
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//重载在类中
	//形参类型是ostream,因为我们打印Date类型数据终究还是要依靠标准输出流cout,只不过我们使用cout自定义如何打印,所以要传参cout
	//返回值是ostream,因为要构成链式反应(重载再类中的链式反应有缺陷),如cout<<a<<b,可以理解为(cout<<a)<<b,先是cout<<a,结果返回cout,再cout<<b。返回引用是因为效率
	ostream& operator<<(ostream& out)
	{
		out << "(" << this->_year << ", " << this->_month << ", " << this->_day << ")";
		return out;
	}

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

int main()
{
	Date d1(2024, 2, 29);
	Date d2(2023, 2, 29);
	
	//这种用法是错误,因为左操作数是运算符函数的第一个参数,而第一个参数是Date类型对象
	cout << d1 << endl;
	//正确用法,但是这种用法和我们平常用法相反
	d1 << cout << endl; 
	//链式反应的缺陷
	d2 << d1 << cout; //错误,因为是从左到右运算,先是d2<<d1,无法被识别
	d2 << cout << 1 << "+" << "2" << endl; //正确(可以看出来每次都是第一次操作是反的:d2<<cout,后面都正常了)
	
	return 0;
}

重载成全局函数并声明为Date类的友元函数,友元函数意思就是我是Date的好朋友,我可以在我的函数体内访问你的私有成员变量了

#include <iostream>
using namespace std;

class Date //日期类
{
	friend ostream& operator<<(ostream& out, const Date& d); //声明为友元函数
public:
	Date(int year = 2000, int month = 1, int day = 1) //默认构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d)
{
	out << "(" << d._year << ", " << d._month << ", " << d._day << ")";
	return out;
}

int main()
{
	Date d1(2024, 2, 29);
	Date d2(2023, 2, 29);
	cout << d1 << "," << d2 << endl;
	
	return 0;
}

三、总结

无论是函数重载还是运算符重载,它们都是满足三个条件:
1、在同一作用域中
2、函数名相同
3、参数列表不同

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值