1. 多态的虚函数的意义
1 案例:父类和子类有同名函数,但是功能不一样,但是同时,子类又继承了父类,就会导致调用的错误,想调用子类的同名函数,
但是在某些情况下,会错误调用父类的同名函数,比如
- 情形一:
//父类指针可以指向子类对象
cout <<"----- Father* men[] = { &father, &son1, &son2 }; -----" << endl;
Father* men[] = { &father, &son1, &son2 };
party(men, sizeof(men)/ sizeof(men[0]));
- 情形二:
//这段函数的本意是想调用儿子的paly函数,但是实际上调用的是父类的
Father* p;
p = &son1;
p->play();
- 上述问题的解决方案就是在父类里面使用虚基类,只需要在父类的同名函数前面加上virtual关键字即可,子类的同名函数可加可不加,再次运行结果如下
#pragma once
class Father
{
public:
virtual void play();
};
son 函数
#pragma once
#include "Father.h"
class Son:public Father
{
public:
virtual void play();
};
#include "Son.h"
#include <iostream>
#include "string"
using namespace std;
void Son::play()
{
cout << "Game" << endl;
}
——————————————————————————————————————————————————————
Father函数
#pragma once
class Father
{
public:
void play();
};
#include "Father.h"
#include <iostream>
#include "string"
using namespace std;
void Father::play()
{
cout << "KTV sing" << endl;
}
——————————————————————————————————————————————————————
主函数
#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include <sstream>
#include "Father.h"
#include "Son.h"
using namespace std;
void party(Father** men, int n)
{
for (int i = 0; i < n; i++)
{
men[i]->play();
}
}
int main(void) {
Father father;
Son son1, son2;
//父类指针可以指向子类对象
cout <<"----- Father* men[] = { &father, &son1, &son2 }; -----" << endl;
Father* men[] = { &father, &son1, &son2 };
party(men, sizeof(men)/ sizeof(men[0]));
//这段函数的本意是想调用儿子的paly函数,但是实际上调用的是父类的
cout << "----- Father* p -----" << endl;
Father* p;
p = &son1;
p->play();
return 0;
}
2. 虚函数的原理,用于实现多态
(1). 虚函数表
#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include <sstream>
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);//将数据类型转换成函数指针, 返回类型是void,输入参数也是void,也就是没有输入参数
int main(void) {
Father father;
//对象里面只装了虚函数表指针,指针为4字节,以及成员函数x,y 4字节+4字节,所以共12个字节
cout << "sizeof(father)= "<< sizeof(father) << endl; //输出值为12
cout << "对象地址:" << (int*)&father << endl; //(int*)会按照16进制进行打印
//(&father)表示取对象地址,将地址转成int型的指针(int*)(&father),然后再次取指针里面的值*(int*)(&father)
//此时取到的是一个整数,再次将值转换成int*,因为左侧定义的是int* vptr,(int*)是强制类型转换
int* vptr = (int*)*(int*)(&father); //此处成功取到对象所指向的虚函数表的指针
//*(vptr + 0 )表示取到了第一个虚函数的指针,虚函数表里面存的本身就是指针,
//函数本身就是一个地址,因此强制类型转换,将取出来的指针转换成函数,就可以根据函数指针调用函数了
cout << "调用第1个虚函数:"; ((func_t)*(vptr + 0 ))();
cout << "调用第2个虚函数:"; ((func_t)*(vptr + 1))();
cout << "调用第3个虚函数:"; ((func_t)*(vptr +2))();
cout << " ********************" << endl;
cout << "对象里面第1个数据成员的地址(方式1打印): " << &father.x << endl;
//为什么加4,因为当前类是有两个成员变量,那么对象内首先存储的就是虚函数表指针,然后是整型变量x,然后是整型变量y
//指针是4个字节,整型变量也是4个字节,&father默认是指向对象的第一个数据,也就是函数表指针,+4就指向了下一个整型数据
cout << "对象里面第1个数据成员的地址(方式2打印): " << hex << (int)&father + 4<< endl;
cout << "第1个数据成员的值(方式1打印): " << dec << father.x << endl;
cout << "第1个数据成员的值(方式2打印): " << *(int*)((int)&father + 4) << endl;
cout << " ********************" << endl;
cout << "对象里面第2个数据成员的地址(方式1打印): " << &father.y << endl;
cout << "对象里面第2个数据成员的地址(方式2打印): " << hex << (int)&father + 8 << endl; //hex表示转为16进制表示
cout << "第2个数据成员的值(方式1打印): " << dec << father.y << endl;
cout << "第2个数据成员的值(方式2打印): " << *(int*)((int)&father + 8) << endl;
return 0;
}
(2).子类的虚函数表
- 父类中有3个函数,子类定义了一个父类的同名函数和一个父类中没有的函数,子类的同名函数要实现自己的功能,观察此时虚函数表的变化。
#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include <sstream>
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;
class Son :public Father {
public:
virtual void func1() { cout << "Son::func1" << endl; };
virtual void func5() { cout << "Son::func5" << endl; };
};
typedef void(*func_t)(void);//将数据类型转换成函数指针, 返回类型是void,输入参数也是void,也就是没有输入参数
int main(void) {
Son son;
cout << "Son 对象地址:" << (int*)&son;//加 (int*)只是让打印按照指针格式打,不会改变值
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 << "size of son = " << sizeof(son) << endl;
return 0;
}
(3)final关键字,用来修饰类,让该类不能被继承
几种用法
- 如果用于类定义时做修饰,那么这个类将不能被继承,如下列定义的类不允许被继承
class Phone8848 final {};
- 用于类在继承上一个类的过程中做修饰,那么当前类将不允许被继承,XiaoMi3 类之后不允许被继承
class XiaoMi {};
class XiaoMi2: public XiaoMi {};
class XiaoMi3 final: public XiaoMi2{};
- 如果用于修饰虚函数(不允许用来修饰其他函数),子类可以继承父类的该函数,但是子类不允许对该函数进行修改
class XiaoMi{
virtual void milioa() final;
};
class XiaoMi2: XiaoMi {
void milioa();
};
(4) override关键字,只能用于虚函数
- 只需要在函数声明的时候用override,函数实现的时候不需要加这个关键字
class XiaoMi{
virtual void milioa();
};
class XiaoMi2: XiaoMi {
void milioa() override; //提示程序员当前函数重写了父类的方法,实际上没有什么用
};
//函数实现
void XiaoMi2::milioa()
{
}
(5) 多态使用过程中,子类的析构函数不调用,导致内存泄露问题
- 以下案例,打印如下结果,有可能提示内存泄漏,有可能根本不提示,但是实际上有内存泄漏,因为没有delete指针
case 3情况,使用了多态,用父类指针指向子类对象,但是释放内存的时候,不调用子类的析构函数,导致内存泄漏 - 解决方案是:在父类的析构函数前面加关键字virtual,把Father类的析构函数定义为virtual函数时,如果对father类的指针使用delete操作时,就会对指针使用动态析构,所谓动态析构:指的时如果father指针指向的子类对象,就会先调用子类的析构函数,再调用自己析构函数释放内存
- 在实际开发过程中,如果某一个类作为基类,推荐是对该类的析构函数都加上virtual关键字
#include <iostream>
#include "string"
#include<Windows.h>
#include <fstream>
#include <sstream>
using namespace std;
class Father {
public:
Father(const char* addr = "china") {
cout << "执行了Father构造函数" << endl;
int len = strlen(addr) + 1;
this->addr = new char[len];
strcpy_s(this->addr, len, addr);
};
//把Father类的析构函数定义为virtual函数时,如果对father类的指针使用delete操作时,就会对指针使用动态析构
//所谓动态析构:指的时如果father指针指向的子类对象,就会先调用子类的析构函数,再调用自己析构函数释放内存
//virtual ~Father() {
~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 = "china"):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;
return 0;
}
3. 纯虚函数定义,什么时候用到纯虚函数
- 一旦类里面使用了纯虚函数,那么这个类就是抽象类,抽象类不能用来具体化对象,抽象类的目的是用来做基类,给其他的类做继承
- 子类继承抽象类之后,如果子类没有对父类所定于的纯虚函数做实现,那么子类也是抽象类,也不能用于具体化对象,但是如果子类对纯虚函数做实现,那么子类将不再是抽象类
class Shape{
public:
Shape(const string& color = "White") { 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 = "White"):Shape(color), r(radius) {};
float area() { return 3.14 * r * r; }
private:
float r;
};
int main(void) {
Circle c1(10);
cout << c1.area() << endl;
return 0;
}