C++之多态(Polymorphism)

shadow :在父子类中,只要出现了重名标识符(函数成员,数据成员),就会构成,shadow。在子类中如果想访问被shadow的成员,加上父类的命名空间。

override:在父子类中出现同名不同参,就会构成覆写override。在生成对象的时候调用成员函数会自行匹配。

1、多态的赋值兼容

如果有几个相似而不完全相同的对象, 有时人们要求在向它们发出同一个消息时, 它们的反应各不相同, 分别执行不同的操作。 这种情况就是多态现象。
例如, 甲乙丙 3 个班都是高二年级, 他们有基本相同的属性和行为, 在同时听到上课铃声的时候, 他们会分别走向 3 个不同的教室, 而不会走向同一个教室。

C++中所谓的多态(polymorphism)是指, 由继承而产生的相关的不同的类, 其对象对同一消息会作出不同的响应。

赋值兼容规则是指, 在需要基类对象的任何地方, 都可以使用公有派生类(public)的对象来替代。

只有在公有派生类中才有赋值兼容, 赋值兼容是一种默认行为, 不需要任何的显示的,并且很安全。

赋值兼容细化

1、 派生类的对象可以赋值给基类对象。

2 、派生类的对象可以初始化基类的引用。

3 、派生类对象的地址可以赋给指向基类的指针

第三个是最重要的。赋值给基类肯定显示的是派生类中初始化的内容。

子类对象初始化的时候, 调用父类的构造器, 也是发生了赋值兼容的问题。 除此之外,其它任何父类成员调用, 均是如此。大范围的指针向小范围的指针转化是安全的。 反之, 不亦然。

父类也可以通过强转的方式转化为子类。 父类对象强转为子类对象后, 访问从父类继承下来的部分是可以的, 但访问子类的部分, 则会发生越界的风险, 越界的结果是未知的,也应当是避免的。

2、多态形成的条件(Conditions of PolyMorphism)

静多态

静多态, 也就是我们说的函数重载。 表面来看, 是由重载规则来限定的, 内部实现却是 Namemangling。此种行为, 发生在编译期, 故称为静多态。

动多态

(动)多态, 不是在编译器阶段决定, 而是在运行阶段决定, 故称为动多态。 动多态行成的条件如下:

1 父类中有虚函数, 即共用接口。

2 子类 override(覆写)父类中的虚函数。

3 通过己被子类对象赋值的父类指针, 调用共用接口。然后实际调用的是子类中覆写的

3、虚函数 virtual function

格式

class 类名
{
    virtual 函数声明;
} 

虚函数必须是类的成员函数否则编译不过
类的静态成员函数不能为虚函数否则编译不过
构造函数不能为虚函数否则编译不过,它将在执行期间被构造, 而执行期则需要对象已经建立, 构造函数所完成的工作就是为了建立合适的对象, 因此在没有构建好的对象上不可能执行多态

4、覆写关键字 override(C++11)

override 是 C++11 中引入的关键字, 用于修饰覆写的虚函数。 表明父类中有此虚函数,同名, 同参, 同返回中有一样不满足, 则会报错。这样的好处是, 假设在覆写中, 写错了, 比如, 作不到同名, 同参, 同返中的任一样,编译器都会给出提示 .这个关键字就放在覆写函数的最后面。

void drawx() override
{}

1 virtual 是声明虚函数的关键字, 它是一个声明型关键字。

2 override 构成的条件, 发生在父子类的继承关系中 同名, 同参, 同返回。

3 虚函数在派生类中仍然为虚函数, 若发生覆写, 最好显示的标注 virtual。若无覆写, 仍然为虚函数。

4 子类中的覆写的函数, 可以为任意访问类型, 依子类需求决定。

5、纯虚函数 pure virtual function

纯虚函数, 是一种虚函数, virutal 修饰, 没有实现体, 且被"初始化"为零的函数。 纯虚函数存在的意义在于, 提供族类接口使用。

格式

class 类名
{
    virtual 函数声明 = 0;
} 

纯虚函数与多态

1 纯虚函数只有声明, 没有实现, 被"初始化"为 0。

2 含有纯虚函数的类, 称为 Abstract Base Class(抽象基类), 不可实例化。 即不能创建对象, 存在的意义就是被继承, 提供族类的公共接口, java 中称为 Interface。

3 如果一个类中声明了纯虚函数, 而在派生类中没有该函数的定义, 则该虚函数在派生类中仍然为纯虚函数, 派生类仍然为纯虚基类。

6、虚析构 Virtual Destructor

需要被继承的类中, 析构函数也应该声明为虚函数, 称为虚析构函数。 在 delete 父类指针, 指向的堆上子类对象时, 会调用子类的析构函数, 实现完整析构

7、依赖倒置原则

设计模式中有一种很重要的原则之一, 叫依赖倒置(Dependency Inversion Principle),简称 DIP。 也是基于多态的。

以前写的东西都是这样的

依赖倒置就是这样的:

依赖倒置原则的核心思想是面向接口编程, 我们依旧用一个例子来说明面向接口编程,比相对于面向实现编程好在什么地方。 场景是这样的, 母亲给孩子讲故事, 只要给她一本书, 她就可以照着书给孩子讲故事了

下边的例子中Mother类就是高层模块,各种书籍报纸就是底层模块,中间的抽象接口IReader就是中间抽象层。book和newspaper继承与IReader,覆写纯虚函数,实现多态,高层模块Mother调用IReader即可,不必调用底层模块

#include <iostream>
using namespace std;
class IReader//虚基类,中间抽象层
{ 
public:
    virtual string getContents() = 0;
};

class Book :public IReader //底层模块
{
public:
    string getContents()
    {
        return "从前有座山, 山里有座庙, 庙里有个小和尚, 要听故事";
    }
};
class NewsPaper:public IReader//底层模块
{ 
public:
    string getContents()
    {
        return "Trump, 赢得的下一届美国总统大选";
    }
};

class Mother//高层模块
{ 
public:
    void tellStroy(IReader *i)
    {
        cout<<i->getContents()<<endl;
    }
};
int main()
{
    Mother m;
    Book b;
    NewsPaper n;
    m.tellStroy(&b);
    m.tellStroy(&n);
    return 0;
}

8、运行时类型信息(RTTI)

RTTI(run time type identificaiton), 允许程序员在运行时进行对象信息识别。 typeid/dynamic_cast 是 C++ 运行时类型信息重要组成部分。运行时信息, 来自于多态, 所以, typeid/dynamic_cast运算符只用于基于多态的继承体系中

typeid

运算符 typeid , 返回包含操作数数据类型信息的 type_info 对象的一个引用, 信息中包括数据类型的名称, 要使用 typeid, 程序中需要包含头文件#include <typeinfo>。其中 type_info 重载了操作符 == 、 != 分别用来比较是否相等、 不等, 函数 name()返回类型名称。 type_info 的拷贝和赋值均是私有的, 故不可拷贝和赋值。常用于返回检查, 调试之用。

用法

cout<<typeid(char).name()<<endl; //基本类型

void (*pf)(int);
cout<<typeid(pf).name()<<endl; //函数类型

确保基类定义了至少一个虚函数(虚析构也算)。

不要将 typeid 作用于指针,应该作用于引用,或解引用的指针。

typeid 是一个运算符,而不是函数。

typeid 运算符返回的 type_info 类型,其拷贝构造函数和赋值运算函数都声明为private 了,这意味着其不能用于 stl 容器,所以我们一般不能不直接保存 type_info信息,而保存 type_info 的 name 信息

#include <iostream>
#include <typeinfo>
using namespace std;
class A
{ 
public:
    virtual ~A(){} // 若无此虚函数, 则称运行时信息不成立
};

class B:public A{
    
};

class C:public A{
    
};


int main(int argc, char *argv[])
{
    B b;
    cout<<typeid(b).name()<<endl;
    return 0;
}

dynamic_cast

在面向对象程序设计中,有时我们需要在运行时查询一个对象是否能作为某种多态类型使用。C++提供了dynamic_cast函数用于动态转型。相比C风格的强制类型转换和C++ reinterpret_cast,dynamic_cast提供了类型安全检查,是一种基于能力查询(Capability Query)的转换,所以在多态类型间进行转换更提倡采用dynamic_cast。

基本用法

dynamic_cast可以获取目标对象的引用或指针:


T1 obj;

T2* pObj = dynamic_cast<T2*>(&obj);//转换为T2指针,失败返回NULL

T2& refObj = dynamic_cast<T2&>(obj);//转换为T2引用,失败抛出bad_cast异常 

多态类型

在使用时需要注意:被转换对象obj的类型T1必须是多态类型,即T1必须公有继承自其它类,或者T1拥有虚函数(继承或自定义)。若T1为非多态类型,使用dynamic_cast会报编译错误。下面的例子说明了哪些类属于多态类型,哪些类不是:

Last modification:August 14th, 2019 at 10:54 pm
如果觉得我的文章对你有用,请随意赞赏

Leave a Comment