学习:类和对象——多态和纯虚和抽象类等

多态的基本概念:

多态是C++面向对象三大特性之一

多态分为两类

1、静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名 ,那么这个我们之前都有用到过
2、动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

1、静态多态的函数地址早绑定 - 编译阶段确定函数地址

2、动态多态的函数地址晚绑定 - 运行阶段确定函数地址

示例代码:

#include<iostream>
#include<string>
using namespace std;
class Animal {
public:
virtual void speak() { // 进行virtual修饰成员函数,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
cout << "动物在说话" << endl;
}
};
class Dog :public Animal{
public:
void speak() {
cout << "小狗在说话" << endl;
}
};
class Cat :public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
void aaa(Animal & animal) {
animal.speak();
}
void test01() {
Cat c1;
aaa(c1);
Dog d1;
aaa(d1);
}
int main() {
test01();
system("pause");
return 0;
}

总结:

多态满足的条件:

1、有继承关系
2、子类重写父类中的虚函数

多态使用条件:

1、父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写


多态的原理:

不理解原理的话,真的很难受

我们先来了解下一个虚函数虚拟指针虚拟表的概念:

虚函数:当函数被virtual修饰过后,这个函数也就会被称为虚函数

虚拟指针::当成为了虚函数,就会生成一个虚拟指针,这个指针保存的地址就是虚拟表的位置

我们通过之前的工具来查看下类的构造

正常的Cat类

那么继承的是来自父类中的一切

我们接着看下父类加了virtual之后的Cat类

这时候的虚拟指针就指向虚拟表中的Cat类中speak函数的地址


纯虚函数和抽象类:

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,所以我们在父类中的虚函数中定义的内容都没啥用

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

1、无法实例化对象
2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例代码:

#include<iostream>
#include<string>
using namespace std;
class Base {
public:
virtual void to_take() = 0; //定义了一个纯虚函数to_take
};
class Son :public Base {
public:
void to_take() {
cout << "这是实现了父类的to_take";
}
};
class Son2 :public Base {
};
int main() {
//Base b1; //直接实例化错误,因为Base为抽象类,为什么呢? 因为Base中用virtual定义了纯虚函数
//Son2 s2; //直接实例化错误,就算继承了Base但是没有实现to_take的纯虚函数,这个类还是一个抽象类,不是实体类
//以上的两个都是在栈区进行实例化的,那我们如果通过堆区实例化可以吗
//b1 = new Base; //答案是一样的,不可以
Base * base = new Son; //实例化成功,因为实现了父类中的纯虚函数
base->to_take(); //这里由于是指针,所以我们通过->来进行调用
system("pause");
return 0;
}

虚析构和纯虚析构:

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性:

1、可以解决父类指针释放子类对象
2、都需要有具体的函数实现

虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

类名::~类名(){}

示例代码:解决方法两种

#include<iostream>
#include<string>
using namespace std;
class A {
public:
virtual void speak() = 0; //纯虚函数
A() {
cout << "这是A的构造函数" << endl;
}
//virtual ~A() { //利用虚析构可以解决父类指针释放子类对象时候不干净的问题
// cout << "这是A的析构函数" << endl;
//}
virtual ~A() = 0; //利用纯虚析构来解决父类指针释放子类对象时候不干净的问题,在全局实现A的析构函数
};
A::~A() {
cout << "这是A的析构函数" << endl;
}
class B:public A {
public:
B(int age) {
cout << "这是B的构造函数" << endl;
this->age = new int(age);
}
~B() {
if (age != NULL) {
delete age;
age = NULL;
cout << "这是B的析构函数" << endl;
}
}
virtual void speak() {
cout << "年龄为"<< *this->age << "岁的B在说话" << endl;
}
public:
int * age; //定义一个指针变量,来储存堆区的数据
};
void test01() {
A * a = new B(100);
a->speak();
delete a; //进行释放 调用相应的父类的析构函数 但是不会走子类的析构函数
}
int main() {
test01();
system("pause");
return 0;
}

总结:

1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3. 拥有纯虚析构函数的类也属于抽象类

posted @   zpchcbd  阅读(249)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示