运算符重载(Part Ⅰ)

运算符重载

什么是重载运算符

  • 不仅函数可以重载,运算符也可以重载。如:"<<“是C++的位运算中的位移运算符(左移),但在输出操作中又是与流对象cout配合使用的流插入运算符,”>>“也是位移运算符(右移),但在输入操作中又是与流对象cin配合使用的流提取运算符。这就是运算符重载。C++系统对”<<“和”>>“进行了重载,用户在不同的场合下使用它们时,作用是不同的。对”<<“和”>>“的重载处理是放在头文件stream中的。因此,如果要在程序中用”<<“和”>>“作流插入运算符和流提取运算符,必须在本文件模块中包含头文件stream(当然还应当包括"using namespace std”)

例:通过函数来实现复数相加

#include<iostream>
using namespace std;
class Complex //定义Complex类
{
public:
	Complex() { real = 0; imag = 0; } //定义构造函数
	Complex(double r, double i) { real = r; imag = i; } //构造函数重载
	Complex complex_add(Complex& c2); //声明复数相加函数
	void display();
private:
	double real; //实部
	double imag; //虚部
};
Complex Complex::complex_add(Complex& c2) //定义复数相加函数
{
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}
void Complex::display()
{
	cout << "(" << real << "," << imag << "i)" << endl;
}
int main()
{
	Complex c1(3, 4), c2(5, -10), c3; //定义3个复数对象
	c3 = c1.complex_add(c2); //调用复数相加函数
	cout << "c1="; c1.display(); //输出c1的值
	cout << "c2="; c2.display(); //输出c2的值
	cout << "c1+c2="; c3.display(); //输出c3的值
	return 0;
}

程序分析:

  • 在Complex类中定义了一个complex_add函数,其作用是将两个复数相加,在该函数体中定义一个Complex类对象c作为临时对象。其中的赋值语句相当于c.real = this->real + c2.real; c.imag = this->imag + c2.imag;
  • this是当前对象的指针。this->real也可以写成(* this).real。现在,在main函数中是通过对象c1调用complex_add函数的,因此,以上两个语句相当于:c.real = c1.real + c2.real; c.imag = c1.imag + c2.imag;。注意函数的返回值是Complex类对象c的值。

运行结果:

运算符重载的方法

  • 运算符重载的方法是定义一个重载运算符的函数,使指定的运算符不仅能实现原有的功能,而且能实现在函数中指定的新的功能。在使用被重载的运算符时,系统就自动调用该函数,以实现相应的运算。也就是说,运算符重载是通过定义函数实现的。运算符重载实质上是函数的重载
  • 重载运算符的函数的一般格式为:
函数类型 operator 运算符名称(形参表)
	{对运算符的重载处理}

-在上面的一般格式中,operator是关键字,是专门用于定义重载运算符的函数的,运算符名称就是C++提供给用户的预定义运算符。注意:函数名是由operator和运算符组成,上面的"operator+“就是函数名,意思是"对运算符+重载的函数”。

#include<iostream>
using namespace std;
class Complex //定义Complex类
{
public:
	Complex() { real = 0; imag = 0; } //定义构造函数
	Complex(double r, double i) { real = r; imag = i; } //构造函数重载
	Complex operator+(Complex& c2); //声明重载运算符+的函数
	void display();
private:
	double real; //实部
	double imag; //虚部
};
Complex Complex::operator+(Complex& c2) //定义重载运算符+的函数
{
	Complex c;
	c.real = real + c2.real; //实现两个复数的实部相加
	c.imag = imag + c2.imag; //实现两个复数的虚部相加
	return c;
}
void Complex::display()
{
	cout << "(" << real << "," << imag << "i)" << endl; //输出复数形式
}
int main()
{
	Complex c1(3, 4), c2(5, -10), c3; //定义3个复数对象
	c3 = c1+c2; //运算符+用于复数运算
	cout << "c1="; c1.display(); //输出c1的值
	cout << "c2="; c2.display(); //输出c2的值
	cout << "c1+c2="; c3.display(); //输出c3的值
	return 0;
}
  • 可以看到:重载运算符是由相应的函数实现的。从用户的角度来看问题,虽然重载运算符所实现的功能完全可以用函数实现,但是使用运算符重载能使用户易于编写、阅读和维护。
  • 当运算符重载为成员函数时,运算符函数的形式参数的个数比运算符规定的运算对象个数要少一个。原因是类的非静态成员函数都有一个隐含的this指针,运算符函数可以用this指针隐式地访问类对象地成员,因此这个对象自身地数据可以直接访问,不需要放到形参列表中进行传递,少了的运算对象就是该对象本身

说明:运算符被重载后,其原有的功能仍然保留, 没有丧失或改变。

重载运算符的规则

  1. C++不允许用户自己定义新的运算符,只能对已有的C++运算符进行重载
  2. C++允许重载的运算符
双目算术运算符+(加),-(减),*(乘),/(除),%(取模)
关系运算符==(等于),!=(不等于),<(小于),>(大于),<=(小于等于),>=(大于等于)
逻辑运算符||(逻辑或),&&(逻辑与),!(逻辑非)
单目运算符+(正),-(负),*(指针),&(取地址)
自增自减运算符++(自增),–(自减)
位运算符|(按位或),&(按位与),~(按位取反),^(按位异或),<<(左移),>>(右移)
赋值运算符=,+=,-=,*=,/=,%=,|=,^=,<<=,>>=
空间申请与释放new,delete,new[ ],delete[ ]
其他运算符()(函数调用),->(成员访问),->*(成员指针访问),,(逗号),[ ](下标)

不能重载的运算符只有5个:
①. (成员访问运算符)
②.* (成员指针访问运算符)
③∷ (域运算符)
④sizeof (长度运算符)
⑤?: (条件运算符)
前两个运算符不能重载是为了保证访问成员的功能不能被改变,域运算符和sizeof运算符的运算对象是类型而不是变量或一般表达式,不具重载的特征

  1. 重载不能改变运算符运算对象(即操作数)的个数
  • 如:关系运算符">“和”<"等是双目运算符,重载后仍为双目运算符,需要两个参数。运算符+,-,*,&等既可以作为单目运算符,也可以作为双目运算符,可以分别将它们重载为单目运算符或双目运算符。
  1. 重载不能改变运算符的优先级别
  • 有时在程序中希望改变某运算符的优先级,只能通过加圆括号的办法强制改变重载运算符的运算顺序
  1. 重载不能改变运算符的结合性
  2. 重载运算符的函数不能有默认的参数,否则就改变了运算符参数的个数,与前面第(3)点矛盾
  3. 重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有一个是类对象(或类对象的引用)。也就是说,参数不能全部是C++的标准类型,以防止用户修改用于标准类型数据的运算符的性质。
  4. 用于类对象的运算符一般必须重载,但有两个例外,运算符“=”和“&”不必用户重载
    ① 赋值运算符(=)可以用于每一个类对象,可以用它在同类对象之间相互赋值。
    ② 地址运算符&也不必重载,它能返回类对象在内存中的起始地址。
  5. 从理论上说,可以将一个运算符重载为执行任意的操作,如可以将加法运算符重载为输出对象中的信息,将">"运算符重载为"小于"运算。但这样违背了运算符重载的初衷,非但没有提高可读性,反而使人莫名其妙,无法理解程序。应当使重载运算符的功能类似于该运算符作用于标准类型数据时所实现的功能

运算符重载函数作为类成员函数和友元函数

  • 对函数重载的函数有两种处理方式:
    (1)把运算符重载的函数作为类的成员函数
    (2)运算符重载的函数不是类的成员函数(可以是一个普通函数),在类中把它声明为友元函数

将运算符重载作为类的成员函数,一般形式为:

class 类名{ //类体
	...
	返回类型 operator 运算符号(形式参数列表)
	{
		函数体
	}
	...
};
//或者
class 类名{ //类体
	...
	返回类型 operator 运算符号(形式参数列表)
	...
};
返回类型 类名::operator 运算符号(形式参数列表)
{
		函数体
}

当运算符重载为友元函数时,运算符函数的形式参数的个数和运算符规定的运算对象个数一致

class 类名{ //类体
	...
	//友元声明
	friend 返回类型 operator 运算符号(形式参数列表);
};
返回类型 operator 运算符号(形式参数列表)
{
	函数体
}

例:将运算符+重载为适用于复数加法,重载函数不作为成员函数,而放在类外,作为Complex类的友元函数

#include<iostream>
using namespace std;
class Complex //定义Complex类
{
public:
	Complex() { real = 0; imag = 0; } //定义构造函数
	Complex(double r, double i) { real = r; imag = i; } //构造函数重载
	friend Complex operator+(Complex& c1,Complex& c2); //重载函数作为友元函数
	void display();
private:
	double real; //实部
	double imag; //虚部
};
Complex operator+(Complex& c1,Complex& c2) //定义重载运算符+的函数
{
	return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
void Complex::display()
{
	cout << "(" << real << "," << imag << "i)" << endl; //输出复数形式
}
int main()
{
	Complex c1(3, 4), c2(5, -10), c3; //定义3个复数对象
	c3 = c1+c2; //运算符+用于复数运算
	cout << "c1="; c1.display(); //输出c1的值
	cout << "c2="; c2.display(); //输出c2的值
	cout << "c1+c2="; c3.display(); //输出c3的值
	return 0;
}

运算符重载函数作为类的成员函数和作为类的友元函数的区别:

  • 如果将运算符重载函数作为成员函数,它可以通过this指针自由地访问本类的数据成员,因此可以少写一个函数的参数。但是必须要求运算表达式第1个参数(即运算符左侧的操作数)是一个类对象,而且与运算符函数的类型相同。因为必须通过类的对象去调用该类的成员函数,而且只有运算符重载函数返回值与该对象同类型,运算结果才有意义。
  • 如果运算符左侧的操作数属于C++标准类型,或是一个其他类的对象,则运算符重载函数不能作为成员函数,只能作为非成员函数。如果函数需要访问类的私有成员,则必须声明为友元函数
  • 双目运算符重载为友元函数时,由于友元函数不是该类的成员函数,因此在函数的形参表列中必须有两个参数,不能省略,形参的顺序任意。但在使用运算符的表达式中,要求运算符左侧的操作数与函数的第1个参数对应,运算符右侧的操作数与函数的第2个参数对应。

由于友元的使用会破坏类的封装,因此从原则上说,要尽量将运算符函数作为成员函数。但还应考虑到各方面的因素和程序员的习惯,以下可供参考:

  1. C++规定,赋值运算符"="、下标运算符"[ ]"、函数调用运算符"()"、成员运算符"->"必须作为成员函数重载
  2. 流插入"<<“和流提取运算符”>>"、类型转换运算符函数不能定义为类的成员函数,只能作为友元函数
  3. 一般将单目运算符和复合运算符(+=,-=,/=,*=,&=,!=,^=,%=,>>=,<<=)重载为成员函数
  4. 一般将双目运算符重载为友元函数

重载双目运算符

双目运算符(或称二元运算符)是C++中最常用的运算符。双目运算符有两个操作数,通常在运算符的左右两侧。在重载双目运算符时,不言而喻在函数中应该有两个参数。

双目运算符重载为类的成员函数,形式如下:

返回类型 类名::operator op(const 所属类型 &obj2)
{
	... //this指针对应obj1运算对象
}

双目运算符重载为类的友元函数,形式如下:

返回类型 类名::operator op(const 所属类型 &obj1,const 所属类型 &obj2)
{
	... //obj1和obj2分别对应两个运算对象
}

例:声明一个字符串类String,用来存放不定长的字符串,重载运算符"==","<“和”>",用于两个字符串的等于、小于和大于的比较运算

#include<iostream>
using namespace std;
class String
{
public:
	String() { p = NULL; } //定义默认构造函数
	String(char* str); //声明构造函数
	friend bool operator>(String& string1, String& sting2);
	friend bool operator<(String& string1, String& sting2);
	friend bool operator==(String& string1, String& sting2);
	void display();
private:
	char* p; //字符型指针,用于指向字符串
};
String::String(char* str) //定义构造函数
{
	p = str; //使p指向实参字符串
}
void String::display() //输出p所指向的字符串
{
	cout << p;
}
bool operator>(String& string1, String& string2) //定义运算符重载函数
{
	if (strcmp(string1.p, string2.p) > 0) return true;
	else return false;
}
bool operator<(String& string1, String& string2)
{
	if (strcmp(string1.p, string2.p) < 0) return true;
	else return false;
}
bool operator==(String& string1, String& string2)
{
	if (strcmp(string1.p, string2.p) == 0) return true;
	else return false;
}
void compare(String& string1, String& string2)
{
	if(operator>(string1,string2)==1)
	{
		string1.display();
		cout << " > ";
		string2.display();
	}
	else if (operator<(string1, string2) == 1)
	{
		string1.display();
		cout << " < ";
		string2.display();
	}
	else if(operator==(string1, string2) == 1)
	{
		string1.display();
		cout << " == ";
		string2.display();
	}
	cout << endl;
}
int main()
{
	String string1("Hello"), string2("Book"), string3("Computer"), string4("Hello");
	compare(string1, string2);
	compare(string2, string3);
	compare(string1, string4);
	return 0;
}

运行结果:

重载单目运算符

  • 单目运算符只有一个操作数,重载单目运算符的方法与重载双目运算符的方法是类似的。但由于单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可省略此参数

前置单目运算符重载为类的成员函数,形式如下:

返回类型 类名::operator op()
{
	... //this指针对应obj运算对象
}

后置单目运算符重载为类的成员函数,形式如下:

返回类型 类名::operator op(int)
{
	... //this指针对应obj运算对象
}

前置单目运算符重载为类的友元函数,形式如下:

返回类型 类名::operator op(const 所属类型 &obj)
{
	... //obj对应运算对象
}

后置单目运算符重载为类的友元函数,形式如下:

返回类型 类名::operator op(const 所属类型 &obj,int)
{
	... //obj对应运算对象
}

例:有一个Time类,包含数据成员minute(分)和sec(秒),模拟秒表,每次走1秒,满60秒进1分钟,此时秒又从0起算。要求输出分和秒的值

#include<iostream>
using namespace std;
class Time
{
public:
	Time() { minute = 0; sec = 0; } //默认构造函数
	Time(int m,int s):minute(m),sec(s){} //构造函数重载
	Time operator++(); //声明运算符重载成员函数
	void display() { cout << minute << ":" << sec << endl; } //定义输出时间函数
private:
	int minute;
	int sec;
};
Time Time::operator++() //定义运算符重载成员函数
{
	if (++sec >= 60)
	{
		sec -= 60; //满60秒进1分钟
		++minute;
	}
		return *this; //返回当前对象值
}
int main()
{
	Time time1(34, 0);
	for (int i = 0; i < 61; i++)
	{
		++time1;
		time1.display();
	}
	return 0;
}

运行结果:

  • "++“和”–"运算符有两种使用方式,前置自增运算符和后置自增运算符,它们的作用是不一样的,针对这一特点,C++约定:在自增(自减)运算符重载函数中,增加一个int型形参,就是后置自增(自减)运算符函数

例:在上个程序的基础上增加对后置自增运算符的重载

#include<iostream>
using namespace std;
class Time
{
public:
	Time() { minute = 0; sec = 0; } //默认构造函数
	Time(int m,int s):minute(m),sec(s){} //构造函数重载
	Time operator++(); //声明前置自增运算符"++"重载函数
	Time operator++(int); //声明后置自增运算符"++"重载函数
	void display() { cout << minute << ":" << sec << endl; } //定义输出时间函数
private:
	int minute;
	int sec;
};
Time Time::operator++() //定义前置自增运算符"++"重载函数
{
	if (++sec >= 60)
	{
		sec -= 60; //满60秒进1分钟
		++minute;
	}
		return *this; //返回自加后的当前对象值
}
Time Time::operator++(int)
{
	Time temp(*this);
	sec++;
	if (sec >= 60)
	{
		sec -= 60; //满60秒进1分钟
		++minute;
	}
	return temp; //返回的是自加前的对象
}
int main()
{
	Time time1(34, 59), time2;
	cout << "time1:  ";
	time1.display();
	++time1;
	cout << "++time1:";
	time1.display();
	time2 = time1++; //将自加前的对象的值赋给time2
	cout << "time1++:";
	time1.display();
	cout << "time2:  ";
	time2.display(); //输出time2对象的值
	return 0;
}

程序分析:

  • 前置自增运算符"++“是先自加,返回的是修改后的对象本身;后置自增运算符”++"返回的是自加前的对象,然后对象相加。
    可以看到,重载后置自增运算符时,多了一个int型的参数,增加这个参数只是为了与前置自增运算符重载函数有所区别,此外没有任何作用,在定义函数时也不必使用此参数,因此可以省写参数名,只须在括号中写int即可。编译系统在遇到重载后置自增运算符时,会自动调用此函数。

运行结果:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值