c++基础(十三)——c++运算符重载

一、运算符重载

对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

二、不同运算符的重载方法

(一)、加号运算符重载

(1)、通过成员函数进行重载

实现两个自定义数据类型相加的运算符。
对于内置数据类型,编译器知道如何进行运算,如下列代码:

int a = 10;
int b = 10;
int c = a + b;

而当我们做一个自定义数据类型时,编译器并不知道要进行怎么运算,这时候就需要我们进行一个成员函数重载+了,如下代码

class Person
{
public:
	int m_A;
	int m_B;
}

Person p1;
p1.m_A = 10;
p1.m_B = 10;

Person p2;
p2.m_A = 10;
p2.m_B = 10;

Person p3 = p1 + p2;

则不会实现该功能,因此需要我们自己实现一个相加的运算操作。

Person PersonAndPerson(Person &p)
{
	Peson temp;
	temp m_A = this->m_A + p.m_A;
	temp m_B = this->m_B + p.m_B;
	return temp;
	
}

我们如果每次实现该功能都需要自己实现运算的话,则十分费力,这时编译器则已经给我们提供好了一个重载类的方法:

operator+()

当我们使用这个方法时,编译器会将Person p3 = p1.operator+(p2)简化为Person p3 = p1 + p2;这就是加号重载的一个方法。
样例如下:

class Person
{
public:
	Person operator+(Person &p)
	{
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_B + p.m_B;
		return temp;
	
	}

	int m_A;
	int m_B;

};

(2)、通过全局函数进行重载

Person PersonAndPerson(Person &p1, Person &p2)
{
	Peson temp;
	temp m_A = p1.m_A + p2.m_A;
	temp m_B = p1.m_A + p2.m_B;
	return temp;
	
}

如果使用operator+()方法来通过全局函数进行重载时,我们可以通过Person p3 = operator+(p1,p2),编译器会将Person p3 = p1.operator+(p2)简化为Person p3 = p1 + p2;
样例如下:

class Person
{
public:

	int m_A;
	int m_B;
};

Person operator+(Person &p1, Person &p2)
{
	Person temp;
	temp.m_A = p1.m_A + p2.m_A;
	temp.m_B = p1.m_B + p2.m_B;
	return temp;
}

(3)、运算符重载也可以发生函数重载

样例如下:

Person operator+(Person &p1, int num)
{
	Person temp;
	temp.m_A = p1.m_A +num;
	temp.m_B = p1.m_B + num;
	return temp;

}

本节总结:
1、对于内置的数据类型的表达式的运算符是不可能发生改变的
2、不要滥用运算符重载

(二)、左移运算符重载

作用:可以输出自定义数据类型
在我们之前的学习过程中,c++输出时采用的语法是cout<< " a"<<endl;
当我们输出一个位置类型的变量时,比如一个类时,会报错:没有与这些操作数匹配的<<运算符。这时候就需要我们调用左移运算符重载了。
在加号运算符重载中我们可以知道,可以通过成员函数以及全局函数进行重载,但是当我们使用左移运算符重载时,我们需要传递一个cout参数,语法如下:

p.operator<<(cout)

简化后则变成了p<<cout,不符合我们平时的语法定义,故左移运算符不可通过成员函数进行重载。
全局函数重载左移运算符:

class Person
{
	friend ostream &operator<<(ostream &out, Person &p);
public:
	Person(int a, int b)
	{
		m_A = a;
		m_B = b;
	}

private:

	int m_A;
	int m_B;

};

ostream &operator<<(ostream &out, Person &p)
{
	out << "m_A" << p.m_A << "m_B" << p.m_B;
	return out;
}

void test10() {
	Person p1(10, 10);
	cout << p1<<"hello"<< endl;


}

解释一下上面的代码,针对全局函数实现左移运算符重载,往往需要将属性私有化,避免对属性干扰。这时候我们就需要对属性私有化,但是在全局函数中调用私有属性时,我们需要使用友元的方法来实现调用,调用方式则为在类内上标出:friend ostream &operator<<(ostream &out, Person &p);,以上的语法中ostream代表的是输入流。而一旦属性私有化之后,在对其进行赋值则需要通过构造函数进行赋值了。

(三)递增运算符重载

在介绍递增运算符重载之前,先讲一下递增运算符的基本运算方法。有如下代码:

int a = 10;
cout<<++a<<endl; // a = 11
cout<<a<<endl; // a = 11

int b = 10;
cout<<b++<<endl; // b = 10
cout<<b<<endl; // b = 11

这是递增运算符的一个运算方法。下面介绍前置递增运算符重载的一个基本语法演示:

class Myint
{
friend ostream& operator<<(ostream& cout, Myint myint);
public:
	Myint()
	{
		m_Num = 0;
	}
	// 递增运算符的重载,返回引用为了一直对一个数据进行递增
	Myint& operator++()
	{	
		m_Num++;	
		return *this;
	}
private:
	int m_Num;
};

ostream& operator<<(ostream& cout, Myint myint)
{
	cout << myint.m_Num;
	return cout;
}

void test11()
{
	Myint myint;
	cout << "myint" << ++myint << endl;
}

后置运算符样例:

	Myint operator++(int)
	{
		Myint temp = *this;
		m_Num++;
		return temp;
	}

下面复习一下函数重载的满足条件:
1、在同一个作用域下;
2、函数名称一样;
3、参数的个数、类型等不一样
那么为什么前置递增返回的是一个引用,而后置递增返回的是一个变量呢?
因为前置递增时,为了重复递增都针对同一块内存进行操作,所以要通过一个全局变量对其进行返回,而后置递增返回的是一个暂存的局部变量,如果调用局部变量的引用则会报错。

(四)、赋值运算符重载

在分析赋值运算符重载之前先简单复习一下c++编译器在创建一个类时,要添加的4个函数:
1、默认构造函数(无参,函数体为空);
2、默认析构函数(无参,函数体为空);
3、默认拷贝构造函数,对属性进行值拷贝;
4、赋值运算符operator=,对属性进行值拷贝。
如果类中有属性指向堆区,做赋值操作时,也会出现深浅拷贝的问题。
样例如下:

class Person
{
public:
	Person(int Age)
	{
		//将这个变量开辟在堆区,并且返回一个指针	
		m_Age = new int(Age);
	}
	~Person()
	{	
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}	
	}
	//定义一个指针,来管理指针指向的内存
	int *m_Age;
};


void test13()
{
	Person p1(10);
	Person p2(20);
	p1 = p2;
	cout << "p1的年龄为" << *p1.m_Age << endl;
	cout << "p2的年龄为" << *p2.m_Age << endl;
}

对以上的代码进行一个解析,当运行test13时,会创建两个对象,一个p1,一个p2。创建p1时,由于我们的person类的m_Age是存放在堆区的,存放在堆区的内存需要程序员手动的开辟,并且需要手动的清除。当我们通过p1 = p2这一浅拷贝操作时,实际上是将p1的m_Age所存放的地址例如(0x0011),拷贝到了p2的m_Age中,也就是p1与p2的m_Age所保存的堆区地址实际是一个。当手动清除指针时,p1清除完之后,再清除p2时会引发异常: 0xC0000005: 写入位置 0xFDFDFDFD 时发生访问冲突。这是以上代码的问题所在。为了解决这一异常,我们需要对将浅拷贝改为深拷贝。
下面为样例代码:

class Persons
{
public:
	Persons(int age)
	{
		//将这个变量开辟在堆区,并且返回一个指针	
		m_Age = new int(age);
	}
	//析构函数,当堆区开辟的内存使用完成之后将其释放
	~Persons() 
	{
		//如果保存指针地址的变量不为空
		if (m_Age != NULL) 
		{
		//将其清除并且置为空指针
		delete m_Age;
		m_Age = NULL;
		}
	}

	//这个操作要返回自身,通过*this来实现,进而实现重复使用该块内存
	Persons& operator=(Persons &p)
	{ 
		//编译器提供的是一个浅拷贝
		// m_Age = p.m_Age

		//先判断是否有数值在堆区,如果有,则释放干净,然后再深拷贝
		if (m_Age!= NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//深拷贝
		m_Age = new int(*p.m_Age);
		return *this;
	}
	//定义一个指针,来管理指针指向的内存
public:
	int *m_Age;
};


void test13()
{
	Persons p1(10);
	Persons p2(20);
	Persons p3(20);
	p3 = p2 = p1;
	cout << "p1的年龄为" << *p1.m_Age << endl;
	cout << "p2的年龄为" << *p2.m_Age << endl;
	cout << "p3的年龄为" << *p2.m_Age << endl;

}

(五)、关系运算符重载

重载关系运算符,可以让两个自定义类型对象进行对比操作,样例如下:

class person
{
public:
	//初始化类属性
	person(string name, int age)
	{
		m_name = name;
		m_age = age;	
	}
	//重载相等符号
	bool operator==(person &p)
	{
		if (this->m_name == p.m_name && this->m_age == p.m_age)
		{
			return true;
		}
		return false;
	
	}
	//重载不相等符号
	bool operator!=(person &p)
	{
		if (this->m_name == p.m_name && this->m_age == p.m_age)
		{
			return false;
		}
		return true;
	}

	//类属性名称
	string m_name;
	int m_age;
};

void test14() {

	person p1("aaa", 18);
	person p2("bbb", 18);
	if (p1 == p2)
	{
		cout << "p1和p2是相等的" << endl;
	}
	if (p1 != p2)
	{
		cout << "p1和p2是不相等" << endl;
	}
}

(六)、函数调用运算符重载

1、函数调用运算符()也可以重载;
2、由于重载后使用的方式非常像函数的调用,因此称为仿函数;
3、仿函数没有固定写法,非常灵活
具体样例如下:

class MyPrint
{
public:
	void operator()(string test)
	{	
		cout << "test" << test <<endl;	
	}
};

void test15() {
	MyPrint myprint;
	myprint("hwll");
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值