thinking in c++ 读书笔记---operator overloading

运算符重载,关键点有这些:1>函数是全局函数,还是类的成员函数,如果是类的成员函数,则函数的参数少一个,左边的为对象本身,右边的为函数参数,如果是单目运算符,默认运算符在左边,如果在右边,则参考下面的例子:

++/--这两个单目运算符都有两种可能,左边右边都可以,现在 列出代码,很容易明白。

 

const  Integer &   operator ++ (Integer &  a)  {
12: Operator Overloading 517
cout 
<< "++Integer ";
a.i
++;
return a;
}

//  Postfix; return the value before increment:
const  Integer  operator ++ (Integer &  a,  int {
cout 
<< "Integer++ ";
Integer before(a.i);
a.i
++;
return before;
}

//  Prefix; return decremented value
const  Integer &   operator -- (Integer &  a)  {
cout 
<< "--Integer ";
a.i
--;
return a;
}

//  Postfix; return the value before decrement:
const  Integer  operator -- (Integer &  a,  int {
cout 
<< "Integer-- ";
Integer before(a.i);
a.i
--;
return before;
}

这两个单目运算符在右边的时候,函数要加一个参数,只是形式,不在内部使用,可以看出在右边时,返回的不是原来的变量,而是在函数内部又定义了一个变量。所以下面的形式是不正确的。

(i++)++    //error

(i--)--       //ERROR

而下面的形式就正确:

++(++i)     //invalid,--the same.

运算符在成员和非成员之间的选择提出了如下的方针:
运算符                                                 建议使用
所有的一元运算符                                成员
=  ( )  [] ->                                                必须是成员
+=  -=  /=  *=  ^=
                                                                 成员
&=  |=  %=  >>=  <<=
所有其他二元运算符                            非成员 

构建运算符重载的一些原则:

  1) 对于任何函数参数,如果仅需要从参数中读而不改变它,缺省地应当按 c o n s t引用来传递它。普通算术运算符(像+和-号等)和布尔运算符不会改变参数,所以以c o n s t引用传递是使用的主要方式。当函数是一个类成员的时候,就转换为 c o n s t成员函数。只是对于会改变左侧参数的赋值运算符( o p e r a t o r- a s s i g n m e n t,像+ =)和运算符‘=’,左侧参数才不是常量( c o n s t a n t ),但因为参数将被改变,所以参数仍然按地址传递。
  2) 应该选择的返回值取决于运算符所期望的类型。(可以对参数和返回值做任何想做的事)如果运算符的效果是产生一个新值,将需要产生一个作为返回值的新对象。例如,i n t e g e r : : o p e r a t o r +必须生成一个操作数之和的i n t e g e r对象。这个对象作为一个c o n s t通过传值方式返回,所以作为一个左值结果不会被改变。
  3) 所有赋值运算符改变左值。为了使得赋值结果用于链式表达式(像 A = B = C),应该能够返回一个刚刚改变了的左值的引用。但这个引用应该是 c o n s t还是n o n c o n s t呢?虽然我们是从左向右读表达式 A = B = C,但编译器是从右向左分析这个表达式,所以并非一定要返回一个n o n c o n s t值来支持链式赋值。然而人们有时希望能够对刚刚赋值的对象进行运算,例如(A = B). f o o( ),这是B赋值给A后调用foo( )。因此所有赋值运算符的返回值对于左值应该是n o n c o n s t引用。
  4) 对于逻辑运算符,人们希望至少得到一个 i n t返回值,最好是b o o l返回值。(在大多数编译器支持C + +内置b o o l类型之前开发的库函数使用i n t或t y p e d e f等价物)。
  5) 因为有前缀和后缀版本,所以自增和自减运算符出现了两难局面。两个版本都改变对象,所以不能把这个对象看作一个 c o n s t。因此,前缀版本返回这个对象被改变后的值。这样,用前缀版本我们只需返回* t h i s作为一个引用。因为后缀版本返回改变之前的值,所以被迫创建一个代表这个值的单个对象并返回它。因此,如果想保持我们的本意,对于后缀必须通过传值方返回。(注意,我们经常会发现自增和自减运算返回一个 int 值或b o o l值,例如用来指示是否有一个循环子( i t e r a t o r )在表的结尾)。现在问题是:这些应该按c o n s t被返回还是按n o n c o n s t被返回?如果允许对象被改变,一些人写了表达式(++A).foo( ),则foo( )作用在A上。但对于表达式A++).foo( ),foo( )作用在通过后缀运算符+ +号返回的临时对象上。临时对象自动定为 c o n s t,所以被编译器标记。但为了一致性,使两者都是 c o n s t更有意义,就像这儿所做的。因为想给自增和自减运算符赋予各种意思,所以它们需要就事论事考虑。
1. 按c o n s t通过传值方式返回
按c o n s t通过传值方式返回,开始看起来有些微妙,所以值得多加解释。我们来考虑二元运算符+号。假设在一个表达式像f ( A + B )中使用它,A + B的结果变为一个临时对象,这个对象用于f( ) 调用。因为它是临时的,自动被定为c o n s t,所以无论使返回值为c o n s t还是不这样做都没有影响。然而,也可能发送一个消息给A + B的返回值而不是仅传递给一个函数。例如,可以写表达式(A+B).g( ),这里g( )是i n t e g e r的成员函数。通过设返回值为 c o n s t,规定了对于返回值只有c o n s t成员函数才可以被调用。用 c o n s t是恰当的,这是因为这样可以防止在很可能丢失的对象中存贮有价值的信息。
2. 返回效率
当为通过传值方式返回而创建一个新对象时,要注意使用的形式。例如用运算符 +号:

return integer (left.i + right.i) ;

一开始看起来像是一个“对一个构造函数的调用”,但其实并非如此。这是临时对象语法,它是这样陈述的:“创建一个临时对象并返回它”。因为这个原因,我们可能认为如果创建一个命名的本地对象并返回它结果将会是一样的。其实不然。如果像下面这样表示,将发生三件事。首先,t m p对象被创建,与此同时它的构造函数被调用。然后,拷贝构造函数把 t m p拷贝到返回值外部存储单元里。最后,当t m p在作用域的结尾时调用析构函数。
integer tmp(left.i + right.i) ;
return tmp ;
相反,“返回临时对象”的方法是完全不同的。看这样情况时,编译器明白对创建的对象没有其他需求,只是返回它,所以编译器直接地把这个对象创建在返回值外面的内存单元。因为不是真正创建一个局部对象,所以仅需要单个的普通构造函数调用(不需要拷贝构造函数),并且不会调用析构函数。因此,这种方法不需要什么花费,效率是非常高的。   

explicit关键字

在C和C + +中,如果编译器看到一个表达式或函数调用使用了一个不合适的类型,它经常会执行一个自动类型转换。在C + +中,可以通过定义自动类型转换函数来为用户定义类型达到相同效果。这些函数有两种类型:特殊类型的构造函数和重载的运算符。
如果我们定义一个构造函数,这个构造函数能把另一类型对象(或引用)作为它的单个参数,那么这个构造函数允许编译器执行自动类型转换。

 

// : C12:ExplicitKeyword.cpp 
//  Using the "explicit" keyword 
class  One 
public
  One() 
{} 
}

 
class  Two 
public
  
explicit Two(const One&{} 
}

 
void  f(Two)  {}  
 
int  main() 
  One one; 
//!  f(one); // No auto conversion allowed 
  f(Two(one)); // OK -- user performs conversion 
}
  ///:~ 

 

有时通过构造函数自动转换类型可能出现问题。为了避开这个麻烦,可以通过在前面加关键字explicit  (只能用于构造函数)来修改构造函数。通过使类t w o的构造函数显式化,编译器被告知不能使用那个构造函数(那个类中其他非显式化的构造函数仍可以执行自动类型转换)执行任何自动转换。如果用户想进行转换必须写出代码。上面代码f ( t w o ( O n e ) )创建一个从类型O n e到t w o的临时对象,就像编译器在前面版本中
做的那样。

自动类型转换

自动类型转换有两种方法,比如void f (A)是函数声明,那么它就期望有一个A类型的变量参数,如果调用的时候如下:

f(B);

那么B类型的参数就会找某种方法自动转换到类型A。方法有两种,一种在类型A里面实现:构造函数方法,一种在类B里面实现,用运算符转换实现。下面分别介绍。

如果我们定义一个构造函数,这个构造函数能把另一类型对象(或引用)作为它的单个参数,那么这个构造函数允许编译器执行自动类型转换。

第二种自动类型转换的方法是通过运算符重载。我们可以创建一个成员函数,这个函数通过在关键字o p e r a t o r后跟随想要转换到的类型的方法,将当前类型转换为希望的类型。这种形式的运算符重载是独特的,因为没有指定一个返回类型——返回类型就是我们正在重载的运算符的名字。这儿有一个例子:

 

// : C12:OperatorOverloadingConversion.cpp 
class  Three 
  
int i; 
public
  Three(
int ii = 0int = 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);  // Calls Three(1,0) 
}
  ///:~ 

 

用构造函数技术,目的类执行转换。然而使用运算符技术,是源类执行转换。构造函数技术的价值是在创建一个新类时为现有系统增加了新的转换途径。然而,创建一个单一参数的构造函数总是定义一个自动类型转换(即使它有不止一个参数也是一样,因为其余的参数将被缺省处理),这可能并不是我们所想要的。另外,使用构造函数技术没有办法实现从用户定义类型向内置类型转换,这只有运算符重载可能做到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值