C++运算符重载

来源:互联网 发布:淘宝禁止卖电子书了 编辑:程序博客网 时间:2024/05/16 10:51
 

什么是操作符重载?


  一看到重载,很容易就让人联想到成员函数重载,函数重载可以使名称相同的函数具有不同的实际功能,只要赋给这些同名函数不同的参数就可以了,操作符重载也是基于这一机制的。系统为我们提供了许多操作符,比如“+”,“[ ]”等,这些操作符都有一些默认的功能,而操作符重载机制允许我们给这些操作符赋予不同的功能,并能够按照普通操作符的使用格式来使用自己定义功能的操作符(即重载的操作符)。

  定义之后,我们就可以按照平常使用操作符的格式来使用我们自己的重载操作符了。

  操作符重载一般在类内部定义,就像成员函数一样定义,这叫做类成员重载操作符。当然也可以在类外定义,即非类成员操作符重载。

  为什么要使用操作符重载?

  举例说明,比如类String,该类有这样一个功能,可以将两个字符串连接成一个字符串,为此,我们可以给类String定义一个成员函数实现此功能,可以给该函数取一个形象的名字,比如concatenate或append,但是相比较,这两个名字都不如操作符“+=”形象直观。在这种情况下,我们就可以定义操作符“+=”的重载,来实现此功能。

  也就是说,如果要定义一个函数,而这个函数的功能与操作符的功能比较类似时,这个时候我们就可以定义重载操作符,而不使用通常的成员函数定义。这里所说的操作符重载,指的是与系统定义的操作符重载,而不是说定义两个“+=”,这两个重载,这一点需要清楚。

  但是这四个操作符不能用于重载::: * ? :

  如何声明操作符重载?

  同普通函数类似,只不过它的名字包括关键字operator,以及紧随其后的一个预定义操作符。例如:

  String& operator+=(const String&);

  String& operator+=(const char*);

  注意:上面的括号表示形式参数,即使操作符重载不需要参数,也应该写上一个空的“( )”,而不是将其省略,这一点其实和普通函数的声明是类似的。其实,声明的唯一区别就是名字不同而已。

  怎样使用操作符重载?

  两种操作符重载:类成员操作符重载和非类成员操作符重载。

  1、类成员操作符重载

  已知类String中声明了两个“==”操作符重载,分别是:

  bool operator==(const char*) const;

  bool operator==(const String&) const;

  其中第一个重载的操作符允许我们比较一个String类对象是否等于一个C风格字符串,第二个允许我们比较两个String类对象是否相等。

  示例代码

  :

  #include<String.h>

  int main()

  {

  String flower;

  If(flower==”lily”) //正确:调用bool operator==(const char*) const;

  ……

  else

  if(“tulip”==flower) //错误

  …….

  }

  关键看一下,为什么第二个重载操作符的使用是错误的?

  因为:只有在左操作数是该类类型的对象时,才会考虑使用作为类成员的重载操作符。

  因为这里的”tulip”不是String类型对象,所以编译器试图找到一个内置操作符,它可以有一个C风格字符串的左操作数,然而事实上并不存在这样的操作符,所以编译时产生错误。

  疑问:我们可以使用String类的构造函数将一个C风格字符串,转换成一个String对象,为什么编译器不能做以上转换呢?即

  if(String(“tulip”)==flower);//这样就是正确的

  答:为了效率和正确性

  重载操作符并不要求两个操作数的类型一定相同。可能有这样一个类Text,这个类的构造函数的参数及其成员重载操作符的参数都与String类一致,如果使编译器能够自动将C风格字符串转换成某个类型的对象,那么编译器首先会检索所有的类定义,选择能够提供正确构造函数和重载操作符的类进行转换,这无疑会增加程序的编译时间,还有就是类String和类Text均合适,编译器也不知道该将C风格字符串转换成String还是Text对象了。

  对于类成员重载操作符,隐式的this指针被用作隐式的第一个参数,对于成员操作符,flower==”lily”会被编译器重写为:flower.operator==(“lily”);

  2、非类成员操作符重载

  为了解决上面的问题,我们可以考虑使用非类成员操作符代替类成员操作符,这样做的好处是左操作数不必非要是某个类的类型对象了,对于需要两个操作数的操作符重载,我们就可以定义两个参数了。比如:

  bool operator==(const String&,const String&);

  bool operator==(const String&,const char*);

  可以看到,这两个全局重载操作符比成员操作符多了一个参数,这样定义之后,还是上面的代码,当调用flower==”lily”时,会调用上面的bool operator==(const String&,const char*);

  然而“tulip”==flower会调用哪个操作符重载呢,我们并没有定义bool operator==(const char*,const String&);,我们是不是必须定义这样一个全局操作符重载呢?答案是否定的,因为当一个重载操作符是一个名字空间函数时,对于操作符的第一个和第二个参数,即等于操作符的左右两个操作数都会考虑转换,就像int vi=1; double vd=2.0; vi=vi+vd; 会先将vd转换成int型,再做加法一样这意味着,编译器将解释第二个用法如下:

  bool operator==(String(“tulip”),flower)。这样会增加系统转换开销。

  因此,如果需要频繁比较C风格字符串和String对象,那么最好定义上面的操作符重载,如果不频繁,我们只需定义下面一个就够了:

  bool operator==(const String&,const String&);

  什么时候定义类成员操作符重载,什么时候定义非类成员操作符重载?

  答:(1)如果一个重载操作符是类成员,那么只有当跟它一起使用的左操作数是该类对象时,它才会被调用,如果该操作符的左操作数必须是其他类型,那么重载操作符必须是非类成员操作符重载。

  (2)C++要求,赋值(=),下标([ ]),调用(())和成员访问箭头(->)操作符必须被指定为类成员操作符,否则错误。

操作符重载
一.运算符重载的含义与定义方式

        C++已有的运算符只适合处理C++的基本数据类型。

        C++允许重新定义已有的运算符(运算符重载),以便它能处理程序员定义类型(类类型)。

        运算符重载就是赋予已有的运算符多重含义。运算符重载与函数重载类似,是它的特殊类型。

        C++通过重新定义运算符,使它能够用于特定类的对象执行特定的功能。

        通过对+,-,*,/运算符的重新定义,使它们可以完成复数、分数等不同类的对象的加、减、乘、除运算操作。增强了C++语言的扩充能力。

        先创建一个运算符函数,一般定义成类的成员函数或友元函数。

 

二.重载一个运算符原则:

1.不能改变运算符的初始意义。

2.不能改变运算符的参数数目。如重载运算符+时只用一个操作数是错误的。

3.运算符函数不能包括缺省的参数。

4.绝大部分C++运算符都可以重载,除以下的例外:

  ::   .*   ?

5.除赋值运算符外,其它运算符函数都可以由派生类继承。

6.运算符重载不改变运算符的优先级和结合性,也不改变运算符的语法结构,即单目、双目运算符只能重载为单目、双目运算符。

7.运算符的重载实际上是函数的重载。编译程序对运算符重载的选择,遵循函数重载的选择原则。当遇到不很明显的运算符时,编译程序将去寻找参数匹配的运算符函数。

8.运算符重载可使程序更简洁,使表达式更直观,增强可读性。但使用不宜过多。

9.重载运算符含义必须清楚:

如有一个类Time,它有三个数据成员时、分、秒

calss Time{

public:

   Time() {hours=minutes=seconds=0;}

   Time(int h,int m,int s){ hours=h; minutes=m; seconds=s;}

private:

int hours, minutes, seconds;

};

Time t1(8,10,20), t2(9,15,30), t3;

t3=t1+t2;

这里加法(+)运算用于类Time的对象,就是含义不清的。所以不能给类Time定义重载运算符+。

 

三.运算符重载函数的两种形式:

成员函数形式和友元函数形式,他们都可访问类中的私有成员。

1.重载为类的成员函数

1)X类中重载一元运算符@

返回类型   X::operator@( )

      …  }

不指定参数,因为它已带有一个隐含的this参数,对X类的一个对象obj:

       表达式                C++编译器的解释

       @obj                  operator @(obj)

       obj@                  operator @(obj,0)

2)  X类中重载二元运算符@

  返回类型   X::operator@(参数说明)

      …  }

由于类的成员函数带有一个this参数,此时只能指定一个参数,对obj对象:

       表达式                C++编译器的解释

     obj1@obj2             obj1·operator@(obj2)

 

 

例:给复数运算符重载的四则运算符。复数由实部和虚部构造,定义一个复数类,再在类中重载复数四则运算的运算符。

 

#include<iostream.h>

class complex

{ private: 

     float real,imag;

public:

     complex(float r=0,float i=0);

     complex operator+(complex &c);

     complex operator-(complex &c);

     friend void print(complex &c);

};

complex::complex(float r,float i)

real=r;  imag=i;  }

complex complex::operator+(complex &c)

float r=real+c.real;       float i=imag+c.imag;      

   return complex(r,i) ; }

complex complex::operator-(complex &c)

float r=real-c.real;       float i=imag-c.imag;      

   return complex(r, i) ; }

void print(complex &c )

{ cout<<'('<<c.real<<','<<c.imag<<')'<<endl;   }

void main( )

{ complex c1(2.5,3.7), c2(4.2,6.5) ;

 complex c;

 c=c1-c2;             //c=c1·operator-(c2)

 print(c);

 c=c1+c2;             //c=c1·operator+(c2)

 print(c);

 }

 

该程序中定义一个complex类,定义了2个成员函数作为运算符重载函数。

c1+c2编译程序解释为:c1.operator+(c2)

c1、c2是complex 类的对象。operator+()是运算符+的重载函数。

该运算符重载函数仅有一个参数c2。当重载为成员函数时,双目运算符仅有一个参数。

对单目运算符,重载为成员函数时,不能再显式说明参数。

重载为成员函数时,总是隐含了一个参数,即this指针,它是指向调用该成员函数对象的指针。

 

在重载运算符函数中,operator +( )中参数用引用传递而不用指针传递。

因为指针传递存在程序的可读性问题。如操作符重载声明为:

complex operator +( complex *c);

则调用时

    complex c1(2.0,3.0),c2(4.0,-2.0),c3;

       c3=&c1+&c2;   会认为是c1的地址和c2的地址相加

 

而声明为complex operator +(const complex &c);

则    c3=c1+c2;

      

 

2. 重载为友元函数

    运算符重载函数还可以为友元函数。当重载为友元函数时,没有隐含的参数this指针。这样对双目运算符,友元函数有2个参数,对单目运算符,友元函数有1个参数。但有些运算符不能重载为友元函数,它们是:=、()、[]、->。

 

1) X类中重载一元运算符@

  返回类型  operator@(X&obj )

      …  }

 只能为友元运算符指定一个参数,对X类的一个对象obj:

       表达式                C++编译器的解释

       @obj                  operator @(obj)

       obj@                  operator @(obj,0)

2) X类中重载二元运算符@

返回类型 operator@(参数说明1,参数说明2)

  {……}]

       两个参数中必须至少有一个是X类类型,设有对象obj1,obj2

       表达式                C++编译器的解释

      obj1@obj2           operator @(obj1,obj2)

 

 

 

例:用友元函数代替成员函数编上述程序:

#include<iostream.h>

class complex

{ private: 

     float real,imag;

public:

     complex(float r=0,float i=0);

     friend complex operator+(complex &c1,complex &c2);

     friend complex operator-(complex &c1,complex &c2);

     friend void print(complex &c);

};

complex::complex(float r,float i)

real=r;  imag=i;  }

complex operator+(complex &c1,complex &c2)

float r=c1.real+c2.real;     float i=c1.imag+c2.imag;  

   return complex(r,i) ; }

complex operator-(complex &c1,complex &c2)

float r=c1.real-c2.real;       float i=c1.imag-c2.imag;  

   return complex(r, i) ; }

void print(complex &c )

{ cout<<'('<<c.real<<','<<c.imag<<')'<<endl;   }

void main( )

{ complex c1(2.5,3.7), c2(4.2,6.5) ;

 complex c;

 c=c1-c2;             //c=c1·operator-(c2)

 print(c);

 c=c1+c2;             //c=c1·operator+(c2)

 print(c);

 }

 

双目运算符重载为成员函数时,仅有一个参数,另一个被隐含;

重载为友元函数时,有两个参数,没有隐含参数;

c1+c2编译程序解释为:operator+( c1,c2)

调用如下函数,进行求值,

complex operator +(const complex &c1,consrt complex &c2)

结论1:对二元运算符,将它重载为一个友元函数比重载为一个成员函数要便于使用。作为一个友元函数,二元运算符不要求第一个参数一定为某类的对象。

结论2:对一元运算符,将它重载为一个成员函数最恰当。重载为友员函数也可以。

 

例:

#include <iostream.h>

class A{

public:

       A(){X=Y=0;}

       A(int i,int j){X=i;Y=j;}

    A(A &p){X=p.X;Y=p.Y;}

    A& operator =(A &p);

       int getX(){return X;}

       int getY(){return Y;}

private:

 

       int X,Y;

};

A& A::operator =(A &p)

{

       X=p.X;

       Y=p.Y;

       cout<<"Assignment operator called.\n";

       return *this;

}

void main()

{

       A a(7,8);

       A b;

       b=a;

       cout<<b.getX()<<","<<b.getY()<<endl;

}

 

Assignment operator called.

7,8

该程序中,在类A中定义了一个赋值运算符函数,被定义为成员函数。

b=a解释为: b.operator=(a)

调用下列函数:A& A::operator=(A &p)完成赋值操作。

原创粉丝点击