32:确定你的public继承塑膜出is-a关系

news/2025/1/12 23:27:06/

“继承”可以是单一继承或多重继承,每一个继承连接可以是public,protected或private,也可以是virtual或non-virtual。然后是成员函数的各个选项:virtual?non-virtual?pure virtual?以及成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有哪些?若class的行为需要修改,virtual函数是最佳选择吗?

以C++面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味"is-a"(是一种)关系。

is-a的解释

若你令class D("Derived")以public形式继承class B("Base"),你便是告诉编译器(以及你的代码读者)说,每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。即,B比D表现出更一般化的概念,而D比B表现出更特殊化的概念。你主张“B对象可派上用场的任何地方,D对象一样可以派上用场”(此即所谓Liskov Subdtitution Principle),因为每一个D对象都是一种(是一个)B对象。反之若你需要一个D对象,B对象无法效劳,因为虽然每个D对象都是一个B对象,反之并不成立。

考虑下面的例子:

class Person{/*...*/};
class Student: public Person{/*...*/};

根据生活经验知道,每个学生都是人,但并非每个人都是学生。这便是这个继承体系的主张。我们预期,对人可以成立的每一件事——例如每个人都有生日——对学生也都成立。但我们并不预期对学生可成立的每一件事——例如他或她注册于某所学校——对人也成立。

于是,承上所述,在C++领域中,任何函数若期望获得一个类型为Person(或pointer-to-Person或reference-to-Person)的实参,都也愿意接受一个Student对象(或pointer-to-Student或reference-to-Student):

void eat(const Person& p);//任何人都会吃
void study(const Student& s);//只有学生才到校学校
Person p;//p是人
Student s;//s是学生
eat(p);//没问题,p是人
eat(s);//没问题,s是学生,而学生也是(is-a)人
study(s);//没问题,s是学生
//study(p);//错误,p不是学生

这个论点只对public继承才成立。只有当Student以public方式继承Person,C++的行为才会如上所述。

再来看另一个例子,

企鹅是一种鸟,而鸟可以飞。

因此,用C++表示这层关系:

class Bird {
public:virtual void fly();//鸟可以飞//...
};
class Penguin :public Bird {//企鹅是一种鸟//...
};

但这个代码是不对的,因为企鹅实际上不会飞。

当说鸟会飞时,真正的意思并不是说所有的鸟都会飞,要说的只是一般的鸟都有飞行能力。

因此,将上述代码进行修改:

class Bird {//...//没有声明fly函数
};
class FlyingBird {
public:virtual void fly();//...
};
class Penguin :public Bird {//...//没有声明fly函数
};

即便如此,我们仍未能完全处理好上述关系,因为对某些软件系统而言,可能不需要区分会飞的鸟和不会飞的鸟。若你的程序忙着处理鸟喙和鸟翅,完全不在乎飞行,原先的“双class继承体系”或许就相当令人满足了。

这反映出一个事实,世界上并不存在一个“适用于所有软件”的完美设计。所谓最佳设计,取决于系统希望做什么事,包括现在与未来。若你的程序对飞行一无所知,而且也不打算未来对飞行“有所知”,那么不去区分会飞的鸟和不会飞的鸟,不失为一个完美而有效的设计。

另一种修改上述代码的方法为:为企鹅重新定义fly函数,令它产生一个运行期错误:

void error(const std::string& msg);//定义于某处
class Penguin :public Bird {virtual void fly() { error("Attempt to make a penguin fly!"); }//...
};

注意,你必须认知这里所说的某些东西可能和你所想的不同。这里并不是说“企鹅不会飞”,而是说“企鹅会飞,但尝试那么做是一种错误”。

如何描述其间的差异?从错误被侦测出来的时间点观之,“企鹅不会飞”这一限制可由编译器强制实施,但若违反“企鹅尝试飞行,是一种错误”这一条规则,只有运行期才能检测出来。

为了表现“企鹅不会飞,就这样”的限制,你不可以为Penuguin定义fly函数:

class Bird {//...//没有声明fly函数
};
class Penguin :public Bird {//...//没有声明fly函数
};

现在,若你试图让企鹅飞,编译器会对你的背信加以谴责:

Penguin p;
//p.fly();//错误

这和采取“令程序与运行期发生错误”的解法不同,若以那种做法,编译器不会对p.fly调用式发出任何抱怨。

public继承常见的错误

现在再来看个例子,正方形与矩形之间有什么关系?

或许你会认为class Square应该以public继承class Rectangle。

但这样的认知可能是错的。

考虑这段代码:

class Rectangle {
public:virtual void setHeight(int newHeight);virtual void setWidth(int newWidth);virtual int height() const;//返回当前值virtual int width() const;//...
};
void makeBigger(Rectangle& r)//这个函数用以增加r的面积
{int oldHeight = r.height();r.setWidth(r.width() + 10);//为r的宽度加10assert(r.height() == oldHeight);//判断r的高度是否未曾改变
}

显然,上述的assert结果永远为真。因为makeBigger只改变r的宽度;r的高度从未被改变。

现在考虑这段代码。其中使用public继承,允许正方形被视为一种矩形:

class Square :public Rectangle {//...
};
//...
Square s;
assert(s.width() == s.height());//对所有正方形来说一定为真
makeBigger(s);//由于是public继承,所以可以增加其面积
assert(s.width() == s.height());//对所有正方形来说应该仍然为真,但是为假

很明显,上述代码中的第二个assert结果也应该永远为真。因为根据定义,正方形的宽度和其高度相同。

但现在我们遇上了一个问题。我们如何调解下面各个assert判断式:

1.调用makeBigger之前,s的高度和宽度相同

2.在makeBigger函数内,s的宽度改变,但高度不变

3.makeBigger返回之后,s的高度本应和其宽度相同,但现在不同。(注意s是以by reference方式传给makeBigger,所以makeBigger修改的是s自身,不是s的副本)

本例的根本困难是,某些可施行于矩形身上的事情(例如宽度可独立于其高度被外界修改)却不可施行于正方形身上(宽度总是应该和高度一样)。但public继承主张,能够施行于base class对象身上的每件事情,也可施行于derived class对象身上。 

但这样的主张,在正方形和矩形身上无法保持,所以以public塑膜它们之间的关系并不正确。

is-a并非是唯一存在于class之间的关系。另两个常见的关系是has-a(有一个)和is-implemented-in-terms-of(根据某物实现出)。

总结

”public继承“意味is-a。适用于base class身上的每一件事情一定也适用于derived class身上,因为每一个derived class对象也都是一个base class对象。 


http://www.ppmy.cn/news/240139.html

相关文章

Java003——记事本编写和运行第一个Java程序HelloWorld

一、使用记事本创建Java并运行 1.1、设置文件显示后缀名 目的是为了方便查看文件类型 1.2、创建一个HelloWorld.java文件 java程序文件都是以.java后缀结尾的 1.3、编写Java程序 编写以下程序,并保存 public class HelloWorld {public static void main(Strin…

黑鲨3能升级鸿蒙5g吗,黑鲨3Pro与红魔5G,同为5G游戏手机,二者相比区别在哪

黑鲨3Pro与红魔5G,同为5G游戏手机,二者相比区别在哪 2020-03-17 16:42:20 1点赞 0收藏 4评论 对于游戏手机,还是有很多用户都是非常关注的,在前一段时间,黑鲨也是发布了旗下全新的腾讯黑鲨3系列游戏手机,其…

java基础面试

目录 0,高级特性 1,设计模式的6大原则和23种设计模式 2,jvm a,内存模型 使用元空间代替永久代的原因: 内存分配原则: b,GC机制 #垃圾回收器 c,类加载 #类加载器 3,集合框架 4,并发 5,并发包java.util.concurrent 6,io/nio(jdk1.4) …

鲁大师发布2021年度手机报告,去年最强的手机一文看完

1月19日,鲁大师2021第四届牛角尖奖颁奖典礼顺利举办,本次共颁出了14个奖项,包括手机的7个奖项、PC的6个奖项和1个电动车智能评测奖项。“牛角尖”奖数据依托自过去一年鲁大师数据中心用户通过鲁大师客户端进行手机、电脑评测得到的真实数据和…

小米android10怎么样,感觉小米10太贵不完美?这些Android旗舰也许就有你的菜!

昨天CFan对比了三星Galaxy S20和小米10系列的异同(详见《Galaxy S20和小米10谁更值?看完这篇文章你就懂了!》)。除了这两款新秀外,接下来我们还将迎来很多旗舰级的Android新品,下面咱们就来一一盘点。 中兴天机 Axon 10s Pro&…

装机——2021年底装机推荐,附9000元DIY介绍

前言 CSDN里发这种,我自认为挺奇葩的 不过我还是想要记录一下自己的目标,等明年618 按照行情置办一套 同时由于能够畅玩3A大作,比如战地2042这种,我都会详细查阅B站评测视频, 进而配置出比较好的一些配置 这套8000落…

python第一行输入整数n、然后输入n行 每行三个字符串_B站2019秋招编程题思路解析[题目要素过多]...

B站在牛客网(https://www.nowcoder.com/test/16519291/summary)上发了一套自己秋招的编程题,恰好今年被老师忽悠着去再参加一次蓝桥杯(我参加C++组,所以下面的题都是用C++做的,没用我熟悉的python),虽然那个比赛很水,但是还是要提高我的编程能力才是。于是准备做点题练习下…

mybatis 9-23

Mybatis框架 Mybatis框架官网一 、 jdbc回顾1 jdbc的问题: 二 、 Mybatis介绍1 五大框架2 mybatis的整体架构导入依赖编写全局配置文件mybatis-config.xml读取所有的xml文件配置,使得target中也有xml文件实体类注解生成getter serter方法编写User对应的U…