C++多态

一、什么是多态

多态就是“多种状态”,具体到面向对象语言中,就是调用一套接口,根据不同的情况会产生不同的结果。这听起来似乎很神奇,以下代码就是对多态最好的解释。

代码:

# include <iostream>

using namespace std;

class Base {
public:
    int a;
    int b;
public:
    virtual void func1() {
        cout << "I'm Base" << endl;
    }
};

class Child1 : public Base {
public:
    int a;
    int b;
public:
    virtual void func1() {
        cout << "I'm Child1" << endl;
    }
};

class Child2 : public Base {
public:
    void func1() {
        cout << "I'm Child2" << endl;
    }
};

int main() {
    Base* base = dynamic_cast< Base* > (new Child1);
    base->func1();
    delete base;

    base = dynamic_cast< Base* >(new Child2);
    base->func1();
    delete base;
    base = NULL;

    getchar();

    return 0;   
}

结果

I'm Child1
I'm Child2

现象:Child1和Child2继承自同一个父类Base,Base中有一个虚函数func1(),子类Child1和Child2分别重写了父类Base中的虚函数func(),然后在主函数中父类指针分别指向了子类Child1和Child2两个子类对象,在父类指针调用func1()函数时,由于指向不同的子类对象,调用了不同子类对象的func1()函数。这就是多态,但是一般程序中不这样用,一般用法如下:

# include <iostream>
# include <string>

using namespace std;

class Animal {
private:
    string name;
    string color;
    string eating;
public:
    Animal() {}
    Animal(string name, string color, string eat) {
        this->name = name;
        this->color = color;
        this->eating = eat;
    }
public:
    virtual void DoSth() {
        eat();
        this->run();
    }
    virtual void eat() {
        cout << color + name + eating << endl;
    }
    virtual void run() {
        cout << color + name + "is running" << endl;
    }
};

class Cat : public Animal {
private:
    string name;
    string color;
    string eating;
public:
    Cat() {}
    Cat(string name, string color, string eat) {
        this->name = name;
        this->color = color;
        this->eating = eat;
    }
public:
    virtual void eat() {
        cout << color + name + eating << endl;
    }
    virtual void run() {
        cout << color + name + "is running" << endl;
    }
};

class Dog : public Animal {
private:
    string name;
    string color;
    string eating;
public:
    Dog() {}
    Dog(string name, string color, string eat) {
        this->name = name;
        this->color = color;
        this->eating = eat;
    }
public:
    virtual void eat() {
        cout << color + name + eating << endl;
    }
    virtual void run() {
        cout << color + name + "is running" << endl;
    }
};

void Do_Things( Animal* an ) {
    an->DoSth();

    delete an;
}

int main() {
    Do_Things(new Dog("小狗", "黑色", "吃狗粮"));
    Do_Things(new Cat("小猫", "花色", "吃猫粮"));

    getchar();

    return 0;   
}

上示代码中,对于函数 void Do_Things( Animal* an ); 虽然传入参数类型是 Animal ,但是如果传入的是 Dog 类的实例化,那么执行的是 dog 对象的方法,如果传入的是 Cat 类的实例化,那么执行的是 cat 对象的方法。当然,为了明显,您可以尝试在 Cat 类中加入新的方法,在Cat继承自 Animal 类的虚函数中去调用,这样会更明显一些。

二、总结多态成立的条件

①子类继承父类
②子类重写父类虚函数
③父类指针指向子类对象

三、多态的实现原理

1、类的内存布局图

多态的实现依赖于编译器的提前布局,这要从继承说起。以上述 Animal 类为例:

class Animal {
private:
    string name;
    string color;
    string eating;
public:
    Animal() {}
    Animal(string name, string color, string eat) {
        this->name = name;
        this->color = color;
        this->eating = eat;
    }
public:
    virtual void DoSth() {
        eat();
        this->run();
    }
    virtual void eat() {
        cout << color + name + eating << endl;
    }
    virtual void run() {
        cout << color + name + "is running" << endl;
    }
};

以下附上 Animal 类的内存布局图:

1>  class Animal    size(88):
1>      +---
1>   0  | {vfptr}
1>   4  | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ name
1>  32  | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ color
1>  60  | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ eating
1>      +---
1>
1>  Animal::$vftable@:
1>      | &Animal_meta
1>      |  0
1>   0  | &Animal::DoSth
1>   1  | &Animal::eat
1>   2  | &Animal::run
1>
1>  Animal::DoSth this adjustor: 0
1>  Animal::eat this adjustor: 0
1>  Animal::run this adjustor: 0

—————————————————————————————

class Animal size(88)

表示该类占用内存字节数
—————————————————————————————

1>   0  | {vfptr}
1>   4  | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ name
1>  32  | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ color
1>  60  | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ eating

表示该类中包含哪些成员(注意:类中不包含成员方法),Animal 类中将要占用内存的是 指向虚表 的虚函数指针和成员属性(那几个string)。类是抽象的概念,不占用内存,只有当类实例化为某个对象才会占用内存。左边的0、4、32、60表示内存地址偏移。
—————————————————————————————

1>  Animal::$vftable@:
1>      | &Animal_meta
1>      |  0
1>   0  | &Animal::DoSth
1>   1  | &Animal::eat
1>   2  | &Animal::run

表示 Animal 类的虚表。所有该类的虚函数入口地址都放在虚表中,左侧的0、1、2表示虚函数编号。
—————————————————————————————
最后那些是关于内存对齐的。

2、基类中包含有virtual函数时子类继承过程(多态机制)

①基类中,只要有virtual函数,那么基类的开始处必然有一个虚指针,该虚指针指向该类对应的虚表,虚表中存放了该类所有虚函数的入口地址。
②当有子类继承基类时,子类中也一定包含一个虚指针,这个虚指针是从父类中继承过来的(子类的虚指针起初指向父类虚表),之后,子类重建自己的虚表,并且在虚表放入了父类的虚函数入口地址(当然如果子类声明了自己的虚函数,也会再加入到子类的虚表中),当子类重写父类虚函数时,子类虚表中对应入口地址将变为子类重写的虚函数入口地址。这就是多态发生的基础。
③调用时传入的是子类对象,但是接收参数的是父类指针,对于指针来说,两大属性,首地址和步长,子类对象指针步长一般大于等于父类对象,将子类对象指针转化成父类对象指针是安全的。当以父类指针的类型调用成员方法时,如果子类对象重写了父类虚函数,实际调用的是传入的子类对象重写父类虚函数后的方法,产生多态;若子类对象没有重写父类虚函数的方法,那么子类虚表中有的是克隆来的父类的方法,没有多态产生。
综上所述,C++中的多态本质应该是以函数指针实现的:父类每一个虚函数,都以一个确定的函数指针类型存入虚表中,并且对应该函数的入口地址;当子类继承该父类时,继承了父类的虚表,虚表中有父类虚函数类型的函数指针,当重写父类方法时,实际相当于改变了子类虚表中函数指针的指向。于是在调用时多态发生。

3、以下附上上述 Cat 类的内存布局图

class Cat : public Animal {
private:
    string name;
    string color;
    string eating;
public:
    Cat() {}
    Cat(string name, string color, string eat) {
        this->name = name;
        this->color = color;
        this->eating = eat;
    }
public:
    virtual void eat() {
        cout << color + name + eating << endl;
    }
    virtual void run() {
        cout << color + name + "is running" << endl;
    }
};
1>  class Cat   size(172):
1>      +---
1>   0  | +--- (base class Animal)
1>   0  | | {vfptr}
1>   4  | | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ name
1>  32  | | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ color
1>  60  | | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ eating
1>      | +---
1>  88  | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ name
1>  116 | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ color
1>  144 | ?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@ eating
1>      +---
1>
1>  Cat::$vftable@:
1>      | &Cat_meta
1>      |  0
1>   0  | &Animal::DoSth
1>   1  | &Cat::eat
1>   2  | &Cat::run
1>
1>  Cat::eat this adjustor: 0
1>  Cat::run this adjustor: 0

四、总结

多态是面向对象最重要的特性,多态主要为了提高代码的可扩展性。多态的产生三大条件缺一不可,这也是区分多态、函数重载等的精辟总结(这是前辈总结,万分感激)。

多态的作用

提高代码可扩展性

形成多态三要素

有继承、子类重写父类虚函数、父类指针指向子类对象

转载于:https://my.oschina.net/dingjingMaster/blog/823911

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值