企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
35.使公有继承体现 “是一个” 的含义。 共有继承意味着 “是一个”。如  class B:public A; 说明类型B的每一个对象都是一个类型A的对象,A比B具有更广泛的概念,而B表示一个更特定的概念。 在C++中任何一个参数为基类的函数都可以实际取一个派生类的对象,只有共有继承会如此,对于共有继承,如AB,若有两个函数 一个函数为 void fun1(A &a);另一个函数为void fun2(B& b);则对于AB的两个对象a,和b,对于 fun1(a)和fun2(b和fun1(b))都是正确的,fun2(a)是错误的。注意只有共有继承才有这个特性,对于私有继承会与此不同。而且这是说 B的对象 "是一个“ A的对象,但是B的数组并不是一个A的数组。 使用公有继承经常遇到的问题是对基类适用的规则并不适用于派生类,但公有继承又要求对基类对象适用的任何东西都适用于派生类对象,使用公有继承会导致一些错误的设计。 如对于企鹅与鸟,鸟是基类,其有嘴,翅膀等数据成员,还有一个飞的成员函数virtual void fly();一开始你认为鸟有一些属性,且鸟会飞,然后你有认为企鹅公有继承于鸟,即企鹅是一种鸟,但是问题出现了,企鹅不会飞。让企鹅直接公有继承于鸟类,这是一个错误的设计,所以你想去改进它,在依然使用公有继承的前提下。 1.世上有很多鸟不会飞,于是你将鸟类分成了两种,FlyingBird 和NoFlyingBird,分成会飞和不会飞两种鸟类,这两种鸟类都公有继承于鸟类,而企鹅公有继承于NoFlyingBird。 2.企鹅中依然有fly()这个函数,但是重新定义了这个fly函数,使之产生一个运行时错误,使企鹅是鸟,企鹅能飞,但是让企鹅飞的这个操作是错误的。这是一个运行时才能检测的错误。 当利用一些知识和常识设计一些类并使用公有继承时,但是公有继承却没那么有效,因为最关键的问题是基类中的规则要同样适用于派生类对象,而我们想要用继承实现的对象却有两者不同的规则。而对于这样的情况,一般要用”有一个“ 和”用。。。来实现“这两种关系来实现。 36.区分接口继承和实现继承。 首先,接口是放在public中给外部调用的,而实现是隐藏在private中的内部逻辑。对于类的继承,有时希望派生类只继承成员函数的接口,有时派生类同时继承函数的接口和实现,且允许派生类改写实现,有时派生类同时继承类的接口与实现,但是不允许修改任何东西。 纯虚函数必须要在具体实现类中重新声明,它们在抽象类中往往没有定义,定义纯虚函数的目的在于使派生类仅仅 继承函数的接口,也就是第一种情况,这种情况很容易理解。但是纯虚函数其实是可以提供定义的。 对于第二种和第三种情况,继承函数的接口和实现,一般使用虚函数来实现。而需要改进的地方是,对于一个基类中的虚函数,其有一定的实现,而派生类可以继承这样的接口和实现,既可以直接继承基类中这个接口,也可以重写这个接口的实现。这样很科学,但是又要一个问题,当一个新的派生类继承这个基类时,由于这个类中使用虚函数做接口,导致新的程序员忘记了重新声明这个虚函数并给予新的实现逻辑而去错误的使用虚函数中的默认逻辑而造成了错误。为了提供更安全的基类,使用纯虚函数做接口,让纯虚函数有自己缺省实现,在派生类继承时,直接调用基类纯虚函数的实现: ~~~ class A{ public: virtual void fun() const = 0; }; void A::fun() const{ cout<<"Class A"<<endl; } class B:public A{ public: virtual void fun() const; }; void B::fun() const{ A::fun(); } ~~~ 如上所示,使用一个纯虚函数,但是带有缺省实现,而派生类继承时就必须重新声明这个纯虚函数,而对于要调用基类的缺省实现时,除了上面直接调用基类的这个纯虚函数外,还可以通过在基类中的protected中设置一个默认的实现函数,如 void defaultFun() const;而派生类会继承这个默认实现,然后在派生类的重新定义的虚函数中调用这个默认的实现函数即可。 这个情况其实就是第二种情况,继承函数的接口和实现,且能够修改实现。一般使用虚函数,但是使用带默认操作的纯虚函数会更加安全。安全是一个很重要的问题,如果不考虑安全性,很多在Effective C++这本书中讨论的问题都是没有意义的,因为如果你明白之前程序的设定,就知道哪些事情该做,哪些事情不该做,就不会去犯一些错误,但是对于一个程序的开发,不是有一个人完成的。当你理解自己的设定时,别人却不知道,维护你代码的人随意的做一些他们认为应该可以做到的安全的事,却由于你之前考虑的不周全而使这些行为极度不安全。所以要认真考虑安全性的问题,写出尽可能完美安全的代码。 对于第三种情况,声明非虚函数,目的在于使派生类继承函数的接口和强制性实现,又由于不应该在派生类中重新声明和定义基类的非虚函数,所以不会修改非虚函数的实现的。 所以,要理解纯虚函数,简单虚函数和非虚函数声明和功能上的区别。不用担心虚函数的效率问题,因为这真的是小问题,所有基类都应该虚函数。一些函数不应该在派生类中重新定义就要将其定义为非虚函数。 37.决不要重新定义继承而来的非虚函数。 首先,对于重新定义继承的非虚函数,称为对这个函数的隐藏,这是一种不常用的东西,正是因为有这个设定,绝不重新定义继承而来的非虚函数。 这样做的原因也是很容易理解的,也是多态的优点: ~~~ class A{ public: void fun() const{ cout<<"Class A"<<endl; } }; class B:public A{ public: void fun() const{ cout<<"Class B"<<endl; } }; int main(){ B* b = new B(); A* a = b; b->fun(); a->fun(); ~~~ 对于以上代码,对同一个对象,也就是b指向的对象,当将其转换为基类指针后,由于其为静态绑定的,其所指向的函数不同,获得了不同的结果,而多态时动态绑定,指向的函数通过虚指针指向相同的地址。 结论是,对于类B的对象,其重新定义的函数fun()被调用时,其行为是不确定的,而决定因素与对象本身没有关系,而取决于指向它的指针的声明类型,引用也会和指针表现出这样的异常行为,这样的行为是不合理的。 而从理论上来考虑,对于公有继承意味着 ”是一个“,对于B中重新定义了A中的实现后,B就不”是一个“ A了。