第十一章,使用类

第十一章,使用类

运算符重载

在头文件中,如果函数在类声明的时候同时也定义了原型,则自动成为内联函数,这种句法适用于声明那些不会对其显示访问的对象进行修改的函数。

1、成员 重载运算符函数 是实现重载运算符的工具,其格式如下:
operato op(arguement-list) ,其中,op为要重载的运算符;
arguement-list为函数的参数,一般为被重载运算符所属的对象(以重载 + 为例,初学便于理解);
 如:
成员运算符函数声明原型  
Time operateor+(const Time & t )const; 其中参数Time & t为要引用的对象;
调用成员运算符函数
total = T1 + T2 ; T1、T2为Time对象,注意,T1,T2的顺序不能反,见下面,
上面的调用函数也可以写成  
total = T1.operateor+(T2);


2、非成员 重载运算符函数 原型格式(即友元函数,作为类的扩展接口的组成部分,类方法和友元只是表达类接口的两种不同机制):

以重载 为例,

其函数声明原型为

friend Time operate+(const  Time & t1   ,const Time & t2); 

以重载  * 为例,

其函数声明原型为
friend Time operate* (double m,const & t);    注意,此处参数的顺序与成员运算符函数的参数顺序相反
其定义函数前面不用加 Time:: 限定符,且在定义中,不用加关键字friend,但如果定义的同时也是原型时,如inline内联函数,则需要加friend前缀;
其函数调用方式如下:
T2 = operateor*(2.75,T1) 
注意:
虽然 operate*()函数是在类中声明的,但是不属于成员函数,因此不能用成员运算符来调用;

虽然 operate*()函数不是类成员函数,但它与成员函数的访问权限相同;

3.1、 重载 << 运算符时,最好用友元函数,因为

如果像重载   *   那样,则

   cout << trip;

如果使用Time成员函数来重载<<,Time对象将是第一个操作数,就像使用成员函数*运算符那样,这意味着必须这样使用

trip <<cout;

这明显不是我们想要的,会让人迷惑,为改变这种做法,可以使用友元函数可以像下面这样重载函数运算符:

void operator<<(ostream & os,const Time & t)

{

os << t.hours<<" hours," <<t.minutes<< " minutes "; 

}

这样,可以使用下面的语句:

cout << trip; 

3.2、如上重载方法不允许如下使用:

cout << “trip time:”<<  trip << " Tuesday\n";

即不能连用 << 运算符;因为cout  << x << y    相当于  (cout << x )  << y;cout<< x肯定返回的是ostream对象,因此,可以对友元函数采用如下做法,

ostream & operator << (ostream & os,const Time & t)

{

os << t.hours << " hours ," << t.minutes << " minutes";

return os;

}

注意,返回类型是ostream&,这意味着该函数返回ostream 对象的引用,则下面的语句:

cout  <<  trip;

将被转化为如下的调用;

operator <<(cout,trip);

而该调用返回cout对象。


4、转换构造函数只用于从某种类型类类型的转换,且只有接受一个参数的构造函数才能作为转换函数;

          operatorComplex(double r){real=r;imag=0;}  //转换构造函数 

要进行相反的转换,必须使用特殊的C++运算符函数------转换函数;那么,如何创建转换函数呢?要转化为typename 类型,需要使用这种形式的转换函数

operator typeName();//转换类型函数

请注意以下几点:

# 转换函数必须是类方法;

# 转换函数不能指定返回类型;

# 转换函数不能有参数;

例如,转换为double类型的函数的原型如下:

operator double();

typeName 指出了要转换成的类型,因此不需要指定返回类型。转换函数是类方法意味着:他需要通过类对象来调用,从而告知函数要转换的值。因此,不需要参数。

        将构造函数用于自动类型转换函数似乎是一项不错的特性。然而,当程序员拥有更丰富的c++经验时,将发现这种自动特性并非总是符合需要的,因为这会导致意外的类型转换。因此,c++新增了关键字   explicit ,用于关闭这种特性。

用转换构造函数可以将一个指定类型的数据转换为类的对象。但是不能反过来将一个类的对象转换为一个其他类型的数据(例如将一个Complex类对象转换成double类型数据)。

C++提供类型转换函数(type conversion function)来解决这个问题。类型转换函数的作用是将一个类的对象转换成另一类型的数据。如果已声明了一个Complex类,可以在Complex类中这样定义类型转换函数:
    operator double( )
    {
        return real;
    }
函数返回double型变量real的值。它的作用是将一个Complex类对象转换为一个double型数据,其值是Complex类中的数据成员real的值。请注意,函数名是operator double,这点是和运算符重载时的规律一致的(在定义运算符“+”的重载函数时,函数名是operator +)。

类型转换函数的一般形式为:
    operator 类型名( )
    {
        实现转换的语句
    }
在函数名前面不能指定函数类型,函数没有参数。其返回值的类型是由函数名中指定的类型名来确定的。类型转换函数只能作为成员函数,因为转换的主体是本类的对象。不能作为友元函数或普通函数。

从函数形式可以看到,它与运算符重载函数相似,都是用关键字operator开头,只是被重载的是类型名。double类型经过重载后,除了原有的含义外,还获得新的含义(将一个Complex类对象转换为double类型数据,并指定了转换方法)。这样,编译系统不仅能识别原有的double型数据,而且还会把Complex类对象作为double型数据处理。

那么程序中的Complex类对具有双重身份,既是Complex类对象,又可作为double类型数据。Complex类对象只有在需要时才进行转换,要根据表达式的上下文来决定。转换构造函数和类型转换运算符有一个共同的功能:当需要的时候,编译系统会自动调用这些函数,建立一个无名的临时对象(或临时变量)。

[例10.9] 使用类型转换函数的简单例子。

    
    
    
    
  1. #include <iostream>
  2. using namespace std;
  3. class Complex
  4. {
  5. public:
  6. Complex( ){real=0;imag=0;}
  7. Complex(double r,double i){real=r;imag=i;}
  8. operator double( ) {return real;} //类型转换函数
  9. private:
  10. double real;
  11. double imag;
  12. };
  13. int main( )
  14. {
  15. Complex c1(3,4),c2(5,-10),c3; //调用构造函数创建3个对象
  16. double d;
  17. d=2.5+c1;//要求将一个double数据与Complex类数据相加
  18. cout<<d<<endl;
  19. return 0;
  20. }
对程序的分析:
1) 如果在Complex类中没有定义类型转换函数operator double,程序编译将出错。因为不能实现double 型数据与Complex类对象的相加。现在,已定义了成员函数 operator double,就可以利用它将Complex类对象转换为double型数据。请注意, 程序中不必显式地调用类型转换函数,它是自动被调用的,即隐式调用 。在什么情况下调用类型转换函数呢?编译系统在处理表达式 2.5 +cl 时,发现运算符“+”的左侧是double型数据,而右侧是Complex类对象,又无运算符“+”重载函数,不能直接相加,编译系统发现有对double的重载函数,因此调用这个函数,返回一个double型数据,然后与2.5相加。

2) 如果在main函数中加一个语句:
    c3=c2;
请问此时编译系统是把c2按Complex类对象处理呢,还是按double型数据处理?由于赋值号两侧都是同一类的数据,是可以合法进行赋值的,没有必要把c2转换为double型数据。

3) 如果在Complex类中声明了重载运算符“+”函数作为友元函数:
    Complex operator+ (Complex c1,Complex c2)//定义运算符“+”重载函数
    {
        return Complex(c1.real+c2.real, c1.imag+c2.imag);
    }
若在main函数中有语句
    c3=c1+c2;
由于已对运算符“+”重载,使之能用于两个Complex类对象的相加,因此将c1和c2按Complex类对象处理,相加后赋值给同类对象c3。如果改为
    d=c1+c2; //d为double型变量
将c1与c2两个类对象相加,得到一个临时的Complex类对象,由于它不能赋值给double型变量,而又有对double的重载函数,于是调用此函数,把临时类对象转换为double数据,然后赋给d。

从前面的介绍可知,对类型的重载和对运算符的重载的概念和方法都是相似的,重载函数都使用关键字operator。因此,通常把类型转换函数也称为类型转换运算符函数,由于它也是重载函数,因此也称为类型转换运算符重载函数(或称强制类型转换运算符重载函数)。

假如程序中需要对一个Complex类对象和一个double型变量进行+,-,*,/等算术运算,以及关系运算和逻辑运算,如果不用类型转换函数,就要对多种运算符进行重载,以便能进行各种运算。这样,是十分麻烦的,工作量较大,程序显得冗长。如果用类型转换函数对double进行重载(使Complex类对象转换为double型数据),就不必对各种运算符进行重载,因为Complex类对象可以被自动地转换为double型数据,而标准类型的数据的运算,是可以使用系统提供的各种运算符的。

[例10.10] 包含转换构造函数、运算符重载函数和类型转换函数的程序。先阅读以下程序,在这个程序中只包含转换构造函数和运算符重载函数。
    
    
    
    
  1. #include <iostream>
  2. using namespace std;
  3. class Complex
  4. {
  5. public:
  6. Complex( ){real=0;imag=0;} //默认构造函数
  7. Complex(double r){real=r;imag=0;}//转换构造函数
  8. Complex(double r,double i){real=r;imag=i;}//实现初始化的构造函数
  9. friend Complex operator + (Complex c1,Complex c2); //重载运算符“+”的友元函数,非标准类型,都需要实现重载运算符后才能相加
  10. void display( );
  11. private:
  12. double real;
  13. double imag;
  14. };
  15. Complex operator + (Complex c1,Complex c2)//定义运算符“+”重载函数
  16. {
  17. return Complex(c1.real+c2.real, c1.imag+c2.imag);
  18. }
  19. void Complex::display( )
  20. {
  21. cout<<"("<<real<<","<<imag<<"i)"<<endl;
  22. }
  23. int main( )
  24. {
  25. Complex c1(3,4),c2(5,-10),c3;
  26. c3=c1+2.5; //复数与double数据相加
  27. c3.display( );
  28. return 0;
  29. }
注意,在Visual C++ 6.0环境下运行时,需将第一行改为#include <iostream.h>,并删去第2行,否则编译不能通过。

对程序的分析:
1) 如果没有定义转换构造函数,则此程序编译出错。

2) 现在,在类Complex中定义了转换构造函数,并具体规定了怎样构成一个复数。由于已重载了算符“+”,在处理表达式c1+2.5时,编译系统把它解释为
    operator+(c1, 2.5)
由于2.5不是Complex类对象,系统先调用转换构造函数Complex(2.5),建立一个临时的Complex类对象,其值为(2.5+0i)。上面的函数调用相当于
    operator+(c1, Complex(2.5))
将c1与(2.5+0i) 相加,赋给c3。运行结果为
    (5.5+4i)
3) 如果把“c3=c1+2.5;”改为c3=2.5+c1; 程序可以通过编译和正常运行。过程与前相同。

从中得到一个重要结论,在已定义了相应的转换构造函数情况下,将运算符“+”函数重载为友元函数,在进行两个复数相加时,可以用交换律。

如果运算符重载函数为成员函数,它的第一个参数必须是本类的对象。当第一个操作数不是类对象时,不能将运算符重载函数变为成员函数。如果将运算符“+”重载函数变为类的成员函数,交换律不适用。

由于这个原因,一般情况下将双目运算符重载函数成为友元函数。单目运算符重载函数则多为成员函数。

4) 如果一定要将运算符函数重载为成员函数,而第一个操作数又不是类对象时,只有一个办法能够解决,再重载一个运算符“+”函数,其第一个参数为double型。当然此函数只能是友元函数,函数原型为
    friend operator+(double, Complex &);
显然这样做不太方便,还是将双目运算符函数重载为友元函数方便些。

5) 在上面程序的基础上增加类型转换函数:
    operator double( ){return real;}
此时Complex类的公用部分为:
   public:
   Complex( ){real=0;imag=0;}
     operator Complex(double r){real=r;imag=0;}  //转换构造函数
   Complex(double r,double i){real=r;imag=i;}
    double( ){return real;}//类型转换函数
   friend Complex operator+ (Complex c1,Complex c2); //重载运算符“+”
   void display( );
其余部分不变。程序在编译时出错,原因是出现二义性。    


你可能感兴趣的:(第十一章,使用类)