实现多态:虚函数
多态的本质:
-
形式上,使用统一的父类指针做一般性处理,但是实际执行时,这个指针可能指向子类对象
-
形式上,原本调用父类的方法,但是实际上会调用子类的同名方法。
- 【注意】
程序执行时,父类指针指向父类对象,或子类对象时,在形式上是无法分辨的!只有通过多态机制,才能执行真正对应的方法。
虚函数的使用
- 虚函数的定义:
-
在函数的返回类型之前使用 virtual
-
只在成员函数的声明中添加 virtual, 在成员函数的实现中不要加 virtual
虚函数的继承:
-
如果某个成员函数被声明为虚函数,那么它的子类【派生类】,以及子类的子类中,所继承的这个成员函数,也自动是虚函数。
-
如果在子类中重写这个虚函数,可以不用再写 virtual, 但是仍建议写 virtual, 更可读!
虚函数原理
单个类的虚函数表
#include<iostream>
using namespace std;
class Father
{
public:
virtual void func1() { cout << "Father func1" << endl; }
virtual void func2() { cout << "Father func2" << endl; }
virtual void func3() { cout << "Father func3" << endl; }
void func4() { cout << "Father func4" << endl; }
int x = 200;
int y = 300;
static int z;
};
int Father::z = 0;
//函数指针类型
typedef void(*func_t)(void);
int main()
{
Father father;
//12字节 两个 int 一个虚函数表指针
cout << sizeof(father) << endl;
cout << endl;
//*(int *)(&father)拿到虚函数表指针的四个字节 (int *):格式转换
//(int *)(&father):就不可以因为这个是father的指针 解引用出来就是father的字节数
//*(int *):保证了解出来的是四个字节
//(int *)*(int *)(&father):这个解引用出来是虚函数表地址字节数,或者说是第一个虚函数指针的字节数
cout << "对象地址" << (int *)&father << endl; //16进制
int * vptr = (int *)*(int *)(&father);
//vptr:虚函数表指针指向函数指针 (*(vptr + 0)):函数指针 (func_t):函数指针转为func_t类型
//调用第一个虚函数
cout << "调用第一个虚函数:" << endl;
((func_t)(*(vptr + 0)))();
cout << "调用第二个虚函数:" << endl;
((func_t)(*(vptr + 1)))();
cout << "调用第三个虚函数:" << endl;
((func_t)(*(vptr + 2)))();
cout << endl;
cout << "第一个数据成员的地址:" << endl;
cout << &(father.x) << endl;
cout << hex << (int)(&father + 4) << endl;
cout << "第一个数据成员的值" << endl;
cout << dec << father.x << endl;
cout << *(int *)((int)&father + 4) << endl;
system("pause");
return 0;
}
-
对象内,首先存储的是“虚函数表指针”,又称“虚表指针”。然后再存储非静态数据成员。
-
对象的内存,只存储虚函数表和数据成员(类的静态数据成员,保存在数据区中,和对象是分开存储的)
-
添加虚函数后,对象的内存空间不变!仅虚函数表中添加条目多个对象,共享同一个虚函数表!
使用继承的虚函数表
#include<iostream>
using namespace std;
class Father
{
public:
virtual void func1() { cout << "Father func1" << endl; }
virtual void func2() { cout << "Father func2" << endl; }
virtual void func3() { cout << "Father func3" << endl; }
void func4() { cout << "Father func4" << endl; }
int x = 200;
int y = 300;
static int z;
};
class Son : public Father
{
public:
void func1() { cout << "Son func1" << endl; }
virtual void func5() { cout << "Son func5" << endl; }
};
int Father::z = 0;
typedef void(*func_t)(void);
int main()
{
Son son;
cout << "son对象地址:" << (int *)&son << endl;
int * vptr = (int *)*(int *)&son;
cout << "虚函数表指针vptr:" << vptr << endl;
for (int i = 0; i < 4; i++)
{
cout << "调用第" << i + 1 << "个虚函数;";
((func_t)*(vptr + i))();
}
for (int i = 0; i < 2; i++)
{
cout << *(int *)((int)&son + 4 + i * 4) << endl;
}
cout << endl;
cout << sizeof(son) << endl;
system("pause");
return 0;
}
多重继承的虚函数表
#include<iostream>
using namespace std;
class Father
{
public:
virtual void func1() { cout << "Father func1" << endl; }
virtual void func2() { cout << "Father func2" << endl; }
virtual void func3() { cout << "Father func3" << endl; }
void func4() { cout << "Father func4" << endl; }
int x = 200;
int y = 300;
static int z;
};
class Mother {
public:
virtual void handle1() { cout << "Mother handle1" << endl; }
virtual void handle2() { cout << "Mother handle2" << endl; }
virtual void handle3() { cout << "Mother handle3" << endl; }
int m = 400;
int n = 500;
};
class Son : public Father , public Mother
{
public:
void func1() { cout << "Son func1" << endl; }
void handle1() { cout << "Son handle1" << endl; }
virtual void func5() { cout << "Son func5" << endl; }
};
int Father::z = 0;
typedef void(*func_t)(void);
int main()
{
Son son;
int * vptr = (int *)*(int *)&son;
cout << "父类虚函数表指针vptr:" << vptr << endl;
for (int i = 0; i < 4; i++)
{
cout << "调用第" << i + 1 << "个虚函数;";
((func_t)*(vptr + i))();
}
//(int)&son + 4跳过虚函数表指针
for (int i = 0; i < 2; i++)
{
cout << *(int *)((int)&son + 4 + i * 4) << endl;
}
cout << "----------------------------------" << endl;
int * vptr2 = (int *)*((int *)&son + 3);
for (int i = 0; i < 3; i++)
{
cout << "调用第" << i + 1 << "个虚函数;";
((func_t)*(vptr2 + i))();
}
//(int)&son + 16跳过虚函数表指针
for (int i = 0; i < 2; i++)
{
cout << *(int *)((int)&son + 16 + i * 4) << endl;
}
system("pause");
return 0;
}
多态的使用:基类析构函数设为虚函数
为了防止内存泄露,最好是在基类析构函数上添加virtual关键字,使基类析构函数为虚函数
- 把Father类的析构函数定义为virtual函数时,
- 如果对 Father类的指针使用delete操作时,
- 就会对该指针使用“动态析构”:
- 如果这个指针,指向的是子类对象,
- 那么会先调用该子类的析构函数,再调用自己类的析构函数
#include <iostream>
#include <Windows.h>
#include <string.h>
using namespace std;
class Father {
public:
Father(const char* addr ="中国"){
cout << "执行了Father的构造函数" << endl;
int len = strlen(addr) + 1;
this->addr = new char[len];
strcpy_s(this->addr, len, addr);
}
virtual ~Father(){
cout << "执行了Father的析构函数" << endl;
if (addr) {
delete addr;
addr = NULL;
}
}
private:
char* addr;
};
class Son :public Father {
public:
Son(const char *game="吃鸡", const char *addr="中国")
:Father(addr){
cout << "执行了Son的构造函数" << endl;
int len = strlen(game) + 1;
this->game = new char[len];
strcpy_s(this->game, len, game);
}
~Son(){
cout << "执行了Son的析构函数" << endl;
if (game) {
delete game;
game = NULL;
}
}
private:
char* game;
};
int main(void) {
cout << "----- case 1 -----" << endl;
Father* father = new Father();
delete father;
cout << "----- case 2 -----" << endl;
Son* son = new Son();
delete son;
//这里不用虚函数的话,会造成内存
cout << "----- case 3 -----" << endl;
father = new Son();
delete father;
system("pause");
return 0;
}