今天来学习一个新的概念,多态!!!多态在C++编程中具有重要的地位与意义,是面向对象的一个重要思想!
加qq1126137994一起学习更多技术~

1、问题引入

父类与子类之间具有赋值兼容性;

*子类对象可以当做父类对象使用(赋值兼容性)

  1. 子类对象可以直接赋值给父类对象
  2. 子类对象可以直接初始化父类对象
  3. 父类指针可以直接指向子类对象
  4. 父类引用可以直接引用子类对象

看一个例子程序来理解一下:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;

    void add(int i)
    {
        mi += i;
    }

    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mv;

    void add(int x, int y, int z)
    {
        mv += (x + y + z);
    }
};

int main()
{
    Parent p;
    Child c;

    p = c;

    Parent p1(c);


    Parent& rp = c;
    Parent* pp = &c;

    rp.mi = 100;
    rp.add(5);             // 没有发生同名覆盖?
    rp.add(10, 10);        // 没有发生同名覆盖?

    /* 为什么编译不过? */
    // pp->mv = 1000; // pp是父类指针,指向子类c,那么c对象就退化为父类对象
    // pp->add(1, 10, 100); // pp只能访问父类对象,并且可以访问父类与子类同名的对象
                            // 而不用指定作用域。
    return 0;
}

-当使用父类指针(引用)指向子类对象时

  1. 子类对象退化为父类对象
  2. 只能访问父类中定义的成员
  3. 可以直接访问被子类覆盖的同名成员而不用指定作用域

2、函数重写

  • 子类中可以重定义父类中已经存在的成员函数
  • 这种重定义发生在继承中,叫做函数重写
  • 函数重写是函数同名覆盖的特殊情况

那么当函数重写遇上赋值兼容会发生什么?
先来看一个简单的例子:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;

    void add(int i)
    {
        mi += i;
    }

    void add(int a, int b)
    {
        mi += (a + b);
    }

    void print()
    {
        cout << "I'm Parent." << endl;
    }
};

class Child : public Parent
{
public:
    int mv;

    void add(int x, int y, int z)
    {
        mv += (x + y + z);
    }

    void print()
    {
        cout << "I'm Child." << endl;
    }
};

void how_to_print(Parent* p)
{
    p->print();
}

int main()
{
    Parent p;
    Child c;

    how_to_print(&p);    // Expected to print: I'm Parent.
    how_to_print(&c);    // Expected to print: I'm Child.

    return 0;
}

运行结果为:
I’m Parent.
I’m Parent.

很显然,这个结果并不是我们想要的,我们想要的是执行 how_to_print(&c); 后打印I’m Child.
这就是函数print重写后,遇到赋值兼容的情况。

问题分析:

  • 编译期,间编译器只能根据指针的类型判断所指向的对象
  • 根据赋值兼容,编译器认为父类指针指向的是父类对象
  • 因此,编译结果只可能是调用父类中定义的同名的函数

小结:

  1. 子类对象可以当做父类对象使用(赋值兼容)
  2. 父类指针可以正确的指向子类对象
  3. 父类引用可以正确的代表子类对象
  4. 子类中可以重写父类的成员函数

3、解决办法-多态的概念

  • 父类中被重写的函数依然会继承给子类
  • 子类中被重写的函数会覆盖父类中的同名函数

而面向对象期望的行为是:

  • 根据实际的对象类型调用具体的成员重写函数
  • 父类指针(引用)指向
    *父类对象,则调用父类中定义的函数
    *子类对象,则调用子类中定义的重写函数

要实现这个行为,就需要引出面向对象中的多态的概念:
面向对象中多态的概念:
- 根据实际的对象类型,决定函数调用的具体目标
- 通用的调用语句,在实际的运行中有多种不同的表现形态

C++语言直接支持多态的概念

  1. 通过使用virtual关键字对多态进行支持
  2. 被virtual声明的函数被重写后具有多态性
  3. 被virtual声明的函数叫做虚函数

修改上一个程序:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    int mi;

    virtual void print()
    {
        cout << "I'm Parent." << endl;
    }

};

class Child : public Parent
{
public:
    int mv;

    void print()
    {
        cout << "I'm Child." << endl;
    }
};

void how_to_print(Parent* p)
{
    p->print();
}

int main()
{   
    Parent p;
    Child c;

    how_to_print(&p);
    how_to_print(&c);


    return 0;
}

运行结果:
I’m Parent.
I’m Child.

可以看出,这是我们想要的结果。

多态的意义:

  1. 在程序运行过程中展现出动态特性
  2. 函数重写必须多态实现,否则没有意义
  3. 多态是面向对象组件化程序设计的基础特性

4、多态在理论中的概念与意义

理论中的概念:

  1. 静态联编
    *在函数编译期间就能确定具体的函数调用
    -如.函数重载
  2. 动态联编
    *在程序实际运行后才能确定函数的具体调用
    -如.函数重写

给个例子说明:

#include <iostream>
#include <string>

using namespace std;

class Parent
{
public:
    virtual void func()
    {
        cout << "void func()" << endl;
    }

    virtual void func(int i)
    {
        cout << "void func(int i) : " << i << endl;
    }

    virtual void func(int i, int j)
    {
        cout << "void func(int i, int j) : " << "(" << i << ", " << j << ")" << endl;
    }
};

class Child : public Parent
{
public:
    void func(int i, int j)
    {
        cout << "void func(int i, int j) : " << i + j << endl;
    }

    void func(int i, int j, int k)
    {
        cout << "void func(int i, int j, int k) : " << i + j + k << endl;
    }
};

void run(Parent* p)
{
    p->func(1, 2);     // 展现多态的特性
                       // 动态联编
}


int main()
{
    Parent p;

    p.func();         // 静态联编
    p.func(1);        // 静态联编
    p.func(1, 2);     // 静态联编

    cout << endl;

    Child c;

    c.func(1, 2);     // 静态联编

    cout << endl;

    run(&p);
    run(&c);

    return 0;
}

上述程序的运行结果为:
void func()
void func(int i) : 1
void func(int i, int j) : (1, 2)

void func(int i, int j) : 3

void func(int i, int j) : (1, 2)
void func(int i, int j) : 3

5、拓展训练

下面是一个展现多态,继承的程序,参考教学视频第49课第二个视频!

#include <iostream>
#include <string>

using namespace std;

class Boss
{
public:
    int fight()
    {
        int ret = 10;

        cout << "Boss::fight() : " << ret << endl;

        return ret;
    }
};

class Master
{
public:
    virtual int eightSwordKill()
    {
        int ret = 8;

        cout << "Master::eightSwordKill() : " << ret << endl;

        return ret;
    }
};

class NewMaster : public Master
{
public:
    int eightSwordKill()
    {
        int ret = Master::eightSwordKill() * 2;

        cout << "NewMaster::eightSwordKill() : " << ret << endl;

        return ret;
    }
};

void field_pk(Master* master, Boss* boss)
{
    int k = master->eightSwordKill();
    int b = boss->fight();

    if( k < b )
    {
        cout << "Master is killed..." << endl;
    }
    else
    {
        cout << "Boss is killed..." << endl;
    }
}

int main()
{
    Master master;
    Boss boss;

    cout << "Master vs Boss" << endl;

    field_pk(&master, &boss);

    cout << "NewMaster vs Boss" << endl;

    NewMaster newMaster;

    field_pk(&newMaster, &boss);

    return 0;
}

运行结果为:
Master vs Boss
Master::eightSwordKill() : 8
Boss::fight() : 10
Master is killed…
NewMaster vs Boss
Master::eightSwordKill() : 8
NewMaster::eightSwordKill() : 16
Boss::fight() : 10
Boss is killed…

6、总结

  1. 函数重写只可能发生在父类与子类之间
  2. 根据实际对象的类型调用具体的函数,多态性
  3. virtual关键字是C++中支持多态的唯一方法
  4. 被重写的虚函数可以表现出多态性

想获得各种学习资源以及交流学习的加我:
qq:1126137994
微信:liu1126137994
可以共同交流关于嵌入式,操作系统,C++语言,C语言,数据结构等技术问题。