Day33.C++07
001.静态联编和动态联编(引出多态的概念)
#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.speak();
}
//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
Cat cat1;
cat1.speak();
}
int main(void)
{
test01();
system("pause");
return EXIT_SUCCESS;
}
002.多态基本概念及其原理解析
概念:
- 多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。
- C++支持编译时多态(静态多态)和运行时多态(动态多态),运算符和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
- 静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。
- 如果函数的调用在编译阶段就能确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能在编译期间确定,而需要在需要在运行时才能决定,就属于晚绑定(动态多态,运行时多态)。
原理解析:
- 当父类中有了虚函数之后,内部结构就发生了改变
- 内部多了一个vfptr(virtual function pointer)虚函数指针,指向vftable
- 父类中 进行继承时, 会继承vfptr vftable
- 构造函数中 会将虚函数指针指向自己的虚函数表
- 如果发生了重写,会替换掉虚函数表中的原有的函数,改为自己的函数调用地址
003.多态案例-计算器案例
总结:
多态成立的条件:
- 有继承
- 子类重写父类虚函数函数(返回值,函数名字,函数参数,必须和父类完全一致(析构函数除外))
- 类型兼容,父类指针、父类引用 指向 子类对象
纯虚函数:
- 如果父类中存在纯虚函数,子类继承父类,就必须要实现 纯虚函数
- 如果父类中有了纯虚函数,就不可以实例化对象了
- 这个类有了纯虚函数,通常又称为 抽象类
开发原则:
开闭原则(对扩展开放,对修改关闭)
代码:
#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;
}
004.虚析构和纯虚析构函数
总结:
虚析构:
解决的问题:通过父类指针指向子类对象释放的时候释放不干净导致的问题
纯虚析构:
纯虚析构,需要声明,还需要实现 类内声明,类外实现
如果函数中出现了 纯虚析构函数,那么这个类也算抽象类
抽象类不可以实例化
代码:
#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;
}
005.向上类型转换和向下类型转换
总结:
基类转派生类
- 向下类型转换 不安全的
派生类转基类
- 向上类型转换 安全的
如果发生了多态
- 总是安全的
006.多态案例二-PK小游戏
- 代码略
实现思路:
创建英雄类(英雄1,英雄2),怪人类,武器类(小刀,屠龙刀)
实现英雄与怪人的PK