c++: 运算符重载(运算符重载碰上友元函数、自增自减(++/--)运算符重载、赋值(=)运算符重载、等于和不等于(==、!=)运算符重载、重载&&、||)

 

目录

一、运算符重载基本概念

二、运算符重载碰上友元函数

三、可重载的运算符

四、自增自减(++/--)运算符重载

五、赋值(=)运算符重载

六、等于和不等于(==、!=)运算符重载

七、不要重载&&、||

 八、符号重载总结


 

一、运算符重载基本概念

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

  •     运算符重载(operator overloading)只是一种”语法上的方便”,也就是它只是另一种函数调用的方式。

语法:

  • 定义重载的运算符就像定义函数,只是该函数的名字是operator@,这里的@代表了被重载的运算符。函数的参数中参数个数取决于两个因素。
  1. 运算符是一元(一个参数)的还是二元(两个参数);
  2. 运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数)还是成员函数(对于一元没有参数,对于二元是一个参数-此时该类的对象用作左耳参数)

 

 

二、运算符重载碰上友元函数

友元函数是一个全局函数,和我们上例写的全局函数类似,只是友元函数可以访问某个类私有数据。

案例: 重载左移操作符(<<),使得cout可以输出对象。

class Person{
	friend ostream& operator<<(ostream& os, Person& person);
public:
	Person(int id,int age){
		mID = id;
		mAge = age;
	}
private:
	int mID;
	int mAge;
};

ostream& operator<<(ostream& os, Person& person){
	os << "ID:" << person.mID << " Age:" << person.mAge;
	return os;
}

int main(){

	Person person(1001, 30);
	//cout << person; //cout.operator+(person)
	cout << person << " | " << endl;

	return EXIT_SUCCESS;
}

 

三、可重载的运算符

几乎C中所有的运算符都可以重载,但运算符重载的使用时相当受限制的。特别是不能使用C中当前没有意义的运算符(例如用**求幂)不能改变运算符优先级不能改变运算符的参数个数。这样的限制有意义,否则,所有这些行为产生的运算符只会混淆而不是澄清寓语意。

c++: 运算符重载(运算符重载碰上友元函数、自增自减(++/--)运算符重载、赋值(=)运算符重载、等于和不等于(==、!=)运算符重载、重载&&、||)_第1张图片

 

四、自增自减(++/--)运算符重载

例如当编译器看到++a(前置++),它就调用operator++(a),当编译器看到a++(后置++),它就会去调用operator++(a,int).

class MyInter
{
	friend ostream& operator<<(ostream & cout, MyInter myInt);
public:
	MyInter()
	{
		num = 0;
	} 

	//重载递增运算符
	//前置
	MyInter& operator++()
	{
		//先++
		num++;
		//后返回自身
		return *this;
	}
	//后置 如果在形参中添加int占位参数,编译器可以识别出这是后置++
	MyInter operator++(int)
	{
		//先 记录原来值
		MyInter temp = *this;
		//再 ++ 
		num++;
		//再将记录的值返回
		return temp;
	}

private:
	int num;
};

//全局函数重载<<
ostream& operator<<( ostream & cout , MyInter myInt)
{
	cout << myInt.num;
	return cout;
}

//前置递增
void test01()
{
	MyInter myInt;
	cout << myInt << endl; // 0
	cout << ++(++myInt) << endl; //  2 
	cout << myInt << endl;   // 2
}

//后置递增
void test02()
{
	MyInter myInt;
	cout << myInt << endl; // 0
	cout << myInt++ << endl; // 0
	cout << myInt << endl; // 1
}


int main(){
    test01();
	test02();
	system("pause");
	return EXIT_SUCCESS;
}
  • 优先使用++和--的标准形式,优先调用前置++。
  • 如果定义了++c,也要定义c++,递增操作符比较麻烦,因为他们都有前缀和后缀形式,而两种语义略有不同。重载operator++和operator--时应该模仿他们对应的内置操作符。
  • 对于++和--而言,后置形式是先返回,然后对象++或者--,返回的是对象的原值。前置形式,对象先++或--,返回当前对象,返回的是新对象。其标准形式为:

c++: 运算符重载(运算符重载碰上友元函数、自增自减(++/--)运算符重载、赋值(=)运算符重载、等于和不等于(==、!=)运算符重载、重载&&、||)_第2张图片

调用代码时候,要优先使用前缀形式,除非确实需要后缀形式返回的原值,前缀和后缀形式语义上是等价的,输入工作量也相当,只是效率经常会略高一些,由于前缀形式少创建了一个临时对象。

 

五、赋值(=)运算符重载

编译器会默认给一个类添加4个函数:

  1. 构造函数(空实现)  
  2. 析构函数(空实现)
  3. 拷贝构造(值拷贝)
  4. operator= (值拷贝)

赋值符常常初学者的混淆。这是毫无疑问的,因为’=’在编程中是最基本的运算符,可以进行赋值操作,也能引起拷贝构造函数的调用。

class Person
{
public:
	Person(){ cout << "Person默认构造函数调用" << endl; }

	Person(const char * name) // "Tom"
	{
		cout << "Person有参构造函数调用" << endl;
		this->m_Name = new char[strlen(name) + 1];
		strcpy(this->m_Name ,name);
	}

	Person(const Person & p)
	{
		cout << "Person拷贝构造函数调用" << endl;
		this->m_Name = new char[strlen(p.m_Name) + 1];
		strcpy(this->m_Name, p.m_Name);
	}

	//重载 = 运算符
	Person& operator=(const Person & p)
	{
		//先判断堆区是否有数据,如果有先释放干净
		if (this->m_Name != NULL)
		{
			delete [] this->m_Name;
			this->m_Name = NULL;
		}
		this->m_Name = new char[strlen(p.m_Name) + 1];
		strcpy(this->m_Name, p.m_Name);
		return *this;
	}

	~Person()
	{
		cout << "Person析构函数调用" << endl;
		if (this->m_Name != NULL)
		{
			delete [] this->m_Name;
			this->m_Name = NULL;
		}
	}

	char * m_Name;
};

void test01()
{
	Person p1("Tom");
	Person p2("Jerry");
	Person p3;

	p3 = p1 = p2; //赋值

	cout << "p1的姓名为: " << p1.m_Name << endl;
	cout << "p2的姓名为: " << p2.m_Name << endl;
	cout << "p3的姓名为: " << p3.m_Name << endl;

	Person p4(p3);
}

int main(){
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

 

六、等于和不等于(==、!=)运算符重载

 

class Complex{
public:
	Complex(char* name,int id,int age){
		this->pName = new char[strlen(name) + 1];
		strcpy(this->pName, name);
		this->mID = id;
		this->mAge = age;
	}
	//重载==号操作符
	bool operator==(const Complex& complex){
		if (strcmp(this->pName,complex.pName) == 0 && 
		    this->mID == complex.mID && 
			this->mAge == complex.mAge){
			return true;
		}
		return false;
	}
	//重载!=操作符
	bool operator!=(const Complex& complex){
		if (strcmp(this->pName, complex.pName) != 0 || 
		    this->mID != complex.mID || 
			this->mAge != complex.mAge){
			return true;
		}
		return false;
	}
	~Complex(){
		if (this->pName != NULL){
			delete[] this->pName;
		}
	}
private:
	char* pName;
	int mID;
	int mAge;
};
void test(){
	Complex complex1("aaa", 10, 20);
	Complex complex2("bbb", 10, 20);
	if (complex1 == complex2){ cout << "相等!" << endl; }
	if (complex1 != complex2){ cout << "不相等!" << endl; }
}

 

七、不要重载&&、||

不能重载operator&& 和 operator|| 的原因是:

无法在这两种情况下实现内置操作符的完整语义。

说得更具体一些,内置版本版本特殊之处在于:内置版本的&&和||首先计算左边的表达式,如果这完全能够决定结果,就无需计算右边的表达式了。而这种计算模式模式我们一般无法实现,所以不能重载。

我们说操作符重载其实是另一种形式的函数调用而已,对于函数调用总是在函数执行之前对所有参数进行求值。

class Complex{
public:
	Complex(int flag){
		this->flag = flag;
	}
	Complex& operator+=(Complex& complex){
		this->flag = this->flag + complex.flag;
		return *this;
	}
	bool operator&&(Complex& complex){
		return this->flag && complex.flag;
	}
public:
	int flag;
};
int main(){

	Complex complex1(0);  //flag 0 
	Complex complex2(1);  //flag 1

	//原来情况,应该从左往右运算,左边为假,则退出运算,结果为假
	//这边却是,先运算(complex1+complex2),导致,complex1的flag变为complex1+complex2的值, complex1.a = 1
	// 1 && 1
	//complex1.operator&&(complex1.operator+=(complex2))
	if (complex1 && (complex1 += complex2)){   
		cout << "真!" << endl;
	}
	else{
		cout << "假!" << endl;
	}

	return EXIT_SUCCESS;
}

 

 八、符号重载总结

  • =, [], ()-> 操作符只能通过成员函数进行重载
  • <<>>只能通过全局函数配合友元函数进行重载
  • 不要重载 &&|| 操作符,因为无法实现短路规则

常规建议:

c++: 运算符重载(运算符重载碰上友元函数、自增自减(++/--)运算符重载、赋值(=)运算符重载、等于和不等于(==、!=)运算符重载、重载&&、||)_第3张图片

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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