C++ 虚函数理解

虚函数

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态“。

虚函数主要解决的是C++中的多态这一特性。多态分为两类

  • 静态多态: 函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

虚函数例子

//基类
class Base {
public:
	virtual void f() {
		cout << "Base::f()" << endl;
	}
};
//子类继承基类
class Son :public Base {
public:
	virtual void f() {
		cout << "Son::f()" << endl;
	}
};

void test() {
	Base *b;
	b = new Son;
	b->f();
	b->g();
}

int main() {
	test();
	system("pause");
	return 0;
}

Base作为基类,在Base中只定义了虚函数f(),在子类Son中重定义了f()的实现,即使我们定义的都是父类Base,将Base初始化指向一个子类Son(注意这里必须是指针)Base *b = new Son。这样做的好处是,我们还可以定义Son2、Son3都是继承自Base,他们有各自的f()的实现,我们可以统一都定义为Base *b,但是在开辟堆的时候,可以开辟不同的子类,例如:

Base *b1 = new Son1;
Base *b2 = new Son2;

这样就可以实现多态。

虚函数查看方法(VS2017)

在cmd中找到VS2017的开发人员命令提示符 -> 进入cpp所在文件路径 ->输入
cl /d1 reportSingleClassLayout类名 文件名.cpp
例如:
cl /d1 reportSingleClassLayoutBase 虚函数.cpp

虚函数的实现原理

我们都知道,如果一个类是空(没有成员也没有方法),类的sizeof是1。而如果一个类中只有一个虚函数,他的sizeof是4,正好是一个指针的大小,这个指针就是vfptr,虚函数指针,指向的是虚函数表中的虚函数。虚函数表为vftable。

可以看到Base类,他的vfptr实际上指向了vftable中的某一项,两个虚函数f、g,分别在vftable中占据一项,调用的时候,利用vfptr去vftable中找到对应的函数地址进行调用。
在这里插入图片描述
同样的,我们再来看看Son类
在这里插入图片描述
可以看到,在子类Son中重写的虚函数,其实就是更新了vftable表中的函数,直接替代掉了父类中的函数,所以我们用子类去初始化父类,在调用函数,调用到的就是子类的函数,而不再是父类的函数。

一般继承(无虚函数覆盖)

在这里插入图片描述
对于这种一般继承,子类没有重写父类的虚函数,虚函数表是这样的
在这里插入图片描述

1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面

一般继承(有虚函数覆盖)

在这里插入图片描述
对于这种子类有重写父类的虚函数(我们上面的例子),虚函数表会发生变化:
在这里插入图片描述

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。

多重继承下的虚函数

多重继承就很简单了,C++允许一个类继承多个类,因此可以让一个Son继承Base1、Base2,他们的虚函数关系就是简单的相加,如果有同名的,全部覆写。
例子:

//基类1
class Base1 {
public:
	virtual void f() {
		cout << "Base1::f()" << endl;
	}
	virtual void g() {
		cout << "Base1::g()" << endl;
	}
};
//基类2
class Base2 {
public:
	virtual void f() {
		cout << "Base2::f()" << endl;
	}
	virtual void g() {
		cout << "Base2::g()" << endl;
	}
};
//子类继承基类
class Son :public Base1, public Base2 {
public:
	virtual void f() {
		cout << "Son::f()" << endl;
	}
	virtual void h() {
		cout << "Son::h()" << endl;
	}
};

子类Son继承了父类Base1、Base2,但是只对父类的f()进行了重写,新引入了一个虚函数h(),我们可以看到此时的虚函数表:
在这里插入图片描述
他有两个父类,所以划分了两个虚函数表Base1和Base2,两个指针vfptr分别指向。Base1中的f()和Base2中的f()都被Son重写的f()覆盖了,但是g()保留了,而且子类新引入的h()添加到了Base1表的最后。

用图例简单理解下:

多重继承(无虚函数覆盖)

在这里插入图片描述
对应的虚函数表:
在这里插入图片描述

1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

多重继承(有虚函数覆盖)

在这里插入图片描述
对应的虚函数表:
在这里插入图片描述

1)对于同名的(子类重写的虚函数),直接替换虚函数表中的父类
2)如果子类重写的虚函数只有部分父类有,则只替换那部分父类(按照虚函数名,只要子类重写了,就要替换父类中同名的,一切以子类重写的为准)
3)子类中新定义的虚函数直接放到了第一个父类的表中。(同样的,第一个父类是按照声明顺序来判断的)

纯虚函数

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
当类中有了纯虚函数,这个类也称为抽象类。抽象类的特点:

1.无法实例化对象
2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类

纯虚函数语法virtual 返回值类型 函数名 (参数列表)= 0 ;
示例:virtual void f() = 0;

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值