三、面向对象之多态
多态性提供接口与具体实现之间的另一层隔离, 从而将 "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; 纯虚函数的类不能实例化
五、虚析构函数和纯虚析构函数
/* 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; }
/* 纯虚析构函数需要声明还需要实现,类内声明,类外实现 如果类出现了 纯虚析构函数,那么这个类也算抽象类,不能实例化 */ #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; }
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); };
// 英雄类 #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); // 攻击功能 };
// 小刀类 #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(); };
#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); };
// 武器类 #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 源文件
#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; }
#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; }
#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; }
#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; }
#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; }