多态是面向对象编程的特性之一
在C++中多态就是用同个函数调用同个内容
多态的分类:
分为静态多态和动态多态
静态多态性(编译时的多态性)
通过函数和运算符重载实现的
编译时确定执行哪一个同名函数调用速度快、效率高,缺乏灵活性口
静态多态的函数地址早绑定,编译阶段确定函数地址
动态多态性(运行时的多态性)
通过派生类和虚函数实现的
在程序运行过程中才动态地确定执行哪个同名函数
动态多态的函数地址晚绑定 ,运行阶段确定函数地址
动态多态性的满足条件:
1.有继承关系
2.子类重写(重写要求:函数返回值类型 函数名 参数列表 完全相同)父类的虚函数
动态多态性的使用:
父类的指针或者引用 指向子类的对象
虚函数和抽象类
虚函数
基本语法:
virtual 返回值类型 函数名(参数列表);
当一个基类的成员函数被声明为虚函数时,它可以在派生类中被重写(重写函数必须具有与基类虚函数相同的函数签名(即相同的返回类型、函数名和参数列表)。
当使用基类指针或引用指向派生类对象并调用这个虚函数时,会调用派生类中的版本,而不是基类中的版本。
#include<iostream>
class first
{
public:
virtual void put();
};
void first::put()
{
std::cout << "first的调用" << std::endl;
}
class secend :public first
{
public:
void put();
};
void secend ::put()
{
std::cout << "secend的调用" << std::endl;
}
int main()
{
first* pr{new secend};
pr -> put();
return 0;
}
//结果是:
secend的调用
纯虚函数
基本语法:
virtual 返回值类型 函数名 (参数列表) = 0;
纯虚函数没有函数体,并且派生类必须提供其实现。
#include<iostream>
class first
{
public:
virtual void put() = 0;
};
class secend :public first
{
public:
void put()
{
std::cout << "secend 的调用 " << std::endl;
}
};
int main()
{
first* pr= new secend;
pr->put();
return 0;
}
//输出的结果是
secend 的调用
虚函数列表
虚函数列表是编译器为实现动态多态性而自动创建的一个数据结构。
它存储了类中所有虚函数的地址。
当一个类包含至少一个虚函数时,编译器会为该类生成一个虚函数表,并在每个类的对象中插入一个指向这个虚函数表的指针(通常被称为虚函数指针或vptr)。
通过举例子说明:
如果没有重写的话:
class first
{
public:
virtual void put() = 0;
};
class secend :public first{};
这个是在vs的开发者控制台可以看到class的内存空间分配
如果重写的话:
class first
{
public:
virtual void put() = 0;
};
class secend :public first
{
public:
void put()
{
std::cout << "secend 的调用 " << std::endl;
}
};
这个是在vs的开发者控制台可以看到class的内存空间分配
虚析构和纯虚析构
虚析构
在C++中主要用于解决多态情况下对象析构时资源释放的问题。
当一个基类的指针或引用指向一个派生类对象,并通过这个基类的指针或引用去删除这个对象时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数。
这可能导致派生类中的资源(如动态分配的内存、文件句柄等)无法得到正确释放,从而引发资源泄露。
通过代码说明:
#include<iostream>
class first
{
public:
first()
{
std::cout << "first构造的调用" << std::endl;
}
~first()
{
std::cout << "first析构的调用" << std::endl;
}
};
class secend :public first
{
public:
secend()
{
std::cout << "secend构造的调用 " << std::endl;
}
~secend()
{
std::cout << "secend析构的调用 " << std::endl;
}
};
int main()
{
class first* pr = new secend;
delete pr;
return 0;
}
//结果是
first构造的调用
secend构造的调用
first析构的调用
上面这个代码可以看出在释放指针内存时派生类的析构函数没有被调用。
#include<iostream>
class first
{
public:
first()
{
std::cout << "first构造的调用" << std::endl;
}
virtual ~first() //虚析构
{
std::cout << "first析构的调用" << std::endl;
}
};
class secend :public first
{
public:
secend()
{
std::cout << "secend构造的调用 " << std::endl;
}
~secend()
{
std::cout << "secend析构的调用 " << std::endl;
}
};
int main()
{
class first* pr = new secend;
delete pr;
return 0;
}
//这个的结果是
first构造的调用
secend构造的调用
secend析构的调用
first析构的调用
如果使用虚析构那就不会出出现派生类析构函数无法调用。
纯虚析构
基本语法·
virtual ~类名() = 0;
//纯虚析构与纯虚函数不同之处是需要声明也需要实现(定义)
在类外定义
不需加virtual
类名::~类名()
{
}
如果一个类不需要作为基类,或者不需要通过基类指针来释放派生类对象,那么通常不需要将其析构函数声明为虚函数。因为虚函数会增加额外的开销(如虚函数表),在没有多态需求的情况下,使用非虚析构函数更为高效
抽象类
包含至少一个纯虚函数或者是出虚析构的类被称为抽象类
抽象类特点: 无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类