【ZZ】C++运算符重载总结 & 返回值优化

问题:运算符重载时如何区分prefix和postfix形式?

(注:晚上看书才发现原来这是《More Effective C++》条款M6 果然不看书是不行的...)

  Answer:下面例子程序中   const Fraction operator ++(int)   中  
  int不过是个哑元(dummy),是永远用不上的  
  它只是用来判断++是prefix   还是   postfix  
  记住,如果有哑元,则是postfix,否则,就是prefix   
  就像其他的一元算法和逻辑运算一样  
  而其实在C++中用到这种哑元的也只有在postfix   ++   和--了

 例子:

int i=10;
cout<<i++<<endl;    //i=11;后缀加;先返回后自增;   10
cout<<++i<<endl;    //i=12;前缀加;先自增后返回;   12

例:

#include<iostream>
using namespace std;

class Fraction                                           //数类;
{
 friend ostream& operator<<(ostream& out, const Fraction& x);
private:
 int den;                                         //加的步进距离,使自加的距离不是1;
 int num;                                         //数(初值);
public:
 Fraction(int d=1, int n=0):den(d),num(n) {}
 Fraction& operator++()                           //前缀自加重载;(前置版本prefix)
  {
   num+=den;                        //先自增,再返回;
   return *this;
  }
 const Fraction operator ++(int)                  //后缀自加重载;(后置版本postfix)
  {
   Fraction old (*this);            //拷贝构造(对象参数是对象)。先返回,再自增;
   ++(*this);                       //调用的是重载的前置版本;
   return old;
  }
};

ostream& operator<<(ostream& out, const Fraction& x)
{
 out<<x.num<<endl;
 return out;
}

int main()
{
 Fraction b(10,10);
 cout<<b++<<endl;
 cout<<++b<<endl;
 return 0;
}

前置版本返回一个引用【Fraction& operator++()】,后置版本返回一个const值【const Fraction operator ++(int)】。

后置版本是利用前置版本来实现的。节约代码,控制代码有余。

前置版本的效率高,因为后置版本需要调用前置版本,所有后置版本效率比前置要低。(++i比i++效率高。)

在后置版本里,人为添加一个参数(int),主要是为了区别前置版本,这个参数不会被使用。

//////////////////// EOF 例子 Prefix vs Postfix///////////////////////////////////

 

运算符重载(operator overloding)只是一种“语法上的方便”(Syntatic Sugar),也就是说它只是为一种
函数调用的方式。
////////////////////////////////////////////////////////////////////////////////////////////////
函数参数表中参数的个数取决于两个因素:
  (1)运算符是一元的,还是二元的。
  (2)运算符被定义为全局函数(对于一元是一个参数,对于二元是两个参数),还是成员函数(对于一
  元没有参数,对于二元是一个参数————此时该类的对象用作左侧参数)
operator overloding 不能改变运算符的优先级,与及运算符参数的个数。
注意:
  全局函数形式:
     prefix:
         friend const Integer&
         operator ++(Integer& a);
     postfix:
         friend const Integer&
         operator ++(Integer& a,int);
  成员函数形式:
     prefix:
        const Byte
        operator ++();
     postfix:
        const Byte
        operator ++(int);
  Assignments modify & return lvalue:
  
     Integer& operator +=(Integerd left,const Integerd& right)
    
  operator=只允许作为成员函数
  
  作为成员函数的operator overloding 总是将实现写在类内,作为inline函数。
  
////////////////////////////////////////////////////////////////////////////////////////////////
  
关于operator overloding 的参数与返回值设计应遵守的模式:
  (1)对于任何函数的参数,如果仅需要从参数中读而不改变它,默认地应当作为const引用来传递它。
  (2)返回值的类型取决于运算符的具体含义。
  (3)所有赋值运算符均改变左值。
  (4)对于逻辑运算符,人们希望至少得到一个int返回值,最好是bool返回值。
  (5)对于prefix,是非const的。postfix,是const的。
  
////////////////////////////////////////////////////////////////////////////////////////////////
返回值优化(return value optimization): 
  例:在operator +:
    return Integer (left.i+right.i);
  它是说:“创建一个临时Integer对象并返回它”。
  对比一下:
    Integer tmp(left.i+right.i);
    return tmp;
  将发生三件事。首先,创建tmp对象,其中包括构造函数的调用。然后,拷贝构造函数把tmp拷贝到外部
  返回值的存储单元里。最后,当tmp在作用域的结尾时调用析构函数。
  “返回临时对象”的方式是完全不同的。当编译器看到我们这样做时,它明白对创建的对象没有其他需求,
  只是返回它,所以编译器直接地把这个对象创建在外部返回值的内存单元。因为不是真正创建一个局部
  对象,所以仅需要一个普通构造函数调用(不需要拷贝构造函数),且不会调用析构函数。效率很高。

总结:按值返回时应该在return表达式中构造返回的对象。
  
////////////////////////////////////////////////////////////////////////////////////////////////
  
在确定operator overloding 时,函数是成员函数,还是外部函数的选择有如下方针:
-------------------------------------------------------------------------------
    运算符                     建议使用
-------------------------------------------------------------------------------    
    所有的一元运算符                成员
    = () [] -> ->*                 必须是成员
    += -= /= *= ^= &= |= %= >>= <<=         成员
    所有其他二元运算符               非成员
-------------------------------------------------------------------------------  

////////////////////////////////////////////////////////////////////////////////////////////////
operator=:
   如果没有创建type::operator= (type),编译器将自动创建一个。
   如果真的不想让人执行赋值运算,可以把operator=声明为private(除非要使用它,否则不要定义它)
  
////////////////////////////////////////////////////////////////////////////////////////////////
  
自动类型转换:
   自动类型转换分为:(1)构造函数转换 (2)运算符重载转换(这个会引起转换的缺陷)
   例子:
   构造函数转换:
         class One(){
           public:
           One(){};
         };
         class Two(){
           public:
           Two(const One&){};
           //explict Two(const One&){};    可以这样来阻止构造函数转换
         };
        
         void f(Two){};
         int main(){
           One one;
           f(one);
         }
   运算符重载转换:
        class Three{
           int i;
           public:
            Three(int ii=0,int =0):i(ii){};
        };
        class Four{
           int x;
           public:
           Four(int xx):x(xx){};
           operator Three() const{return Three(x);};
        };
        
        void g(Three){};
        int main(){
           Four four(1);
           g(four);
           g(1);
        }
////////////////////////////////////////////////////////////////////////////////////////////////
        
使用全局重载函数运算符而不用成员运算符的最便利的原因之一是在全局版本中的自动类型转换可以针对左右
任一操作数。而成员版本必须保证左侧操作数已处于正确的形式。如果想两个操作数都被转换,全局版本可以
节省很多代码。
////////////////////////////////////////////////////////////////////////////////////////////////

posted on 2009-05-07 16:50  TobyLin的学习之路  阅读(1113)  评论(0编辑  收藏  举报

导航