06-C++ 类和对象-多态

类与对象

多态

1. 简介

一个事物的多种形态,简称多态。

  • 物的多态

    同一个人在不同人面前,角色不同

    如:

    1. 在父母面前
    2. 在对象面前
    3. 在朋友面前
    4. 在同事面前
  • 事的多态

    同一种事情,在不同情况下展现不同

    如:

    1. 吃饭

      • 中国人 筷子 熟食
      • 美国人 刀叉 7分熟
      • 印度人 手 咖喱饭
    2. 睡觉

      • 中国人 床上
      • 日本人 地上

      平躺

      侧卧

      趴着

2. 上行与下行

2.1 上行

子类 转 父类

语法:

父类名 *父类对象指针 = 子类对象指针;
或
父类名& 父类对象名 = 子类对象;

注意:

无风险,无需强转。

2.2 下行

父类 转 子类

语法:

子类名 *子类对象指针 = (子类名 *)父类对象指针;
或
子类名& 子类对象 = (子类名&) 父类对象;

注意:

有风险,需强转。

2.3 示例
#include 
using namespace std;

class Anim{
public:
    int a;
    Anim(){}
    Anim(int a):a(a){}
};

class Dog:public Anim{
public:
    int d;
    Dog(){}
    Dog(int a, int d):Anim(a),d(d){}
};
class Cat:public Anim{
public:
    int c;
};

int main(int argc, char *argv[])
{
    //上行
    //将子类对象的地址赋值给父类对象的引用或指针
    Dog d1(10, 100);
    Anim& a1 = d1;
    //赋值给指针,记得取地址,不然报错
    Anim* a2 = &d1;
    cout << "d1.d=" << d1.d << endl;    //d1.d=100
    cout << "a1.a=" << a1.a << endl;    //a1.a=10
    //子类转父类后, 父类对象不能使用子类所特有的属性
//    cout << "a1.d=" << a1.d << endl;      //报错

    //下行
    Dog d2 = (Dog &)a1;
    Dog *d3 = (Dog *)a2;
    cout << "d2.d=" << d2.d << endl;    //d2.d=100
    //父类转换子类后,子类可以使用父类的
    cout << "d2.a=" << d2.a << endl;    //d2.a=10
    //下行父转子
    Cat& c1 = (Cat &)a1;
    cout << "c1.a=" << c1.a << endl;    //c1.a=10
    //d1的内存中没有c,所以c1.c 的值为 d1中d的值
    cout << "c1.c=" << c1.c << endl;    //c1.c=100

    return 0;
}

06-C++ 类和对象-多态_第1张图片

分析:

  • Dog继承了 Anim,所以d1对象中 有a 也有 d;
  • 父转子,c1也指向 d1(可能会报错,编译器优化可能也不报错)

3. 重写

继承关系中,返回值类型相同,函数名形同,形参列表相同,函数体不同 。

重载

同一作用域下,函数名相同,形参列表不同

重定义

继承关系中,函数名相同即可。一旦重定义后,子类会将父类的函数覆盖掉

4. c++多态分类

多态分为:

  • 物的多态(上行、下行)

  • 事的多态(静态多态,动态多态)

4.1 静态多态(早绑定,静态联编)

概念: 在编译阶段 就确定函数的入口地址
又名: 静态联编,早绑定
如: 函数重载,运算符重载,重定义等

函数在代码区,函数名就是这个函数在代码区的地址,这个地址就是这个函数的地址

4.2 动态多态(晚绑定,动态联编)

概念: 在运行阶段确定程序入口地址
又名: 动态联编,晚绑定
如: 虚函数,重写

5. 引入

要求:设计一个函数,根据传入的对象调用重写的方式。

5.1 示例

需求:

小明开了一个宠物医院,可以给狗看病,可以给猫看病
    可以给猪看病

    张女士带着他家的狗旺财去找小明给狗看病
    李女士带着他家的猫布丁去找小明给猫看病
    王先生带着他家的猪佩奇去找小明给猪看病
    
分析:
    对象
        小明
        张女士
        李女士
        王先生
        旺财
        布丁
        佩奇
    类
        动物类
            属性:
               姓名
               
            狗类
            猫类
            猪类
            人类
                属性:
                    动物
                get与set
                宠物医生类
                    看病

代码:

#include 
#include 
using namespace std;

class Anim{
private:
    char name[50];
public:
    Anim(){}
    Anim(char *name)
    {
        strcpy(this->name,name);
    }
    char* getName()
    {
        return name;
    }
    void setName(char *name)
    {
        strcpy(this->name,name);
    }
    void call()
    {
        cout << "动物叫" << endl;
    }
};
class Dog:public Anim
{
public:
    Dog(){}
    Dog(char *name):Anim(name){}
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":汪汪汪" << endl;
    }
};
class Cat:public Anim
{
public:
    Cat(){}
    Cat(char *name):Anim(name){}
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":喵喵喵" << endl;
    }
};
class Pig:public Anim
{
public:
    Pig(){}
    Pig(char *name):Anim(name){}
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":哼哼哼" << endl;
    }
};
class Person:public Anim
{
private:
    Anim* anim;
public:
    Person(){
        anim = NULL;
    }
    Person(char *name):Anim(name){}
    Person(char *name,Anim *anim):Anim(name),anim(anim){}
    ~Person()
    {
        if(anim != NULL)
        {
            delete anim;
            anim = NULL;
        }
    }
    void setAnim(Anim* anim){
        this->anim = anim;
    }
    Anim* getAnim()
    {
        return anim;
    }
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":哇哇哇" << endl;
    }
};
class AnimDoctor:public Person{
public:
    AnimDoctor(){}
    AnimDoctor(char *name):Person(name)
    {

    }
    AnimDoctor(char *name,Anim* anim):Person(name,anim)
    {

    }
    void cb(Anim* anim)
    {
        cout << this->getName() << "给" << anim->getName() << "看病" << endl;
        
        anim->call();
    }
};
int main(int argc, char *argv[])
{
    Dog * dog = new Dog("旺财");
    Cat * cat = new Cat("布丁");
    Pig * pig = new Pig("佩奇");

    Person *p1 = new Person("张女士",dog);
    Person *p2 = new Person("李女士",cat);
    Person *p3 = new Person("王先生",pig);

    AnimDoctor * doctor = new AnimDoctor("小明");

    doctor->cb(p1->getAnim());
    return 0;
}

//小明给旺财看病
//动物叫
//小明给布丁看病
//动物叫
//小明给佩奇看病
//动物叫
5.2 问题

父类有个函数 void call(){},要求是子类继承并重写 父类 该方法,每个子类 动物在看病时,都会叫 “狗:汪汪汪,猫:喵喵喵…” ;

但是,此时现状是,每次打印出来的都是 父类 void call(){} 函数中 的 “动物叫”(子传父 上行之后调用的依旧是父类的call函数),而我们需要的是 子传父之后,调用的是每个子类特有的方法。

所以需要引入 虚函数

6. 虚函数

概念:virtual 修饰的成员函数,就是虚函数

语法:

virtual 返回值类型 函数名(形参列表)
{
	函数体
}

注意:

  • 子类在继承父类时,会生成 虚函数指针

对比如下:以上边动物看病为例

  • 不是虚函数

06-C++ 类和对象-多态_第2张图片

06-C++ 类和对象-多态_第3张图片

  • 虚函数:下面第2幅图可以看出,子类继承了父类的 name 和 虚函数指针 vfptr ,此时指针指向的是 子类自己的 函数 Dog::call

06-C++ 类和对象-多态_第4张图片

06-C++ 类和对象-多态_第5张图片

特点:

子类转换为父类 后:

  • 使用该父类调用 使用virtual修饰的函数,调用的是子类重写后的函数

  • 使用该父类调用普通函数,调用的是父类的该函数

6.1 上边示例修改
#include 
using namespace std;
#include 
class Anim{
private:
    char name[50];
public:
    Anim(){}
    Anim(char *name)
    {
        strcpy(this->name,name);
    }
    char* getName()
    {
        return name;
    }
    void setName(char *name)
    {
        strcpy(this->name,name);
    }
    virtual void call()
    {
        cout << "动物叫" << endl;
    }
};
class Dog:public Anim
{
public:
    Dog(){}
    Dog(char *name):Anim(name){}
    virtual void call()
    {
        cout << this->getName() << ":汪汪汪" << endl;
    }
};
class Cat:public Anim
{
public:
    Cat(){}
    Cat(char *name):Anim(name){}
    void call()
    {
        cout << this->getName() << ":喵喵喵" << endl;
    }
};
class Pig:public Anim
{
public:
    Pig(){}
    Pig(char *name):Anim(name){}
    void call()
    {
        cout << this->getName() << ":哼哼哼" << endl;
    }
};
class Person:public Anim
{
private:
    Anim* anim;
public:
    Person(){
        anim = NULL;
    }
    Person(char *name):Anim(name){}
    Person(char *name,Anim *anim):Anim(name),anim(anim){}
    ~Person()
    {
        if(anim != NULL)
        {
            delete anim;
            anim = NULL;
        }
    }
    void setAnim(Anim* anim){
        this->anim = anim;
    }
    Anim* getAnim()
    {
        return anim;
    }
    void call()
    {
        cout << this->getName() << ":哇哇哇" << endl;
    }
};
class AnimDoctor:public Person{
public:
    AnimDoctor(){}
    AnimDoctor(char *name):Person(name)
    {

    }
    AnimDoctor(char *name,Anim* anim):Person(name,anim)
    {

    }
    void cb(Anim* anim)
    {
        cout << this->getName() << "给" << anim->getName() << "看病" << endl;
        anim->call();
    }
};
int main(int argc, char *argv[])
{
    Dog * dog = new Dog("旺财");
    Cat * cat = new Cat("布丁");
    Pig * pig = new Pig("佩奇");

    Person *p1 = new Person("张女士",dog);
    Person *p2 = new Person("李女士",cat);
    Person *p3 = new Person("王先生",pig);

    AnimDoctor * doctor = new AnimDoctor("小明");

    doctor->cb(p1->getAnim());
    doctor->cb(p2->getAnim());
    doctor->cb(p3->getAnim());
    /*
        当子类转换为父类后
        使用该父类调用使用virtual修饰的函数,调用的是子类重写后的函数
        使用该父类调用普通函数,调用的是父类的该函数
    */
    // 虚函数所在的类,依据可以直接创建对象
    Anim a;
    return 0;
}
//小明给旺财看病
//旺财:汪汪汪
//小明给布丁看病
//布丁:喵喵喵
//小明给佩奇看病
//佩奇:哼哼哼
6.2 动态绑定的条件(重要)
有继承,子类重写父类的虚函数,父类指针或引用指向子类空间(上行)。父类指针或引用才能调用子类重写的虚函数。

错误演示:
    B b;
    //此时会调用父类的拷贝构造,会产生一个新的父类对象,该 父类对象a 与 子类对象b 是两个独立空间
    //所以此时使用a对象调用test01依据会执行父类的test01函数
    A a = b; //拷贝构造
    a.test01();
6.3 动态绑定原理(机制)(重要)
  • 父类有虚函数,产生的 虚函数指针 指向 虚函数表,表中记录的是 父类的虚函数地址
  • 如果子类继承 父类,那么子类会继承父类的虚函数指针以及虚函数表。
  • 如果子类 重写父类的虚函数,会将将虚函数表纪录的入口地址修改成子类重写的函数入口地址。
  • 这时 父类指针指向子类空间,父类指针调用虚函数就 间接 调用子类重写的虚函数。

7. 纯虚函数

概念:父类的虚函数没有函数体

语法:

virtual 返回值类型 函数名(形参列表) = 0;

注意:

  • 纯虚函数所在的类不能 直接 创建对象,这种类被称为抽象类
  • 子类继承与抽象类,要么重写父类提供的所有纯虚函数,要么自己也是抽象类

示例:

#include 
#include 
using namespace std;
//纯虚函数所在的类称为抽象类
/*特点:
 *  1,抽象类不能直接创建对象
 *  2,子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类
 */
class Anim{
private:
    char name[50];
public:
    Anim(){}
    Anim(char *name)
    {
        strcpy(this->name,name);
    }
    char* getName()
    {
        return name;
    }
    void setName(char *name)
    {
        strcpy(this->name,name);
    }
    //纯虚函数
    virtual void call() = 0;
    virtual void sleep() = 0;
};
class Dog:public Anim{
public:
    Dog(){}
    Dog(char *name):Anim(name){}
    void call(){
        cout << "汪汪汪" << endl;
    }
};
class Cat:public Anim{
public:
    Cat(){}
    Cat(char *name):Anim(name){}
    void call(){
        cout << "喵喵喵" << endl;
    }
    void sleep(){
        cout << "在猫窝睡" << endl;
    }
};
int main(int argc, char *argv[])
{
    //有纯虚函数所在的类为抽象类,抽象类不能直接创建对象
    //Anim anim
    //子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类
    //Dog类只重写了call纯虚函数,但是没有重写sleep纯虚函数
    //所以Dog类也是抽象类,所以不能直接创建对象
    //Dog dog;
    //Cat类重写了Anim类所有的纯虚函数,所以Cat类不是抽象类
    Cat cat;

    Anim& anim = cat;
    cout << "Hello World!" << endl;
    return 0;
}

8. 虚析构造

8.1 问题引入
#include 

using namespace std;
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    
    ~Anim(){
         cout << "父类析构函数" << endl;
    }
};

class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}
//父类构造函数
//子类构造函数
//父类析构函数

问题:没有调用 子类析构函数

8.2 解决方案

将父类的析构函数 设置成 虚析构

虚析构函数是为了解决:基类的指针指向派生类对象,并用基类的指针删除派生类对象。

语法:

virtual ~析构函数()
{

}

示例:

#include 

using namespace std;
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    //虚析构
    virtual ~Anim(){
         cout << "父类析构函数" << endl;
    }
};

class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}
//父类构造函数
//子类构造函数
//子类析构函数
//父类析构函数

9. 纯虚析构(了解)

效果等同于 虚析构

语法:

virtual 析构函数名() = 0;

注意:需要在类外实现析构函数

示例:

#include 

using namespace std;
//class Anim{
//public:
//    Anim()
//    {
//        cout << "父类构造函数" << endl;
//    }
//    //虚析构
//    virtual ~Anim(){
//         cout << "父类析构函数" << endl;
//    }
//};
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    //纯虚析构
    //类中定义
    virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{
    cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}
virtual 析构函数名() = 0;

注意:需要在类外实现析构函数

示例:

#include 

using namespace std;
//class Anim{
//public:
//    Anim()
//    {
//        cout << "父类构造函数" << endl;
//    }
//    //虚析构
//    virtual ~Anim(){
//         cout << "父类析构函数" << endl;
//    }
//};
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    //纯虚析构
    //类中定义
    virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{
    cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}

你可能感兴趣的:(C/C++,c++,开发语言)