三、面向对象之多态

一、静态联编和动态联编

  多态性提供接口与具体实现之间的另一层隔离, 从而将 "what" 和 "how" 分离开来。多态性改善了代码的可读性和组织性,同时也使得创建的程序具有可扩展性,项目不紧在最初创建时期可以扩展,而且当项目在需要新的功能时候也能扩展。
  C++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
  静态多态和动态动态的区别在于函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数调用,在编译阶段就可以确定函数调用的地址,并产生代码,就是说地址是早绑定(静态多态,编译时多条)的。而乳沟函数的调用地址不能编译不在在编译间确定,而需要在函数执行时才能确定,这就是属于晚绑定(动态多态,运行时多态)

多态分类:
  静态多态 函数重载和运算符重载
  动态多态 虚函数的继承关系
静态联编:
  地址早绑定 编译阶段绑定好地址
动态联编:
  地址晚绑定 运行阶段绑定好地址
多态:
  父类的引用或者指针指向子类对象

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
    void speak()
    {
        cout << "动物在说话" << endl;
    }
    virtual void eat()
    {
        cout << "动物在吃" << endl;
    }
};

class Cat : public Animal
{
public:
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
    void eat()
    {
        cout << "小猫在吃" << endl;
    }
};


// 调用 doSpeak,在编译时 speak的地址早就绑定好了,被称为早绑定,静态联编,编译阶段就确定了地址
// 如果想要调用猫的speak, 就不能提前绑定好函数的地址,所以需要在运行时绑定, 晚绑定,动态联编
// 需要动态联编, 写法就是将 dospeak方法改为 虚函数, 在父类上声明虚函数,发生了多态,用eat函数实现
// 多态: 父类的引用或者指针来指向子类对象
void doSpeak(Animal & animal)
{
    animal.speak();
}

void doEat(Animal & animal)
{
    // 多态: 父类的引用或者指针来指向子类对象
    animal.eat();
}



void test()
{
    Cat cat;
    // cat是Cat类, 传给了 Animal类。如果发生了继承的关系,编译器会允许类型的转换
    doSpeak(cat); // 动物在说话
    doEat(cat);   // 小猫在吃
    
    // 父类指针指向子类对象,发生多态
    Animal *animal = new Cat;
    animal->eat();  // 小猫在吃
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

二、多态的原理

  父类声明一个 Animal 类,类中含有虚函数 speak。 在编译的过程中,Animal内部会产生一个 vfptr(vittual function pointer)虚函数表指针。这个指针指向产生的虚函数表(vftable)。
  在子类继承父类的时候,同样会拷贝一份一样的虚函数指针指向虚函数表,编译器会自动将指针指向子类的虚函数表。当重写 speak方法的时候,会用 &Cat::speak 替换自己映射父类的 &Animal::speak;这样子类就有自己的speak,父类也有父类的speak,这个过程是子类创建对象的时候,调用构造函数的时候自动触发。
  经过上述步骤,就实现了多态

 

 

三、多态设计案例之----计算器

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

class Calculator
{
public:
    void setVal1(int val)
    {
        this->val1 = val;
    }
    void setVal2(int val)
    {
        this->val2 = val;
    }
    int getResult(string oper)
    {
        if (oper == "+")
        {
            return val1 + val2;
        }
        else if (oper == "-")
        {
            return val1 - val2;
        }
    }
    int val1;
    int val2;
};

void test()
{
    Calculator cal;
    cal.setVal1(10);
    cal.setVal2(10);
    cout << cal.getResult("+") << endl;
    cout << cal.getResult("-") << endl;
}


// 利用多态实现计算器
class abstractCalculator
{
public:
   
    virtual int getResult()
    {
        return 0;
    }
   
    void setVal1(int val)
    {
        this->val1 = val;
    }
    void setVal2(int val)
    {
        this->val2 = val;
    }
    int val1;
    int val2;
};

// 加法计算器
class PlusCalculator : public abstractCalculator
{
public:
    virtual int getResult() // virtual可写可不写
    {
        return val1 + val2;
    }
};

// 减法计算器
class SubCalculator : public abstractCalculator
{
public:
    int getResult() // virtual可写可不写
    {
        return val1 - val2;
    }
};

// 乘法计算器
class MulCalculator : public abstractCalculator
{
public:
    int getResult()
    {
        return val1 * val2;
    }
};

void test01()
{
    abstractCalculator *abc;
    abc = new PlusCalculator;
    abc->setVal1(10);
    abc->setVal2(20);
    cout << abc->getResult() << endl;
    delete abc;
    abc = new SubCalculator;
    abc->setVal1(10);
    abc->setVal2(20);
    cout << abc->getResult() << endl;
    delete abc;
    abc = new MulCalculator;
    abc->setVal1(10);
    abc->setVal2(20);
    cout << abc->getResult() << endl;
}
int main()
{
    test();
    test01();
    return EXIT_SUCCESS;
}

 

四、抽象类和纯虚函数

/*
    如果父类中有纯虚函数,子类继承父类,就必须要实现纯虚函数, 
    纯虚函数的父类不能实例化, 
    一个类如果有纯虚函数,被称为抽象类。
    类似python中的抽象类
    
    纯虚函数: virtual int getResult() = 0;
*/

class abstractCalculator
{
public:
   
    virtual int getResult()
    {
        return 0;
    }
};

/*
    上面是上个案例中抽象类的一部分
    result 0在这个没有什么意义,可以写为下面的纯虚函数
*/

// 纯虚函数
// 如果父类中有纯虚函数,子类继承父类,就必须要实现纯虚函数
class abstractCalculator
{
public:
    virtual int getResult() = 0;
};

// abstractCalculator *abc = new abstractCalculator; 纯虚函数的类不能实例化
// abstractCalculator aa;    纯虚函数的类不能实例化

 

五、虚析构函数和纯虚析构函数

5.1 虚析构函数

/*
    virtual ~Animal()
    解决问题: 通过父类指针指向子类对象释放时候不返京导致的问题
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
    virtual void speak()
    {
        cout << "Animal在说话" << endl;
    }
    // 普通的析构函数是不会调用子类的析构函数,所以可能会导致释放不干净
    virtual ~Animal()
    {
        cout << "Animal的析构函数" << endl;
    }
};

class Cat : public Animal
{
public:
    Cat(const char *name)
    {
        this->Name = new char[strlen(name) + 1];
        strcpy(this->Name, name);
    }
    ~Cat()
    {
        cout << "Cat 的析构函数" << endl;
        if (this->Name != NULL)
        {
            delete[] this->Name;
            this->Name = NULL;
        }
        
    }
    virtual void speak()
    {
        cout << "小猫在说话" << endl;
    }
    char *Name;
};

void test()
{
    Animal *animal = new Cat("Tom");
    animal->speak();
    delete animal; //  普通的析构函数是不会调用子类的析构函数,所以可能会导致释放不干净,所以需要用虚析构函数
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

5.2 纯虚析构函数

/*
    纯虚析构函数需要声明还需要实现,类内声明,类外实现
    如果类出现了 纯虚析构函数,那么这个类也算抽象类,不能实例化
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
using namespace std;

class Animal
{
public:
    virtual void speak()
    {
        cout << "Animal在说话" << endl;
    }
    // 纯虚析构函数
    // 纯虚析构函数需要声明还需要实现,类内声明,类外实现
    // 如果类出现了 纯虚析构函数,那么这个类也算抽象类,不能实例化
    virtual ~Animal() = 0;
    
};

Animal::~Animal()
{
    // 纯虚析构函数实现
    cout << "纯虚析构函数实现" << endl;
}

class Cat : public Animal
{
public:
    Cat(const char *name)
    {
        this->Name = new char[strlen(name) + 1];
        strcpy(this->Name, name);
    }
    ~Cat()
    {
        cout << "Cat 的析构函数" << endl;
        if (this->Name != NULL)
        {
            delete[] this->Name;
            this->Name = NULL;
        }
        
    }
    virtual void speak()
    {
        cout << "小猫在说话" << endl;
    }
    char *Name;
};

void test()
{
    Animal *animal = new Cat("Tom");
    animal->speak();
    delete animal; //  调用的时候,会销毁Animal的析构函数 ~Animal(),但是现在是纯虚析构函数,找不到了,所以会报错,所以春旭西沟函数需要声明和实现
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

 

六、多态设计案例之----PK游戏对战

 

6.1 头文件

6.1.1 DragonSword.h

// 屠龙刀类
#pragma once
#include "Weapon.h"
#include<string>
using namespace std;

class DragonSword : public Weapon
{
public:
    DragonSword();
    // 获取基础伤害
    virtual int getBaseDamage();
    // 获取吸血
    virtual int getSuckBlood();
    // 获取是否定身
    virtual bool getHold();
    // 获取是否暴击了
    virtual bool getCrit();

    // 吸血率,定身率, 暴击率
    int suckRate;
    int holdRate;
    int critRate;

    // 传入概率,判断是否触发
    bool isTrigger(int rate);

};

 

6.1.2 Hero.h

// 英雄类
#pragma once
#include<iostream>
#include<string>
#include "Weapon.h"
#include "Monster.h"
using namespace std;

class Monster;

class Hero
{
public:
    Hero();
    string Name; // 人名
    int Atk;     // 攻击力
    int Def;     // 防御力
    int Hp;      // 血量
    Weapon * weapon;  // 武器
    void EquipWeapon(Weapon *weapon);  // 装备武器
    void Attack(Monster *monster);  // 攻击功能
};

6.1.3 Knife.h

// 小刀类
#pragma once
#include "Weapon.h"
#include<string>
using namespace std;

class Knife : public Weapon
{
public:
    Knife();
    // 获取基础伤害
    virtual int getBaseDamage();
    // 获取吸血
    virtual int getSuckBlood();
    // 获取是否定身
    virtual bool getHold();
    // 获取是否暴击了
    virtual bool getCrit();
};

 

6.1.4 Monster.h

#pragma once
#include<iostream>
#include<string>
#include "Weapon.h"
#include "Hero.h"
using namespace std;
class Hero;
class Monster
{
public:
    Monster();

    string Name;
    int Hp;
    int Atk;
    int Def;
    bool isHold;
    void Attack(Hero * hero);
};

 

6.1.5 Weapon.h

// 武器类
#pragma once
#include<iostream>
#include<string>
using namespace std;

class Weapon
{
public:
    // 获取基础伤害
    virtual int getBaseDamage() = 0;
    // 获取吸血
    virtual int getSuckBlood() = 0;
    // 获取是否定身
    virtual bool getHold() = 0;
    // 获取是否暴击了
    virtual bool getCrit() = 0;

    string WeaponName; // 武器名称
    int BaseDamage; // 基础伤害
};

 

6.2 源文件

6.2.1 DragonSword.cpp

#include "DragonSword.h"

DragonSword::DragonSword()
{
    this->BaseDamage = 20;
    this->WeaponName = "屠龙宝刀";
    this->suckRate = 20;
    this->holdRate = 30;
    this->critRate = 35;
}

int DragonSword::getBaseDamage()
{
    return this->BaseDamage;
}

int DragonSword::getSuckBlood()
{
    if (isTrigger(this->suckRate))
    {
        // 按照武器的基础伤害一半吸血
        return this->BaseDamage * 0.5;
    }
    return 0;
}

bool DragonSword::getHold()
{
    if (isTrigger(this->holdRate))
    {
        return true;
    }
    return false;
}

bool DragonSword::getCrit()
{
    if (isTrigger(this->critRate))
    {
        return true;
    }
    return false;
}

bool DragonSword::isTrigger(int rate)
{
    // 通过 isTrigger来判断是否触发特效
    // 随机一个1-100之间的数字
    int num = rand() % 100 + 1;   // 1-100
    if (num < rate)
    {
        return true;
    }
    return false;
}

 

6.2.2 Hero.cpp

#include "Hero.h"

Hero::Hero()
{
    this->Hp = 500;
    this->Atk = 50;
    this->Def = 50;
    this->Name = "法师";
    this->weapon = NULL;
}


// 装备武器
void Hero::EquipWeapon(Weapon * weapon)
{
    this->weapon = weapon;
    cout << "英雄: " << this->Name << " 装备了武器: " << this->weapon->WeaponName << endl;
}

// 攻击
void Hero::Attack(Monster *monster)
{
    int damage = 0;
    int addHp = 0;
    bool isHold = false;
    bool isCrit = false;
    if (this->weapon == NULL) // 武器为空,没有加成
    {
        damage = this->Atk;
    }
    else
    {
        // 基础伤害
        damage = this->Atk + this->weapon->getBaseDamage();
        // 计算吸血
        addHp = this->weapon->getSuckBlood();
        // 计算定身
        isHold = this->weapon->getHold();
        // 计算暴击
        isCrit = this->weapon->getCrit();
    }
    if (isCrit)  // 暴击伤害加成
    {
        damage = damage * 2;
        cout << "英雄的武器触发了暴击效果, 怪物收到了双倍的伤害, 伤害值为: " << damage << endl;
    }
    if (isHold)
    {
        cout << "英雄的武器触发了定身效果, 怪物停止攻击一回合" << endl;
    }
    if (addHp > 0)
    {
        cout << "英雄的武器触发了吸血效果, 英雄增加了血量: " << addHp << endl;
    }

    // 设置怪物是够被定身
    monster->isHold = isHold;
    // 计算真实伤害
    int trueDamage = (damage - monster->Def > 0) ? damage - monster->Def : 1;
    monster->Hp -= trueDamage;

    // 自己加血
    this->Hp += addHp;
    cout << "英雄: " << this->Name << " 攻击了敌人: " << monster->Name << " 造成了伤害: " << trueDamage << endl;
}

 

6.2.3 Knife.cpp

#include "Knife.h"

Knife::Knife()
{
    this->BaseDamage = 10;
    this->WeaponName = "小刀";
}

int Knife::getBaseDamage()
{
    return this->BaseDamage;
}

int Knife::getSuckBlood()
{
    return 0;
}

bool Knife::getHold()
{
    return false;
}

bool Knife::getCrit()
{
    return false;
}

 

6.2.4 Monster.cpp

#include "Monster.h"


Monster::Monster()
{
    this->Hp = 300;
    this->Atk = 70;
    this->Def = 40;
    this->isHold = false;
    this->Name = "射手";
}

void Monster::Attack(Hero *hero)
{
    if (this->isHold)
    {
        cout << "怪物 " << this->Name << " 被定身了,此回合无法攻击" << endl;
        return;
    }
    // 计算攻击伤害
    int damage = (this->Atk - hero->Def > 0) ? this->Atk - hero->Def : 1;
    hero->Hp -= damage;
    cout << "怪物: " << this->Name << " 攻击了英雄: " << hero->Name << " 造成了伤害: " << damage << endl;
}

 

6.2.5 main.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include"Hero.h"
#include "DragonSword.h"
#include "Knife.h"
#include "Monster.h"
#include "Weapon.h"
#include<ctime>
using namespace std;



void play()
{
    // 创建怪物
    Monster *monster = new Monster;
    // 创建英雄
    Hero *hero = new Hero;
    // 创建武器
    Weapon *knife = new Knife;
    Weapon *dragon = new DragonSword;
    // 让用户选择武器
    cout << "请选择武器" << endl;
    cout << "1. 赤手空拳" << endl;
    cout << "2. 小刀" << endl;
    cout << "3. 屠龙刀" << endl;
    int oper;
    cin >> oper;
    switch (oper)
    {
    case 1:
        cout << "赤手空拳, 你还是太年轻了" << endl;
        break;
    case 2:
        hero->EquipWeapon(knife);
        break;
    case 3:
        hero->EquipWeapon(dragon);
        break;
    default:
        break;
    }
    getchar();  // 输入缓冲区里面有回车,多获取一次
    int round = 1; // 当前第1回合
    while (true)
    {
        getchar();     // 用户每输入一次出现一次,以免速度太快
        system("cls"); // 清屏
        cout << "-----当前第" << round << "回合"<< endl;
        if (hero->Hp <= 0)
        {
            cout << "英雄 " << hero->Name << "已挂 " << "游戏结束" << endl;
            break;
        }
        hero->Attack(monster);
        if (monster->Hp <= 0)
        {
            cout << "怪物 " << monster->Name << "已挂 " << "顺利通过" << endl;
            break;
        }
        monster->Attack(hero);
        if (hero->Hp <= 0)
        {
            cout << "英雄 " << hero->Name << "已挂 " << "游戏结束" << endl;
            break;
        } 
        cout << "英雄 " << hero->Name << "剩余血量 " << hero->Hp << endl;
        cout << "怪物 " << monster->Name << "剩余血量 " << monster->Hp << endl;
        round++;
    }
    delete monster;
    delete hero;
    delete knife;
    delete dragon;
}

int main()
{
    srand((unsigned int)time(NULL));
    play();
    return EXIT_SUCCESS;
}

 

posted on 2022-03-15 20:08  软饭攻城狮  阅读(18)  评论(0编辑  收藏  举报

导航