C++ 类的虚表 20130929

关键技术:封装、继承、组合、虚函数、抽象基类、动态绑定、多态性等等

1.首先整理一下在阿里巴巴面试遇到的函数虚表的问题。

在C++中的Class中的函数式存储在Class数据机构的虚表中。每一个Class对应的所有的函数地址都会在Class的数据结构虚表中,每一个Class的对象在对象开始的地方都会有一个指针(计算机的位数一般是是32位)指向Class的函数虚表,函数虚表中每一个函数地址是按照在Class中声明的顺序。一般也是一个32bit的指针。

C++的多态机制正是基于这种虚标实现的。

首先看一个父类:

class Base {public:

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

};

这样Class Base的函数虚表中就是三个函数指针,分别指向这三个对应的函数,但是我们如何获取这些函数指针的值。

我们声明一个对象Base base;

这样在对象base中的首地址开始的一个机器长度的空间是一个指针,指向的是虚表的首地址,其中虚表的首地址第一个函数指针就是指向class中的第一个声明的函数。然后对该函数指针++ 操作便可以指向下一个函数,这也正是可以通过函数指针访问private函数的方式。(防君子不防小人)。

一般继承中是没有函数覆盖的,所以在虚表中首先是父类的函数指针,后面是子类的函数指针。

继承中有虚函数覆盖的情况,则子类中的Class虚表中对应的函数指针指向的是子类的函数地址,其他的不变。

对于多重继承,则在对象的首地址前面有n个父类的虚表指针,分别指向对应父类的虚表,子类的函数地址存放在第一个父类的虚表中。这样是为了解决不同的父类指针指向同一个子类的对象的时候,可以调用实际对应的函数。

当出现覆盖的情况的时候,就会将这些函数虚表中的虚函数换成对应子类的虚函数地址,在调用的时候会根据父类指针的类型,分别调用不同的父类的虚函数,而对于多态,则调用子类中的虚函数,在三个对应的虚表中,所有的改虚函数指针都会被修改指向子类的虚函数地址。

对于子类实例中的虚函数表,是下面这个样子:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

Derive d;Base1 *b1 = &d;Base2 *b2 =
&d;Base3 *b3 = &d;

b1->f(); //Derive::f()

b2->f(); //Derive::f()

b3->f(); //Derive::f()

b1->g(); //Base1::g()

b2->g(); //Base2::g()

b3->g(); //Base3::g()

存在的一些不友好的地方:任何使用父类的指针调用子类没有覆盖父类成员函数的行为都会被编译器视为非法行为,出现编译出错的问题。但是可以在运行期间通过指针的方式访问虚函数表来达到违反C++语义的行为。

正常的方式访问

typedef
void(*Fun)(void);//

这是一个函数指针,使用Func
来表示指向一个函数,该函数的返回值类型是
void,参数是void。使用方式: Func
pFunc = NULL ; 指向一个函数指针

pFunc=(Fun)*((int*)*(int*)(&base));

class
Base{

public:

virtual
void
fun_a(void){

cout
<< "base::fun_a"
<< endl;

}

virtual
void
fun_b(void){

cout
<< "base::fun_b"
<< endl;

}

virtual
void
fun_c(void){

cout
<< "base::fun_c"
<< endl;

}

private:

int
a;

};

class
Derive:
public
Base{

public:

virtual
void
fun_a(void){

cout
<< "derive::fun_a"
<< endl;

}

virtual
void
fun_b(void){

cout
<< "derive::fun_b"
<< endl;

}

virtual
void
fun_d(void){

cout
<< "derive::fun_d"
<< endl;

}

private:

int
b;

};

int
main(){

Derive derive;

cout
<< "sizeof(derive):"<<
sizeof(derive)
<< endl;

cout
<< "derive  首地址:"  << &derive <<endl;

((Fun)*((int*)*(int*)(&derive)
))();

((Fun)*((int*)*(int*)(&derive)
+ 1 ))();

((Fun)*((int*)*(int*)(&derive)
+ 2 ))();

((Fun)*((int*)*(int*)(&derive)
+ 3 ))();

Base base;

Fun pFun = NULL;

cout
<< "对象base的地址:"
<< &base << endl;

cout
<< "第一个虚函数的地址:"
<<  (int*)*(int*)(&base)
<< endl;

pFun
= (Fun)*((int*)*(int*)(&base));

pFun();

((Fun)*((int*)*(int*)(&base)
+ 1))();

((Fun)*((int*)*(int*)(&base)
+ 2))();

cout
<<"sizeof(base):"<<
sizeof(base)
<< endl;

return
0;

}

防君子不防小人的实现:

对于父类中的private and protected修饰的虚函数,在继承的时候,同样会存在在函数的虚表中,这样的话我们便可以通过访问虚函数的方式访问这些非public函数。

typedef
void(*Fun)(void);

class
Base{

private:

virtual
void
fun_a(void){

cout
<< "base::fun_a"
<< endl;

}

virtual
void
fun_b(void){

cout
<< "base::fun_b"
<< endl;

}

virtual
void
fun_c(void){

cout
<< "base::fun_c"
<< endl;

}

private:

int
a;

};

class
Derive:
public
Base{

private:

int
b;

};

int
main(){

Derive derive;

Base* base = &derive;

Fun pFun = NULL;

pFun
= (Fun)*(int*)*(int*)&(*base);

pFun();

((Fun)* ((int*)*(int*)&(*base)
+ 1 ) )();

((Fun)* ((int*)*(int*)&(*base)
+ 2 ) )();

return
0;

}

C++复习7.虚表的概念的更多相关文章

  1. Delegate 委托复习(-) 委托的基本概念

    1. 声明一个delegate对象,它应当与你想要传递的方法具有相同的参数和返回值类型.      声明一个代理的例子:     public delegate int MyDelegate(stri ...

  2. [白话解析] 通过实例来梳理概念 :准确率 (Accuracy)、精准率(Precision)、召回率(Recall)和F值(F-Measure)

    [白话解析] 通过实例来梳理概念 :准确率 (Accuracy).精准率(Precision).召回率(Recall)和F值(F-Measure) 目录 [白话解析] 通过实例来梳理概念 :准确率 ( ...

  3. Javascript操作DOM常用API总结

    基本概念 在讲解操作DOM的api之前,首先我们来复习一下一些基本概念,这些概念是掌握api的关键,必须理解它们. Node类型 DOM1级定义了一个Node接口,该接口由DOM中所有节点类型实现.这 ...

  4. NOIP2003pj栈[卡特兰数]

    题目背景 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表. 栈有两种最重要的操作,即pop(从栈顶弹出一个元素)和push(将一个元素进栈). 栈的重要性不言自明,任何 ...

  5. AC日记——codevs 1086 栈 (卡特兰数)

    题目描述 Description 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表. 栈有两种最重要的操作,即pop(从栈顶弹出一个元素)和push(将一个元素进栈). ...

  6. Oracle 学习系列之二(会话与事务级临时表和dual表 )

    一. 会话临时表 --创建会话临时表create global temporary table tmp_user_session(user_id int, user_name varchar2(20) ...

  7. [NOIP2003]栈

    2003年普及组 题目背景 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表. 栈有两种最重要的操作,即pop(从栈顶弹出一个元素)和push(将一个元素进栈). 栈的重 ...

  8. JavaScript 操作 DOM 常用 API 总结

    文本整理了javascript操作DOM的一些常用的api,根据其作用整理成为创建,修改,查询等多种类型的api,主要用于复习基础知识,加深对原生js的认识. 基本概念 在讲解操作DOM的api之前, ...

  9. 怎样从一个DLL中导出一个C++类

    原文作者:Alex Blekhman    翻译:朱金灿 原文来源: http://www.codeproject.com/KB/cpp/howto_export_cpp_classes.aspx 译 ...

随机推荐

  1. angular-selcet

    常规用法代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> < ...

  2. Python中常用技巧整理

    Python中os.path的妙用  http://xpleaf.blog.51cto.com/9315560/1736956

  3. OpenStack、KVM、VMWare和Docker

    一.虚拟化 1.什么是虚拟化 虚拟化,是指通过虚拟化技术将一台计算机虚拟为多台逻辑计算机.在一台计算机上同时运行多个逻辑计算机,每个逻辑计算机可运行不同的操作系统,并且应用程序都可以在相互独立的空间内 ...

  4. FTP 两种工作模式

    主动模式port FTP主动模式:TCP链接客户端访问FTP,客户端会开启一个大于1024的端口N访问FTP的21端口(控制端口),并通过21端口发送port命令与N+1的端口,服务端收到命令后会使用 ...

  5. 深入学习js之——词法作用域和动态作用域

    开篇 当我们在开始学习任何一门语言的时候,都会接触到变量的概念,变量的出现其实是为了解决一个问题,为的是存储某些值,进而,存储某些值的目的是为了在之后对这个值进行访问或者修改,正是这种存储和访问变量的 ...

  6. 重新想,重新看——CSS3变形,过渡与动画③

    这一篇主要谈谈CSS3的过渡属性. 过渡属性被设计的十分通俗易懂,属性写法为transition,有四个子属性: <transition-property> 表示需要过渡的属性[必须](本 ...

  7. MySQL表损坏修复【Incorrect key file for table】

    今天机房mysql服务器异常关机,重新启动后报错如下: -- :: [ERROR] /usr/local/mysql/bin/mysqld: Incorrect key file for table ...

  8. OpenStack与Hadoop的区别与联系

    Openstack是云操作系统,是将物理机虚拟化的云服务平台,包含各种管理组件及API.Hadoop则是“云计算”中分布式计算核心:存储与计算.但其两者面向是不同层面的.举个例子:比如现有多台底层的物 ...

  9. unix_timestamp() 和 from_unixtime()

    unix_timestamp() 将时间转换为时间戳.(date 类型数据转换成 timestamp 形式整数) select unix_timestamp('2016-03-23 11:10:10' ...

  10. Django Nginx配置

    1.安装uwsgi.flup.djangowget http://www.saddi.com/software/flup/dist/flup-1.0.2.tar.gz 2.项目创建和配置2.1.创建项 ...