当前位置: 首页 > article >正文

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

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.kler.cn/news/357097.html

相关文章:

  • [环境配置]macOS上怎么查看vscode的commit id
  • 力扣动态规划基础版(斐波那契类型)
  • Android 禁止App字体随系统大小而更改
  • 其他css的用途
  • 前端excel的实现方案Luckysheet
  • 用HTML标签承载页面内容:前端开发的基础知识
  • 每天5分钟玩转C#/.NET之goto跳转语句
  • React基础知识(一) - React初体验
  • 在Android中如何切割一张图片中的不规则“消息体/图片/表情包等等”?
  • nginx在access日志中记录请求头和响应头用作用户身份标识分析
  • 微信小程序上传组件封装uploadHelper2.0使用整理
  • NodeJS 使用百度翻译API
  • scala继承
  • Java中常见的自带数据结构类
  • (小白教程)MPV.NET 播放器安装和添加Bilibili弹幕
  • 速盾:cdn加速访问网站过程
  • 物理安全概述
  • 矩阵系统哪家好~矩阵短视频运营~怎么矩阵OEM
  • 【MR开发】在Pico设备上接入MRTK3(三)——在Unity中运行MRTK示例
  • C++算法练习-day9——24.两两交换链表中的节点