Day 12 C++面向对象编程 之 运算符重载

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

对于内置的数据类型,编译器知道如何进行运算,但是编译器不知道自定义数据类型的数据的运算,比如一个类的两个对象的成员属性的运算,编译器就不知道怎么算;

可以自己写一个函数进行运算操作,编译器就写了一些通用名称:

一、加号运算符重载 operator+

实现两个自定义数据类型的相加运算:返回数据类型 operator+ (参数列表){运算操作}

//写在类中的
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;
}
...
//Person p3=p1.operator+(p2);
//可以简化为:
Person p3=p1+p2;


//通过全局函数重载“ + ”号
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;
}
//Person p3=operator+(p1,p2);
//也可以简化为:
Person p3=p1+p2;
  • 成员函数重载的本质调用:Person p3=p1.operator+(p2);
  • 全局函数重载的本质调用:Person p3=operator+(p1,p2);
  • 运算符重载也可以发生函数重载,比如将第二个参数p2变成其他数据类型,比如int
Person operator+ (Person &p1,int m)
{
    Person temp;
    temp.m_a=p1.m_a+m;
    temp.m_b=p1.m_b+m;
    return temp;
}
Person p4=p1+77;

对于内资的数据类型(int、double、float…)的表达式的运算符是不可能改变的;不能滥用运算符重载(加就是加,不要把加法写成减法、乘法等等)

二、左移运算符重载 <<

可以输出自定义数据类型:返回数据类型 operator<<(ostream &cout, Person &p){操作}

class Person
{
public:
	Person(int a, int b)
	{
		m_a = a;
		m_b = b;
	}
	friend void operator<<(ostream& cout, Person& p);

private://一般将成员属性写为私有,若要赋值,写内部构造函数即可
	//1.成员函数重载左移运算符
	//void operator<< (cout)//p.operator<<(cout),简化版本是p<<cout,与预想的cout<<p不一致
	//{
	//所以一般不用成员函数重载左移运算符
	//}
	int m_a;
	int m_b;
};
//2.全局函数重载左移运算符
void operator<<(ostream &cout, Person &p)//本质是operator<<(cout,p),简化版本是cout<<p
{
	cout << "p.m_a="<<p.m_a << "p.m_b=" << p.m_b ; 
}
//ostream &operator<<(ostream &cout, Person &p)//本质是operator<<(cout,p),简化版本是cout<<p
//{
//	cout << "p.m_a="<<p.m_a << "p.m_b=" << p.m_b ; 
//    return cout;
//}
void test01()
{
	Person p(7,6);
	//p.m_a = 7;
	//p.m_b = 6;

	//cout << p.m_a << endl;
	cout << p;//重载此符号,返回是void就不能追加别的,若是返回链式,将函数的返回值进行更改为ostream &,以及return cout即可
	//cout << p << endl;
	//cout<<p<<"Hello"<<endl;//后面可以无限追加
}
三、递增运算符重载(只可整型)

通过递增运算符,实现自己的整型数据:

class MyInteger
{
	friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
	MyInteger()
	{
		m_Num = 7;
	}
	//1.成员函数重载前置++运算符
	MyInteger &operator++()//一定返回引用,就是一直对本身进行修改,而不是对复制的值进行修改
	{
		m_Num++;//做++运算,再返回自身
		return *this;
	}
	//1.成员函数重载后置++运算符
	MyInteger operator++(int)//加了int,编译器就能知道这个是后置的++,int表示占位参数,可以用于区分前置与后置运算
	{
		MyInteger temp = *this;
		m_Num++;
		return temp;//这里返回的是局部变量,函数执行完之后,就会被释放,所以,不可再访问,不能用引用,直接返回值
	}
private:
	int m_Num;
};
//重载左移运算符
ostream& operator<<(ostream& cout, MyInteger myint)
{
	cout << myint.m_Num;
	return cout;
}
void test01()
{//前置
	MyInteger myint;
	cout << ++myint << endl;
}
void test02()
{//后置
	MyInteger myint;
	cout << myint++ << endl;
	cout << myint << endl;
}
  • 前置递增:先运算,后输出
  • 后置递增:先输出,再运算
四、赋值运算符重载

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

  • 默认构造函数(无参,函数体为空);
  • 默认析构函数(无参,函数体为空);
  • 默认拷贝构造函数,对属性进行值拷贝;
  • 赋值运算符 operator=,对属性进行值拷贝
class Person
{
public:
	Person(int age)
	{
		m_Age=new int(age);//将传入进来的age参数,进行new化,并将值存放在堆区,然后用本对象的m_Age指向那个堆区的age
	}
	//因为存放在堆区,所以堆区的数据需要程序员手动创造,手动释放,在析构函数中释放
	~Person()
	{
		if (m_Age != NULL)//判断是否为空,若不为空,则释放
		{
			delete m_Age;
			m_Age = NULL;
		}
	}
	void operator=(Person &p)//Person& operator(Person &p)
	{
		//先判断是否有属性存放在堆区,如果有,则先释放干净,再进行深拷贝
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		m_Age = new int(*p.m_Age);//深拷贝
		//return *this;//若想要连等,就返回对象本身
	}
	int *m_Age;//将年龄写成一个指针,用指针指向,年龄真实的值存放在堆区,指针指向堆区里面的地址空间
};
void test01()
{
	Person p1(22);
	Person p2(18);
	p2 = p1;
	//这个复制是简单的值拷贝,因为m_Age是指针,这个属性内部存放是数值在堆区中的位置,所以,p2复制的就是位置地址那一串数字,而在执行析构函数时,这个地址指向的空间在第一次析构时就已经被释放了,在第二次析构时,就会造成非法访问
	//利用深拷贝解决这个浅拷贝的问题,在堆区重新开辟一片空间,存放和p1指向的值一模一样的值,然后p2指向那个新的空间,自己写等号赋值运算
	cout << "p1的年龄:" << *p1.m_Age<<endl;//因为p1.m_Age是一个指针,若要取得内部的指向的值,要进行解引用*
	cout << "p2的年龄:" << *p2.m_Age << endl;
}

p2 = p1;
这个复制是简单的值拷贝,因为m_Age是指针,这个属性内部存放是数值在堆区中的位置,所以,p2复制的就是位置地址那一串数字,而在执行析构函数时,这个地址指向的空间在第一次析构时就已经被释放了,在第二次析构时,就会造成非法访问。利用深拷贝解决这个浅拷贝的问题,在堆区重新开辟一片空间,存放和p1指向的值一模一样的值,然后p2指向那个新的空间。

内置数据类型允许连等的操作,比如c=b=a,重载的话,若返回值是void就不可以连等,但是给返回值类型改为返回对象本身(return *this),就可以进行连等

五、关系运算符重载(==、!=)

让两个自定义对象进行对比操作

class Person
{
public:
	Person(string name,int age)
	{
		m_Name = name;
		m_Age = age;
	}
	//1.成员函数重载
	bool operator==(Person& p)
	{
		if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
		{
			return true;
		}
		return false;
	}
	string m_Name;
	int m_Age;
};
void test01()
{
	Person p1("懒羊羊", 10);
	Person p2("喜羊羊", 13);
	if (p1 == p2)
	{
		cout << "二者相同" << endl;
	}
	else
	{
		cout << "二者不同" << endl;
	}
}class Person
{
public:
	Person(string name,int age)
	{
		m_Name = name;
		m_Age = age;
	}
	//1.成员函数重载
	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 test01()
{
	Person p1("懒羊羊", 10);
	Person p2("喜羊羊", 13);
	if (p1 == p2)
	{
		cout << "二者相同" << endl;
	}
	else
	{
		cout << "二者不同" << endl;
	}
	if (p1 != p2)
	{
		cout << "二者不相同" << endl;
	}
	else
	{
		cout << "二者相同" << endl;
	}
}
六、函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常想函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
class MyPrint
{
public:
	//重载函数调用运算符
	void operator()(string test)
	{
		cout << test << endl;
	}
};
void MyPrint22(string test)
{
	cout << test << endl;
}
//非常灵活,没有固定写法,这里写成一个加法,依照需求可以写成各种各样的
class MyAdd
{
public:
	int operator()(int a, int b)
	{
		return a + b;
	}
};
void test01()
{
	MyPrint myPrint;
	myPrint("你好!懒羊羊!!");//使用起来非常像一个函数,所以称为仿函数
	MyPrint22("你好!懒羊羊!!");//函数
	MyAdd myadd;
	int ret = myadd(7, 7);
	cout << ret << endl;

}

匿名函数对象

cout<<MyAdd()(7,7)<<endl;//类型+括号,是匿名对象,后面再加一个括号是一个函数,变成匿名函数对象
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐