分享
 
 
 

“瑜珈山夜话”--- 寻根究底谈“继承”(一)

王朝vc·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

摘要:继承是C++的一个很重要的特性,也是OO的三大特征之一,希望对此做一个简单的论述,能消除你一些困惑。

继承是什么?

继承是将相关的类组织起来,并分亨其间的共通数据和操作行为的一种方法,同时也要注意到继承关系是一种强耦合的关系。

继承的目的是什么?

说到继承的目的,人们总是会想到代码重用,实则不然,代码重用只不过是继承的一个副作用,继承的主要目的是表达一个外部有意义的关系,该关系描述了问题域内的2个实体之间的行为关系。换句话说,继承是因问题域的现实性而产生的,并不是由于解域内的技术目的而出现的。

继承的障碍是什么?

继承的使用并不像我们想象的那么简单,在决定继承的时候,有很多语言特性会构成一定的障碍。

1、非虚成员函数的存在。

如果我们确定了一个基类中的某个成员函数是非虚的,那就意味着这个函数在派生类中不应该被重新定义,如果你重新定义了,所得的结果很可能不是你所期望的,例如:

class A

{ public: void f() { cout<<"A::f"<<endl; } };

class B: public A

{ public: void f() { cout<<"B::f"<<endl; } };

A* pA=new B;

pA->f();

delete pA;

这里,我们可能期望pA->f()会输出B::f,但是实际上是A::f,当然,如果把它声明为virtual就没有问题了,关键是我们怎么能够明确确定那个函数应该声明为virtual呢?如何使基类能够完全预测到子类的各种需求?毫无疑问,这是一个挑战!也许把所有的基类成员函数都声明为virtual是一个简单的解决办法,但是这样做会大大降低程序的执行效率,对于如此注重效率的C++来说,这么做是对它的一个背叛,C++更希望我们只把那些需要重定义的函数声明为virtual。

2、基类成员的过度保护

封装是一个很好的特性,但是封装的度很难掌握,例如:

class A

{ private: class P { ...}; };

class B : public A::P { ... };

有经验的程序员马上就会意识到这是一个错误:无法获取A::P,因为它的权限是Private!当然这里只需要把private改为protected就可以了,但是问题的关键在于基类如何预测到子类需要继承的类究竟是什么?同上一个障碍一样,这也是一个挑战。天真的程序员可能以为只要把基类中所有的成员都声明为public/protected就万事大吉了,但是实际上如果我们的类发布之后,public/protected的成员就再也无法改变,否则势必会中断客户的代码,这就要求我们尽量把实现细节封装为private的,只把那些子类需要变动的成员声明为public/protected权限(虚函数可以声明为private的,这是一个例外),但是对基类的设计者要求如此之高,也是非常困难的。

3、基类中模块化设计不足

模块化会使程序更加简洁、有效,但是对于基类来说,要做到有效的模块化并不容易。例如我们有一个二分查找树BSTree,定义如下:

template<class T>

class BSTree

{

private:

class Node

{

public:

T t;

Node* left;

Node* right;

Node(const T& _t):t(_t){ }

...

};

Node* root;

...

public:

void insert(const T& t);

...

protected:

virtual void doinsert(const T& t, Node*& n);

...

};

template<class T>

void BSTree<T>::doinsert(const T& t, Node*& n)

{

if(n==0) n=new Node(t);

else

{

if(t<n->t) doinsert(t, n->left);

else doinsert(t, n->right);

}

}

现在呢,我们要定义一个红黑树,定义如下:

template<class T>

class RBTree: public BSTree<T>

{

protected:

class Node: public BSTree<T>::Node

{

public:

bool is_red;

Node(const T& t);

};

void doinsert(const T& t, BSTree<T>::Node*& n);

virtual void rebalance(Node* n);

...

};

template<class T>

void BSTree<T>::doinsert(const T& t, Node*& n)

{

if(n==0)

{

Node m=new Node(t);

n=m;

rebalance(m);

}

else

{

if(t<n->t) doinsert(t, n->left);

else doinsert(t, n->right);

}

}

我们发现BSTree::doinsert和RBTree::doinsert代码大致相同,这就存在着复制代码操作,我们知道代码复制工作十分乏味、易出错、代码臃肿、维护困难...所以一个好的基类应该使派生类尽量少的复制代码,最好不复制。看看我们的基类:很多二分查找树都需要创建不同的节点,也有rebalance操作。好了,我们应该对基类BSTree作如下修改:

Template<class T>

class BSTree

{

protected:

virtual Node* new_node(const T& t)

{ return new Node(t); }

virtual void rebalance(Node* n) { }

...

};

这时候doinsert改动如下:

template<class T>

void BSTree<T>::doinsert(const T& t, Node*& n)

{

if(n==0)

{

n=new_node(t);

rebalance(n);

}

else

{

if(t<n->t) doinsert(t, n->left);

else doinsert(t, n->right);

}

}

这时候派生类RBTree定义改为:

template<class T>

class RBTree: public BSTree<T>

{

protected:

Node* new_node(const T& t)

{ return new Node(t); }

void rebalance(BSTree<T>::Node* n)

{ ... }

...

};

这样一来,程序员就无需复制代码了。我们发现,如果要使派生类的客户永远不复制代码,那么就要把派生类需要改变的代码分离出来,形成一个单独的模块函数(虚),但是在我们没有足够的派生类的信息的时候,这样做是不可能的,就算可能,难度也是相当得高,同时,大量的虚函数也会降低程序的执行效率。

4、friend关键字的过分使用

这个问题的根源在于友员关系的不继承性。我们仍然用上面的例子,不过做一下变动:

template<class T> class BSTree;

template<class T>

class BSNode

{

protected:

T t;

BSNode(const T& t);

friend class BSTree<T>;

};

template<class T>

class BSTree

{

...没有了nested Node类

};

这里,由于BSNode的实现属于BSTree的实现细节,同时为了防止BSNode派生类偶然存取BSNode的成员,所以我们把他的所有成员都声明为Protected,同时让BSTree称为它的友员。但是由于RBTree要存取BSNode的成员,再加上友员的非继承,使事情变得复杂起来,通常有2种办法解决这个问题:

1、将BSNode的成员声明为public,但是这样一来friend也就没有什么意义了。

2、在RBNode类中增加一个存取函数,但是和不用friend相比,麻烦多了。

另外还有一些其它的抉择也是让人头疼,例如:基类中的成员变量过多,继承的属性选择等。

未完(待续...)

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有