zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C++基础学习笔记----第十三课(操作符重载-下)

C++基础笔记学习 ---- 重载 操作符
2023-09-11 14:20:12 时间

本节主要讲使用成员函数重载操作符,包括[],=,(),->四种操作符的重载以及&&和||的问题。

类的成员函数进行操作符重载

基本概念

类的成员函数也可以进行操作符的重载。类的普通成员函数是被C++编译器默认的隐藏了一个默认的参数this指针,这里的这个this指针指向的是当前的对象。所以在使用类的成员函数在进行操作符重载的时候要比全局函数少一个参数,左操作数不需要使用friend关键字

全局函数重载操作符和成员函数重载操作符的使用场景

①当无法修改左操作数的类时,使用全局函数进行重载。

②=,[],(),->这四个操作符只能通过成员函数进行重载。

基本例程:

 

#include <iostream>

using namespace std;

struct A
{
private:
	int a;
	int b;
public:
	A(int a = 0, int b = 0)
	{
		this->a = a;
		this->b = b;
	}
	
	/*这里使用的成员函数进行操作符重载,因为成员函数有一个默认this指针,所以这里只要求一个参数*/
	A operator+ (A& a2);
	/*{
		A ret;
		ret.a = this->a + a2.a;
		ret.b = this->b + a2.b;
		return ret;
	}*/

	friend ostream& operator<< (ostream& out, const A& d);
};

A A::operator+ (A& a2)
{
	A ret;
	ret.a = this->a + a2.a;
	ret.b = this->b + a2.b;
	return ret;
}

ostream& operator<< (ostream& out, const A& d)
{
	out<<d.a<<"+"<<d.b<<"i"<<endl;
	return out;
}

int main()
{
	A a1 = A(1,2);
	A a2 = A(3,4);

	A a3 = a1 + a2;
	
	cout<<a1<<endl;
	cout<<a2<<endl;
	cout<<a3<<endl;

	return 0;
}

注:在上面的程序中,ostream& operator<< (ostream& out, const A& d)这个<<操作符函数并不能够进行重载,因为这个函数的左操作数ostream& out实际上是一个ostream类的对象,但是这个时候无法修改这个左操作数,如果要以成员函数的方式去重载这个操作符,但是不能够修改ostream这个类,所以无法重载。
通过上面使用成员函数进行了+这个操作符的重载,实际上主函数中的A a3 = a1 + a2;就是调用了operator+这个函数,实际的形式如下:

 

 

A a3 = a1.operator+(a2);

这里实际上就是调用A类对象的a1的operator+这个函数,参数是a2这个对象。来完成两个类的相加的操作符重载。

 

=,[],比较操作符的重载

[]数组操作符的重载

数组操作符的重载实际上是为了编程的方便,同时也是为了方便程序的查看和使用。

以第九课的博文点击打开链接中的数组类为例:

在A类中定义数组操作符([])的重载函数,具体代码如下:

 

int& A::operator[] (int i)
{
	return ap[i];
}

通过上面的代码,在主函数中不需要再使用b.getdata(j)getdata函数不需要继续使用了,因为可以直接通过b[j]的数组操作符重载函数得到j这个位置所对应的数组成员。

 

在主函数中a[i]的实际上就是:

 

a.operator [](i)

通过上面的程序可以看出, 数组操作符的重载函数的返回值是int& 而不是int,如果这里的数组返回值是int,那么其实这里返回的是一个整数,无法作为左值使用。所以如果一个函数的返回值想要作为左值使用就必须返回引用。

 

=赋值操作符的重载

在完成数组类的程序中定义了数组类的对象a,并通过构造函数对对象a进行初始化。A a = 5;然后再通过类A定义对象b,并初始化A b = 0;如果执行下列代码:
b = a;
这个时候程序是可以编译通过的,但是当调用拷贝函数的时候将会挂掉。原因如下: C++编译器为每一个类提供了默认的赋值操作符的重载,这个重载的机制就是当类的对象使用赋值操作符的时候只是进行简单的值复制。所以这个时候实际上a对象和b对象指向同一块内存空间,所以在调用析构函数的程序会释放相同的一块内存空间,所以程序将会挂掉。
在A类中定义赋值操作符(=)的重载函数,具体代码如下:
A& A::operator= (A& aj)
{
	/*
		这里先释放ap指针原来指向的空间,这里的ap指针属于这个类,也就是外界的对象都是通过这个指针
		来申请空间的,实际上这个指针可以说是公用的。
	*/
	delete []ap;

	/*重新为数组分配空间,然后再进行赋值操作*/

	alength = aj.alength;
	ap = new int[alength];
	
	for (int i = 0; i < alength; i++)
	{
		ap[i] = aj.ap[i];
	}
	
	/*返回this指针所对应的内容,实际上就是自己这个对象*/
	return *this;
}
注:这里的重载函数返回到是A这个类的引用,如果这里的函数返回值的类型是void,那么如果在主函数中调用a = b = c。根据C语言赋值规则,从右向左进行赋值,a = b = c的中的赋值符号经过重载后的原型如下:
c.operator= (b.operator= (a));
或者
c = b.operator= (a);
通过c = b.operator= (a);所以如果这个函数返回的类型是void,所以这里将会给void类型赋值给一个A类的对象c,所以程序将会崩溃。

比较操作符的重载

代码如下:
bool A::operator== (A& aa)
{
	bool ret = true;
	if (alength == aa.alength)
	{
		for (int i = 0; i < alength; i++)
		{
			if (ap[i] != aa.ap[i])
			{
				ret = false;
				break;
			}
		}
	}
	else
	{
		ret = false;
	}
	return ret;
}

附上经过改进的数组类:

main.cpp

 

#include <stdio.h>
#include "a.h"

int main()
{
	A a = 5;
	//A b = a;
	
	A b = 0;

	A c = 0;
	/*A c = 5;*/

	for (int i = 0; i < a.getlength(); i++)
	{
		/*
			通过[]重载后这里a[i]返回的是ap指向的这块内存中第i个元素的引用
			然后再使用for循环中的i进行初始化。
		*/
		a[i] = i;
		printf ("%d\n",a[i]);

		/*数组操作符函数重载的原型*/
		/*printf ("%d\n",a.operator [](i));*/
	}

	c = b = a;

	/*经过操作符的重载之后,可以直接对比这两个数组的对象来比较这两个数组是否相等*/
	if (a == b)
	{
		printf ("123\n");
	}
	else
	{
		printf ("CYX\n");
	}

	/*c.operator= (b.operator= (a));
	c = b.operator= (a);*/

	/*A b = a;*/
	for (int j = 0; j < b.getlength(); j++)
	{
		printf ("%d\n",b[j]);
	}
	
	for (int k = 0; k < c.getlength();k++)
	{
		printf("%d\n",c[k]);
	}

	
	return 0;
}
a.cpp

 

 

#include <stdio.h>
#include "a.h"

A::A(int length)
{
	if (length < 0)
	{
		length = 0;
	}
	alength = length;
	ap = new int[alength];
}

A::A(const A& aj)
{
	alength = aj.alength;
	ap = new int[alength];
	
	for (int i = 0; i < alength; i++)
	{
		ap[i] = aj.ap[i];
	}
}

int A::getlength()
{
	int ret = 0;

	ret = alength;

	return ret;
}

/*
	这里返回的是int类型的引用,因为如果返回的int类型,那么将是具体的数值,这个具体
	数值将不能够作为左值使用的,如果要是引用就可以作为左值使用。
*/
int& A::operator[] (int i)
{
	return ap[i];
}

A& A::operator= (A& aj)
{
	/*
		这里先释放ap指针原来指向的空间,这里的ap指针属于这个类,也就是外界的对象都是通过这个指针
		来申请空间的,实际上这个指针可以说是公用的。
	*/
	delete []ap;

	/*重新为数组分配空间,然后再进行赋值操作*/

	alength = aj.alength;
	ap = new int[alength];
	
	for (int i = 0; i < alength; i++)
	{
		ap[i] = aj.ap[i];
	}
	
	/*返回this指针所对应的内容,实际上就是自己这个对象*/
	return *this;
}

/*返回值只要求分辨这两个数组是否相等*/
bool A::operator== (A& aa)
{
	bool ret = true;
	if (alength == aa.alength)
	{
		for (int i = 0; i < alength; i++)
		{
			if (ap[i] != aa.ap[i])
			{
				ret = false;
				break;
			}
		}
	}
	else
	{
		ret = false;
	}
	return ret;
}

A::~A()
{
	alength = -1;

	delete []ap;
}
a.h

 

 

#ifndef _A_H
#define _A_H

struct A
{
private:
	int alength;
	int* ap;
public:
	A(int length);
	A(const A& aj);
	int getlength();
	int& operator[] (int i);
	A& operator= (A& aa);
	bool operator== (A& aa);
	~A();	
};


#endif

 

++,--操作符的重载

++操作符只有一个操作数,而且操作符有前缀(++a)和(a++)之分。如果要重载这个操作符,可以通过一个函数的占位参数来区分前缀和后缀。 后缀的操作符带有占位参数,前缀的操作符不具有占位参数。

根据C语言中的规则,a++的含义是先返回a变量的值,然后再对a变量进行自加操作。

 

例程如下:

 

#include <stdio.h>

class A
{
private:
	int a;
	int b;
public:
	A(int i)
	{
		a = i;
		b = i;
	}
	void plus()
	{
		a++;
		b++;
	}
	void print()
	{
		printf("a = %d, b = %d\n",a,b);
	}
	A& operator++(int);
	A& operator++();
	A& A::operator--(int);
	A& A::operator--();
};

A A::operator++(int)
{
	/*首先将当前的对象值返回,这是a++的规则*/
	A ret = *this;
	/*然后分别对a成员和b成员进行自加*/
	a++;
	b++;

	return ret;
}

A& A::operator++()
{
	++a;
	++b;

	/*最后都要返回操作后的本身的值*/
	return *this;
}

A A::operator--(int)
{
	A ret1 = *this;
	--a;
	--b;

	return ret1;
}

A& A::operator--()
{
	a++;
	b++;

	return *this;
}


int main()
{
	A a = 1;
	A b = 1;
	
	/*a.plus();
	a.print();
	b.plus();
	b.print();*/

	/*a++;
	b++;
	a.print();
	b.print();*/

	/*++a;
	++b;
	a.print();
	b.print();*/

	/*a--;
	b--;
	a.print();
	b.print();*/

	--a;
	--b;
	a.print();
	b.print();

	return 0;	
}

 

注:重载++运算符的时候,前置比后置的++效率高很多。类的成员函数,隐含了一个this指针。操作符重载函数也不例外,也隐含了一个this指针。这个this指针是一个引用类型,所以,在返回的时候,也返回这个引用类型。而后置++操作,在函数中进行了一次值的拷贝,返回的不再是this指针指向的对象,而是一个副本。所以返回的就是一个类对象。

不要重载&& 和 ||运算符

 

1.从C++语法的角度来说,&&和||这两个运算符可以进行重载。

2.&&和||这两个运算符内置了短路规则(短路规则就是当某一个条件满足的时候就不再执行,在单个变量的时候不会发生太多问题,但是涉及到函数返回值的时候将会发生问题)。操作符重载是依靠函数重载来完成的。C++的函数参数都会被返回一个值,所以无法实现短路规则。

基本例程如下:

 

#include <stdio.h>

class A
{
private:
	int a;
public:
	A(int i)
	{
		a = i;
	}
	void print()
	{
		printf("a = %d\n",a);
	}
	bool operator|| (A& aa)
	{
		printf("123\n");

		return this->a && aa.a;
	}
	A& operator+ (A& aa)
	{
		A ret = 0;
		ret.a = this->a + aa.a;
		printf ("bugai\n");
		return ret;
	}
};

int main()
{
	A a = 1;
	A b = 1;
	
	if (a || (a + b))
	{
		printf ("CYX\n");
	}
	return 0;	
}

 

程序打印结果如下:


当程序运行时首先执行if(a && (a + b)),如果a,b是两个真正的变量,那么当判定a为真的时候程序将会直接打印CYX,并不会再继续判定(a + b),但是将||运算符进行重载之后,if(a && (a + b))的实际格式如下:

 

if (a.operator|| (a.operator+ (b)))

所以这个判定是先从内层进行判定,然后根据+重载函数的返回值作为参数再调用||运算符重载函数,所以程序打印结果如上图。