C++学习笔记----9、发现继承的技巧(三)---- 尊重父类(2)

server/2024/10/20 13:09:48/

4、指向父类名字

        当在继承类中重载一个成员函数时,只要与其它代码相关就要有效替换掉原有的代码。然而,成员函数的父版本仍然存在,你可能会想使用它。例如,一个重载的成员函数会保持基类实现的行为,加上其它的一些。看一下WeatherPrediction类的getTemperature()成员函数返回一个string表示当前温度:

export class WeatherPrediction
{
public:virtual std::string getTemperature() const;// Remainder omitted for brevity.
};

        可以在MyWeatherPrediction类中重载这个成员函数如下:

export class MyWeatherPrediction : public WeatherPrediction
{
public:std::string getTemperature() const override;// Remainder omitted for brevity.
};

        假定继承类想要通过首先调用基类的getTemperature()成员函数给字符串添加°F,然后就给它加了°F。代码可能如下:

string MyWeatherPrediction::getTemperature() const
{// Note: \u00B0 is the ISO/IEC 10646 representation of the degree symbol.return getTemperature() + "\u00B0F"; // BUG
}

        然而,这样是不灵的,因为,在c++命名解析规则之下,它首先解析的是本地范围,然后解析类范围,以调用MyWeatherPrediction::getTemperature()结束。这会导致无限递归,直到栈空间耗尽(有些编译器会检测到这种错误在编译时报错)。

        真的想要这么做,需要使用范围解析符如下:

string MyWeatherPrediction::getTemperature() const
{// Note: \u00B0 is the ISO/IEC 10646 representation of the degree symbol.return WeatherPrediction::getTemperature() + "\u00B0F";
}

        注意:微软Visual c++支持非标准的__super关键字(带有两个下划线)。允许写出如下代码:

return __super::getTemperature() + "\u00B0F";

        调用当前成员函数的父类版本在c++中广泛使用。如果有一个继承类链,每个可能想要执行早已在基类中定义但是还要加一些另外的功能的操作。

        我们看另外一个例子,设想一个书类型的类层次结构,如下图所示:

        因为每个在层次结构中低层次的类指出书的类型,获得书描述的成员函数确实需要考虑层次结构的所有层次。可以通过将父类成员函数分链条来完成。下面的代码展示了这种模式:

import std;using namespace std;class Book
{
public:virtual ~Book() = default;virtual string getDescription() const { return "Book"; }virtual int getHeight() const { return 120; }
};class Paperback : public Book
{
public:string getDescription() const override {return "Paperback " + Book::getDescription();}
};class Romance : public Paperback
{
public:string getDescription() const override {return "Romance " + Paperback::getDescription();}int getHeight() const override { return Paperback::getHeight() / 2; }
};class Technical : public Book
{
public:string getDescription() const override {return "Technical " + Book::getDescription();}
};int main()
{Romance novel;Book book;println("{}", novel.getDescription()); // Outputs "Romance Paperback Book"println("{}", book.getDescription());  // Outputs "Book"println("{}", novel.getHeight());      // Outputs "60"println("{}", book.getHeight());       // Outputs "120"}

        Book基类有两个virtual成员函数:getDescription()与getHeight()。所有的继承类都重载了getDescription(),但是只有Romance类通过在父类(Paperback)上调用getHeight()并将其除以2重载了getHeight()。Paperback()没有重载getHeight(),但是c++沿着类层次结构往上找,发现实现了getHeight()的类。在这个例子中,Paperback::getHeight()解析到了Book::getHeight()。

5、向上转化与向下转化

        你早已看到,对象可以被转化或赋值给它的父类。下面为示例:

Derived myDerived;
Base myBase { myDerived };    // Slicing!

        分片会在像这种情况下出现,因为结果是一个Base对象,Base对象缺少在Derived类中定义的另外的功能。然而,如果继承类被赋值给指向它的基类的指针或引用的话就不会出现分片:

Base& myBase { myDerived }; // No slicing!

        对于有关基类的指向继承类,这是通常的正确的方式,也叫做向上转化。这也是为什么对于函数来说用用类的引用而不是直接使用类的对象总是一个好主意的原因。通过使用引用,继承类被传递而没有分片。

        警告:当向上转化时,使用基类的指针或引用避免分片。

        从基类转化为继承类,也叫做向下转化,常令专业c++程序员皱眉,原因是无法保证对象确实属于继承类,还因为向下了范围内是糟糕设计的标志。例如,考虑如下代码:

void presumptuous(Base* base)
{Derived* myDerived { static_cast<Derived*>(base) };// Proceed to access Derived member functions on myDerived.
}

        如果presumptuous()的作者也去写调用presumptuous()的代码,可能一切都没有问题,虽然很丑,因为作者知道函数椟要Derived*类型的参数。然而,如果其它程序员调用presumptuous(),他们可能会传进来一个Base*。编译时是不检查参数类型的,函数盲目地就会认为base就是一个指向Derived对象的指针。

        向下转化有时是必要的,在控制环境下使用它是有效的。然而,如果你想要向下转化,应该使用dynamic_cast(),它使用对象内建的类型知识来拒绝没道理的转化。这种内建知识典型地存在于vtable中,意味着dynamic_cast()只在对象拥有vtable时才有效,也就是说,对象至少要有一个virtual成员。如果dynamic_cast在指针上失败了,结果就是nullptr而不是指向无效的数据。如果dynamic_cast在对象引用上失败了,就会抛出std::bad_cast例外。本章的最后一节会详细讨论转化的不同选项。

        上面的例子可以写成如下代码:

void lessPresumptuous(Base* base)
{Derived* myDerived { dynamic_cast<Derived*>(base) };if (myDerived != nullptr) {// Proceed to access Derived member functions on myDerived.}
}

        然而,要记住使用向下转化是糟糕设计的标志。要重新思考并且 修改设计以避免向下转化。例如,lessPresumptuous()函数只在Derived对象上能好好干活,所以浊接受Base指针,它应该只是接受Derived指针。这减少了向下转化的需要。如果函数应该在不同的继承类上工作,所有从Based继承的,会找一个使用多态的解决方案,这个我们后面再讨论。

        警告:只在确实需要的时候使用向下转化,确保使用了dynamic_cast()。


http://www.ppmy.cn/server/133344.html

相关文章

HTB:Return[WriteUP]

目录 连接至HTB服务器并启动靶机 使用nmap扫描靶机开放端口 将靶机开放端口进行脚本、服务扫描 使用浏览器访问靶机80端口并进入Setting选项 将其修改为本地IP&#xff0c;并在本地侧开启nc监听389端口 查看user_flag内容 USER_FLAG&#xff1a;de9d4982df48629d7457ef2…

【每日一题】【算法双周赛】【第 20 场 小白入门赛评价/分享】赛后另类AI写题分析分享

第 20 场 小白入门赛 1. 四个亲戚【算法赛】2. 黛玉泡茶【算法赛】AI分析具体实现代码解析复杂度分析示例运行 结果二 3. 宝玉请安【算法赛】AI分析问题分析路径计算代码实现代码解析示例运行复杂度分析 结果&#xff1a; 交上去 4. 贾母祝寿【算法赛】AI分析问题分析实现步骤代…

git gui基本使用

一、图形化界面 二、创建新项目 创建文件&#xff0c;加入暂存区&#xff0c;提交到版本库 三、创建分支 四、合并分支 1.切换至master 五、更新分支 六、解决冲突 修改冲突&#xff0c;加入暂存区&#xff0c;提交到版本库 七、远程创建库 Gitee - 基于 Git 的代码托管和研…

Spring Boot实现接口限流

API限流是一种重要的策略&#xff0c;用于控制对API的访问速率&#xff0c;以保护后端服务免受过载和滥用。以下是API限流的必要性&#xff1a; 防止服务过载&#xff1a; 当API的请求量突然激增时&#xff0c;如果没有限流措施&#xff0c;可能会导致服务器资源耗尽&#xff0…

网站cms系统 开源cms建站系统

在数字化时代&#xff0c;企业对于快速、灵活且成本效益高的网站构建方案的需求日益增长。开源CMS&#xff08;内容管理系统&#xff09;建站系统因其灵活性、可定制性和强大的社区支持而成为众多企业和开发者的首选。本文将探讨开源CMS系统的优势、功能、应用案例以及如何选择…

《京东金融APP的鸿蒙之旅系列专题》新特性篇:意图框架接入

作者&#xff1a;京东科技 杨拓 一、意图框架服务介绍 HarmonyOS NEXT引入了多项创新特性&#xff0c;其中的意图框架能够将应用中的业务功能智能分发至手机的各大系统入口&#xff0c;其中系统入口包括小艺对话、小艺搜索和小艺建议等。通过这一特性&#xff0c;用户不仅可以主…

【Golang】Go语言Web开发之模板渲染

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Vue Google 广告的配置

前置条件&#xff1a;已经在Google AdSense 中 添加网站 并通过审核 同时已创建广告单元。 因 VUE 的 Script 配置问题&#xff0c;所以不能直接拷贝内容。 index.html 配置 添加 Google 广告的脚本。 //index.template.html /* * 在head标签中添加 script 【 **** 】&#…