(C/C++学习)4.C++类中的虚函数表Virtual Table
说明:C++的多态是通过一张虚函数表(Virtual Table)来实现的,简称为V-Table。在这个表中,主要为一个类的虚函数的地址表,这张表解决了继承、覆写的问题,保证其真实反应实际的虚函数调用过程。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。
下面介绍一下与这张虚函数表有关的几个问题:
1.普通成员函数不占存储空间,而所有虚函数入口地址存储在一张虚函数表中,由一个指针指向该虚函数表;
2.指向该虚函数表的指针位于类实例对象内存的最前面,占四个字节;
3.若子类覆写了父类的虚函数,则父类的虚函数被覆盖,即虚函数表中只存在子类的虚函数地址;否则,父类和子类的虚函数都存在于虚函数表中(当然,没有覆写父类的虚函数是毫无意义的),这就是多态形成的原因。
通过上面的介绍,我们对虚函数表有了大致的了解,下面通过一个实例来加深一下认识:
#include <iostream>
using namespace std; class base
{
public:
virtual void f(){cout<<"base::f()"<<endl;}
virtual void g(){cout<<"base::g()"<<endl;}
virtual void h(){cout<<"base::h()"<<endl;}
private:
int a;
}; //定义一个函数指针,并别名为pfunc,用时不需再加*,
typedef void (*pfunc)(void); int main()
{
base b; //C++编译器使虚函数表的指针存在于对象实例中的最前面(四个字节)
cout<<"sizeof(base) = "<<sizeof(base)<<'\t'<<"sizeof(b) = "<<sizeof(b)<<endl<<'\n'; //分别打印对象b的起始地址和虚函数表中首个函数指针指向的地址
//对象实例最前面的四个字节为指向虚函数表的指针,取内容后才为虚函数表
cout<<"&b = "<<&b<<"\t\t"<<"&VTable = "<<(int **)*(int *)(&b)<<endl<<"\n\n"; pfunc pf;
//定义一个函数指针
void(*p)(void);
//还可以这样定义一个函数指针 //虚函数表里面存放的是指向各个虚函数的指针,取内容后才是各个相应的虚函数
pf = (pfunc)*((int **)*(int *)(&b)+0);
pf();
pf = (pfunc)*((int **)*(int *)(&b)+1);
pf();
pf = (void(*)())*((int **)*(int *)(&b)+2);
pf(); cout<<"\n\n"; p = (pfunc)*((int **)*(int *)(&b)+0);
p();
p = (void(*)())*((int **)*(int *)(&b)+1);
p();
p = (void(*)())*((int **)*(int *)(&b)+2);
p(); return 0;
}
程序运行结果:
通过以上示例,我们把类实例对象b取址,然后将&b强转成int*型,然后对其取内容,取得虚函数表的地址,然后再对其取内容,就得到了第一个虚函数的地址了,然后再将其通过(int**)强转成步长为4的指针,通过加1来得到虚函数表中不同的虚函数的地址,最终强转成为函数指针,再通过该函数指针访问相应的虚函数.
5.下面我们将通过几个例子来解释一下虚函数表的存在形式,在这部分,主要弄清楚虚函数表是怎么一回事,至于程序运行结果,读者自行实验。
a.在父子类中,若子类没有对父类的虚函数进行覆写(当然,前面提到过,没有覆写父类的虚函数是毫无意义的。之所以要讲述没有覆写的情况,主要目的是为了给一个对比,在比较之下,我们可以更加清楚地知道其内部的具体实现),如下代码,
#include<iostream>
using namespace std;
class base
{
public:
virtual void func(){};
virtual void foo(){};
};
class derive:public base
{
public:
virtual void func1(){};
virtual void foo1(){};
};
int main()
{
derive d;
return 0;
}
则其虚函数表如下所示:
注意:
1.上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。
2.虚函数是按照其声明顺序放于表中的。
3.父类的虚函数在子类的虚函数前面。
b.在父子类中,若子类对父类的虚函数进行了覆写(为了对比,假设只覆写父类一个虚函数),如下代码,
#include<iostream>
using namespace std;
class base
{
public:
virtual void func(){};
virtual void foo(){};
virtual ~base(){}
};
class derive:public base
{
public:
virtual void func(){cout<<"___"<<endl;};
virtual void foo1(){};
virtual ~derive(){}
};
int main()
{
base *p = new derive;
p->func();
delete p;
return 0;
}
则其虚函数表如下所示:
由此,可得覆写的子类func()放在了虚函数表中原来父类func()的位置,没有覆写的虚函数依旧原样存放。这样,在上述代码中,由于p所指的func()的位置已经被derive::func()的函数地址所取代,因此在发生实际调用的时候,调用的是子类的func(),这就实现了多态。
(C/C++学习)4.C++类中的虚函数表Virtual Table的更多相关文章
- C++中的虚函数表是什么时期建立的?
虚函数表是在什么时期建立的? 最近参加阿里巴巴公司的内推,面试官问了“虚函数表是在什么时期建立的?”.因为以前对虚函数表的理解不够多,所以就根据程序构建(Build)的四个过程(预编译.编译.汇编和链 ...
- C++中的虚函数表
(感谢http://blog.csdn.net/haoel/article/details/1948051/) C++中的虚函数的作用主要是实现了多态的机制. 多态,简而言之就是用父类型别的指针指向其 ...
- C++ 中的虚函数表及虚函数执行原理
为了实现虚函数,C++ 使用了虚函数表来达到延迟绑定的目的.虚函数表在动态/延迟绑定行为中用于查询调用的函数. 尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的. 首先,每个包含虚 ...
- 在基类中的析构函数声明为virtual
#include <iostream> using namespace std; class Father { public: ~Father() { cout << &quo ...
- c++ 继承类强制转换时的虚函数表工作原理
本文通过简单例子说明子类之间发生强制转换时虚函数如何调用,旨在对c++继承中的虚函数表的作用机制有更深入的理解. #include<iostream> using namespace st ...
- 深入理解类成员函数的调用规则(理解成员函数的内存为什么不会反映在sizeof运算符上、类的静态绑定与动态绑定、虚函数表)
本文转载自:http://blog.51cto.com/9291927/2148695 总结: 一.成员函数的内存为什么不会反映在sizeof运算符上? 成员函数可以被看作是类 ...
- C++ 类中有虚函数(虚函数表)时 内存分布
虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表 ...
- C++ 类的存储方式以及虚函数表
一.C++成员函数在内存中的存储方式 用类去定义对象时,系统会为每一个对象分配存储空间.如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间.按理说,如果用同一个类定义了10个对象,那么就 ...
- C++虚函数表解析(图文并茂,非常清楚)( 任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法)good
C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术 ...
随机推荐
- LeetCode 690. Employee Importance (职员的重要值)
You are given a data structure of employee information, which includes the employee's unique id, his ...
- LeetCode 242. Valid Anagram (验证变位词)
Given two strings s and t, write a function to determine if t is an anagram of s. For example,s = &q ...
- Codeforces Round #273 (Div. 2)D. Red-Green Towers DP
D. Red-Green Towers There are r red and g green blocks for construction of the red-green tower. Re ...
- Polymorphism (C# Programming Guide)
https://msdn.microsoft.com/en-us/library/ms173152.aspx Polymorphism is often referred to as the thir ...
- WCF Odata 开放数据协议应用
OData简介 说起 WCF Data Service ,不得不说的是 OData.对于一个标准的 Web 服务,它往往会提供了一些功能,比如说:订货.退货这些,然后使用者通过HTTP协议来使用这些功 ...
- POJ 3264 Balanced Lineup 区间最值
POJ3264 比较裸的区间最值问题.用线段树或者ST表都可以.此处我们用ST表解决. ST表建表方法采用动态规划的方法, ST[I][J]表示数组从第I位到第 I+2^J-1 位的最值,用二分的思想 ...
- bzoj2648
http://www.lydsy.com/JudgeOnline/problem.php?id=2648 kdtree裸题... 抄板子一边抄对了... 挺好理解的,就是说我们先找出中间的元素,然后小 ...
- akka设计模式系列-Backend模式
上一节我们介绍了Akka使用的基本模式,简单点来说就是,发消息给actor,处理结束后返回消息.但这种模式有个缺陷,就是一旦某个消息处理的比较慢,就会阻塞后面所有消息的处理.那么有没有方法规避这种阻塞 ...
- “国家队爷”杯液体战争AI比赛!!__SymenYang
原帖 这两天一直在搞这个AI,提供的样例更本不是我的风格啊,看不懂更不会改... 所以我自己写了一个AI的平台,现在在不断的修改AI的策略,smart样例还是很容易过的,让line的行走速度变慢一点到 ...
- Servlet访问路径的两种方式、Servlet生命周期特点、计算服务启动后的访问次数、Get请求、Post请求
Servlet访问路径的两种方式: 1:注解 即在Servlet里写一个@WebServlet @WebServlet("/myServlet") 2:配置web.xml < ...