IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《Effective C++》学习笔记(条款32:确定你的public继承塑模出is-a关系) -> 正文阅读

[C++知识库]《Effective C++》学习笔记(条款32:确定你的public继承塑模出is-a关系)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

1 public 继承是一种 is-a 的关系

以C++进行面向对象编程,最重要的一个规则是:public inheritance(公开继承)意味"is-a"的关系
如果你令 class D(派生类)以 public 形式继承 class B(基类),你便是告诉C++编译器(亦或是看你代码的人),每一个类型为D的对象同时也是一个类型为B的对象,反之则不成立。看以下例子:

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

根据生活经验我们知道,每个学生都是人,但并非每个人都是学生,这便是上面代码的主张。
在C++领域中,任何函数如果期望获得一个类型为 Person (或 Person*Person& )的实参,都也愿意接受一个 Student 对象(或 ``Student*Student&` ):

void eat(const Person& p);    		// 任何人都会吃
void study(const Student& s);    	// 只有学生才到校学习
 
Person p;    						// p是人
Student s;    						// s是学生
eat(p);    							// ok,p是人,可以执行吃这个动作
eat(s);    							// ok,s是学生,根据is-a,可以执行吃这个动作
study(s);    						// ok,s是学生,可以执行到校学习这个动作
study(p);    						// no! p是人,并非每个人都可以执行到校学习这个动作

这个论点只对 public 继承才成立。private 继承的意义于此完全不同(见条款39),至于 protected 继承,那是一种其意义至今仍然困惑我的东西。

2 产生的各种问题

public 继承和 is-a 之间的等价关系听起来非常简单,但有时候可能被误导,比如:企鹅是一种鸟,鸟可以飞,但如果我们用C++来描述这种关系:

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

这个继承体系说企鹅可以飞,而这是错误的。这是怎么回事呢?

在这个例子中,我们成了不严谨语言(英语)下的牺牲品。当我们说鸟会飞的时候,我们真正的意思并不是说所有的鸟都会飞,我们要说的只是一般的鸟都会飞,但有些鸟不会飞,应该这样描述:

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

class FlyingBird : public Bird  {//会飞的鸟
public:
  virtual void fly();
  ...
};
class Penguin : public Bird  {//企鹅
  ...	// 没有声明fly函数
};

但是,我们还未能完全处理好这件事。因为对某些软件系统而言,可能不需要区分会飞的鸟和不会飞的鸟。假如,你的程序对鸟喙和鸟翅更加感兴趣,完全不在乎飞行,那么原本的"双 class 继承体系"或许就可以满足了。

这实际上说明了一个事实,世界上并不存在一个"适用于所有软件"的完美设计;所谓的最佳设计,取决于系统希望做什么事,包括现在和未来。

还有一种方法处理“所有的鸟都会飞,企鹅是鸟,但它不会飞”的问题:为企鹅重新定义 fly 函数,令它产生一个运行期错误:

void error(const std::string& msg);	//定义于另外某处

class Penguin : public Bird  {
public:
  virtual void fly()  {  error("Attempt to make a penguin fly!");  }
  ...
};

我们得认知到一点:上述代码并不是说“企鹅不会飞”,而是说“企鹅会飞,但尝试那么做是一种错误”。

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

我们再说一个例子,正方形和矩形之间的关系,人们都认为正方形是一种矩形,矩形却不一定是正方形,那么 正方形类应该以 public 形式继承矩形类吗?看以下代码:

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的宽度加10
  assert(r.height()==oldHeight);    // 判断r的高度是否未曾改变
}

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

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

class Square : public Rectangle  {  ...  };
Square s;
...
assert(s.width()==s.height());    // 这对于所有的正方形一定为真
makeBigger(s);                    // 由于public继承,s是一种(is-a)矩形
 
 
assert(s.width()==s.height());    // 对所有的正方形应该仍然为真

这也很明显,第二个 assert 结果也应该永远为真(事实上不为真)。因为根据定义,正方形的高度和宽度相同。

但现在我们遇上了一个问题,我们如黄河调节下面各个 assert 判断式:

  • 调用 makeBigger() 之前,s的高度和宽度相同
  • makeBigger() 函数内,s的宽度改变,但高度不变
  • makeBigger()返回之后,s的高度再度和其宽度相同。(注意,s是以引用传递方式传给 makeBigger(),所以 makeBigger() 修改的是s自身,不是s的副本)

其实,本例的根本困难是,某些可施行于矩形身上的事(例如宽度可独立于其高度被外界修改)却不可实行于正方形身上(宽度总应该与高度一样)。但是,public 继承主张,能够实行于基类对象身上的每件事情,每件事情也同时可以实行于派生类身上。再正方形和矩形的例子中,那样的主张无法保持,所以以 public 继承建模它们之间的关系并不正确。这让我们认识到了最最重要的一点:代码通过编译并不代表可以正确的运作

is-a 并非是唯一存在于类之间的关系。另两个常见的关系是 has-a(有一个)和 is-implemented-in-terms-of(根据某物实现出)。这些关系将在条款38和39讨论。将上述这些重要的相互关系中的任何一个误塑为 is-a 而造成的错误设计,在C++中并不罕见,所以你应该确定你确实了解这些个"class 相互关系"之间的差异,并知道如何在C++中最好的塑造它们。

Note:

  • "public继承"意味着 is-a。适用于基类身上的每一件事情一定也适用于派生类身上,因为每一个派生类对象也都是一个基类对象

条款33:避免遮掩继承而来的名称

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 21:25:37  更:2021-11-14 21:28:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:44:16-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码