C++学习笔记----9、发现继承的技巧(四)---- 多态继承(2)

4、单独的继承类

写StringSpreadsheetCell与DoubleSpreadsheetCell类只是实现定义在父类中的功能的实现的总是。因为想让客户能够实例化并且在string单元格与double单元格上工作,单元格不能是抽象的--它们必须实现继承自父类的所有干净的virtual成员函数。如果继承类不实现所有的来自基类的干净的virtual成员函数,那么继承类也是抽象的,客户也不能够实例化继承类的对象。

4.1、StringSpreadsheetCell类定义

StringSpreadsheetCell类定义在了叫做string_spreadsheet_cell的模块中。写StringSpreadsheetCell类定义的第一步是继承SpreadsheetCell。为了做到这一点,spreadsheet_cell模块应该被import进来。

下一步,继承的干净的virtual成员函数被重载,这一次没有被设置为0.

最后,string单元格添加 一个私有的数据成员,m_value,它保存了真实的单元格数据。该数据成员为std::optional,区分单元格的值从来没有被设置过还是被设置为空的字符串是可能的。

cpp 复制代码
export module string_spreadsheet_cell;
export import spreadsheet_cell;
import std;

export class StringSpreadsheetCell : public SpreadsheetCell
{
public:
    void set(std::string_view value) override;
    std::string getString() const override;

private:
    std::optional<std::string> m_value;
};

4.2、StringSpreadsheetCell实现

set()成员函数是直接的,因为内部表示已经是string。getString()成员函数必须要保持住m_value是optional类型,可能没有值。当m_value没值的时候,getString()应该返回一个缺省的string,在本例中是空的string。用optional的value_or()成员函数是很容易的。通过使用m_value.value_or(""),如果m_value包含一个真实值,真实值就会被返回;否则的话,就会返回空的string。

cpp 复制代码
void set(std::string_view value) override { m_value = value; }
std::string getString() const override { return m_value.value_or(""); }

4.3、DoubleSpreadsheetCell类定义与实现

double遵从同样的模式,但是用了不同的逻辑。除了给来自基类的接受string_view的set()成员函数,也提供了新的set()成员函数,允许客户用double参数设置值。还有,它提供了一个新的getValue()成员函数作为double来访问它的值。两个新的私有的静态的成员函数用于相互转换string与doulbe。与在StringSpreadsheetCell一样,它有一个叫做m_value的数据成员,这次是optional<double>类型。

cpp 复制代码
export module double_spreadsheet_cell;
export import spreadsheet_cell;
import std;

export class DoubleSpreadsheetCell : public SpreadsheetCell
{
public:
    virtual void set(double value);
    virtual double getValue() const;
    void set(std::string_view value) override;
    std::string getString() const override;

private:
    static std::string doubleToString(double value);
    static double stringToDouble(std::string_view value);
    std::optional<double> m_value;
};

set()成员函数使用double是显而易见的,与在getValue()中的实现一样。string_view重载使用了私有的静态的成员函数stringToDouble()。getString()成员函数返回了保存的double值作为string,或者返回一个空的string,如果没有值被保存的话。它使用std::optional的has_value()的成员函数来查询optional是否有值。如果它有值,value()成员函数用于查询。

cpp 复制代码
virtual void set(double value) { m_value = value; }
virtual double getValue() const { return m_value.value_or(0); }
void set(std::string_view value) override { m_value = stringToDouble(value); }
std::string getString() const override
{
    return (m_value.has_value() ? doubleToString(m_value.value()) : "");
}

你可能已经注意到了用层次结构实现spreadsheet单元格的一个巨大的好处是--代码简化了很多。每个类可以是自我为中心的,只需要处理自己的功能。

记住doubleToString()与strinToDouble()的实现省略了,因为与以前是一样的,就不浪费大家的精力了。

5、利用多态

现在SpreadsheetCell层次结构是多态的了,客户代码可以利用多态提供的这么多好处。下面的测试程序探索了许多特性。

为了展示多态,测试程序声明了三个SpreadsheetCell指针的一个vector。记住因为SpreadsheetCell是一个抽象类,不能生成该类型的对象。然而,仍然可以有SpreadsheetCell的指针或引用,因为它实际上指向继承类中的一个。该vector,因为它是父类SpreadsheetCell的vector,允许保存两个继承类的各种各样的混合。这意味着vector的元素可以是StringSpreadsheetCell或DoubleSpreadsheetCell。

cpp 复制代码
vector<unique_ptr<SpreadsheetCell>> cellArray;

vector最先的两个元素设置为指向新的StringSpreadsheetCell,而第三个是一个新的DoubleSpreadsheetCell。

cpp 复制代码
cellArray.push_back(make_unique<StringSpreadsheetCell>());
cellArray.push_back(make_unique<StringSpreadsheetCell>());
cellArray.push_back(make_unique<DoubleSpreadsheetCell>());

现在vector包含多种类型的数据,在基类中声明的任何成员函数都可以应用于vector中的对象。代码只是使用了SpreadsheetCell指针--编译器在编译时不知道对象实际上是什么类型的。然而,因为对象继承自SpreadsheetCell,它们必须支持SpreadsheetCell的成员函数。

cpp 复制代码
cellArray[0]-­>set("hello");
cellArray[1]-­>set("10");
cellArray[2]-­>set("18");

当调用getString()成员函数时,每个对象恰当地返回一个代表其值的string。重要的,有点儿令人惊奇的事情是要意识到不同的对象行为方式不同。StringSpreadsheetCell返回保存的值,或者空的string。DoubleSpreadsheetCell如果有值的话首先执行转换;否则的话,它返回一个空的string。作为程序员,不必知道对象做了什么--只需要知道由于对象是一个SpreadsheetCell,它可以执行这种行为。

cpp 复制代码
println("Vector: [{},{},{}]",   cellArray[0]-­>getString(),
                                cellArray[1]-­>getString(),
                                cellArray[2]-­>getString());
相关推荐
诚丞成25 分钟前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
Smile灬凉城66637 分钟前
反序列化为啥可以利用加号绕过php正则匹配
开发语言·php
lsx2024061 小时前
SQL MID()
开发语言
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶1 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
言、雲1 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
OopspoO1 小时前
qcow2镜像大小压缩
学习·性能优化
东风吹柳1 小时前
观察者模式(sigslot in C++)
c++·观察者模式·信号槽·sigslot
A懿轩A1 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列