第十二讲:重载单、双目、插入、提取运算符
本讲基本要求
* 掌握:重载单、双目算符的重载。
* 理解:插入、流提取算符的使用方法。
重点、难点
* 重载单、双目算符的重载。
一、 重载双目运算符
双目运算符(或称二元运算符)是中最常用的运算符。双目运算符有两个操作数,通常在运算符的左右两侧,如3+5,a=b,i<10等。在重载双目运算符时,不言而喻在函数中应该有两个参数。下面再举一个例子说明重载双目运算符的应用。
例4 定义一个字符串类String,用来存放不定长的字符串,重载运算符“==”, “<”和“>”,用于两个字符串的等于、小于和大于的比较运算。(注意观察C++程序设计的规则)
(1)先建立一个String类:
#include <iostream>
using namespace std;
class String //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; }
先编写出最简单的程序框架,这是一个可供运行的程序。编写和调试都比较容易。在定义对象stringl时给出字符串"Hello"作为实参,它的起始地址传递给构造函数的形参指针str。在构造函数中,使p指向"Hello"。执行main函数中的stringl.display()时,就输出p指向的字符串"Hello"。在定义对象string2时给出字符串"Book"作为实参,同样,执行mam函数中的string2.display()时,就输出p指向的字符串"Book"。运行结果为:
Hello
Book
(2)有了这个基础后,再增加其他必要的内容。现在增加对运算符重载的部分。为便于编写和调试,先重载运算符“>”。程序如下:
#include <iostream>
#include <string>
using namespace std;
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;} //输出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; }
程序所增加的部分是很容易看懂的。将运算符重载函数声明为友元函数,运算符重载函数为bool型(逻辑型),它的返回们是一个逻辑值(true或false)。在函数中调用库函数中的strcmp函数,stringl.p指向"Hello",strmg2.p指向"Book",如果'"Helllo">"Book>,则返回true(以1表示),否则返回false(以0表示)。在main函数中输出比较结果。程序运行结果为l。
这只是一个并不很完善的程序,但是,已经完成了实质性的工作了,运算符“>”重载成功了。既然对运算符“>”的重裁成功了,具他两个运算符的重载如法炮制即可。
(3)扩展到对3个运算符重载。
在String类中十声明3个重载函数为友元函数:
friend bool operalor>(String&stringl,Siring&string2);
friend bool operator<(String&stringl,String&string2);
fidend bool operator==(Stnng&stringl,String&strin2);
在类外分别定义3个运算符重载函数:
bool operator>(String&slringl,String&strinZ2) //对运算符”>”重载
{ if(strcmp(stringl.p,string2.P)>0)
return true;
else
return false; }
bool operator<(String&stringl,String&string2)//对运算符“<”重载
{ if(strcmp(stringl.p,stuns2.p)<0)
return true;
else
return false; }
bool operator==(String&stringl,String&strinS2)//对运算符“==”重载
{ if(strcmp(stfingl.p,stfing2.P)==O)
return true;
else
return false; }
再修改主函数:
int main()
{ String string1(”Hello”),string2(”Book”),stnng3("Computer");
cout<<(stringl>strlng2)<<endl;//比较结果应该为true
cout(<(stringl<string3)<<endl;//比较结果应该为false
cout<<(stringl==string2)<<endl //比较结果应该为false
retum O;}
运行结果为:
1
0
0
(4)再进一步修饰完善,使输出结果更直观。下面给出最后的程序。
#include <iostream>
#include <string>
using namespace std;
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;} //输出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; }
Hello>Book
Book<Computer
Hello==Hello
增加了一个compare函数,用来对两个字符串进行比较,并输出相应的信息。这样可以减轻主函数的负担,使主函数简明易读。
通过这个例子,不仅可以学习到有关双目运算符重载的知识,而且还可以学习怎样去编写程序。由于程序包含类,一般都比较长,有的初学的读者见到比较长的程序就发怵,不知该怎样着手去阅读和分析它。轮到自己编程序,更不知道从何人手,往往未经深思熟虑,想到什么就写什么,一门气把程序写了出来,结果一上机调试,错误百出,为找出错位置就花费了大量的时间。根据许多初学者的经验,上面介绍的方法是很适合没有编程经验的初学者的,能使人以清晰的思路进行程序设计,减少出错机会,提高调试效率。
这种方法的指导思想是:先搭框架,逐步扩充,由简到繁,最后完善。边编程,边调试,边扩充。千万不要企图在一开始时就解决所有的细节。类是可扩充的,可以一步一步地扩充它的功能。最好直接在计算机上写程序,每一步都要上机调试,调试通过了前面一步再做下一步,步步为营。这样编程和调试的效率是比较高的。
二、 重载单目运算符
单目运算符只有一个操作数,如!a,-b,&c,*p,还有常用的++i和--i等。重载单目运算符的方法与重载双目运算符的方法是类似的。但由于单目运算符只有一个操作数,因此运算符重载函数只有一个参数,如果运算符重载函数作为成员函数,则还可省略此参数。
下面以自增运算符“++”为例,介绍单目运算符的重载。
例5:有一个Time类,包含数据成员minute(分)和sec(秒),模拟秒表,每次走一秒,满60秒进一分钟,此时秒又从。开始算。要求输出分和秒的值。
#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; }运行情况如下:
34:1
34;2
34:59
35:0
35:1 (共输出61行)
可以看到:在程序中对运算符“++”进行了重载,使它能用于Time类对象。细心的读者可能会提出一个问题:“++”和“--”运算符有两种使用方式,前置自增运算符和后置自增运算符,它们的作用是不一样的,在重载时怎样区别这二者呢?
针对“++”和“--”这—特点,C++约定:如果在自增(自减)运算符重载函数中,增加一个int型形参,就是后置自增(自减)运算符函数。
例6 在例5程序的基础上增加对后置自增运算符的重载。修改后的程序如下:
#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++; //将自加前的对象的值赋给time2
cout<<"time1++: ";
time1.display();
cout<<" time2 : "; //输出time2对象的值
time2.display();
return 0; }
运行结果如下:
time1:34:59 (time1原值)
++timel:35:0 (执行++time1后timel的值)
time1++:35:1 (再执行tiinel++后time1的值)
time2:35:0 (time2保存的是执行timel++前time1的值)
可以看到:重载后置自增运算符时,多了一个int型的参数,增加int型的参数参数只是为了与前置自增运算符重载函数有所区别,此外没有任何作用,在定义函数时也不必使用此参数,因此可省写参数名,只需在括号中写int即可。编译系统在遇到重载后置自增运算符时,会自动调用此函数。
三、重载流插入运算符“<<”
问题提出:
C++的流插入运算符“<<”和流提取运算符“>>”是在类库中提供的,所有编译系统都在类库中提供输入流类itream和输出流类ostream。cin和cout分别是istream类和ostream类的对象。在类库提供的头文件中已经对“<<”和“>>”进行了重载,使之作为流插入运算符和流提取运算符,能用来输出和输入标准类型的数据。因此,在本书前面几章中,凡是用“cout <<”和“cin>>”对标准类型数据进行输入输出的,都要用#include<iostream>把头文件包含到本程序文件中。
用户自己定义的类型的数据,是不能直接用“<<”和“>>”来输出和输入的。如果想用它们输出和输入自己声明的类型的数据,必须对它们重载。
对“<<”和“>>”重载的函数形式如下:
istream&operator>>(istream&,自定义类&);
ostream&operator<<(ostream&,自定义类&);
说明:即重载运算符“>>”的函数的第一个参数和函数的类别都必须是istream&类型,第二个参数是要进行输入操作的类。重载“<<”的函数的第一个参数和函数的类型都必须是ostream&类型,第二个参数是要进行输出操作的类。因此,只能将重载“>>’和“<<”的函数作为友元函数或普通的函数,而不能将它们定义为成员函数(思考这是为什么???)
在程序中,人们希望能用插入运算符“<<”来输出用户自己声明的类对象的信息,这就需要重载流插入运算符“<<”。
例7 在例2的基础上,用重载的“<<”输出复数。
#include <iostream> //本行在VC6.0中需要修改,并删下一行,VC6不支持将成员函数重载为友员函数。
using namespace std;
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; } // output是cout的别名,本句是为了连续输出,保留输出流现状。int main()
{ Complex c1(2,4),c2(6,10),c3;
c3=c1+c2;
cout<<c3; //c3不是基本数据类型,是类对象,只有把<<重载方可用。
return 0; }运行结果为:
(8+14i)
四、重载流提取运算符“>>”
学习了重载流插入运算符以后,再理解重载流提取运算符“>>”就不难了。预定义的运算符“>>”的作用是从一个输人流中提取数据,如"cin>>i;"表示从输入流中提取一个整数赋给变量i(假设已定义i为int型)。重载流提取运算符的目的是希望将“>>”用于输入自定义类型的对象的信息。
例8 在例7的基础上,增加重载流提取运算符“>>”,用"cin>>"输入复数,"cout<<"输出复数。
#include <iostream>
using namespace std;
class Complex
{ public:
friend ostream& operator << (ostream&,Complex&);//声明重载运算符“<<”函数
friend istream& operator >> (istream&,Complex&);//声明重载运算符“>>”函数
private:
double real;
double imag; };
ostream& operator << (ostream& output,Complex& c)//定义重载运算符“<<”函数
{ output<<"("<<c.real<<"+"<<c.imag<<"i)";//当输入虚部为负时如何处理
return output; }istream& operator >> (istream& input,Complex& c) //定义重载运算符“>>”函数
{ cout<<"input real part and imaginary part of complex number:";
input>>c.real>>c.imag;
return input; }int main()
{ Complex c1,c2;
cin>>c1>>c2; //一次>>调用一次重载函数
cout<<"c1="<<c1<<endl;
cout<<"c2="<<c2<<endl;
return 0; }运行情况如下:
input real part and imaginary part Of complex number:3 6/
input real part and imaginary part Of complex number:4 10/
c1=(3+6i)
c2=(4+10i)
注意:cin语句中两个“>>”,每遇到一次“>>”就调用一次重载运算符“>>”函数,因此,两次输出提示输入的信息,然后要求用户输入对象的值。
以上运行结果无疑是正确的,但并不完善。在输入复数的虚部为正值时,输出的结果是没有问题的,但是虚部如果是负数,就不理想,请观察输出结果。
input real part and imaginary part Of complex number:3 6/
input real part and imaginary part Ofcomplex number:4 -10/
cl=(3+6i)
c2=(4+-10i)
根据前面提到过的先调试通过,最后完善的原则,可对程序作必要的修改。将重载运算符“<<”函数修改如下:
osfream&operator<<(ostream&output,Complex&c)
{ output<<"("<<c.real;
if(c.imag>=0)output<<"+"; //虚部为正数时,在虚部前加“+”号
output<<c.imag<<"i)¨<<endl;//虚部为负数时,在虚部前不加“+”号
return output;}
这样,运行时输出的最后一行为:c2=(4-10i)
本章实例应注意两点:
A、 运算符重载中使用引用 (reference) 的重要性。利用引用作为函数的形参可以在调用函数的过程中不是用传递值的方式进行虚实结合,而是通过传址方式使形参成为实参的别名,因此不生成临时变量 ( 实参的副本 ) ,减少了时间和空间的开销。
B、如果重载函数的返回值是对象的引用时,返回的不是常量,而是引用所代表的对象,它可以出现在赋值号的左侧而成为左值 (left value) ,可以被赋值或参与其他操作 ( 如保留 cout 流的当前值以便能连续使用“ << ”输出 ) 。但使用引用时要特别小心,因为修改了引用就等于修改了它所代表的对象。
作业:http://210.44.195.12/cgyy/text/HTML/text/12.htm