01 静态联编和动态联编
概念:
1.多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。
2.C++支持编译时多态(静态多态)和运行时多态(动态多态),运算符和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
3.静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。
4.如果函数的调用在编译阶段就能确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能在编译期间确定,而需要在需要在运行时才能决定,就属于晚绑定(动态多态,运行时多态)。
1.如果发生了继承的关系,编译器允许进行类型转换
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
void doSpeak(Animal& animal)
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
int main(void)
{
test01();
system("pause");
return EXIT_SUCCESS;
}
2.动态联编
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//调用doSpeak ,speak函数的地址早就绑定好了(早绑定,静态联编,编译阶段就确定好了地址)
//如果想调用猫的speak,不能提前绑定好函数的地址,所以需要在运行时候再去确定函数地址
//动态联编,写法 doSpeak 方法改为虚函数,在父类上声明虚函数,发生了多态
//多态:父类的引用或者指针 指向 子类对象
void doSpeak(Animal& animal) //Animal & animal = cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
int main(void)
{
test01();
system("pause");
return EXIT_SUCCESS;
}
注意:子函数的virtual可写可不写
02 多态原理解析
不写虚函数:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//调用doSpeak ,speak函数的地址早就绑定好了(早绑定,静态联编,编译阶段就确定好了地址)
//如果想调用猫的speak,不能提前绑定好函数的地址,所以需要在运行时候再去确定函数地址
//动态联编,写法 doSpeak 方法改为虚函数,在父类上声明虚函数,发生了多态
//多态:父类的引用或者指针 指向 子类对象
void doSpeak(Animal& animal) //Animal & animal = cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
void test02()
{
cout << sizeof(Animal) << endl;
}
int main(void)
{
test02();
system("pause");
return EXIT_SUCCESS;
}
写vitrual:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//调用doSpeak ,speak函数的地址早就绑定好了(早绑定,静态联编,编译阶段就确定好了地址)
//如果想调用猫的speak,不能提前绑定好函数的地址,所以需要在运行时候再去确定函数地址
//动态联编,写法 doSpeak 方法改为虚函数,在父类上声明虚函数,发生了多态
//多态:父类的引用或者指针 指向 子类对象
void doSpeak(Animal& animal) //Animal & animal = cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
void test02()
{
cout << sizeof(Animal) << endl;
}
int main(void)
{
test02();
system("pause");
return EXIT_SUCCESS;
}
继承但未重写前:
当speak()重写后:
重写前的对象模型:
重写后的对象模型:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
class Cat :public Animal
{
public:
};
//调用doSpeak ,speak函数的地址早就绑定好了(早绑定,静态联编,编译阶段就确定好了地址)
//如果想调用猫的speak,不能提前绑定好函数的地址,所以需要在运行时候再去确定函数地址
//动态联编,写法 doSpeak 方法改为虚函数,在父类上声明虚函数,发生了多态
//多态:父类的引用或者指针 指向 子类对象
void doSpeak(Animal& animal) //Animal & animal = cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
void test02()
{
//cout << sizeof(Animal) << endl;
//分类指针指向子类对象,多态
Animal* animal = new Cat;
animal->speak();
//* (int*)*(int*)animal 函数地址
//(void(*)()) 函数指针
((void(*)()) (*(int*)*(int*)animal))();
}
int main(void)
{
test02();
system("pause");
return EXIT_SUCCESS;
}
总结:
- 当父类中有了虚函数之后,内部结构就发生了改变
- 内部多了一个vfptr(virtual function pointer)虚函数指针,指向vftable
- 父类中 进行继承时, 会继承vfptr vftable
- 构造函数中 会将虚函数指针指向自己的虚函数表
- 如果发生了重写,会替换掉虚函数表中的原有的函数,改为自己的函数调用地址
增加eat()函数:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual 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 方法改为虚函数,在父类上声明虚函数,发生了多态
//多态:父类的引用或者指针 指向 子类对象
void doSpeak(Animal& animal) //Animal & animal = cat
{
animal.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat;
doSpeak(cat);
}
void test02()
{
//cout << sizeof(Animal) << endl;
//分类指针指向子类对象,多态
Animal* animal = new Cat;
animal->speak();
//* (int*)*(int*)animal 函数地址
//(void(*)()) 函数指针
((void(*)()) (*(int*)*(int*)animal))();
//*((int*)*(int*)animal+1) 小猫吃鱼的地址
((void(*)()) (*((int*)*(int*)animal + 1)))();
}
int main(void)
{
test02();
system("pause");
return EXIT_SUCCESS;
}
对象模型:
03 多态案例-计算器案例
多态成立的条件:
1.有继承
2.子类重写父类虚函数函数(返回值,函数名字,函数参数,必须和父类完全一致(析构函数除外))
3.类型兼容,父类指针、父类引用 指向 子类对象
开发原则:
开闭原则(对扩展开放,对修改关闭)
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//利用多态实现计算器
class abstrackCalculator
{
public:
abstrackCalculator() :val1(1), val2(1) {}
public:
//虚函数
virtual int getResult()
{
return 0;
}
void setVal1(int v)
{
this->val1 = v;
}
void setVal2(int v)
{
this->val2 = v;
}
public:
int val1;
int val2;
};
class addCalculator :public abstrackCalculator
{
public:
virtual int getResult()
{
return val1 + val2;
}
};
class subCalculator :public abstrackCalculator
{
public:
virtual int getResult()
{
return val1 - val2;
}
};
class mulCalculator :public abstrackCalculator
{
public:
virtual int getResult()
{
return val1 * val2;
}
};
//真正的开发中,有个开发原则(开闭原则)
//对扩展开放 对修改关闭
void test02()
{
abstrackCalculator* abs;
abs = new addCalculator;
abs->setVal1(105);
abs->setVal2(200);
cout << abs->getResult() << endl;
delete abs;
abs = new subCalculator;
abs->setVal1(105);
abs->setVal2(200);
cout << abs->getResult() << endl;
delete abs;
abs = new mulCalculator;
abs->setVal1(105);
abs->setVal2(200);
cout << abs->getResult() << endl;
delete abs;
}
int main(void)
{
test02();
system("pause");
return EXIT_SUCCESS;
}
04 抽象类和纯虚函数
纯虚函数:
1.如果父类中存在纯虚函数,子类继承父类,就必须要实现 纯虚函数
2.如果父类中有了纯虚函数,就不可以实例化对象了
3.这个类有了纯虚函数,通常又称为 抽象类
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//利用多态实现计算器
class abstrackCalculator
{
public:
abstrackCalculator() :val1(1), val2(1) {}
public:
//虚函数
/*virtual int getResult()
{
return 0;
}*/
//纯虚函数
//如果父类中存在纯虚函数,子类继承父类,就必须要实现 纯虚函数
//如果父类中有了纯虚函数,就不可以实例化对象了
//这个类有了纯虚函数,通常又称为 抽象类
virtual int getResult() = 0;
void setVal1(int v)
{
this->val1 = v;
}
void setVal2(int v)
{
this->val2 = v;
}
public:
int val1;
int val2;
};
class addCalculator :public abstrackCalculator
{
public:
virtual int getResult()
{
return val1 + val2;
}
};
class subCalculator :public abstrackCalculator
{
public:
virtual int getResult()
{
return val1 - val2;
}
};
class mulCalculator :public abstrackCalculator
{
public:
virtual int getResult()
{
return val1 * val2;
}
};
//真正的开发中,有个开发原则(开闭原则)
//对扩展开放 对修改关闭
void test02()
{
abstrackCalculator* abs;
abs = new addCalculator;
abs->setVal1(105);
abs->setVal2(200);
cout << abs->getResult() << endl;
delete abs;
abs = new subCalculator;
abs->setVal1(105);
abs->setVal2(200);
cout << abs->getResult() << endl;
delete abs;
abs = new mulCalculator;
abs->setVal1(105);
abs->setVal2(200);
cout << abs->getResult() << endl;
delete abs;
}
int main(void)
{
test02();
system("pause");
return EXIT_SUCCESS;
}
05 虚析构和纯虚析构
1.普通析构 是不会调用子类析构的,所以可能导致释放不干净,利用虚析构来解决这个问题
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak() = 0;
public:
//普通析构 是不会调用子类析构的,所以可能导致释放不干净
~Animal()
{
cout << "Animal析构函数调用" << endl;
}
};
class Cat :public Animal
{
public:
virtual void speak()
{
cout << "小猫" << this->m_Name << " 在说话" << endl;
}
public:
Cat(const char* name)
{
this->m_Name = new char[strlen(name) + 1];
strcpy(this->m_Name, name);
}
~Cat()
{
cout << "Cat析构函数调用" << endl;
if (this->m_Name != NULL)
{
delete[] this->m_Name;
this->m_Name = NULL;
}
}
public:
char* m_Name;
};
void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main(void)
{
test01();
system("pause");
return EXIT_SUCCESS;
}
普通析构 是不会调用子类析构的,所以可能导致释放不干净,利用虚析构来解决这个问题
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak() = 0;
public:
//普通析构 是不会调用子类析构的,所以可能导致释放不干净
//利用虚析构来解决这个问题
virtual ~Animal()
{
cout << "Animal析构函数调用" << endl;
}
};
class Cat :public Animal
{
public:
virtual void speak()
{
cout << "小猫" << this->m_Name << " 在说话" << endl;
}
public:
Cat(const char* name)
{
this->m_Name = new char[strlen(name) + 1];
strcpy(this->m_Name, name);
}
~Cat()
{
cout << "Cat析构函数调用" << endl;
if (this->m_Name != NULL)
{
delete[] this->m_Name;
this->m_Name = NULL;
}
}
public:
char* m_Name;
};
void test01()
{
Animal* animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main(void)
{
test01();
system("pause");
return EXIT_SUCCESS;
}
2.纯虚析构
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void speak() = 0;
public:
//普通析构 是不会调用子类析构的,所以可能导致释放不干净
//利用虚析构来解决这个问题
/*virtual ~Animal()
{
cout << "Animal析构函数调用" << endl;
}*/
//纯虚析构
//如果函数中出现了 纯虚析构函数,那么这个类也算抽象类
//抽象类不可以实例化
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal 的纯虚析构函数调用" << endl;
}
class Cat :public Animal
{
public:
virtual void speak()
{
cout << "小猫" << this->m_Name << " 在说话" << endl;
}
public:
Cat(const char* name)
{
this->m_Name = new char[strlen(name) + 1];
strcpy(this->m_Name, name);
}
~Cat()
{
cout << "Cat析构函数调用" << endl;
if (this->m_Name != NULL)
{
delete[] this->m_Name;
this->m_Name = NULL;
}
}
public:
char* m_Name;
};
void test01()
{
Animal* cat1 = new Cat("Tom");
cat1->speak();
delete cat1;
}
int main(void)
{
test01();
system("pause");
return EXIT_SUCCESS;
}
06 向上类型转换和向下类型转换
基类转派生类:向下类型转换 不安全的
派生类转基类:向上类型转换 安全的
如果发生了多态:总是安全的
原理如下:
07 多态案例二-PK小游戏
1.需求
2.游戏构建及运行
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 m_WeaponName; //武器名称
int m_BaseDamage; //基础伤害
};
Knife.h:
#pragma once
#include<iostream>
#include<string>
using namespace std;
#include "Weapon.h"
class Knife :public Weapon
{
public:
Knife();
//获取基础伤害
virtual int getBaseDamage();
// 获取吸血
virtual int getSuckBlood();
// 获取是否定身
virtual bool getHold();
// 获取是否暴击
virtual bool getCrit();
};
Knife.cpp:
#include"Knife.h"
Knife::Knife()
{
this->m_BaseDamage = 10;
this->m_WeaponName = "小刀";
}
int Knife::getBaseDamage()
{
return this->m_BaseDamage;
}
int Knife::getSuckBlood()
{
return 0;
}
bool Knife::getHold()
{
return false;
}
bool Knife::getCrit()
{
return false;
}
DragonSword.h:
#pragma once
#include<iostream>
#include<string>
using namespace std;
#include "Weapon.h"
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);
};
DragonSword.cpp:
#include"DragonSword.h"
DragonSword::DragonSword()
{
this->m_BaseDamage = 20;
this->m_WeaponName = "屠龙宝刀";
this->suckRate = 20;
this->holdRate = 30;
this->critRate = 35;
}
int DragonSword::getBaseDamage()
{
return this->m_BaseDamage;
}
int DragonSword::getSuckBlood()
{
if (isTrigger(suckRate))
{
return this->m_BaseDamage * 0.5; //按照武器基础伤害一半吸血
}
}
bool DragonSword::getHold()
{
if (isTrigger(holdRate))
{
return true;
}
return false;
}
bool DragonSword::getCrit()
{
if (isTrigger(critRate))
{
return true;
}
return false;
}
bool DragonSword::isTrigger(int rate)
{
//通过isTrigger判断是否触发特效
//随机1-100的数字
int num = rand() % 100 + 1;
if (num < rate) {
return true;
}
return false;
}
Hero.h:
#pragma once
#include<iostream>
#include<string>
using namespace std;
#include "Weapon.h"
#include"Monster.h"
class Monster;
class Hero
{
public:
Hero();
string m_Name;//人名
int m_Atk;//攻击力
int m_Def;//防御力
int m_Hp;//血量
Weapon* weapon;//武器
void EquipWeapon(Weapon* weapon);
void Attack(Monster* monster);
};
Hero.cpp:
#include"Hero.h"
Hero::Hero()
{
this->m_Hp = 500;
this->m_Atk = 50;
this->m_Def = 50;
this->m_Name = "法师";
this->weapon = NULL;
}
//装备武器
void Hero::EquipWeapon(Weapon* weapon)
{
this->weapon = weapon;
cout << "英雄:" << this->m_Name << "装备了武器<<" << this->weapon->m_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->m_Atk;
}
else
{
//基础伤害
damage = this->m_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->m_Hold = isHold;
//计算真实伤害
int trueDamage = (damage - monster->m_Def) > 0 ? damage - monster->m_Def : 1;
monster->m_Hp -= trueDamage;
this->m_Hp += addHp;
cout << "英雄" << this->m_Name << "攻击了敌人" << monster->m_Name << "造成了伤害" << trueDamage << endl;
}
Monster.h:
#pragma once
#include<iostream>
#include<string>
using namespace std;
#include "Weapon.h"
#include"Hero.h"
class Hero;
class Monster
{
public:
Monster();
string m_Name;//人名
int m_Atk;//攻击力
int m_Def;//防御力
int m_Hp;//血量
bool m_Hold;
void Attack(Hero* hero);
};
Monster.cpp:
#include "Monster.h"
Monster::Monster()
{
this->m_Hp = 300;
this->m_Atk = 70;
this->m_Def = 40;
this->m_Hold = false;
this->m_Name = "比克大魔王";
}
void Monster::Attack(Hero* hero)
{
if (this->m_Hold)
{
cout << "怪物" << this->m_Name << "被定身了,本回合无法攻击" << endl;
return;
}
//计算攻击伤害
int damage = (this->m_Atk - hero->m_Def) > 0 ? this->m_Atk - hero->m_Def : 1;
hero->m_Hp -= damage;
cout << "怪物" << this->m_Name << "攻击了英雄" << hero->m_Name << "造成了伤害" << damage << endl;
}
多态案例二-PK小游戏.cpp:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<ctime>
#include"Hero.h"
#include"Monster.h"
#include"Weapon.h"
#include"Knife.h"
#include"DragonSword.h"
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 << "1.屠龙刀" << 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;
while (true)
{
getchar();
system("cls");//防止输出太快,回车清屏
cout << "---当前第" << round << "回合开始---" << endl;
if (hero->m_Hp <= 0)
{
cout << "英雄" << hero->m_Name << "已挂,游戏结束" << endl;
break;
}
hero->Attack(monster);
if (monster->m_Hp <= 0)
{
cout << "怪物" << monster->m_Name << "已挂,顺利通过" << endl;
break;
}
monster->Attack(hero);
if (hero->m_Hp <= 0)
{
cout << "英雄" << hero->m_Name << "已挂,游戏结束" << endl;
break;
}
cout << "英雄" << hero->m_Name << "剩余血量:" << hero->m_Hp << endl;
cout << "怪物" << monster->m_Name << "剩余血量:" << monster->m_Hp << endl;
round++;
}
delete monster;
delete hero;
delete knife;
delete dragon;
}
int main(void)
{
srand((unsigned int)time(NULL));//定义随机种子
play();
system("pause");
return EXIT_SUCCESS;
}