条款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),它为派生类仅提供函数接口,完全没有实现。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 的关系。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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:也可以这样说,决不要重新定义继承而来的带有缺省数值的虚函数。
目的:让我们从一开始就把问题简化。...如果忽视了本条款的建议,就会带来混乱。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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++中,要使用虚函数。不要在代码中随处乱扔条件语句或开关语句;让编译器来为你效劳。
糟糕的设计
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#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
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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
};