运算符重载

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

第4章运算符重载
4.1 什么是运算符重载
所谓重载,就是重新赋予新的含义。

函数重载就是对一个已有的函数赋予新的含义,使之实现新功能。

运算符也可以重载。

实际上,我们已经在不知不觉之中使用了运算符重载。

如:+可以对int、float、double的数据进行加法运算。

现在要讨论的问题是:用户能否根据自己的需要对C++已提供的运算符进行重载,赋予它们新的含义,使之一名多用。

譬如,能否用“+”号进行两个复数、两个点的相加。

在C++中不能在程序中直接用运算符“+”对复数进行相加运算。

用户必须自己设法实现复数相加。

例如用户可以通过定义一个专门的函数来实现复数相加。

见下例。

//例4.1 通过函数来实现复数相加。

#include <iostream>
using namespace std;
class 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, imag;
};
Complex Complex::complex_add(Complex &c2)
{ Complex c;
c.real=real+c2.real;
c.imag=imag+c2.imag;
return c;
//以上可简写为:return Complex(real+c2.real,imag+c2.imag);
}
void Complex::display()
{ cout<<"("<<real<<","<<imag<<"i)"<<endl;
}
int main()
{ Complex c1(3,4),c2(5,-10),c3;
c3=plex_add(c2);
cout<<"c1="; c1.display();
cout<<"c2="; c2.display();
cout<<"c1+c2="; c3.display();
return 0;
}
结果无疑是正确的,但调用方式不直观、太烦琐,使人感到很不方便。

能否也和整数的加法运算一样,直接用加号“+”来实现复数运算呢?如c3=c1+c2;,编译系统就会自动完成c1和c2两个复数相加的运算。

如果能做到,就为对象的运算提供了很大的方便。

这就需要对运算符“+”进行重载定义。

我们来想一下,一个整数加a+b,其实它的操作就是函数形式add(a,b)。

而写成a+b的形式更简单通俗而已。

所以我们对操作符也能像进行函数那样的定义。

思考:上例是用成员函数来实现的,那么用其它的函数形式能实现吗?
//例:通过友元函数来实现复数相加。

#include <iostream>
using namespace std;
class Complex
{
public:
Complex(){real=0;imag=0;} //构造函数
Complex(double r,double i){real=r;imag=i;} //构造函数重载
friend Complex complex_add(Complex &c1,Complex &c2);//声明复数相加的函数
void display(); //声明输出函数
private:
double real, imag;
};
Complex complex_add(Complex &c1,Complex &c2)
{ Complex c;
c.real=c1.real+c2.real;
c.imag=c1.imag+c2.imag;
return c;
//以上可简写为:return Complex(real+c2.real,imag+c2.imag);
}
void Complex::display()
{ cout<<"("<<real<<","<<imag<<"i)"<<endl;
}
int main()
{ Complex c1(3,4),c2(5,-10),c3;
c3=complex_add(c1,c2);a
cout<<"c1="; c1.display();
cout<<"c2="; c2.display();
cout<<"c1+c2="; c3.display();
return 0;
}
通过普通函数怎么实现呢?
4.2 运算符重载的方法
运算符重载的方法是定义一个重载运算符的函数,在需要执行被重载的运算符时,系统就自动调用该函数,以实现相应的运算。

也就是说,运算符重载是通过定义函数实现的。

运算符重载实质上是函数的重载。

重载运算符的函数一般格式如下:函数类型operator 运算符名称(形参表列)
{ 对运算符的重载处理}
例如,想将“+”用于Complex类(复数)的加法运算,函数可以是这样的:
Complex operater+ (Complex& c1,Complex& c2);
在定义了重载运算符的函数后,可以说:函数operator+重载了运算符+。

可以在例4.1程序的基础上重载运算符“+”,使之用于复数相加。

//例4.2 重载运算符“+”
#include <iostream>
using namespace std;
class 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;
}
//或简写为:{return Complex(real+c2.real,imag+c2.imag);}
void Complex::display()
{cout<<"("<<real<<","<<imag<<"i)"<<endl;}
int main()
{Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2; //+重载定义后可以直接用+
cout<<"c1="; c1.display();
cout<<"c2="; c2.display();
cout<<"c1+c2="; c3.display();
return 0;
}
请比较例4.1和例4.2,只有两处不同:
●在例4.2中以operator+函数取代了例4.1中的complex_add函数,而且只是函数名不同,
函数体和函数返回值的类型都是相同的。

●在main函数中,以“c3=c1+c2;”取代了例10.1中的“c3=plex_add(c2);”。

在将运算
符+重载为类的成员函数后,C++编译系统将程序中的表达式c1+c2解释c1.operator+(c2)
其中c1和c2是Complex类的对象,即以c2为实参调用c1的运算符重载函数
operator+(Complex &c2),进行求值,得到两个复数之和。

虽然重载运算符所实现的功能完全可以用函数实现,但是使用运算符重载能使用户程序易于编写、阅读和维护。

在实际工作中,类的声明和类的使用往往是分离的。

假如在声明Complex类时,对运算符+,-,*,/都进行了重载,那么使用这个类的用户在编程时可以完全不考虑函数是怎么实现的,放心大胆地直接使用+,-,*,/进行复数的运算即可,十分方便。

c1+c2就相当于c1.operator+(c2),c1+c2的书写形式更加人性化。

4.3 重载运算符的规则
重载运算符的规则如下:
1、C++不允许用户自己创建新的运算符,只能对已有的C++运算符进行重载。

2、操作个数不变、优先级不变、结合性不变
3、不能有默认的参数
4、重载运算符必须和用户定义的对象一起使用,其参数至少应有一个是类对象或类对象的
引用。

也就是说参数不能全是C++的标准类型,以防用户改变了原标准类型的运算性质。

5、重载后的操作符的新意思要与原意一致。

6、基本规则(有些规则在后面的实例中会讲到)
1)一元操作符可以是不带参数的成员函数或带一个参数的非成员函数。

2)二元操作符可以是带一个参数的成员函数或带两个参数的非成员函数。

3)operator=、operator[]、operator()、operator->只能定义为成员函数。

4)operator->的返回值必须是一个指针或能使用->的对象。

5)重载operator++ 和operator-- 时带一个int 参数表示后缀,不带参数表示前缀。

6)除operator new 和operator delete 外,重载的操作符参数中至少要有一个自定义数据类型。

4.4 运算符重载函数作为类成员函数和友元函数
在我们前面举的实例中,“+”是双目运算,但我们只看到一个参数。

那是因为重载函数是类中的成员函数,有一个参数是隐含的,运算符函数是用this指针隐式地访问类对象的成员。

运算符重载函数除了可以作为类的成员函数,还可以是非成员函数。

//例4.3 +重载,重载函数不作为成员函数,而是作为Complex的友员函数
#include <iostream.h>
//VC++没有完全实现C++标准,不带后缀.h的头文件不支持操作符重载为友元函数,
//所以这里要加上.h,并删去使用名空间那句话。

class Complex
{public:
Complex(){real=0;imag=0;}
Complex(double r){real=r;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;
c3=c1+c2;
cout<<"c1="; c1.display();
cout<<"c2="; c2.display();
cout<<"c1+c2="; c3.display();
return 0;
}
与例4.2相比较,只作了一处改动,将运算符函数不作为成员函数,而把它放在类外,在Complex类中声明它为友元函数。

同时将运算符函数改为有两个参数。

在将运算符“+”重载为非成员函数后,C++编译系统将程序中的表达式c1+c2解释为:operator+(c1,c2),即执行c1+c2相当于调用以下函数:Complex operator + (Complex &c1,Complex &c2)
{return Complex(c1.real+c2.real, c1.imag+c2.imag);}
求出两个复数之和。

为什么把运算符函数作为友元函数呢?因为运算符函数要访问Complex类对象中的成员。

如果运算符函数不是Complex类的友元函数,而是一个普通的函数,它是没有权利访问Complex类的私有成员的。

在4.2节中曾提到过:运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数。

现在分别讨论这3种情况。

1)只有在极少的情况下才使用既不是类的成员函数也不是友元函数的普通函数,原因是上面提到的,普通函数不能直接访问类的私有成员。

//自编,用普通函数实现+重载
#include <iostream>
using namespace std;
class Complex
Complex(){real=0;imag=0;}
Complex(double r){real=r;imag=0;}
Complex(double r,double i){real=r;imag=i;}
void display();
int getreal(){return real;}
int getimag(){return imag;}
private:
double real;
double imag;
};
Complex operator+(Complex &c1,Complex &c2)
{return Complex(c1.getreal()+c2.getreal(), c1.getimag()+c2.getimag());}
void Complex::display()
{cout<<"("<<real<<","<<imag<<"i)"<<endl;}
int main()
{Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2;
cout<<"c1="; c1.display();
cout<<"c2="; c2.display();
cout<<"c1+c2="; c3.display();
return 0;
}
因为不能直接访问私有成员,只有定义公有的getreal和getimag两个函数来读取私有数据成员。

这样转了一个圈子,编程不便,运行时调用函数的降低了效率。

2)将运算符重载函数作为成员函数,如例4.2,它可以通过this指针自由地访问本类的数据成员,因此可以少写一个函数的参数。

但必须要求运算表达式第一个参数(即运算符左侧的操作数)是一个类对象,而且与运算符函数的类型相同。

因为必须通过类的对象去调用该类的成员函数,而且只有运算符重载函数返回值与该对象同类型,运算结果才有意义。

在例4.2中,表达式c1+c2中第一个参数c1是Complex类对象,运算符函数返回值的类型也是Complex,这是正确的。

如果c1不是Complex类,它就无法通过隐式this指针访问Complex 对象了。

如果想将一个复数和一个整数相加,如c1+i,可以将运算符重载函数作为成员函数,如下形式:
Complex Complex∷operator+(int &i) //运算符重载函数作为Complex类的成员函数{return Complex(real+i,imag);}
注意在表达式中重载的运算符“+”左侧应为Complex类的对象,如:c3=c2+i; 不能写成:c3=i+c2; //运算符“+”的左侧不是类对象,编译出错
//自编,复数+整数
#include<iostream.h>
class Complex
{
private:
double real,imag;
Complex operator+(int i){return Complex(real+i,imag);}
Complex(){}
Complex(double r,double i){real=r;imag=i;}
void display()
{ i f(imag>=0)
cout<<real<<"+"<<imag<<'i'<<endl;
else
cout<<real<<imag<<'i'<<endl;
}
Complex add(Complex &c)
{ return Complex(real+c.real,imag+c.imag);
}
};
int main()
{ Complex c1(1,2);
(c1+3).display();
return 0;
}
3)友元函数方式
如果出于某种考虑,要求在使用重载运算符时运算符左侧的操作数是整型量(如表达式i+c2,运算符左侧的操作数i是整数),这时是无法利用前面定义的重载运算符的,因为无法调用i.operator+函数。

可想而知,如果运算符左侧的操作数属于C++标准类型(如int)或是一个其他类的对象,则运算符重载函数不能作为成员函数,只能作为非成员函数。

如果函数需要访问类的私有成员,则必须声明为友元函数。

可以在Complex类中声明:friend Complex operator+(int &i,Complex &c); //第一个参数可以不是类对象
在类外定义友元函数:
Complex operator+(int &i, Complex &c) //运算符重载函数不是成员函数{return Complex(i+c.real,c.imag);}
//自编,整数+复数
#include<iostream.h>
class Complex
{
private:
double real,imag;
public:
Complex operator+(int i){return Complex(real+i,imag);}
friend Complex operator+(int i,Complex &c);
Complex(){}
Complex(double r,double i){real=r;imag=i;}
void display()
{
if(imag>=0)
cout<<real<<"+"<<imag<<'i'<<endl;
else
cout<<real<<imag<<'i'<<endl;
}
Complex add(Complex &c)
{
return Complex(real+c.real,imag+c.imag);
}
};
Complex operator+(int i, Complex &c) //运算符重载函数不是成员函数{return Complex(i+c.real,c.imag);}
int main()
{
Complex c1(1,2);
(c1+3).display();
(3+c1).display();
return 0;
}
将双目运算符重载为友元函数时,在函数的形参表列中必须有两个参数,不能省略,形参的顺序任意,不要求第一个参数必须为类对象。

但在使用运算符的表达式中,要求运算符左侧的操作数与函数第一个参数对应,运算符右侧的操作数与函数的第二个参数对应。

如c3=i+c2; //正确,类型匹配
c3=c2+i; //错误,类型不匹配
请注意,数学上的交换律在此不适用。

如果希望适用交换律,则应再重载一次运算符“+”。


Complex operator+(Complex &c, int &i) //此时第一个参数为类对象
{return Complex(i+c.real,c.imag);}
这样,使用表达式i+c2和c2+i都合法,编译系统会根据表达式的形式选择调用与之匹配的运算符重载函数。

可以将以上两个运算符重载函数都作为友元函数,也可以将一个运算符重载函数(运算符左侧为对象名的) 作为成员函数,另一个(运算符左侧不是对象名的)作为友元函数。

但不可能将两个都作为成员函数,原因是显然的。

C++规定,有的运算符(如赋值运算符、下标运算符、函数调用运算符)必须定义为类的成员函数,有的运算符则不能定义为类的成员函数(如流插入“<<”和流提取运算符“>>”、类型转换运算符)。

由于友元的使用会破坏类的封装,因此从原则上说,要尽量将运算符函数作为成员函数。

但考虑到各方面的因素,一般将单目运算符重载为成员函数,将双目运算符重载为友元函数。

4.5 重载双目运算符
双目运算符(或称二元运算符)是C++中最常用的运算符。

双目运算符有两个操作数,通常在运算符的左右两侧,如3+5,a=b,i<10等。

在重载双目运算符时,不言而喻在函数中应该有两个参数。

在前面所举的例子都是两目运算的。

下面再举一个例子说明重载双目运算符的应用。

下面我们来定义一个字符串String,用来存放不定长的字符串,重载运算符“==”,“<”和“>”,用于两个字符串的等于、小于和大于的比较运算。

//例4.4
//第一步:建立一个String类,具备最基本功能
#include <iostream>
using namespace std;
class String
{
public:
String( ){p=NULL;} //默认构造函数
String(char *str); //构造函数重载
void display( );
private:
char *p; //字符型指针,用于指向字符串
};
String::String(char *str) //定义构造函数
{p=str;} //使p指向实参字符串
void String::display() //输出p所指向的字符串
{cout<<p;}
int main( )
{
String string1("Hello"),string2("Book");
string1.display( );
cout<<endl;
string2.display( );
return 0;
}
在有了String这个类之后,再增加其他必要的内容。

现在增加对运算符重载的部分。

为便于编写和调试,先重载一个运算符“>”。

程序如下:
//例4.4
//第二步,定义一个运算符”>”重载
#include <iostream.h>
#include <string.h>
class String
{public:
String(){p=NULL;}
String(char *str);
friend bool operator>(String &string1,String &string2);
/*bool operator>(String s)
{
if(strcmp(p,s.p)>0)
return true;
else
return false;
}*/ //也可以用成员函数来实现
void display();
private:
char *p;
};
String::String(char *str)
{p=str;}
void String::display()
{cout<<p;}
bool operator>(String &string1,String &string2) {if(strcmp(string1.p,string2.p)>0)
return true;
else return false;
}
int main()
{String string1("Hello"),string2("Book");
cout<<(string1>string2)<<endl;
return 0;
}
程序运行结果为1。

思考:用成员函数或普通函数怎么实现?//成员函数实现
#include<iostream>
using namespace std;
class String
{
char *p;
public:
String(){p=NULL;}
String(char *str){p=str;}
void display(){cout<<p<<endl;}
bool operator>(String s)
{
if(strcmp(p,s.p)>0)
return true;
else
return false;
}
};
int main()
{
String str1("Hello"),str2("hello");
str1.display();
cout<<(str1>str2)<<endl;
这只是一个并不很完善的程序,但是,已经完成了实质性的工作了,运算符>重载成功了。

其他两个运算符的重载基本是一样的。

//例4.4
//第三步:重载运算符>、==
#include <iostream.h>
#include <string.h>
class String
{public:
String(){p=NULL;}
String(char *str);
friend bool operator>(String &string1,String &string2);
friend bool operator<(String &string1,String &string2);
friend bool operator==(String &string1,String &string2);
void display();
private:
char *p;
};
String::String(char *str)
{p=str;}
void String::display()
{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;
}
int main()
{String string1("Hello"),string2("Book");
cout<<(string1>string2)<<endl;
cout<<(string1<string2)<<endl;
cout<<(string1==string2)<<endl;
return 0;
//说明:该实例也可以通过成员函数和普通函数的形式实现。

共三种方法。

最后还可以进一步完善程序,使得输出结果更直观。

这里就不给出源程序了。

见P326。

通过这个例子,不仅可以学习到有关双目运算符重载的知识,而且还可以学习怎样去编写C++程序。

这种方法的指导思想是:先搭框架,逐步扩充,由简到繁,最后完善。

边编程,边调试,边扩充。

千万不要企图在一开始时就解决所有的细节。

类是可扩充的,可以一步一步地扩充它的功能。

最好直接在计算机上写程序,每一步都要上机调试,调试通过了前面一步再做下一步,步步为营。

这样编程和调试的效率是比较高的。

4.6 重载单目运算符
单目运算符只有一个操作数,如!a,-b,&c,*p,还有最常用的++i和--i等。

重载单目运算符的方法与重载双目运算符的方法是类似的。

但由于单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可省略此参数。

下面以自增运算符“++”为例,介绍单目运算符的重载。

我们知道,++有前后之分,使用前增量时,对对象(操作数)进行增量修改,然后再返回该对象。

所以前增量运算符操作时,参数与返回的是同一个对象,这与基本数据类型的前增量操作类似,返回的也是左值。

使用后增量时,必须在增量之前返回原有的对象值。

为此,需要创建一个临时对象,存放原有的对象,以便对操作数(对象)进行增量修改时,保存最初的值。

后增量操作返回的是原有对象值,不是原有对象,原有对象已经被增量修改,所以,返回的应该是存放原有对象值的临时对象。

返回类型是类就可以了。

C++约定,在增量运算符定义中,放上一个整数形参,就是后增量运算符。

/*例4.5
有一个Time类,包含数据成员minute和sec,模拟秒表,每次走1秒,
满60秒进一分钟。

此时又从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;
++minute;}
return *this; //return Time(minute,sec);
}
int main()
{Time time1(34,0);
for (int i=0;i<61;i++)
{++time1;
time1.display();}
return 0;
}
思考:上例是用成员函数来实现,如果用友元函数、普通函数怎么实现?//上例用友元函数实现
#include<iostream>
using namespace std;
class Time
{
int minute,sec;
public:
Time():minute(0),sec(0){}
Time(int m,int s):minute(m),sec(s){}
void display(){cout<<minute<<":"<<sec<<endl;}
friend Time operator++(Time &t );
};
Time operator++(Time &t) //这里一定要用引用
{ t.sec++;
if(t.sec>=60)
{ t.sec-=60;
t.minute++;
}
return t;
}
int main()
{
Time t(3,59);
for(int i=1;i<=70;i++)
{
(++t).display();
}
}
用普通函数也可以实现,程序如下:
#include<iostream>
using namespace std;
class Time
{
int minute,sec;
public:
Time():minute(0),sec(0){}
Time(int m,int s):minute(m),sec(s){}
void display(){cout<<minute<<":"<<sec<<endl;}
int getmin(){return minute;}
int getsec(){return sec;}
};
Time operator++(Time &t)
{ int s,m;
s=t.getsec();
m=t.getmin();
s++;
if(s>=60)
{ s-=60;
m++;
}
t=Time(m,s);
return t;
}
int main()
{
Time t(3,59);
for(int i=1;i<=70;i++)
{
(++t).display();
}
}
C++约定,在增量运算符定义中,放上一个整数形参,就是后增量运算符。

下面我们在上例的基础上进行后++的重载定义。

#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;
++minute;}
return *this;
}
Time Time::operator++(int)
{Time temp(*this); //要返回的,前暂存起来,因为在后面会对其数据成员改变sec++;
if(sec>=60)
{sec-=60;
++minute;}
return temp;
}
int main()
{Time time1(34,59),time2;
cout<<" time1 : ";
time1.display();
++time1;
cout<<"++time1: ";
time1.display();
time2=time1++;
cout<<"time1++: ";
time1.display();
cout<<" time2 : ";
time2.display();
return 0;
}
我们看到,前++是先自加,返回修改后的对象本身。

后者返回的是自加前的对象,然后对象自加。

下面来看一下我们在前面定义的Time(包括hour,minute,second)进行++运算的实例。

//==================================
// 自编,Time类的++重载
//==================================
#include<iostream.h>
#include<iomanip.h>
//----------------------------------
class Time{
int hour, minute, second;
public:
Time(int h, int m, int s){ hour=h, minute=m, second=s; }
friend Time& operator++(Time& a); //这是前增量,返回的是类的引用
friend Time operator++(Time& a, int); //这是后增量,返回的是类
void disp(){cout<<hour<<":"<<minute<<":"<<second<<endl;}
};//-------------------------------
Time& operator++(Time& a){
if(!(a.second=(a.second+1)%60)&&!(a.minute=(a.minute+1)%60))
a.hour=(a.hour+1)%24;
return a;
}//--------------------------------
Time operator++(Time& a, int){
Time t(a);
if(!(a.second=(a.second+1)%60)&&!(a.minute=(a.minute+1)%60))
a.hour=(a.hour+1)%24;
return t;
}//------------------------------------
int main(){
Time t(11, 59, 58);
(t++).disp();
(++t).disp();
}//================================
前后增量操作的意义,决定了其不同的返回方式。

前增量运算符返回引用,后增量运算符返回值。

后增量运算符中的参数int只是为了区别前增量与后增量,除此之外没有任何作用。

因为定义中,无须使用该参数,所以形参名在定义头中省略。

4.7 重载流插入运算符和流提取运算符
C++的流插入运算符“<<”和流提取运算符“>>”是C++在类库中提供的,所有C++编译系统都在类库中提供输入流类istream和输出流类ostream。

cin和cout分别是istream类和ostream类的对象。

在类库提供的头文件中已经对“<<”和“>>”进行了重载(<<左移操作符,>>右移操作符),使之作为流插入运算符和流提取运算符,能用来输出和输入C++标准类型的数据。

因此,在本书前面几章中,凡是用“cout<<”和“cin>>”对标准类型数据进行输入输出的,都要用#include <iostream>把头文件包含到本程序文件中。

用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。

如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。

对“<<”和“>>”重载的函数形式如下:
istream &operator >> (istream &,自定义类&); //返回类型和第一个参数必须是istream & ostream & operator << (ostream &,自定义类&); //返回类型和第一个参数必须是ostream& 即重载运算符“>>”的函数的第一个参数和函数的类型都必须是istream&类型,第二个参数是要进行输入操作的类。

重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。

因此,只能将重载“>>”和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数。

(因为一个操作数不是类对象)
4.7.1 重载流插入运算符<<
//例4.7 在4.2的基础上,用重载的“<<”来输出复数
#include <iostream.h>
class Complex
{public:
Complex(){real=0;imag=0;}
Complex(double r,double i){real=r;imag=i;}
Complex operator + (Complex &c2);
friend ostream& operator << (ostream&,Complex&); //运算符<<重载为友元函数private:
double real;
double imag;
};
Complex Complex::operator + (Complex &c2)
{return Complex(real+c2.real,imag+c2.imag);}
ostream& operator << (ostream& output, Complex& c) //定义运算符<<重载函数
{output<<"("<<c.real<<"+"<<c.imag<<"i)"<<endl;
return output;
}
int main()
{Complex c1(2,4),c2(6,10),c3;
c3=c1+c2;
cout<<c3;
/*可以直接输出用户自己定义的类对象了,即operator<<(cout,c3)
cout是实参,output是形参,c3是实参,c是形参,代入后即执行
cout<< output<<"("<<c3.real<<"+"<<c3.imag<<"i)"<<endl;*/
cout<<c1<<c2<<c3;
/*return output的作用就是可以重叠操作,return output就是return cout,
((cout<<c1)<<c2) <<c3*/
return 0;
}
//说明,如果用普通函数也能实现,但要定义可以获得数据成员的公有函数才行。

下面我们来对Point这个类来进行<<的重载
//==================================
// 自编
// 重载操作符<<
//==================================
#include<iostream.h>
//----------------------------------
class Point{
int x, y;
public:
Point(int a, int b){ x=a, y=b; }
friend ostream& operator<<(ostream&,Point&);
friend Point operator+(const Point& a, const Point& b);
};//===============================
Point operator+(const Point& a, const Point& b)
{
return Point(a.x+b.x, a.y+b.y);
}//--------------------------------
ostream& operator<<(ostream& o,Point& a){
return o<<"("<<a.x<<","<<a.y<<")"<<endl;
}//----------------------------------
int main(){
Point a(3,2), b(1,5);
cout<<a;cout<<b;
cout<<(a+b);
}//================================
为什么Point的+操作返回Point类的值,而<<操作返回流的引用呢?
因为对于operator+(),两个对象相加,不改变其中任一个对象。

而且它必须生成一个结果对象来存放加法的结果,并将该结果对象以值的方式返回给调用者。

而<<就不同了,cout<<a的结果是cout流,加上操作符的左结合性,所以其<<可以进行cout<<…<<…<<…;式的重叠操作。

所以其返回值必须是流。

而且返回的改变了的对象其返回值要求是左值,这个条件决定了它不能以值返回。

而要用引用返回。

通俗地讲,如果返回的值还要再用它来参加运算(改变它的值)就要返回引有,如果返回的值不用再参加运算(改变它的值)就用值方式返回。

4.7.2 重载流插入运算符>>
学习了<<的重载,再来学>>的重载就简单多了。

//例4.8 在4.7的基础上,增加>>的重载定义。

//以下只写了重载>>的部分
istream& operator>>(istream & input,Complex&c) //注意,必须是引有
{
cout<<"please input:";
input>>c.real>>c.imag;
return input;
}
同样的道理,>>也可以重叠操作。

4.8 不同类型数据间的转换
4.8.1 标准类型数据间的转换
在C++中,某些不同类型数据之间可以自动转换,例如5/8与5.0/8结果不同,原因是C++执行了两种不同的操作。

5.0/8匹配了两个double类型数的除法,C++知道如何将8转换成double型,这是基本数据类型的转换。

这种转换是C++编译系统自动完成的,用户不需干预。

这种转换称为隐式类型转换。

C++还提供了显式转换,其形式为:类型名(数据)
4.8.2 用转换构造函数进行类型转换
转换用户定义的类类型,必须由用户告知。

用户告知的方式就是定义只包含一个参数的转换构造函数。

转换构造函数的作用是将一个其他类型的数据转换成一个类的对象。

我们前面学过以下几种构造函数,再来回顾一下。

均以Complex类为例
默认构造函数:Complex();
用于初始化的构造函数:Complex(double r,double i);
用于复制对象的复制构造函数:Complex(Complex &c);
用于类型转换的转换构造函数:转换构造函数只有一个参数,如:
Complex(double r){real=r;imag=0;}其作用是将double型的参数r转换成Complex类的对象,将r作为复数的实数,虚部为0。

用户可以根据需要定义转换构造函数。

//自编,将一个double的数转换为复数。

#include <iostream.h>
class Complex
{public:
Complex(){real=0;imag=0;}
Complex(double r,double i){real=r;imag=i;}
Complex operator+(Complex &c){return Complex(real+c.real,imag+c.imag);}
friend ostream & operator<<(ostream &,Complex);
Complex(double r){real=r;imag=0;} //转换构造函数
private:
double real;
double imag;
};
ostream & operator<<(ostream & output,Complex c)
{
if(c.imag>=0)
output<<"("<<c.real<<"+"<<c.imag<<"i)"<<endl;
else
output<<"("<<c.real<<c.imag<<"i)"<<endl;。

相关文档
最新文档