C++运算符重载
2011年08月18日


序:比如常见的 cout<<c3<<c2

    “<<”和“>>”本来是在C++中被定义为左/右位移运算符的,由于在iostream头文件中对它们进行了重载,所以使用它们能用作标准数据类型数据的输入和输出运算符。因此,在使用它们的程序中必须包含:#include <iostream>

 

1. 运算符重载例子:

例1 重载函数作为Complex类的成员函数

class Complex
{
public:
	Complex operator +(Complex &c2);	//声明重载运算符’+’的函数
	……
};
Complex Complex::operator + (Complex &c2)	//定义该函数
{ return Complex(real+c2.real, imag+c2.image); }
int main()
{
	Complex c1(3, 4), c2(5,-10), c3;
	c3 = c1 + c2;	//调用
	……
}

分析:C++编译系统将程序中的表达式c1 + c2解释为:

       c1.operator +(c2)

即以c2为实参调用对象c1的运算符重载函数operator +(Complex &c2)。实际上,运算符重载函数有两个参数,由于重载函数是Coplex类中的成员函数,有一个参数是隐含的,运算符函数是用this指针隐式地访问类对象的成员,如this->real+c2.real,this代表c1,即实际上是c1.real+c2.real。

 

运算符重载函数可以是类的成员函数,也可以是类的友元函数,也可以是普通函数(不推荐)。

 

例2 重载函数作为Complex类的友元函数

friend Complex operator +(Complex &c1, Complex &c2);	//声明

Complex operator +(Complex &c1, Complex &c2)	//定义
{ return Complex(c1.real+c2.real, c1.imag+c2.image); }
c3 = c1+c2;	//调用

为什么把运算符函数作为友元函数呢?理由很简单,因为运算符函数要访问Complex类对象中的成员。如果作为成员函数,就必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象,而且与运算符函数的类型相同,在例1中,表达式c1+c2中的第一个参数c1是Complex类对象,运算符函数返回值的类型也是Complex,这是正确的。如果c1不是Complex类,就无法启用c1.operator +(c2),也就无法通过隐式this指针访问Complex类的成员了。

说明:有的C++编译系统(如VC++6.0)没有完全实现C++标准,它所提供的后缀.h的头文件不支持把成员函数重载为友元函数,因此在VC++6.0,应把程序的头两行:

#include <iosteam>

using namespace std;

改为一行:#include <iosteam.h>   //C语言的风格

 

例3 重载双目运算符

bool operator >(String &string1, String &string2)
{
	if( strcmp(string1.p, string2.p) > 0 )	return true;
	else		return false;
}

例4 重载单目运算符

Time Time::operator ++()	//定义前置自增运算符“++”重载函数
{
	if( ++sec >= 60)	//隐含了this指针
	{ sec -=60;
	  ++minute; }
	return *this;	//返回当前新的this指针内容
}
Time Time::operator ++(int) 	//定义后置“++”重载函数
{
	Time temp(*this);	//定义新对象temp,将当前this指针指向的对象拷贝给temp
	sec++;
	if( sec >= 60)
	{ sec -=60;
	  ++minute; }
	return temp;	//返回未更新值的对象temp,实际上实参的值已经自增了。
}

说明:重载后置自增运算符时,多了一个int型的参数,增加这个参数只是为了与前置自增运算符重载函数有所区别,此外没有任何作用。

 

例5 重载流插入/提取运算符

 

对“<<”和“>>”重载的函数形式如下:

istream & operator >>(istream &, 自定义类 &);       //input

ostream & operator >>(ostream &, 自定义类 &);     //output

例如:

{
	output<<”(”<<c.real<<”+”<<c.imag<<”i)”<<endl;
	return output;
}

cout<<c3<<c2;    //调用重载后的"<<",等价于(operator <<(cout, c3))<<c2;

编译系统把”cout<<c3”解释为:operator <<(cout, c3),即以cout和c3作为实参,调用operator <<(ostream &output, Complex &c)函数。因此,相当于执行了:

cout<<”(”<<c3.real<<”+”<<c3.image<<”i)”<<endl; return cout;

返回的是流提取对象cout,作为引用返回。

 

如果有以下输出:

cout<<c3<<c2;

先处理cout<<c3,即(cout<<c3)<<c2;而cout<<c3其实是operator <<(cout, c3),返回的是流提取对象cout,所以cout再和后面的c2结合,输出c2的内容。可见为什么C++规定“流提取运算符重载函数的第一个参数和函数的类型都必须是ostream类型的引用”了,就是为了返回cout,以便连续输出。

 

问:为什么要在operator前面加’&’?(即“返回引用”)

答:开始我以为ostream & operator >>()的这个返回类型“ostream &”是为了和括号中的参数“ostream &”类型相对于,其实不然。因为只有运算符重载函数为成员函数时,才要求第一个参数和运算符重载函数的类型相同,而友元函数没有这个要求。

       先分析“ostream &”作为参数的情况,传入的第一个参数显然是cout,cout是什么,它其实也是一个类的对象,“cin和cout分别是istream类和ostream类的对象”,所以说形参定义成引用,避免了对象的拷贝。

       再来分析函数类型为什么要定义为“ostream &”,先看看原理http://blog.csdn.net/zollty/article/details/6695311。因为要使返回的对象cout能够直接使用,所以要将函数的返回值定义为引用。

 

 

2. 不同数据类型之间的转换

1) 标准类型数据之间的转换

显示类型转换:

C语言格式:(类型名)数据,例如:(int)89.5

C++格式:类型名(数据),例如:int(89.5)

 

2) 用构造函数进行类型转换

几种构造函数:

l         默认构造函数(无参,直接指定默认值):Complex();

l         用于初始化的构造函数:Complex(double r, double i);

l         用于复制对象的复制构造函数:Complex(Complex &c);

l         现在要讲解的新构造函数——转换构造函数:Complex(double r){ real=r; imag=0; }(它只有一个参数)

以上几种构造函数可以同时出现在同一个类中,它们是构造函数的重载。

 

假如有以下声明语句:

Complex c1(3.5);       //建立对象c1,由于只有一个参数,调用转换构造函数

也可以用声明语句建立一个无名的Complex类对象。如:

Complex(3.6);     //用声明语句建立一个无名对象,合法,但是无法使用

可以在一个表达式中使用无名对象,如

c1=Complex(3.6);      //假设c1已经被定义为Complex类对象

若在程序中有以下表达式:

c=c1+2.5;

编译出错,因为不能用运算符“+”将一个Complex类对象和一个浮点数相加。可以先将2.5转换成Complex类无名对象,然后相加:

c=c1+Complex(2.5);

请对比Complex(2.5)和int(2.5)。可以认为Complex(2.5)的作用也是强制类型转换。通常把有一个参数的构造函数用作类型转换,所以,称为转换构造函数。如可以将一个学生类对象转换为教师类对象,可以在Teacher类中写出下面的转换构造函数:

Teacher(Student &s)

{ num=s.num; strcpy(name, s.name); sex=s.sex; }

但应注意:对象s中的num, name, sex必须是公用成员,否则不能被类外调用。

 

3) 用类型转换函数进行类型转换

       用转换构造函数可以将一个指定类型的数据转换为类的对象。但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成double类型数据)。C++提供了类型转换函数来解决这个问题。类型转换函数的作用是将一个类的对象转换成另一类型的数据。如果已经声明了一个Complex类:

class Complex
{
public:
	……
private:
	double real;
	double imag;
};

可以在Complex类中这样定义类型转换函数:

operator double()

{ return real; }

       它的作用是将一个Complex类对象转换为一个double型数据,其值是Complex类中得数据成员real的值。请注意:函数名是operator double。这点是和运算符重载时的规律一致的(都是用关键字operator开头,只是被重载的是类型名)。其特点:在函数名前面不能指定函数类型,函数也没有参数。其返回值的类型是由函数名中指定的类型名来确定的(函数返回double型变量real的值)。

       在定义了前面的数据类型转换函数后,程序中的Complex类对象是不是一律都转换成double类型的数据呢?不是的,它们具有双重身份,既是Complex类对象,又可以作为double类型数据。

       转换构造函数和类型转换运算符有一个共同的功能:当需要的时候,编译系统会自动的调用这些函数,建立一个无名的临时对象(或临时变量)。例如,若已定义d1, d2为double型变量,c1, c2为Complex类对象,且类中已经定义了类型转换函数。设程序中有以下表达式:

       d1 = d2 + c1;

编译系统发现“+”左侧的d2是double型,而右侧的c1是Complex类对象,如果没有对运算符“+”进行重载,就会检查有无类型转换函数,结果发现有,就调用operate double函数把Complex类对象c1转换为double型数据,建立了一个临时的double变量,并与d2相加,最后将一个double的值赋给d1。相当于执行表达式:

       d1 = d2 + c1.operate double();

如果类中已经定义了转换构造函数并重载了运算符“+”(作为Complex类的友元函数),但未对double定义类型转换函数(或者说未对double重载),若有以下表达式:

       c2 = c1 + d2;

则系统将转换为:

       c2 = c1+ Complex(d2);

当然,如果既有转换构造函数和运算符重载,又有double类型转换函数,出现歧义的话就会出错。