OOP Lec8: Polymorphism

Polymorphism

You are a shape. You know how to draw yourself. So do it!

子类型

对于 OOP 语言,子类 (Subclass) 和子类型 (Subtype) 是一样的。

任何需要父类参数的时刻,子类都可以被当作父类。这个时候将舍弃子类的独有方法和独有成员。

class Item{};
class DVD: public Item{};
class CD: public Item{};
void addDVD(DVD& v);
void addCD(CD& v);
void addItem(Item& v);

int main(){
    DVD myDVD;
    addItem(DVD);
}

造型

向上造型 (up-casting) 是指将一个对象当成其父类使用。这一般是通过用父类的指针 / 引用指向子类对象实现的。

造型不改变对象的内容和值,这使得它区别于类型转换。

Example

我们想实现一个画图的程序,包括圆形、长方形、正方形和椭圆。

先考虑公共部分,

方法:render, move, resize

数据: center

层次上,圆继承于椭圆,正方形继承于长方形。

为什么不是椭圆继承于圆?

在数学概念上,圆是椭圆,更特殊;在实现上,椭圆可以由圆添加一个参数得到,很方便。

这种情况下,对椭圆成立的性质对圆也成立,而我们应该在父类中维护公共的性质与方法,

所以椭圆应该作为父类,圆作为子类。

#include <algorithm>
#include <iostream>
using namespace std;

class XYpos{
public:
    double x, y;
    XYpos(double _x = 0, double _y = 0):
        x(_x), y(_y){}
};

class Shape{
protected:
    XYpos center;
public:
    virtual void render(){cout << "Shape::render()" << i << endl;}
};

class Rectangle: public Shape{
private:
    double r, c;
public:
    void render(){cout << "Rectangle::render()" << i << endl;}
};

class Ellipse: public Shape{
private:
    double a, b;
public:
    void render(){cout << "Ellipse::render()" << i << endl;}
};

void draw(Shape *p){
    p->render();
}
signed main(){
    Rectangle r;
    Ellipse e;
    draw(&r); r.render();
    draw(&e);
    Shape *p = &e;
    p->render();
}

多态变量

一个变量被声明的类型,就称为它的静态类型。对于编译器而言,只能检查变量静态类型的方法。

对于指针 / 引用,它指向的对象的类型称为它的动态类型

这时它可能指向一个其静态类型的子类,所以指针和引用被称为多态变量(polymorphic variable)

绑定

静态绑定

一般的函数调用都是静态绑定,使用时直接调用。只有 C++ 默认为静态绑定,这一点与其他编程语言不同。

动态绑定

virtual 关键字:告诉编译器,子类中会有该函数的别的版本,而调用哪个版本的决定推迟到运行时。
这种不确定的调用就是动态绑定。

使用动态绑定的两个条件:

  1. 指针 / 引用的对象是 polymoriphic variable。
  2. 调用的方法有 virtual

但如果 Shape 类型没有 render() 方法,检查 p->render() 时会报错。这是由于指针只检查其静态类型。

如果函数的返回值是对象,那么派生类不能重写为派生类对象;如果函数的返回值是引用或指针,那么派生类可以重写为派生类对象的引用或指针。

虚函数

virtual 声明的函数称为虚函数,它可以被子类中同名同参数的函数重写(override)

如何工作:

对于非虚函数:只生成一条指令,调用唯一的版本。

对于虚函数:要决定调用的函数版本,引入了一个由对象确定的虚函数表。

一个父类的成员函数被声明为虚函数后,所有子类的同名函数都被隐式地声明为虚函数。

代码覆盖上面的 main()

signed main(){
    Rectangle r;//define an object
    long long **vptr = (long long**)(&r);//take its head address
    //it works on the assumption that we know it's a pointer to somewhere
    void (*fp)() = (void(*)())(**vptr);//convert the element on 'somewhere' to a function pointer
    fp();
}

实测发现,fp() 调用了 Rectangle::render()

一旦类中有虚函数,其每一个对象的开头都会有一个指针 vptr ,指向该类的虚函数表。

对于子类的函数表,子类函数会覆盖父类函数的位置,这样在调用虚函数时就可以准确地调用。

如果用子类对象赋值父类,vptr 会一并修改吗?答案是否定的。

vptr 在构造的时候被确定。

重载函数

对于基类中的重载函数,在派生类中应该全部重载,否则未被重载的变体将不能调用。

在子类内部实现中,可以使用 using 语句来简化对父类成员的调用。

class Base { public: void f();};
class Child : public Base {
public:
    using Base::f;
    void func(){ f(); f();} // Base::f
};

虚析构函数

Shape *p = new Ellipse(100.0F, 200.0F);
delete p;

此时将执行两步:1. 调用 p->~Shape() 2. 调用 free(p)

为了能调用子类的析构,我们应该将父类的析构函数声明为虚函数。

从后续维护和拓展的角度考虑,所有的析构函数都应该声明为虚函数。

进一步,所有的类都应该存在 vptr,这是 RTTI(RunTime Type Identification) 的基础。

构造函数

在构造函数中,仍然使用动态绑定。但是因为 vptr 在构造时确定,所以子类在执行父类构造函数时,vptr 指向父类的虚函数表,会默认调用父类的成员函数。

class A {
public:
    A() {
        f();
    }
    virtual void f() {
        cout << "A::f()";
    }
};
class B : public A {
public:
    B() {
        f();
    }
    void f() {
        cout << "B::f()";
    }
};
B temp;

输出可以发现,先调用了 A::f() 后调用了 B::f()

纯虚函数

对于公共、抽象的父类(例子中的 Shape 类),我们既不想费脑筋完成其某些功能 render(),也不希望能够生成其对象。

那么我们就需要将成员函数定义为纯虚函数,这时候这个类就成为了抽象类

抽象类不能制造对象。

class Shape{
protected:
    XYpos center;
public:
    virtual void render() = 0;
};

语句的实际意义:将虚函数表中对应的函数指针赋为 0。

多继承

C++ 是唯一的支持多继承的语言。原因:没有实现单根结构(BS 设计)

多继承的目的:实现容器(Ceil 容器)

菱形继承

A 同时被 BC 继承,D 继承 BC,那么 D 相当于继承了两次 A

那么当我们把 A 的指针指向 D 的对象时,就不知道应该指向 B::A 还是 C::A,出现冲突。

同理,如果 D 访问 A 的成员时,不知道应该访问 B::A 还是 C::A

虚继承

通过在继承时添加 virtual 关键字实现。

虚继承时子类中不存在父类的对象,而是保有父类的指针。

class A {public: int m_i;};
class B: virtual public A {};
class C: virtual public A {};
class D: public B, public C{};
int main(){
    D temp;
    temp.m_i++; // Ok, only one A in temp
    A* p = new D; // Ok
}

唯一应用方案:多继承时只有一个类是实际类(有成员变量的类),其他类只提供接口。

最优建议:别用

最终方案:模板

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇