EffectiveC++第六章继承和面向对象设计学习笔记

条款35: 使公有继承体现 "是一个" 的含义

    当写下类D("Derived" )从类B("Base")公有继承时,你实际上是在告诉编译器(以及读这段代码的人):类型D的每一个对象也是类型B的一个对象,但反之不成立;你是在说:B表示一个比D更广泛的概念,D表示一个比B更特定概念;你是在声明:任何可以使用类型B的对象的地方,类型D的对象也可以使用,因为每个类型D的对象是一个类型B的对象。相反,如果需要一个类型D的对象,类型B的对象就不行:每个D "是一个" B, 但反之不成立。

条款41: 区分继承和模板

    "类的行为" 和 "类所操作的对象的类型"之间的关系。如果T不影响行为,你可以使用模板。如果T影响行为,你就需要虚函数,从而要使用继承。

当然,"是一个" 的关系不是存在于类之间的唯一关系。类之间常见的另两个关系是 "有一个" 和 "用...来实现"。

条款36: 区分接口继承和实现继承

    作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写任何东西。

    纯虚函数最显著的特征是:它们必须在继承了它们的任何具体类中重新声明,而且它们在抽象类中往往没有定义。把这两个特征放在一起,就会认识到:定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。有时,声明一个除纯虚函数外什么也不包含的类很有用。这样的类叫协议类(Protocol class),它为派生类仅提供函数接口,完全没有实现。


ExpandedBlockStart.gif 代码
#include  < iostream >

using  std::cout;

using  std::endl;

using  std::cin;

class  Shape

{

public :

 
virtual   void  Draw()  =   0 ;

};

//  纯虚函数也可以有定义

void  Shape::Draw()

{

 cout 
<<   " Draw Shape "   <<  endl;

}

class  Rectangle :  public  Shape

{

public :

 
virtual   void  Draw();

};

void  Rectangle::Draw()

{

 cout 
<<   " Draw Rectangle "   <<  endl;

}

void  main()

{  

 Rectangle rect;

 rect.Draw();

 
//  可以通过这种方法调用纯虚函数的方法,如果定义的话.

 
// 这种用法一般没大的作用

 rect.Shape::Draw();

 cin.
get ();

}

声明简单虚函数(即虚函数)的目的在于,使派生类继承函数的接口和缺省实现。

条款37: 决不要重新定义继承而来的非虚函数

"在一个类中声明一个非虚函数实际上为这个类建立了一种特殊性上的不变性"。Me:要保持 is a 的关系。


ExpandedBlockStart.gif 代码
#include  < iostream >

using  std::cout;

using  std::endl;

using  std::cin;

class  B {

public :

  
void  mf();

  
virtual   void  vir();

};

void  B::mf()

{

 cout 
<<   " B "   <<  endl;

}

void  B::vir()

{

 cout 
<<   " B virtual "   <<  endl;

}

class  D :  public  B

{

public :

 
void  mf();

 
virtual   void  vir();

};

void  D::mf()

{

 cout 
<<   " D "   <<  endl;

}

void  D::vir()

{

 cout 
<<   " D virtual "   <<  endl;

}

void  main()

{  

 D x;

 B 
*  pB  =   & x;

 pB
-> mf();

 pB
-> vir();

 D 
*  pD  =   & x;

 pD
-> mf();

 pD
-> vir();

 cin.
get ();

}


/*

B

D virtual

D

D virtual

*/

条款38: 决不要重新定义继承而来的缺省参数值

Me:也可以这样说,决不要重新定义继承而来的带有缺省数值的虚函数。

目的:让我们从一开始就把问题简化。...如果忽视了本条款的建议,就会带来混乱。


ExpandedBlockStart.gif 代码
#include  < iostream >

using  std::cout;

using  std::endl;

using  std::cin;

namespace  Difa

{

enum  Color { RED, GREEN, BLUE };

}

//  一个表示几何形状的类

class  Shape 

{

public :

  
//  所有的形状都要提供一个函数绘制它们本身

 
virtual   void  draw(Difa::Color color  =   Difa::RED)  const   =   0 ;

};

class  Rectangle:  public  Shape 

{

public :

  
//  注意:定义了不同的缺省参数值 ---- 不好!

  
virtual   void  draw(Difa::Color color  = Difa::GREEN)  const ;

};

void  Rectangle::draw(Difa::Color color)  const

{

    cout 
<<  color  <<  endl;

}

class  Circle:  public  Shape 

{

public :

  
virtual   void  draw(Difa::Color color)  const ;

};

void  Circle::draw(Difa::Color color)  const

{

 cout 
<<  color  <<  endl;

}

void  main()



 Shape 
* ps;                       //  静态类型= Shape*

 Shape 
* pc  =   new  Circle;          //  静态类型= Shape*

 Shape 
* pr  =   new  Rectangle;       //  静态类型= Shape*

 pc
-> draw(Difa::RED);         //  调用 Circle::draw(RED)    0

 pr
-> draw(Difa::GREEN);      //  调用 Rectangle::draw(Difa::GREEN)  1

 pr
-> draw();         //  调用Rectangle::draw(RED)! 0 ,而不是 Difa::GREEN --->1

 cin.
get ();

}

 

/*

0

1

0

*/

条款39: 避免 "向下转换" 继承层次

从一个基类指针到一个派生类指针 ---- 被称为 "向下转换",因为它向下转换了继承的层次结构。任何时候发现自己写出 "如果对象属于类型T1,做某事;但如果属于类型T2,做另外某事" 之类的代码,就要扇自己一个耳光。这不是C++的做法。是的,在C,Pascal,甚至Smalltalk中,它是很合理的做法,但在C++中不是。在C++中,要使用虚函数。不要在代码中随处乱扔条件语句或开关语句;让编译器来为你效劳。

糟糕的设计


ExpandedBlockStart.gif 代码
#include  < iostream >

#include 
< list >

using  std::list;

using  std::cout;

using  std::endl;

using  std::cin;

class  BankAccount 

{

public :

  
virtual   ~ BankAccount();

};

BankAccount::
~ BankAccount()

{

}

class  CheckingAccount:  public  BankAccount {

public :

  
void  creditInterest();     //  给帐户增加利息

};

void  CheckingAccount::creditInterest()

{

 cout 
<<   " Checking interest "   <<  endl;

}

class  SavingsAccount:  public  BankAccount 

{

public :

  
void  creditInterest();                 //  给帐户增加利息

};

void  SavingsAccount::creditInterest()

{

 cout 
<<   " savings interest! "   <<  endl;

}

void  main()



 list
< BankAccount *>  allAccounts;

 allAccounts.push_back(
new  CheckingAccount());

 allAccounts.push_back(
new  SavingsAccount);

 
for  (list < BankAccount *> ::iterator p  =  allAccounts.begin(); p  !=  allAccounts.end(); ++ p) 

 {

  
//  尝试将*p安全转换为SavingsAccount*;

  
//  psa的定义信息见下文

  
if  (SavingsAccount  * psa  =

    dynamic_cast
< SavingsAccount *> ( * p))

  {

   psa
-> creditInterest();

  }

  
//  尝试将它安全转换为CheckingAccount

  
else   if  (CheckingAccount  * pca  =

             dynamic_cast
< CheckingAccount *> ( * p))

  {

   pca
-> creditInterest();

  }

  
//  未知的帐户类型

  
else

  {

   cout 
<<   " Unknown account type! "   <<  endl;

  }

 }

  cin.
get ();

}

 

   如果某个人在类层次结构中增加了一种新类型的帐户,但又忘了更新上面的代码,所有对它的转换就会失败。所以,处理这种可能发生的情况十分重要。大部分情况下,并非所有的转换都会失败;但是,一旦允许转换,再好的程序员也会碰上麻烦。为了消除向下转换,无论费多大工夫都是值得的,因为向下转换难看、容易导致错误,而且使得代码难于理解、升级和维护

 

条款40: 通过分层来体现 "有一个" 或 "用...来实现"

    某个类的对象成为另一个类的数据成员,从而实现将一个类构筑在另一个类之上,这一过程称为 "分层"(Layering)。条款35解释了公有继承的含义是 "是一个"。对应地,分层的含义是 "有一个" 或 "用...来实现"。

 

区分 "是一个" 和 "用...来实现"。

// Set中使用list的正确方法 me:List来实现Set


ExpandedBlockStart.gif 代码
template < class  T >

class  Set {

public :

 
bool  member( const  T &  item)  const ;

 
void  insert( const  T &  item);

 
void  remove( const  T &  item);

 
int  cardinality()  const ;

private :

 list
< T >  rep;                       //  表示一个Set

};


 

 

转载于:https://www.cnblogs.com/dreamcs/archive/2010/02/10/1667037.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值