#C++ Day30 November 14 2025

//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;

}