运算符重载

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


 

目录

 一、加号运算符重载

分类:

①成员函数重载+号

②全局函数重载+号

二、左移运算符重载

作用:以输出自定义数据类型

三、递增运算符重载 

前置递增

后置递增

总结:前置递增返回引用,后置递增返回值

四、赋值运算符重载

前提:C++编译器至少给1个类添加4个函数

五、关系运算符重载

作用:重载关系运算符,使2个自定义对象进行对比

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


 一、加号运算符重载


“+”号一般代表2个数字之间相加,是内置数据类型
加号运算符重载代表2个自定义数据类型之间相加

分类:

①成员函数重载+号=首先创建代码,对象n1,n2,n3。尝试用n1+n2=n3;

class number
{
public:
	int m_a;
	int m_b;
};
void test01()
{
	number n1;
	n1.m_a = 10;
	n1.m_b = 10;
	number n2;
	n2.m_a = 5;
	n2.m_b = 6;
	number n3;
	n3 = n1 + n2;
}

 运算符重载_第1张图片

 接下来,我们在类中创建加号运算符重载函数operator+

class number
{
public:
	number operator+(number& n) // 加号运算符重载函数
	{
		number tmp;
		tmp.m_a = this->m_a + n.m_a;
		tmp.m_b = this->m_b + n.m_b;
		return tmp;
	}
	int m_a;
	int m_b;
};

运算符重载_第2张图片

实际上number n3 = p1.operator+(p2);;等价于 number n3 = n1 + n2;

成功相加

②全局函数重载+号

同样的,只不过是在类外实现,而且参数是2个对象

number operator+(number& n1, number& n2)
{
	number tmp;
	tmp.m_a = n1.m_a + n2.m_a;
	tmp.m_b = n1.m_b + n2.m_b;
	return tmp;
}

运算符重载_第3张图片

 实际上number n3 = operator+(p1,p2);等价于 number n3 = n1 + n2;

同时,运算符重载也可以发生函数重载

而且,内置数据类型不可以修改


二、左移运算符重载

作用:以输出自定义数据类型

首先创建一个person类与测试函数test01,类中有属性m_a/m_b;

class person
{
public:
	int m_a;
	int m_b;
};

void test01()
{
	person p;
	p.m_a = 10;
	p.m_b = 5;
	cout << p.m_a << endl;
}

如果只是想 输出m_a或m_b,可以直接输出

运算符重载_第4张图片

 不过,如果想直接输出p,则不行

运算符重载_第5张图片

接下来,就可以使用operator函数重载左移运算符<<


不过首先,如果我们想要使用成员函数重载<<运算符,这么写

这样的话,实际调用效果是这样:

p.operator(p) 

而如果参数改成cout:

 实际调用效果是这样:

p.operator(cout) == p<

并非我们要的 cout<

因此,我们要使用全局函数重载

void operator<<(ostream &cout, person &p)
{
	cout << p.m_a << p.m_b;
}

这样实际调用:operator << (cout,p) == cout << p;

运算符重载_第6张图片

 成功输出

不过,如果我们想换行

运算符重载_第7张图片

 因为<<返回的是void不可再输出其他内容。

因此,我们要使它仍然返回cout,修改operator函数

ostream& operator<<(ostream& cout, person& p)
{
	cout << p.m_a << p.m_b;
	return cout;
}

运算符重载_第8张图片

 这样就可以实现换行

甚至还可以随便输出其他内容

运算符重载_第9张图片


而此时,如果我们把属性设为私有,函数将无法访问

这样就可以使用上节课学到的友元来解决

class person
{
	friend ostream& operator<<(ostream& cout, person& p);
	friend void test01();
private:
	int m_a;
	int m_b;
};

三、递增运算符重载 

分为2种:前置递增与后置递增

而且是对我们自定义数据类型进行递增,并非内置数据类型


前置递增

首先创建myInteger类,并创建初始化函数myInteger

这样,在cout时就需要自定义<<左移运算符

class myInteger
{
public:

	myInteger()
	{
		m_a = 5;
	}
	int m_a;
};
ostream& operator<<(ostream& cout, myInteger myint) // 自定义<<左移运算符重载
{
	cout << myint.m_a << myint.m_b;
	return cout;
}
void test01()
{
	myInteger myint;
	cout << ++myint << endl;
}

运算符重载_第10张图片

此时提示没有对应的++运算符


 接下来实现前置++

	myInteger& operator++()
	{
		m_a++; // 先自增
		return *this; // 再返回自身
	}

令 数据自增后返回即可,同时注意要返回自身返回值要引用&

运算符重载_第11张图片


如果返回值不加引用

	myInteger operator++()
	{
		m_a++; // 先自增
		return *this; // 再返回自身
	}

会导致每次返回的是新的对象,不是原来接收的数据本身

运算符重载_第12张图片

 因此要加上&


后置递增

思路差不多,只是需要先保存值,再递增,最后输出保存的值

注意,只要在参数列表里写个int,编译器就会认为是后置递增了,而这个int是占位参数

myInteger operator++(int) // int 占位参数
	{
		myInteger tmp = *this;
		m_a++; // 先自增
		return tmp; // 再返回自身
	}

注意返回值不能写引用&,因为返回的是tmp,是局部变量函数执行完就被销毁了,引用会返回它自身但是已经被销毁了,相当于非法访问

运算符重载_第13张图片

 因此,也不能连续递增(myint++)++


总结:前置递增返回引用,后置递增返回值

而递减与递增基本相同,只需有把++改成--即可


四、赋值运算符重载

前提:C++编译器至少给1个类添加4个函数

①默认构造函数(无参,空实现,函数体为空)

②默认析构函数(无参,空实现,函数体为空)

③默认拷贝构造函数(对属性进行值拷贝)

④赋值运算符operator=(对属性进行拷贝)

当值拷贝时,也会出现堆区内存重复释放的问题


首先,创建person类和测试函数test01

class person
{
public:
	person(int age)
	{
		m_age = new int(age); // 堆区开辟空间存储
	} 
	int *m_age;
};

void test01()
{
	person p1(10);
	cout << *p1.m_age << endl;
	
	person p2(12);
	cout << *p2.m_age << endl;

	p2 = p1;
	cout << *p2.m_age << endl;
}

运算符重载_第14张图片

正常输出

p2=p1是将p1所有属性浅拷贝给p2

而此时,由于我们上面new出的堆区数据需要我们手动释放,因此补全析构函数

~person()
	{
		if (m_age != NULL)
		{
			delete m_age;
			m_age = NULL;
		}
	}

 再次运行

 运算符重载_第15张图片

 因为p1与p2指向同一块m_age,delete一次后又进行delete,造成非法访问

这就是因为浅拷贝造成的堆区内存重复释放的问题

因此,我们赋值时,使用深拷贝,同时要先判断对象是否有属性在堆区,有则释放

	void operator=(person& p1) // 复制运算符的重载
	{
		if (m_age != NULL) // 判断是否有属性在堆区,有则释放
		{
			delete m_age;
			m_age = NULL;
		}
		m_age = new int(*p1.m_age);
	}

运算符重载_第16张图片

 不会再崩溃

但是,如果想连续赋值

运算符重载_第17张图片

 因为函数返回值是void,所以不能实现,因此修改返回值为person&,而且必须是引用才是返回自身,否则返回一个新的对象

person& operator=(person& p1) // 复制运算符的重载,返回引用才是返回自身
	{
		if (m_age != NULL) // 判断是否有属性在堆区,有则释放
		{
			delete m_age;
			m_age = NULL;
		}
		m_age = new int(*p1.m_age);
		return *this;
	}

运算符重载_第18张图片

成功实现 


五、关系运算符重载

作用:重载关系运算符,使2个自定义对象进行对比

而内置数据类型,int等编译器知道如何比对


class person
{
public:
	person(string name, int age)
	{
		m_name = name;
		m_age = age;
	}
private:
	string m_name;
	int m_age;
};
void test01()
{
	person p1("joyce", 22);
	person p2("tatina", 22);
	if (p1 == p2)
	{
		cout << "相等" << endl;
	}
	else
	{
		cout << "不等" << endl;
	}
}

运算符重载_第19张图片

目前是不行的

接下来实现对比函数

bool operator==(person& p)
	{
		if (this->m_age == p.m_age && this->m_name == p.m_name)
		{
			return true;
		}
		return false;
	}

运算符重载_第20张图片

如果要实现不等关系(!=)判断,把==改为!=,判断条件反过来就行  


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


实现一个 打印 类 

class func
{
public:

	void operator()(string line) // 打印操作
	{
		cout << line << endl;
	}
	string line;
};

void test01()
{
	func fun;
	fun("hello,nana");
}

运算符重载_第21张图片

接下来实现一个同样作用的函数

void print(string line)
{
	cout << line << endl;
}

运算符重载_第22张图片

 正常输出,而由于使用非常类似于函数调用,因此又称为仿函数


实现一个 加法 类

class my_add
{
public:
	int operator()(int x, int y)
	{
		return x + y;
	}
};
void test01()
{
	my_add myadd;
	int ret = myadd(100, 200);
	cout << ret << endl;
}

因此,仿函数非常灵活

同时,加上匿名函数对象

void test01()
{
	my_add myadd;
	int ret = myadd(100, 200);
	cout << ret << endl;
	// 匿名函数对象
	cout << my_add()(200, 300) << endl;
}

运算符重载_第23张图片

你可能感兴趣的:(c++)