《随笔二十二》—— C++中的“ 运算符重载 ”

目录

前言

重载运算符的两种形式

运算符成员函数  和 运算符友元函数的比较

C++运算符重载的规则


前言


● 为什么要对运算符进行重载:

C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。

● C++运算符重载的实质:

运算符重载的实质还是函数重载。C++中的每一个运算符对应着一个运算符函数, ,在实现过程中,把指定的运算表达式中的运算符转化为对运算符函数的调用,而表达式中的运算对象转化为运算符函数的实参,这个过程是在编译阶段完成的。


重载运算符的两种形式



●  重载运算符有两种方式,即:重载为类的成员函数 ,重载为类的非成员函数。对于每一种重载形式,由于运算符不同,都可以分为双目运算符和单目运算符的实现。

重载为类的成员函数的时候:


将运算符重载为类的成员函数 , 称为运算符成员函数。实际使用时, 总是通过该类的对象访问重载的运算符。运算符成员函数在类内进行声明, 在类外进行定义, 一般形式为:

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

在类外定义为:

返回类型  类名:: operator 运算符 (参数表)
{

  // Statement
}

双目运算符重载为成员函数:

双目运算符重载为成员函数时, 左操作数是访问该重载运算符的对象本身的数据, 此时成员运算符函数只有一个参数。

双目运算符重载为成员函数后,  就可以在主函数或其他类中进行调用了。在C++中,一般有显式和隐式两种调用方法:

  显式调用: <对象名>.operator <运算符>(<参数>)

  隐式调用:<对象名><运算符><参数>

 例如:a+b 等价于a.operator +(b)

单目运算符重载为成员函数:

单目运算符重载为成员函数时,  操作数是访问该重载运算符对象本身的数据, 由this指针指出, 此时成员运算符函数没有参数。

与双目运算符的重载类似,单目运算符重载为成员函数后,在调用时也有显式和隐式两种:

  显式调用: <对象名>.operator <运算符>()

  隐式调用:<对象名><运算符>

 例如:++a等价于a.operator++()

 

重载为类的非成员函数的时候:

通常我们都将其声明为友元函数,因为大多数时候重载运算符要访问类的私有数据,(当然也可以设置为非友元非类的成员函数。但是非友元又不是类的成员函数是没有办法直接访问类的私有数据的),如果不声明为类的友元函数,而是通过在此函数中调用类的公有函数来访问私有数据会降低性能。所以一般都会设置为类的友元函数,这样我们就可以在此非成员函数中访问类中的数据了。

友元运算符函数在类内声明语法为:

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

在类外定义为:

返回类型  operator 运算符 (参数表)
{
   // Statement
}

双目运算符重载为友元函数:

双目运算符重载为友元函数时,由于没有this指针,所以两个操作数都要通过友元运算符函数的参数指出。双目运算符重载为友元函数后,其调用有显式和隐式两种方法:

  显式调用: operator <运算符>(<实参1>,<实参2>)

  隐式调用:<实参1><运算符><实参2>

注意:  友元运算符函数的形参 和和调用的实参 各方面要一一对应。


单目运算符重载为友元函数:

与单目运算符重载为成员函数不同, 单目运算符重载为友元函数时, 由于没有this指针, 所以操作数要通过友元运算符函数的参数指出。单目运算符重载为友元函数后也有显式和隐式两种调用方法:

  显式调用: operator <运算符>(<实参1>)

  隐式调用:<运算符><实参2>

注意;  在将运算符重载为友元函数时, 除运算符 =、( )、[]、-> 不能用友元函数重载外, 只能通过成员函数重载。其余的运算符都可以进行重载。此外, 使用友元函数重载单目运算符 “++"和“-” 时,由于要改变操作数自身的值, 所以应采用引用参数传递操作数,否则会出现错误。

 


运算符成员函数  和 运算符友元函数的比较


在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点:

  • 双目运算符可被重载为友元函数,也可被重载为成员函数, 但是在运算符的左操作数是 一个标准数据类型,而右操作数是对象的情况下, 必须将它重载为友元函数,原因是标准数据类型的数据不能产生对重载运算符的调用。
  • 对于双目运算符 运算符成员函数是类的成员, 带有this指针, 只需一个参数; 而友元运算符函数不是类的成员, 不带this指针,参数必须是两个。对于单目运算符, 成员运算符函数不带参数;  而友元运算符必须带一个参数。
  • 一般而言, 对于双目运算符重载为友元函数较好, 若运算符的操作数特别是左操 作数需要进行隐式类型转换, 必须重载为友元运算符函数。若一个运算符需要修改对象的状态, 则选择运算符成员函数较好。、

 

  • 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部类型的对象,该运算符函数必须作为一个友元函数来实现。
  • 当需要重载运算符具有可交换性时,选择重载为友元函数。

C++允许重载的运算符

  • 双目算术运算符 + (加),-(减),*(乘),/(除),% (取模)
  • 关系运算符 ==(等于),!= (不等于),< (小于),> (大于>,<=(小于等于),>=(大于等于)
  • 逻辑运算符 ||(逻辑或),&&(逻辑与),!(逻辑非)
  • 单目运算符 + (正),-(负),*(指针),&(取地址)
  • 自增自减运算符 ++(自增),--(自减)
  • 位运算符 | (按位或),& (按位与),~(按位取反),^(按位异或),,<< (左移),>>(右移)
  • 赋值运算符 =, +=, -=, *=, /= , % = , &=, |=, ^=, <<=, >>=
  • 空间申请与释放 new, delete, new[ ] , delete[], ->*
  • 其他运算符 ( ) (函数调用),-> (成员访问),->*(成员指针访问),,(逗号),[](下标)
  • 输入输出运算符  << 、>>

运算符 建议使用
所有一元运算符 成员函数
= ( ) [ ]  -> 必须是成员函数
+= -= /= *= ^= &= != %= >>= <<= , 似乎带等号的都在这里了 成员函数
所有其它二元运算符, 例如: –, + , * , / 友元函数
<< >> 必须是友元函数

不能重载的运算符只有6个:

成员访问运算符  . 
成员指针访问运算符 .*
作用域运算符  ::
长度运算符  sizeof
条件运算符 ?:  

 

参数和返回值

     当参数不会被改变,一般按const引用来传递 (若是使用成员函数重载,函数也为const).

     对于返回数值的决定:

  •     如果返回值可能出现在=号左边, 则只能作为左值, 返回非const引用。
  •     如果返回值只能出现在=号右边, 则只需作为右值, 返回const型引用或者const型值。
  •    如果返回值既可能出现在=号左边或者右边, 则其返回值须作为左值, 返回非const引用。

C++运算符重载的规则


  • 不能改变操作运算符的初始意义,如运算符“+”意味的加法,重载时不能改变它加法的意义。
  • 不能改变运算符的参数数目, 如重载运算符“+"时, 只用一个操作数是错误的。
  • 运算符函数不能包括默认的参数。

 

  • 除赋值运算符"-”外,其他运算符函数都可以由派生类继承。
  • 运算符重载不改变运算符的优先级和结合性,也不改变运算符的语法结构, 即单目、双目运算符只能重载为单目、双目运算符。

​​​​​​​

  • 运算符的重载实际上是函数的重载,编译程序对运算符重载的选择遵循函数重载的选择原则,当遇到不明显的运算符时,编译程序将去寻找参数匹配的运算符函数。
  • 运算符的重载实际上是函数的重载,编译程序对运算符重载的选择遵循函数重载的选择原则,当遇到不明显的运算符时,编译程序将去寻找参数匹配的运算符函数。
  • 重载运算符含义必须清楚, 如对于 时间类"Time", 包含了参数 小时、分钟和秒 3个成员变量,如果对该类重载操作符“+",就显得含义不清楚,因此不能对该类重载操作符“+".

 

  • 重载单目操作符可以是不带参数的成员函数或带一个参数的非成员函数。
  • 重载双目操作符可以是带一个参数的成员函数或带两个参数的非成员函数。当重载操作符为类的非成员函数时,原则上应该把该函数声明为类的友元函数数,  因为友元函数可以访问类的私有成员。

 

  • 对于操作运算符“=”、"I"、"0"和 " >”只能定义为成员函数。
  • 操作运算符“ -> ”的返回值必须是一个指针或能使用“ ->" 的对象。
  • 重载自增操作符"++,”或自减操作符" -- ”时,带一个int参数表示后缀, 不带参数表示前缀。

下面看具体代码:看不懂,可以复制到编译器上, 一个个的看。

#include 
using namespace std;

class Complex
{
public:
	Complex() = default;
	Complex(int r  , int i )
	{
		m_real = r;
		m_img = i;
	}
	
	~Complex()
	{
		delete arr;
		arr = nullptr;
	}
	friend Complex operator+(Complex &m, Complex &s);   //两对象相加,友元

	//还可以定义一个转换构造函数,来实现 类对象和基本类型的计算
	 Complex operator+(Complex &m);  //两对象相加,  成员函数

	friend Complex operator+(Complex &i, int b);   //对象与整型数相加,友元
	Complex operator+(const int b); //对象与整型数相加,成员函数
	
	// 前置++ 成员和友元函数实现
	 Complex &operator++();  
	friend Complex &operator++(Complex &a);

	// 后置++ 成员和友元函数实现
	 Complex operator++(int);
	friend Complex operator++(Complex &a, int);

	friend ostream &operator<<(ostream &output,const Complex &obj);
	friend istream &operator>>(istream &input, Complex &obj);
	int &operator[](int i); //下标运算符重载
	 Complex &operator=(const Complex &source);
	

	void display()
	{
		cout << "输出m_real:" << m_real << " 输出m_img:" << m_img << "\n" << endl;
	}
private:
	int m_real;
	int m_img;
	int *arr = new int[3]{ 1,2,3};
};
int &Complex::operator[](int i) //下标运算符重载
{
	if (i < 0 || i >= 3)
	{
		cout << "错误,下标越界!" << endl;
		system("pause");
		exit(1);
	}
	return arr[i];
}
ostream &operator<<(ostream &output, const Complex &obj)
{
	output << "m_real:" << obj.m_real << " " << "m_img:" << obj.m_img << endl;
	output << "输出arr 数组中的值:";
	for (size_t i = 0; i != 3; ++i)
	{
		output << obj.arr[i] << " ";
	}
	cout << endl;
	return output;
}
istream &operator>>(istream &input, Complex &obj)
{
	cout << "请输入m_real的值:";
	input >> obj.m_real;
	cout << "请输入m_img的值:";
	input >> obj.m_img;
	delete obj.arr;
	obj.arr = nullptr;
	obj.arr = new int[3];
	cout << "请分别输入arr 数组的元素,然后用回车符隔开:" << endl;
	for (size_t i = 0; i != 3; ++i)
	{
		input >> obj.arr[i];
	}
	cout << endl;
	return input;
}
 Complex &Complex::operator=(const Complex &source)
{
	 if (this == &source) //自我赋值的检查
	 {
		 return *this;
	 }	
	m_real = source.m_real;
	m_img = source.m_img;
	if (arr)
	{
		for (size_t i = 0; i != 3; ++i)
		{
			arr[i] = source.arr[i];
		}
	}
	return *this;
}
 Complex &Complex::operator++()// 成员函数实现 前置++
{
	++m_img;
	++m_real;
	return *this;
}
Complex &operator++(Complex &a)
{
	++a.m_img; ++a.m_real;
	return a;
}
 Complex Complex::operator++(int) // 成员函数实现 后置++
{
	 int i = m_real++;
	 int r = m_img++;
	 return Complex(i,r );
}
Complex operator++(Complex &a, int) // 友元函数实现 后置++
{
	return Complex(a.m_real++, a.m_img++);
}

Complex operator+(Complex &m, Complex &s)
{
	return Complex(m.m_real + s.m_real, m.m_img + s.m_img);
}
 Complex Complex::operator+(Complex &m)
{
	return Complex(m_real + m.m_real, m_img + m.m_img);
}


Complex operator+(Complex &i, int b)   //对象与整型数相加,友元
{
	return Complex(i.m_real + b, i.m_img + b);
}

Complex Complex::operator+(const int b) //对象与整型数相加,成员函数
{
	return Complex(m_real + b, m_img + b);
}

int main()
{
	Complex c1(2, 3);
	Complex c2(5, 4);
	Complex c3;

	cout << "\n\n使用两个Complex对象的加法\n" << endl;
	c3 = operator+(c1, c2);  //两对象的加法,友元
	c3.display();

	c3 = c1.operator+(c2);  //调用成员函数
	c3.display();

	cout << "对象Complex 和 整型数的加法" << "\n" << endl;

	c3 = operator+(c1, 10);   //对象与整型数的加法,调用的友元
	c3.display();

	c3 = c1.operator+(10);  //对象与整型数的加 法,  调用成员函数
	c3.display();

	cout << "使用前置++运算符 递增Complex对象" << "\n" << endl;
	//前置++ 实现, 成员友元实现
	c3 = c1.operator++();
	c1.display();
	c3.display();

	c3 = operator++(c1);
	c1.display();
	c3.display();


	//后置++ 实现, 成员友元实现
	cout << "使用后置 ++运算符 递增Complex对象" << "\n" << endl;
	c3 = operator++(c1, 0);
	c1.display();
	c3.display();

	c3 = c1.operator++(0);
	c1.display();
	c3.display();

	cout << "使用重载输入输出运算符输出Complex对象" << "\n" << endl;
	// 使用重载输入输出运算符,重载输入输出运算符必须是友元函数
	cin >> c1;
	cout << "输出对象c1的数据成员的值:" << c1 << endl;

	cout << "使用 重载赋值运算符" << "\n" << endl;

	c3 = c1;
	cout << "输出c3 数据成员的值:" << c3 << endl;
	cout << "输出c1 数据成员的值:" << c1 << endl;

	//记住,下标运算符必须以成员函数进行重载
	cout << "使用 重载下标运算符" << "\n" << endl;
	cout << "输出 c1 中arr数组的第一个值:" << c1.operator[][0] << endl;
	cout << "输出 c2 中arr数组的第一个值:" << c2[0] << endl;
	cout << "输出 c3 中arr数组的第一个值:" << c3[0] << endl;
	system("pause");
	return 0;
}

输出结果为:

《随笔二十二》—— C++中的“ 运算符重载 ”_第1张图片

 

●  运算符“++”和“ -- ”的重载要区分前置和后置两种形式。例如, 表达式"a++"和表达式 " ++a " 是不一样的。如果不区分前置和后置, 则使用operator++()或operator-()即可; 否则要使用operator)或operator-()来重载前置运算符, 使用operator++(int)或operator--(int)来重载后置运算符,调用时,参数int被传递值0。

 ●  在进行赋值运算符“ = ”的重载时, 要注意赋值运算符只能重载为运算符成员函数,不能重载为友元运算符函数, 而且赋值运算符重载后不能被继承。

●  下标运算符operator [ ] 通常用来访问数组中的某个元素, 可以看做是一个双目运算符, 在类对象中可以重载下标运算符用它来定义相应对象的下标运算。一般来说,下标运算符定义的形式如下:

返回类型  &operator[] ( int 下标标识符);

在类外定义为:

返回类型  &类名::operator[] ( int 下标标识符)
{

}

返回类型可以是任何类型, 下标的类型必须一定是个无符号整型数。

在实际的程序中, 可以通过下面两种方式来调用下标运算符:

对象名. [下标值] 或者 对象名. operator [][下标值];
  • 注意: 在重载 下标运算符 [ ] 时 的实现代码中一定要去判断  下标是否越界,如上述程序。
  • 注意:C++不允许把下标运算符函数作为外部函数来定义, 只能是 非静态的成员函数。

 

 

你可能感兴趣的:(C++中的随笔)