虚函数
一 实现多态:虚函数
多态的本质:
形式上,使用统一的父类指针做一般性处理,但是实际执行时,这个指针可能指向子类对象。
形式上,原本调用父类的方法,但实际上会调用子类的同名方法。
注意:
程序执行时,父类指针指向父类对象,或子类对象时,在形式上是无法分辨的,只有通过多态机制,才能执行真正对应的方法
虚函数的定义:
在函数的返回值类型之前使用virtual,只在成员函数的声明中添加virtual,在成员函数的实现中不要加virtual
虚函数的继承:
如果某个成员函数被声明为虚函数,那么它的子类(派生类),以及子类的子类中,所继承的这个成员函数,也自动是虚函数
如果在子类中重写这个虚函数,可以不用加virtual,也可以加virtual,加上更可读
二 单个类的虚函数表
#include <iostream>
using namespace std;
class Father
{
public:
virtual void fun1() { cout << "Father::fun1" << endl; }
virtual void fun2() { cout << "Father::fun2" << endl; }
virtual void fun3() { cout << "Father::fun3" << endl; }
void fun4() { cout << "非虚函数:Father::fun4" << endl; }
public: // 这里用public是为了方便测试
int num1 = 100;
int num2 = 200;
static int x; // 静态成员变量
};
// 初始化静态成员变量,放在数据区
int Father::x = 0;
// 定义函数指针
typedef void (*fun_t)(void);
int main()
{
Father f1;
// 定义指向f1的虚函数表的指针
int* vptr = (int*)*(int*)&f1;
// 访问f1虚函数表中的每个虚函数
for (int i = 0; i < 3; i++)
{
cout << "调用第" << i + 1 << "个虚函数:";
((fun_t) * (vptr + i))();
}
// 访问f1对象的数据成员num1和num2
for (int i = 0; i < 2; i++)
{
cout << "num" << i + 1 << ":" << *((int*)&f1 + i + 1) << endl;
}
return 0;
}
在命令行中添加:/d1 reportSingleClassLayoutFather 查看Father类的内存布局
手绘内存分布:
对象内,首先存储的是“虚函数表指针”,又称“虚表指针”,然后再存储非静态数据成员。
对象的非虚函数,保存在类的代码中。
对象的内存,只存储虚表指针和数据成员。
类的静态数据成员,保存在数据区中,和对象是分开存储的
新添加虚函数后,对象的内存空间不变,仅虚函数表中添加条目
多个对象,共享同一个虚函数表
三 使用继承的虚函数表
#include <iostream>
using namespace std;
class Father
{
public:
virtual void fun1() { cout << "Father::fun1" << endl; }
virtual void fun2() { cout << "Father::fun2" << endl; }
virtual void fun3() { cout << "Father::fun3" << endl; }
void fun4() { cout << "非虚函数:Father::fun4" << endl; }
public:
int num1 = 100;
int num2 = 200;
static int x; // 静态成员变量
};
// 初始化静态成员变量,放在数据区
int Father::x = 0;
class Son :public Father
{
public:
void fun1() { cout << "Son::fun1" << endl; } // 重写父类的虚函数fun1方法
virtual void fun5() { cout << "Son::fun5" << endl; }
public:
int num3 = 300;
};
// 定义函数指针
typedef void (*fun_t)(void);
int main()
{
Son s1;
// 定义指向s1对象的虚表指针
int* vptr = (int*)*(int*)&s1;
// 访问Son对象虚表函数中的每个虚函数
for (int i = 0; i < 4; i++)
{
cout << "访问Son对象第" << i + 1 << "个虚函数:";
((fun_t) * (vptr + i))();
}
// 访问Son对象的数据成员num1,num2,num3
for (int i = 0; i < 3; i++)
{
cout << "num" << i + 1 << ":" << *((int*)&s1 + 1 + i) << endl;
}
return 0;
}
查看Son类内存布局:
手绘内存分布:
补充:
四 多重继承的虚函数表
#include <iostream>
using namespace std;
class Father
{
public:
virtual void fun1() { cout << "Father::fun1" << endl; }
virtual void fun2() { cout << "Father::fun2" << endl; }
virtual void fun3() { cout << "Father::fun3" << endl; }
void fun4() { cout << "非虚函数:Father::fun4" << endl; }
public:
int num1 = 100;
int num2 = 200;
static int x; // 静态成员变量
};
// 初始化静态成员变量,放在数据区
int Father::x = 0;
class Mother
{
public:
virtual void test1() { cout << "Monter::test1" << endl; }
virtual void test2() { cout << "Monter::test2" << endl; }
virtual void test3() { cout << "Monter::test3" << endl; }
void test4() { cout << "非虚函数 Monter::test4" << endl; }
public:
int num3 = 300;
int num4 = 400;
static int x; // 静态成员变量
};
class Son :public Father,public Mother
{
public:
void fun1() { cout << "Son::fun1" << endl; } // 重写Father类的虚函数fun1方法
virtual void test1() { cout << "Son::test1" << endl; } // 重写Mother类的虚函数fun1方法
virtual void fun5() { cout << "Son::fun5" << endl; } // 新添加的虚函数fun5
public:
int num5 = 500;
};
// 定义函数指针
typedef void (*fun_t)(void);
int main()
{
Son s1;
// 定义指向Son对象继承的Father类的虚表指针
int* vptr = (int*)*(int*)&s1;
// 访问Son对象继承的Father类虚表函数中的每个虚函数
for (int i = 0; i < 4; i++)
{
if (i < 3)
{
cout << "访问Son对象继承Father类的第" << i + 1 << "个虚函数:";
((fun_t) * (vptr + i))();
}
else
{
cout << "访问Son对象新添加的虚函数:";
((fun_t) * (vptr + i))();
}
}
// 访问Son对象继承的Father类的数据成员num1,num2
for (int i = 0; i < 2; i++)
{
cout << "num" << i + 1 << ":" << *((int*)&s1 + 1 + i) << endl;
}
cout << endl;
// 定义指向Son对象继承的Mother类的虚表指针
int* vptr2 = (int*)*((int*)&s1 + 3);
// 访问Son对象继承的Mother类虚表函数中的每个虚函数
for (int i = 0; i < 3; i++)
{
cout << "访问Son对象继承Mother类的第" << i + 1 << "个虚函数:";
((fun_t) * (vptr2 + i))();
}
// 访问Son对象继承的Mother类的数据成员num3,num4 和新添加的数据成员num5
for (int i = 0; i < 3; i++)
{
cout << "num" << i + 3 << ":" << *((int*)&s1 + 4 + i) << endl;
}
return 0;
}
查看Son类内存布局:
手绘内存分布:
注意:子类新添加的虚函数是放在继承的第一个父类的虚函数表中的
五 final
5.1 用final修饰类,让该类不能被继承
class Test1
{
};
class Test2 final : public Test1 // 这里用final修饰,使Test2类不能被继承
{
};
class Test3 :public Test2 // 这里会报错 不能将"final"类类型作基类
{
};
5.2 用final修饰虚函数,使该方法不能被子类重写
final在修饰函数的时候,只能用来修饰虚函数。final只能在函数声明中使用,不能在函数的实现中使用。
class Test1
{
public:
virtual void fun1() final; // final只能在声明的时候添加,实现的时候不能添加final
};
void Test1::fun1() // final只能在声明的时候添加,实现的时候不能添加final
{
}
class Test2 : public Test1
{
public:
//void fun1() {} // 报错,在子类中不能重写父类final修饰的方法
};
六 override 提示程序的阅读者,这个函数是重写父类的方法
override仅能用于修饰虚函数。override只能在函数的声明中使用,不能在函数的实现中使用
作用:
1.提示程序的阅读者,这个函数是重写父类的方法
2.防止程序员在重写父类的函数时,把函数名写错
#include <iostream>
using namespace std;
class Father
{
public:
virtual void fun1() { cout << "Father::fun1" << endl; }
};
class Son : public Father
{
public:
void fun1() override; // override告诉程序员,fun1是重写父类的方法
};
void Son::fun1()
{
cout << "Son::fun1" << endl;
}
七 基类的析构函数使用虚函数
基类的析构函数是非虚函数时,delete父类指针指向子类对象时,不会调用子类的析构函数
#include <iostream>
#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);
}
~Father() // 这里没有使用virtual
{
cout << "调用Father类的析构函数" << endl;
if (addr)
{
delete addr;
addr = NULL;
}
}
private:
char* addr;
};
class Son : public Father
{
public:
Son(const char* addr, const char* game = "王者荣耀") :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()
{
cout << "----------- case 1 ------------" << endl;
Father* father;
father = new Father();
delete father;
cout << endl;
cout << "----------- case 2 ------------" << endl;
Son* son;
son = new Son("China");
delete son;
cout << endl;
cout << "----------- case 3 ------------" << endl;
father = new Son("China");
delete father;
return 0;
}
这里,我们将Father类的析构函数改为虚函数
virtual ~Father() // 这里将Father类的析构函数改为虚函数
{
cout << "调用Father类的析构函数" << endl;
if (addr)
{
delete addr;
addr = NULL;
}
}
再来运行:
注意:
为了防止内存泄漏,最好是在基类析构函数上添加virtual关键字,使基类析构函数为虚函数
目的在于,当使用delete释放基类指针时,会实现“动态析构”:
如果基类指针指向的是基类对象,那么只调用基类的析构函数
如果基类指针指向的是子类对象,那么先调用子类的析构函数,再调用父类的析构函数
八 纯虚函数与抽象类
什么时候使用纯虚函数:
某些类,在现实角度和项目实现角度,都不需要实例化(不需要创建它的对象),这个类中定义的某些成员函数,只是为了提供一个形式上的接口,准备让子类来做具体的实现。此时,这个方法,就可以定义为“纯虚函数”。
什么是抽象类:
包含纯虚函数的类,就称为抽象类
纯虚函数的用法:
纯虚函数,使用 virtual 和 =0
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Shape
{
public:
Shape(const string color = "weight")
{
this->color = color;
}
virtual float area() = 0; // 纯虚函数不用做具体的实现
string getColor() { return color; }
private:
string color;
};
class Circle : public Shape
{
public:
Circle(float radius = 0, const string color = "black"):Shape(color),r(radius){ }
float area() // 在子类中实现这个纯虚函数
{
return 3.14 * r * r;
}
private:
float r; // 半径
};
int main()
{
//Shape s1; // 报错,不能使用抽象类创建对象
Circle c1(10);
cout << "颜色:" << c1.getColor() << " 面积:" << c1.area() << endl;
Shape* p = &c1;
cout << "颜色:" << p->getColor() << " 面积:" << p->area() << endl;
return 0;
}
纯虚函数注意事项:
父类声明纯虚函数,那么这个类就是抽象类,就不能创建对象
并且它的子类:
1.要么实现这个纯虚函数(最常见)
2.要么继续把这个纯虚函数声明为纯虚函数,这个子类也成为抽象类
3.要么不对这个纯虚函数做任何处理,等效于上一种情况(该方法不推荐)
九 常见错误总结
1.子类在重写继承的虚函数时,要和基类的函数原型必须保持一致
2.析构函数是否使用虚函数? 有子类时,析构函数就应该使用虚函数