前言
本篇文章主要介绍了C++虚函数、动态析构、纯虚函数与抽象类的实现
1.多态的概念
多态的本质: 形式上,使用统一的父类指针做一般性处理, 但是实际执行时,这个指针可能指向子类对象, 形式上,原本调用父类的方法,但是实际上会调用子类的同名方法。2.虚函数
C++通过虚函数(在成员函数前加virtual关键字,在子类中重载该函数)来实现多态。
多态的本质:
形式上,使用统一的父类指针做一般性处理,但是实际执行时,这个指针可能指向子类对象,
形式上,原本调用父类的方法,但是实际上会调用子类的同名方法。
2.1 虚函数的使用
虚函数的定义:
在函数的返回类型之前使用virtual
只需要在成员函数的声明中添加virtual
虚函数的继承:
1. 如果某个成员函数被声明为虚函数,那么它的子类【派生类】,以及子类的子类中,所继承的这个成员函数,也自动是虚函数。
2. 如果在子类中重写这个虚函数,可以不用再写virtual, 但是仍建议写virtual, 更可读。(或者在声明时加 override 或 final)
3. final 关键字禁止子类重写该函数(声明时使用即可)。
4. override 表示该函数是从父类继承的,增加可读性(声明时使用即可)。
#include <iostream>
using namespace std;
class Person{
public:
virtual void print(const char *word){
cout << "person:" << word << endl;
}
};
class Father:public Person{
public:
void print(const char *word) override{
cout << "father:" << word << endl;
}
};
class Son:public Father{
public:
void print(const char *word) final{
cout << "son:" << word << endl;
}
};
void say(Person *peo, const char *word){
peo->print(word);
}
//void say(Person &peo, const char *word){
// peo.print(word);
//}
int main() {
Father f1;
Son s1;
say(&f1,"playing games?");
say(&s1,"no,I'm learning");
// say(f1,"playing games?");
// say(s1,"no,I'm learning");
return 0;
}
2.2 虚函数表的概念
1. 存在虚函数的类都有一个一维的虚函数表叫做虚表。当类中声明虚函数时,编译器会自动在类中生成一个虚函数表。
2.类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
3.虚函数表是一个存储类成员函数指针的数据结构。
4.virtual成员函数会被编译器放入虚函数表中。
5.当存在虚函数时,每个对象中都有一个指向虚函数表的指针(C++编译器给父类对象,子类对象提前布局vptr指针),当进行test(father *base)函数的时候,C++编译器不需要区分子类或者父类对象,只需要再base指针中,找到vptr指针即可)。
6. vptr一般作为类对象的第一个成员。
使用继承的虚函数表构建过程:
1.直接复制父类的虚函数表;
2.如果子类重写了某个虚函数,那么就在这个虚函数表中进行相应的替换;
3.如果子类增加了新的虚函数,就把这个虚函数添加到虚函数表中
如果继承了多个父类(会继承多个虚函数表),如果第一个父类有其他数据成员(存放在栈中),那么第二个虚函数表会排在父类所有成员的后面,子类新加的虚函数放在第一个虚函数表中。
#include <iostream>
using namespace std;
class Person {
public:
virtual void fun1() {
cout << __FUNCTION__ << endl;
}
virtual void fun2() {
cout << __FUNCTION__ << endl;
}
};
class Father : public Person {
public:
void fun1() override {
cout << __FUNCTION__ << endl;
};
virtual void fun3(){
cout << __FUNCTION__ << endl;
}
int a = 10;
};
class Mather {
public:
virtual void funMather() {
cout << __FUNCTION__ << endl;
}
};
class Son : public Father ,public Mather{
public:
virtual void fun4() {
cout << __FUNCTION__ << endl;
}
void fun1() final {
cout << __FUNCTION__ << endl;
}
};
typedef void (*fun)();//定义一个函数指针方便调用虚函数表里的函数
int main(void) {
//含有虚函数的对象中,最先存储的就是"虚函数表"的地址(即对象名,就是虚函数表的地址)
Person peo;
/*
先拿到虚函数表的地址 将其转换为int类型指针(int *)&peo,
然后寻址找拿到虚函数表的首个函数地址 *(int *)&peo,
最后用int *vptr接收拿到的地址 int *vptr = (int *)*(int *)&peo(此时vptr指向的是虚函数表里的首个虚函数)。
*/
int *vptr = (int *)*(int *)&peo;
(fun (*(vptr + 0)))();
(fun (*(vptr + 1)))();
/*
使用继承的虚函数表构建过程:
1.直接复制父类的虚函数表;
2.如果子类重写了某个虚函数,那么就在这个虚函数表中进行相应的替换;
3.如果子类增加了新的虚函数,就把这个虚函数添加到虚函数表中
如果继承了多个父类(继承多张虚函数表),那么虚函数表按照继承的先后顺序排序(class Son : public Father, public Mather
先Father 后Mather)在对象的内存中排列。并且子类增加的虚函数会添加到第一个表中。
*/
cout << "---------一条优雅的分割线-----------" << endl;
Son son;
vptr = (int *)*(int *)&son;
(fun (*(vptr + 0)))();
(fun (*(vptr + 1)))();
(fun (*(vptr + 2)))();
(fun (*(vptr + 3)))();
int *vptr2 = (int *)*((int *)&son + 2);// int 类型指针加2,拿到vptr2(第二张虚函数表的地址)
(fun(*vptr2 + 0))();
system("pause");
return 0;
}
vs分析类内内存分布(在项目属性的命令行选项中加/d1 reportSingleClassLayout类名):
虚函数表内存分布:
2.3 虚析构函数(多态中的动态析构)
为了防止内存泄露,最好是在基类析构函数上添加virtual关键字,使基类析构函数为虚函数
目的在于,当使用delete释放基类指针时,会实现动态的析构:
如果基类指针指向的是基类对象,那么只调用基类的析构函数
如果基类指针指向的是子类对象,那么先调用子类的析构函数,再调用父类的析构函数
#include <iostream>
using namespace std;
class Person {
public:
Person(const char *name = "null") {
int len = strlen(name) + 1;
this->name = new char[len];
strcpy_s(this->name,len, name);
}
virtual ~Person() {
cout << "执行了Person析构函数" << endl;
if (this->name) {
delete this->name;
name = NULL;
}
}
protected:
char *name;
};
class Student : public Person {
public:
Student(const char *name="null", const char *addr="null") : Person(name) {
int len = strlen(addr) + 1;
this->addr = new char[len];
strcpy_s(this->addr,len, addr);
}
~Student() {
cout << "执行了Student析构函数" << endl;
if (this->addr) {
delete this->addr;
addr = NULL;
}
}
private:
char *addr;
};
int main(void) {
Person *peo = new Person();
delete peo;
Student *stu = new Student();
delete stu;
peo = new Student();
//这里如果Person的析构函数不加virtual,\
在析构时只会执行Person的析构函数,从而造成内存泄漏
delete peo;
system("pause");
return 0;
}
3. 纯虚函数与抽象类
1.C++ 接口是使用抽象类来实现的。
2.如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的。
3.抽象类是不允许被实例化的。
#include <iostream>
#include <Windows.h>
using namespace std;
class Person {
public:
virtual void fun1() = 0;
};
class Student{
public:
virtual void fun1() {
cout << __FUNCTION__ << endl;
}
};
int main(void) {
Student stu;
stu.fun1();
system("pause");
return 0;
}