//Object – Oriented 9 – 1 Syntax of Polymorphism
//多态的语法
#include <iostream>
using namespace std;
//多态分为静态多态和动态多态
//静态多态主要是函数重载
//我们现在主要学习动态多态 主要是利用派生类和虚函数 来实现一个运行时多态
class Animal {
public:
//虚函数 这样写 就可以调用子类的函数了
virtual void eat() {
cout << “动物在吃东西” << endl;
}
};
class Cat :public Animal {
public:
void eat() {
cout << “猫在吃东西” << endl;
}
};
class Pig :public Animal {
public:
void eat() {
cout << “猪在吃东西” << endl;
}
};
//调用链:main -> test -> eat -> Animal::eat
//函数传参是个动物,但是传入不同的动物,会产生不同的行为,这就叫多态
void eat(Animal& a) {//eat函数里面调用一个Animal的引用
//子类对象通过隐式转换 转换成了父类对象 那么肯定调用父类的东西
//在父类的函数前面可以加virtual 关键字 引入虚函数
a.eat();
}
void Test() {
Cat c;
Pig p;
//这里是多态:子类重写了父类的虚函数
//多态还表现在函数传参中,通过传入不同的动物产生不同的行为
eat(c);
eat(p);
}
int main() {
Test();
return 0;
}
//Object – Oriented 9 – 2 Virtual Function
//虚函数
#include <iostream>
using namespace std;
class Animal {
public:
virtual void eat() {//可以认为这个函数不占空间 没有成员变量时 类只占1字节
//但是不占用1字节的话 就可以无限创建没有成员的类 这显然是不合理的
cout << “动物在吃东西” << endl;
}
virtual void run() {
cout << “动物在跑” << endl;
}
};
class Cat:public Animal {
public:
void eat() {//函数存在另一个地方 它跟成员变量分开存储
cout << “猫在吃东西” << endl;
}
};
void eat(Animal& a) {//函数传进来时直接把Cat转成Animal 隐式转换了
//调试是发现a没有东西 那就是因为这个存储在这个对象本身上面
Animal b;//这个b本身就是Animal,只有一个虚函数指针
a.eat();//这个a是由Cat转换过来的,除了虚函数指针还会有Cat
//如果当我们产生继承的时候 如果子类覆盖了父类的虚函数 在虚函数指针里面只需要把这条记录覆盖掉就好了
//覆盖掉的好处,当我在调用a.eat()的时候,我实际上是在虚函数表里面去找这个函数的,去找Cat::eat 这就是我想要的函数,所以这才是我想调用的不是已经被覆盖的父类的函数,所以才会产生多态
//子调用的时候实际会生成一个虚函数指针,虚函数指针保存了这个类上所有虚函数的地址,并且在子类继承父类时会覆盖掉这个虚函数
}
void Test() {
Cat c;
eat(c);
cout << “Animal’s size = ” << sizeof(Animal) << endl;//如果没有任何成员变量 Animal的大小是1字节 这个1字节是占位用的
//如果Animal变成virtual虚函数 这里就会占8字节 在64位机上 double long long 指针变量就会占8字节 这里是指针(虚函数指针)
//虚函数指针可以认为是一个数组的首地址 这个数组是所有虚函数组成的一个数组
}
int main() {
Test();
return 0;
}
//Object – Oriented 9 – 3 Pure Virtual Function and Abstract Class
//纯虚函数和抽象类
#include <iostream>
using namespace std;
//有时在写代码时父类里面的函数有可能没有任何实现,都需要子类去实现,子类去重写,然后纯虚函数的目的就是这个
class Animal {
public:
virtual void eat() =0;//定义一个虚函数 如果不实现就会报错,解决方法加上=0就可以了
//只要有纯虚函数的类 我们叫它抽象类 抽象类无法实例化对象 这个类是为继承而生的
};
class Cat : public Animal {
public:
/*
virtual void eat() {//子类的eat()前面还要加virtual是因为子类也可能被继承
cout << “猫在吃东西” << endl;
//如果不重写纯虚函数Animal,Cat也会变成一个抽象类
}
*/
//当Cat同样变成了一个抽象类时 实例化Cat时就会报错
};
int main() {
//Animal a;//定义一个虚函数 如果不实现就会报错 有纯虚函数的类也无法实例化对象
//Cat c;//继承抽象类Animal的类Cat可以实例化对象
//c.eat();
return 0;
}
//Object – Oriented 9 – 4 Virtual Destructor and Pure Virtual Destructor
//虚析构和纯虚析构
#include <iostream>
using namespace std;
//虚析构 主要是解决内存泄露的问题
//我现在有个父类的指针 指向派生类的对象 那么在对它进行销毁的时候 如果父类的析构函数 不是virtual的 就会调用不到子类的析构函数 从而导致内存泄露
class BaseA {
public:
//只有一个构造函数和一个析构函数,并且析构函数和构造函数都是空实现 因为没有任何成员变量
BaseA(){}
~BaseA(){ cout << “BaseA 销毁了” << endl; }
};
class SonA : public BaseA {
public:
SonA() : m_Value(NULL) {//构造函数,先把m_Value初始化为一个空指针
m_Value = new int(10);//在构造函数里把这个内存申请出来
}
~SonA() {
cout << “SonA 销毁了” << endl;
delete m_Value;//在析构函数里面把这个内存销毁掉
}
int* m_Value;//这个是它的成员变量 并且需要在堆上分配内存的
};
class BaseB {
public:
//只有一个构造函数和一个析构函数,并且析构函数和构造函数都是空实现 因为没有任何成员变量
BaseB() {}
/*
virtual ~BaseB() { cout << “BaseB 销毁了” << endl; }//把基类的这个地方改成 virtual 变成虚析构
*/
//虚析构可以这么干
virtual ~BaseB() = 0;//纯虚析构也可以这么干
};
BaseB::~BaseB(){
//纯虚析构需要在外部被实现一下 因为加上纯虚会变成 抽象类不能实例化对象的
cout << “BaseB 销毁了” << endl;
}
class SonB : public BaseB {
public:
SonB() : m_Value(NULL) {//构造函数,先把m_Value初始化为一个空指针
m_Value = new int(10);//在构造函数里把这个内存申请出来
}
~SonB() {
cout << “SonB 销毁了” << endl;
delete m_Value;//在析构函数里面把这个内存销毁掉
}
int* m_Value;//这个是它的成员变量 并且需要在堆上分配内存的
};
int main() {
BaseA* a = new SonA();//首先new一个SonA的对象出来,然后把它指向基类的指针
delete a;//然后把指针delete掉 实际上是调用了SonA的析构
//调用时会发现只有基类的析构函数被调用了 子类的析构函数没有被调用
//带来问题是 子类的堆上的 分配的内存 m_Value 已经new了但是没有被delete 这样就会导致内存泄露
BaseB* b = new SonB();//这里可能只是定义了一个指针 并没有真正的实例化
delete b;
//BaseB x;//发现这里无法进行实例化 因为已经变成抽象类了
//抽象类无法进行实例化
return 0;
}