C++7 --- 运算符重载

一、运算符重载

1、为什么需要运算符重载

原因:

目的是让类类型的对象基本数据类型一样去操作,例如可以实现对象+、-、*、/、%、==、!=、[]、() 、= 、<<、 >>

class A
{
public:
	A(int i = 0):m_i(i){}
private:
	int m_i;
}
class STR
{
public:
	STR(const char* str = "\0")
	{
		m_str = new char[strlen(str) + 1];
		//strcpy(m_str,str);//高级编译器会提示不安全
		strcpy_s(m_str,strlen(str) + 1,str);
	}
private:
	char *m_str;
}
void main()
{
	//A a(5),b(7);
	//STR s("123456");
	
	//想要实现以下代码,使其能像基本类型一样就行相加操作
	//s[2] = 'k';
	//cout<<a+b<<endl;//error
	//cout << a.m_i + b.m_i << endl; //error,m_i为私有成员

	//例如库函数string类:  (头文件:#include <string> )
	string str = "123456789";
	cout << str << endl;
	str[3] = 'k';
	cout << str << endl;

	string s1 = "123",s2 = "abc";
	string s3 = s1 + s2;
	cout << s1 << "+" << s2 <<"=" <<s3 <<endl;

	//例如基本数据类型
	int aa = 10,bb = 20;
	cout<<aa + bb<<endl;
}

运行结果:

在这里插入图片描述

强行实现(可实现,但可读性和视觉效果很差):

class A
{
public:
	A(int i = 0,int j = 0):m_i(i),m_j(j){}
	const int &GetI()const
	{
		return m_i;
	}
	const int &GetJ()const
	{
		return m_j;
	}
	A Add(const A& b)//声明一个Add函数,将传进来的数据进行相加
	{
		return A(m_i + b.m_i,m_j + b.m_j);
	}
	void Print()
	{
		cout<<m_i<<" "<<m_j<<endl;
	}
private:
	int m_i;
	int m_j;
};
void main()
{
	A a(5),b(7);
	//想要实现如下操作:(想要类像基本类型一样去操作)
	int aa = 10,bb = 20;
	cout << aa + bb << endl;

	//cout<<a + b<<endl;//error
	//cout<<a.m_i + b.m_i<<endl;//error

	//以下代码可行,但可读性和视觉效果很差
	cout<<"i="<<a.GetI() + b.GetI()<<endl;
	cout<<"j="<<a.GetJ() + b.GetJ()<<endl;

	a.Add(b).Print();
}

2、使用前提1:值返回 、引用返回

返回的都是对象:(a 和 b 是个对象)
a+b、 a-b 、a*b 、a/b …
a=b、 a[4] 、cout<<a

值返回

会产生拷贝构造
所以一般避免值返回,能引用返回就引用返回;
只能作为=表达式的右边,则必须值返回

引用返回

结论:

只能作为=(赋值)表达式的右边,则必须值返回;
如果可以作为=(赋值)表达式的左边,建议引用返回;

代码理解:

void main()
{
	int a = 10, b = 3;
	int c = 0;
	c = a + b;//这句话的意思:从a的内存空间把a的值取出来,从b的内存空间把b的值取出来,然后把两个值进行相加,将相加后的值将其放在左边,写入c的内存空间
	//其中a + b的表达式只能放在左边
	//a + b = c;//error,这句话的意思为:将c的值从其确定的内存单元中取出来,放入到左边(将a的值将其确定的内存单元中取出来,将b的值从其确定的内存单元中取出来,然后将这两个值进行相加,生成一个没有确定内存单元的临时值),将a的值写入到这个临时值当中,
	//但临时空间只能进行临时存放,不能最终结果存放

	++a = c;//前置++,先执行++,再执行=      所以a变为11,在进行赋值, 左边的表达式为11 ,将c的值放到a的内存单元 ,++a返回的是引用,返回的是对象本身,可以作为左值
	//b++ = c;//error,b++求的是b++表达式的值,不是b不是,其值在临时空间存放着,

	cout<<a<<endl;//a的值为c的值

	(a = b) = c;//=(赋值号)可以做为左值 ,先执行小(),先将b的值给a,所以最终结果再a的内存空间存放在,再执行a=c,将c的值写入a的内存空间
}

3、使用前提2:a + b 的解读

对于 a + b ;
可以被解读为两种形式:
1)a + (b)
可以用a对象区调用 + ,再将b对象传进去;
能用a对象去调用的,说明这种形式是重载成成员形式
2) +(a,b)
调 + ,传两个参数,一个 a对象,一个b对象。
这个形式是我们的非成员形式,是普通函数,需要调用到有源

4、运算符重载基础使用

形式:

返回类型 operator 运算符(参数列表)

使用形式示例:

//返回类型 operator 运算符(参数列表)
//   |       |        |     |
//   A    operator   +   (const A &b)

//对a+b 的运算符重载
A operator+(const A &b) //a+b a.operator+(b)本质即是实现对象的数据成员的加法
{
	
}

示例代码:

class A
{
public:
	A(int i = 0):m_i(i){}
	void Print()
	{
		cout<<m_i<<endl;
	}
	//重载函数
	A operator+(const A &b) //a+b a.operator+(b)本质即是 实现对象的数据成员的加法
	{
		return m_i + b.m_i;//返回值为整型,类类型可以实现类型转换
	}
	//A operator-(const A &a) //b-a   b.operator-(a)	
	//{
	//	return this->m_i - a.m_i;
	//}
	A operator-(const A &s) //b-a   b.operator-(a)	 s接收第二个操作数
	{
		return this->m_i - s.m_i;
	}
	A& operator++() //++a  a.++()
	{
		++m_i;         //将a自己的数据成员进行修改
		return *this;  //修改完后,返回自己本身
	}
	/*
		b++,表达式的值是b没有加以前的值,表达式执行完后,b的值变成加1之后的值,
		----所以在重载后++的时候,得体现出两种值
	*/
	A operator++(int) //C++规定,在后++的时候,参数里面写个int,这个int没有实际作用,只是为了区分于前++
	{
		//int t = m_i;   //先保存以前的值
		//m_i = m_i +1;  //自己本身加1
		//return t;      //返回没加以前保存的值

		//等价于
		return m_i++;
	}
private:
	int m_i;
};
void main()
{
	A a(2),b(10);
	//a + b;//a.+(b)     +(a,b)
	(a + b).Print(); //a.+(b) +(a,b)  12
	(b - a).Print(); //a.-(b)   8
	(a - b).Print(); //a.-(b)   -8
	(++a).Print();  //a.++()    3
	a.Print();  //值为3,++a将值改变   3
	(b++).Print(); //表达式b++的值,是b,应该输出没有加以前的值 为10,但这为11,表面其重载的是前++    10
	b.Print();  //b的值是上面自加后的值  11
}

运行结果:

在这里插入图片描述

5、强行实现 a+b = c; (只做测试,会违背平时的规则)

//-----如下写,只做测试讲解,会违背平时的规则
#if 1class A
{
public:
	A(int i = 0):m_i(i){}
	void Print()
	{
		cout<<m_i<<endl;
	}
	A &operator +(A &t) //a+b
	{
		m_i = m_i + t.m_i;  //thie->m_i = this->m_i+ b.m_i   相当于 a.m_i = a.m_i + b.m_i
		return *this;       //return a
	}
private:
	int m_i;
};
//强行实现 a+b = c; 
//要如此写,a+b必须有确定的内存单元,可以将结果放到a或者b中
void mian()
{
	A a(7),b(10);
	(a+b).Print();
	a.Print();
	b.Print();
	((a+b) = 6).Print();
}

6、类中默认的赋值运算符重载函数

示例代码:

//类中有默认的赋值运算符重载函数
/*
如果程序员没有提供赋值运算符重载,则类会提供一个默认的,默认的功能为:
  用右边的对象的数据成员依次给左边对象的数据成员赋值
*/
#if 1
class A
{
public:
	A(int i = 0, int j = 0) :m_i(i), m_j(j) {}
	void Print()
	{
		cout << m_i << " " << m_j << endl;
	}
private:
	int m_i;
	int m_j;
};

void main()
{
	A a(2, 6);
	A b;
	a.Print();  //2 6
	b.Print();  //0 0
	b = a;  //用a的值重新给b赋值,调用了默认的赋值运算符重载函数
	b.Print();
}

#endif

运行结果:

在这里插入图片描述

7、为什么要自己写运算符重载函数 – 指针

示例代码:

//为什么要自己写运算符重载函数
/*
总结:
如果有指针作为数据成员,则赋值运算符重载也要写出来,让两个不同的对象的指针分别指向内存单元,不过里面的内容保持相同

赋值=能作为左值,所以用引用返回
a = b
*/
#if 1
class Person
{
public:
	Person(const char *name = "\0")
	{
		m_name = new char[strlen(name) + 1];
		strcpy_s(m_name,strlen(name)+1,name);

	}
	void Print()
	{
		cout << m_name <<endl;
	}
	~Person()
	{
		delete[] m_name;
		m_name = NULL;

		//cout << "~ " << endl;//用于显示的表明析构的调用,用于代码观察
	}
	Person &operator=(const Person& s)//可认为 析构+拷贝构造
	{
		if(this == &s)  //判断自赋值
			return *this;
		//先将左边对象原有的内存空间释放掉
		delete[]m_name;
		//接着开辟和右边对象s相同大小的内存单元
		m_name = new char[strlen(s.m_name) + 1];
		strcpy_s(m_name,strlen(s.m_name)+1,s.m_name);

		return *this;

	}	
	/*
	Person &operator=(const Person& s)//可认为 析构+拷贝构造
	{
		if(this != &s)//判断自赋值
		{
			//创建一个临时实例tmp,在当前{}结束后,会自动调用自己的析构函数释放相关内存
			Person Tmp(s);

			//把Tmp.m_name 和实例自身的m_name进行交换  当临时变量Tmp析构时,因为进行了交换,所以会释放原来m_name的内存
			char* tmp = Tmp.m_name; 
			Tmp.m_name = m_name;
			m_name = tmp; 
		}
	}
	*/
private:
	char *m_name;
};
void main()
{
	Person p1("lisi");
	Person p2("zhangsan");
	p1.Print(); //lisi
	p2.Print(); //zhangsan
	p2 = p1;  //error 目的是用p1 给p2 重新赋值  
	//如果调用默认的赋值重载,会报错,会使得p2的指针重新指向了p1的指针所指向的空间"lisi"
	//p1和p2的指针指向同一块空间,但是p2原本的空间还在,导致函数释放时,释放了两次这个内存单元,而p2原本所指向的空间反而没有没释放
	//用p1给p2重新赋值,如果调用默认的赋值函数,则让p2的指针重新指向了p1的指针
	p2.Print();
	
	//cout << "main end " << endl;
}
#endif

运行结果:

在这里插入图片描述

调用默认赋值函数的结果:

可以输出结果,但再再{}结束前会出错;
而且可以发现是再析构p2的时候出现了问题

在这里插入图片描述

原因:

如果调用默认的赋值重载,会报错,会使得p2的指针重新指向了p1的指针所指向的空间"lisi"
p1和p2的指针指向同一块空间,但是p2原本的空间还在,导致函数释放时,释放了两次这个内存单元,而p2原本所指向的空间反而没有没释放
用p1给p2重新赋值,如果调用默认的赋值函数,则让p2的指针重新指向了p1的指针

在这里插入图片描述

8、运算符重载的使用 — 带指针 :+、[]、==

示例代码:

class STR
{
public:
	STR(const char *str = "\0")
	{
		m_str = new char[strlen(str) + 1];
		strcpy_s(m_str,strlen(str) + 1,str);
	}
	STR(const STR& s)//拷贝构造函数 ,用旧对象去构造新对象
	{
		m_str = new char[strlen(s.m_str) + 1];
		strcpy_s(m_str,strlen(s.m_str) + 1,s.m_str);
	}
	~STR() //析构函数
	{
		delete[]m_str;
		m_str = NULL;

		/*if(m_str != NULL)
		{
			delete[]m_str;
			m_str = NULL;
		}*/
	}
	STR operator+(const STR& s) //a+b 实际就是将两个对象中的字符串合并
	{
		
		//正确方法
		char* temp = new char[strlen(m_str) + strlen(s.m_str) + 1];
		strcpy_s(temp,strlen(m_str) + 1,m_str);
		//strcat也是不安全,串接
		strcat_s(temp,strlen(m_str) + strlen(s.m_str) + 1,s.m_str);//第二个参数是拼接后的字符串大小
		STR ss(temp);
		delete[]temp;//在出当前{}前,要将其temp这个新开辟的局部变量释放
		temp = NULL;
		return ss;
		
		
		/*可强行实现方法,但不合适,会出现问题,需要在析构函数内加if(m_str != NULL)判断才可行,否则赋值可正常运行,但运行完会报错

		STR ss;//在退出这个函数,这个对象也需要析构,但出错没有析构
		delete[]ss.m_str;//需要将其原本的空间释放掉
		ss.m_str = new char[strlen(m_str) + strlen(s.m_str) + 1];
		strcpy_s(ss.m_str,strlen(m_str) + 1,m_str);
		strcat_s(ss.m_Str,strlen(m_str) + strlen(m_str) + 1,s.m_str);
		temp = NULL;
		return ss;
		*/
	}

	//----- 常考题 -----
	STR& operator=(const STR& s) //要考虑空间是否够,不知道赋值对象的大小,所以不能直接赋值
	{
		//判断自赋值
		if(this == &s) //5分
			return *this;

		//先释放,再开辟
		delete[]m_str; //3分

		m_str = new char[strlen(s.m_str) +  1];
		strcpy_s(m_str,strlen(s.m_str) + 1,s.m_str);
		return *this;
	}
	void Print()
	{
		cout << "m_str = " <<m_str << endl;
	}
	char& operator[](int index)
	{
		if(index >= 0 && index < strlen(m_str)) //"1234" =的话,不行只能取出123,所以是<
			return m_str[index];
	}
	bool operator==(const STR& s)
	{
		//return strcmp(m_str, s.m_str);//不可行,没有值输出

		return strcmp(m_str, s.m_str)==0;

		/*
		if (strcmp(m_str, s.m_str) == 0)
		{
			cout << "yes" << endl;
			return 1;
		}
		else
		{
			cout << "no" << endl;
			return 0;
		}*/
	}
private:
	char *m_str;
};
void main()
{
	STR a("111"),b("222");
	STR c = a + b; //a+b 拷贝构造    --思路,先开辟总大小的内存单元,在逐个将两个值写入,再
	//STR d;
	STR d("hell");
	d = c; //把c的值赋给d, 重新给d赋值 调用 =(赋值)运算符重载
	//d = a;
	cout<<"c =  ";
	c.Print();
	cout<<"d =  ";
	d.Print();
	
	c[5] = 'h'; // c.[](5)
	cout << c[5] << endl;
	cout<<"c' = ";
	c.Print();

	int n = (a == b);
	cout << n << endl;
	cout << (a == b) << endl; //a.==(b)
	
}

运行结果

在这里插入图片描述

9、()的运算符重载 – 将当前的类名当成函数名来使用

目的是:将当前的类名当成函数名来使用,要让当前的类名和函数名一样去使用。

class Greate
{
public:
	int operator()(int x, int y)
	{
		return x > y;
	}
};
class SMall
{
public:
	int operator()(int x, int y)
	{
		cout << "operator()" << endl;
		return x < y;
	}
/*	int& operator[](int n)
	{
	}*/
};

思考:

如果定义一个 SMall sm;
类比于sm[3] -> sm.[](3)
是否可以实现:sm()(5,7)

直接使用,不行
在这里插入图片描述

()重载调用,可行:

class SMall
{
public:
	int operator()(int x, int y)
	{
		cout << "operator()" << endl;//用于显示的表明是否调用operator()

		return x < y;
	}
/*	int& operator[](int n)
	{
	}*/
};
#if 1
void main()
{
	SMall sm;
	cout << sm(5, 8) << endl;//函数对象(仿函数 -- 把对象名当中函数名使用)
}

运行结果:

在这里插入图片描述
其中调用sm(5, 8) ,相当于sm.()(5,8) ,sm调用了一个(),()其中的参数为5,8
类比于:cout << sm[7] << endl;//sm.[](7)

所以 sm(5, 8) ,给一个对象后加(),()其中还传参,
相当于要去调用当前对象所对应类里所重载的()。也就是说,把当前对象当作函数名来使用,即为函数对象(仿函数 – 把对象名当中函数名使用)。

把对象名能当函数名来使用的前提为:
在当前这个对象所对应的这个类里面,必须重载()。

这里不能这样写//cout << SMall(5, 8) << endl;//error

但作为函数的参数来使用的话,可以实现:
class SMall
{
public:
	int operator()(int x, int y)
	{
		cout << "operator()" << endl;//用于显示的表明是否调用operator()

		return x < y;
	}
/*	int& operator[](int n)
	{
	}*/
};
void main()
{
	//假设要对这段元素进行排序
	int a[] = {12,5,36,2,5,48,12,33,21,10,5,9,7};
	int n = sizeof(a) / sizeof(a[0]);
	
	sort(a, a + n, SMall());//函数对象 调用的是SMall类中的()重载  ,  这里其实生成了一个无名的对象,其调用(),不传参
	//sort(a, a + n, Greate());
	
	int i;
	for(i = 0;i < n;i++)
		cout << a[i] << " ";
	cout << endl;

}

运行结果;

在这里插入图片描述
在这里插入图片描述
测试代码:

#if 1
#include <algorithm>

//sort(first,)
/*
int My_Great(int x,int y) //普通函数
{
	return x > y;
}
int My_Small(int x,int y)
{
	return x < y;
}
class Greate
{
public:
	int operator()(int x, int y)
	{
		return x > y;
	}
};
*/
class SMall
{
public:
	int operator()(int x, int y)
	{
		cout << "operator()" << endl;//用于显示的表明是否调用operator()

		return x < y;
	}
/*	int& operator[](int n)
	{
	}*/
};
#if 0
void main()
{
	SMall sm;
	cout << sm(5, 8) << endl;
	//cout << sm[7] << endl;//sm.[](7)
	//cout<<sm(5, 7)<<endl;//函数对象(仿函数) sm.()(5,7)
}
#endif
#if 1
void main()
{
	//假设要对这段元素进行排序
	int a[] = {12,5,36,2,5,48,12,33,21,10,5,9,7};
	int n = sizeof(a) / sizeof(a[0]);
	//这里调用类库里的一个函数sort,头文件为#include <algorithm>

	//sort(a,a + n);//默认是从小到大
	//sort(a,a + n,less<int>);//从小到大排序 <>为模板,是一个类模板 less是stl标准库库文件里的一个类
	//sort(a,a + n,greater<int>{});

	//sort(a,a + n,My_Great);
	//sort(a,a + n,My_Small);

	sort(a, a + n, SMall());//函数对象 调用的是SMall类中的()重载
	//sort(a, a + n, Greate());
	
	int i;
	for(i = 0;i < n;i++)
		cout << a[i] << " ";
	cout << endl;

}
#endif
#endif

10、输出运算符的存在 – 有源

二、总结

1、运算符重载的总结:

运算符的重载实际是一种特殊的函数重载,必须定义一个函数,并告诉C++编译器,当遇到该重载的运算符时调用此函数。这个函数叫做运算符重载函数,通常为类的成员函数。

定义运算符重载函数的一般格式:

返回值类型 类名::operator重载的运算符(参数表)
{…}

operator是关键字,它与重载的运算符一起构成函数名。因函数名的特殊性,C++编译器可以将这类函数识别出来。

2、运算符重载函数的总结

1)运算符重载函数的函数名必须关键字operator加一个合法的运算符

在调用该函数时,将右操作数作为函数的实参。

2)当用类的成员函数实现运算符的重载时,运算符重载函数的参数(当为双目运算符时) 为一个或 (当为单目运算符时) 没有。

运算符的左操作数一定是对象,因为重载的运算符是该对象的成员函数,而右操作数是该函数的参数。

例如:
A a;
A c = a + 1;// a.+(1)

3)单目运算符“++"和”–"存在前置与后置问题。

前置"++"格式为:

返回类型类名::operator++(){......}

而后置"++"格式为:

返回类型类名::operator++(int){......}

后置"++"中的参数int仅用作区分,并无实际意义,可以给一个变量名,也可以不给变量名。

重载++的时候,有个规则:int和引用不见面
前面返回值有如果引用,后面一定不要写int,
如果后面写了int,那么前面一定不要写返回值为引用。
原因:
++i,前置++ ,返回值为&引用,
i++,后置++ ,返回值为值返回,参数写int

4)C++中只有极少数的运算符不允许重载

在这里插入图片描述
还有# , ## , // , /**/

注意:sizeof是个运算符

5) 重载运算符有以下几种限制

不可臆造新的运算符。

不能改变运算符原有的优先级、结合性和语法结构,不能改变运算符操作数的个数.

运算符重载不宜使用过多。

重载运算符含义必须清楚,不能有二义性.

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值