C++经典问题_13 运算符重载

一. 运算符重载的意义

  1. 对于内置的数据类型,运算符是可以进行直接运算使用的.
  2. 自定义的类型如果需要用到运算符的这些操作,就要自己定一个运算符的重载函数.

二. 加号运算符(+)的重载

① 函数原型(成员函数)
Obj operator+(const Obj&p){};
② 函数原型(全局函数)
Obj operator+(Obj1 o1,Obj2 o2){};
③ 成员函数的例子
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
using namespace std;

class Person
{
public:
	Person()
	{
		mMoney = 0;
	}
	Person(int money) :mMoney(money)
	{

	}
	// 加号运算符的重载,其实本质上这里有两个参数 operator+(this,Person p)
	Person operator+(Person p)
	{
		cout << "成员函数加号操作符重载调用!" << endl;
		Person ret;
		ret.mMoney = this->mMoney + p.mMoney;
		return ret;
	}

public:
	int mMoney;
};

int main()
{
	Person p1(100);
	Person p2(200);
	// 这里其实调用的是p1.operator+(p2) 成员函数的操作符重载的显示参数少了一个,
	// this变成了一个隐藏的参数
	Person p3 = p1 + p2; // 成员函数操作符重载的调用本质 p3 = p1.operator+(p2);
	cout << "金钱总和: " << p3.mMoney << endl;
	Person p4 = p1.operator+(p2);
	cout << "金钱总和: " << p4.mMoney << endl;
	system("pause");
	return 0;
}

④ 全局函数调用的例子
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/

#include <iostream>
using namespace std;
class Person
{
public:
	Person()
	{
		mMoney = 0;
	}
	Person(int money) :mMoney(money)
	{

	}
public:
	int mMoney;
};

Person operator+(const Person& p1, const Person& p2)
{
	// 全局函数的重载加号运算符
	Person temp;
	temp.mMoney = p1.mMoney + p2.mMoney;
	return temp;
}


int main()
{
	Person p1(100);
	Person p2(100);
	Person p3 = p1 + p2;
	cout << "金钱总和: " << p3.mMoney << endl;
	// 全局函数的本质调用
	Person p4 = operator+(p1, p2);
	cout << "金钱总和: " << p4.mMoney << endl;
	system("pause");
	return 0;
}
⑤ 成员函数重载和全局函数重载可不可以同时存在
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/

#include <iostream>
using namespace std;
class Person
{
public:
	Person()
	{
		mMoney = 0;
	}
	Person(int money) :mMoney(money)
	{

	}

	Person operator+(const Person& p)
	{
		Person temp;
		temp.mMoney = this->mMoney + p.mMoney;
		return temp;
	}

public:
	int mMoney;
};

Person operator+(const Person& p1, const Person& p2)
{
	// 全局函数的重载加号运算符
	cout << "全局的加号操作符重载被调用!" << endl;
	Person temp;
	temp.mMoney = p1.mMoney + p2.mMoney;
	return temp;
}


int main()
{
	Person p1(100);
	Person p2(100);
	//Person p3 = p1 + p2; // 会报错,多个操作符+函数被匹配上,编译器不知道调用哪一个
	// cout << "金钱总和: " << p3.mMoney << endl;
	// 全局函数的本质调用
	// 但是可以这么来使用
	Person p3 = p1.operator+(p2);
	cout << "金钱总和: " << p3.mMoney << endl;
	Person p4 = operator+(p1, p2); // 这里不会报错,因为它是显示的调用了operator+的全局重载函数
	cout << "金钱总和: " << p4.mMoney << endl;
	system("pause");
	return 0;
}

结论:

可以同时存在,但是不能使用+号运算符了,也就失去了重载操作符的意义.所以一般使用过程中,成员的操作符重载和全局的操作符重载一般保留一个即可

三. 左移运算符重载(<<)

① 成员函数重载左移运算符

如果采用成员函数重载左移运算符,因为opertor<<操作符重载的时候,第一个参数永远都是this.
而cout在使用的额时候的习惯是 cout <<,cout在左侧,所以成员函数重载的cout运算符在使用的时候
不符合使用习惯,因为cout在右侧.

函数原型

ostream& operator<<(ostream &cout){} // 本质上 opertor<<(obj,cout) 
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/

#include <iostream>
#include <ostream>
using namespace std;
class Person
{
public:
	Person(string name, int age):mName(name),mAge(age)
	{

	}
	ostream &operator<<(ostream &cout)
	{
		cout << "姓名:  " << mName << ",年龄: " << mAge << endl;
		return cout;
	}
public:
	string mName;
	int mAge;
};
int main()
{
	Person p("张三", 18);
	p << cout; // 使用的时候必须这么去使用,不符合平时的使用习惯
	//p << cout << p << cout; // 不能连续的使用cout了,因为上一个表达式的结果在左侧,也就是cout在左侧,如果要连续使用
	p << (p << cout); // 必须要这么去使用,很不习惯,并且很繁琐
	// cout << p; // 这里会报错,匹配不上
	system("pause");
	return 0;
}

结果:

② 全局函数重载运算符

因为成员函数重载运算符的时候限定了第一个参数只能是this,所以有些操作符第一个参数必须是非this对象的时候,就必须用到全局函数的重载.
原型:

ostream& operator<<(ostream& cout ,Obj o) {}
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/

#include <iostream>
#include <string>
#include <iostream>
using namespace std;

class Person
{
public:
	Person(string name, int age) :mName(name),mAge(age)
	{

	}
	friend ostream &operator<<(ostream &cout, Person p); // 声明友元函数
private:
	string mName;
	int mAge;
};

ostream &operator<<(ostream &cout, Person p)
{
	cout << "姓名: " << p.mName << ", 年龄: " << p.mAge;
	return cout;
}

int main()
{

	Person p("Fioman", 28);
	Person p2("张三", 18);
	cout << p << endl;
	cout << p <<"  " <<  p2 << endl;
	system("pause");
	return 0;
}

四. 递增递减运算符重载

  1. 前递增,先递增,再计算表达式
  2. 后递增,先计算表达式,再递增自己
  3. 前递增运算符重载的时候,返回的是一个引用,为什么要返回一个引用,是因为如果不返回一个引用,不能实现类似++(++a)这种连续的操作.
  4. 后递增运算符重载的时候,怎么和前递增区分,加一个int占位符参数,并且后递增返回的是一个对象,不是一个引用,为什么不返回一个引用,因为后递增返回的是一个局部对象,如果返回一个局部对象的引用,后续就是非法操作了.既然是返回的是一个局部对象,如何保证是对同一个对象操作的呢?因为这个局部对象每次都会调用一次temp=*this的赋值,保证了每次操作的都是当前的对象.

下面创建一个自定义的Integer类来重载一下上面四种运算符

/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/

#include <iostream>
#include <iostream>
using namespace std;
class MyInteger
{
public:
	MyInteger()
	{
		mData = 0;
	}

	// 递增运算符,前递增,返回引用的原因是,可以实现连续的操作,保证连续的操作是同一个对象.
	MyInteger &operator++()
	{
		++mData;
		return *this;
	}

	// 后递增,因为是先返回,然后再递增,所以要创建一个临时的变量保存之前的值,
	// 为了和前自增区分开来,需要使用一个int来占位
	MyInteger operator++(int)
	{
		MyInteger temp = *this;
		mData++;
		return temp; // 不返回引用的原因是temp是一个临时变量,
		//不能返回一个临时变量的引用,后面是非法的操作
	}

	// 前递减
	MyInteger &operator--()
	{
		--mData;
		return *this;
	}

	// 后递减
	MyInteger &operator--(int)
	{
		MyInteger temp = *this;
		mData--;
		return temp;
	}

	// 输出运算符的重载

	friend ostream &operator<<(ostream &cout, MyInteger i);


private:
	int mData;
};

ostream &operator<<(ostream &cout, MyInteger i)
{
	cout << i.mData;
	return cout;
}

int main()
{
	MyInteger a;
	cout << a++ << endl; // 0
	cout << ++(++a) << endl; // 3
	cout << a++ << endl; // 3
	cout << a-- << endl; // 4
	cout << --(--a) << endl;// 1
	system("pause");
	return 0;
}

五. 赋值运算符的重载

一个类在创建的时候,编译器会自动创建一个赋值运算符函数

① 赋值运算符的原型
Obj & operator=(const Obj o){};
② 赋值运算符的例子

和拷贝构造类似,默认的赋值运算符,也是将数据成员进行一一赋值,就是浅拷贝.所以如果成员属性有指针的情况,尽量要重载一下赋值运算符,使用深拷贝代替默认的浅拷贝带来的问题

默认的赋值运算符可能出现的问题

/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/

#include <iostream>
using namespace std;
class Person
{
public:
	Person(int age)
	{
		mAge = new int(age);
	}
	~Person()
	{
		// 堆区的变量要手动的释放掉
		if (mAge != NULL)
		{
			delete mAge;
			mAge = NULL;
		}
	}
public:
	int *mAge;
};

void test_01(void)
{
	Person p1(18);
	Person p2(20);
	p2 = p1; // 堆区的内存重复释放 因为p2 和 p1指向了同一块内存空间.
	cout << "p1的年龄为: " << *p1.mAge << endl;
	cout << "p2的年龄为: " << *p2.mAge << endl;
}

int main()
{
	test_01();

	system("pause");
	return 0;
}

结果:会报错,报堆区内存重复释放,内存非法操作.

实现深拷贝来重载赋值操作符的重载

/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/

#include <iostream>
using namespace std;
class Person
{
public:
	Person(int age)
	{
		mAge = new int(age);
	}
	Person& operator=(const Person& p)
	{
		if (mAge != NULL) // 如果被赋值的那个对象已经存在了,要先释放掉它之前的内存,不然就会造成内存泄漏
		{
			delete mAge;
			mAge = NULL;
		}
		mAge = new int(*p.mAge); // 从新分配一块内存
		return *this;
	}
	~Person()
	{
		if (mAge != NULL)
		{
			delete mAge;
			mAge = NULL;
		}
	}

public:
	int *mAge;
};

int main()
{
	Person p1(10);
	Person p2(20);
	p2 = p1;
	cout << "p2.age = " << *p2.mAge << endl;

	system("pause");
	return 0;
}

六. 关系运算符的重载(== !=)

  1. 关系运算符的重载 == 和 != 应该都是成对出现的
  2. 关系运算符一般都是返回一个bool值
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/


#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
	Person(string name,int age):mName(name),mAge(age)
	{

	}
	bool operator==(const Person p)
	{
		if (this->mName == p.mName && this->mAge == p.mAge)
		{
			return true;
		}
		return false;
	}

	bool operator!=(const Person p)
	{
		if (this->mName == p.mName && this->mAge == p.mAge)
		{
			return false;
		}
		return true;
	}
public:
	string mName;
	int mAge;
};

int main()
{
	Person p1("Fioman", 18);
	Person p2("Fioman", 18);
	Person p3("Fioman", 20);
	cout << "p1 == p2: " << (p1 == p2 ? "相等":"不相等") << endl; 
	// 这里加括号的是因为左移运算符的优先级比==号的优先级高
	cout << "p2 == p3: " << (p2 != p3 ? "不相等":"相等") << endl;
	system("pause");
	return 0;
}

七. 函数调用运算符重载

  1. 函数调用运算符()也可以重载
  2. 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  3. 仿函数没有固定的写法,非常的灵活,可以根据自己的需求,灵活定义
/*----------------------------------------------------------------
* 项目: Classical Question
* 作者: Fioman
* 邮箱: geym@hengdingzhineng.com
* 时间: 2022/3/22
* 格言: Talk is cheap,show me the code ^_^
//----------------------------------------------------------------*/
#include <iostream>
#include <string>
using namespace std;
class MyPrint
{
public:
	void operator()(string s)
	{
		cout << s << endl;
	}
};

void print_func(string s)
{
	cout << s << endl;
}

class MyAdd
{
public:
	// 小括号的重载非常的灵活,可以根据需要定义参数和返回值
	int operator()(int a, int b)
	{
		return a + b;
	}
};


int main()
{
	MyPrint mp;
	mp("你好"); // 很像在调用一个函数,所以也叫仿函数
	print_func("你好"); // 普通的函数调用

	MyAdd add;
	add(3, 4);

	// 可以使用匿名对象来直接使用
	cout << MyAdd()(10, 20) << endl;

	system("pause");
	return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值