http://blog.csdn.net/alps1992/article/details/45052403

虚函数

虚函数就是用virtual来修饰的函数。虚函数是实现C++多态的基础。

虚表

每个类都会为自己类的虚函数创建一个表,来存放类内部的虚函数成员。

虚函数表指针

每个类在构造函数里面进行虚表和虚表指针的初始化。

下面看一段代码:

 //
// main.cpp
// VirtualTable
//
// Created by Alps on 15/4/14.
// Copyright (c) 2015年 chen. All rights reserved.
// #include <iostream>
using namespace std; class Base{
public:
virtual void func(){
printf("Base\n");
}
virtual void hunc(){
printf("HBase\n");
}
private:
virtual void gunc(){
printf("Base Private\n");
}
}; class Derive: public Base{
public:
virtual void func(){
printf("Derive\n");
}
}; class DeriveSecond: public Base{
public:
void func(){
printf("Second!\n");
}
}; class DeriveThird: public Base{
}; class DeriveForth: public Base{
public:
void gunc(){
printf("Derive Forth\n");
}
}; int main(int argc, const char * argv[]) {
Derive d;
Base *pb = &d;
pb->func();
// 1 输出:Derive DeriveSecond sec;
pb = &sec;
pb->func();
// 2 输出:Derive Second DeriveThird thi;
pb = &thi;
pb->func();
//3 输出:Base DeriveForth forth;
pb = &forth;
// pb->gunc();
// 4 报错
return ;
}

在这个里面我创建了一个基类Base还有其他派生类。

  • 首先// 1部分,表示了虽然我们声明的是一个Base类的指针,但是指向的是派生类的实例,所以调用的就是派生类的函数。

  • 其次// 2部分,表示的和1差不多,只不过在// 2里不是虚函数了,覆盖了父类的虚函数。但还是存放在派生类的虚表里。

  • // 3的代码里可以看到,派生类没有覆盖父类的虚函数的时候,虽然指向的是派生类的实例,但是调用的是父类的方法,是因为在继承时候,子类也有一个虚表,里面存放了父类的虚函数表。

  • // 4里是私有的虚函数是不能直接被外部调用的。

虚表详解

先看如下代码:代码来源:RednaxelaFX,编程语言厨此人我觉得很厉害,这里借用一下他的代码,无任何商用,如果有问题,请联系我删除。

 #include <string>
#include <iostream> class Object {
int identity_hash_; public:
Object(): identity_hash_(std::rand()) { } int IdentityHashCode() const { return identity_hash_; } virtual int HashCode() { return IdentityHashCode(); }
virtual bool Equals(Object* rhs) { return this == rhs; }
virtual std::string ToString() { return "Object"; }
}; class MyObject : public Object {
int dummy_; public:
int HashCode() override { return ; }
std::string ToString() override { return "MyObject"; }
}; int main() {
Object o1;
MyObject o2;
std::cout << o2.ToString() << std::endl
<< o2.IdentityHashCode() << std::endl
<< o2.HashCode() << std::endl;
} /*
Object vtable
-16 [ offset to top ] __si_class_type_info
-8 [ typeinfo Object ] --> +0 [ ... ]
--> +0 [ vptr ] --> +0 [ &Object::HashCode ]
+8 [ identity_hash_ ] +8 [ &Object::Equals ]
+12 [ (padding) ] +16 [ &Object::ToString ] MyObject vtable
-16 [ offset to top ] __si_class_type_info
-8 [ typeinfo MyObject ] --> +0 [ ... ]
--> +0 [ vptr ] --> +0 [ &MyObject::HashCode ]
+8 [ identity_hash_ ] +8 [ &Object::Equals ]
+12 [ dummy_ ] +16 [ &MyObject::ToString ] */

这里最主要的是我认为R大的这个虚表画的实在是好看。所以直接借用了,一看就比我上面自己写的代码好看多了(T T)。

首先我们学习的时候,可以暂时先无视小于0的虚表内容。从+0开始存放了vptr这个虚表指针指向了类的虚表。可以很清楚的看到在MyObject的虚表里其中HashCode 和 ToString函数已经是派生类的虚函数了,把父类的函数重写了。

所以这两个R大画的类已经很清楚的说明了类的虚表虚函数的操作。

那么有没有比较暴力的办法强行自己来控制虚表呢。其实这个来源于当时我做的一个阿里笔试题,做完当天我就看到知乎的R大已经做了详细的解释,这里还是引用他的代码好了。

虚表和虚函数地址

以下代码同出自R大之手:RednaxelaFX,编程语言厨

 #include <iostream>
using namespace std; class animal
{
protected:
int age_;
animal(int age): age_(age) { } public:
virtual void print_age(void) = ;
virtual void print_kind() = ;
virtual void print_status() = ;
}; class dog : public animal
{
public:
dog(): animal() { }
~dog() { } virtual void print_age(void) {
cout << "Woof, my age = " << age_ << endl;
} virtual void print_kind() {
cout << "I'm a dog" << endl;
} virtual void print_status() {
cout << "I'm barking" << endl;
}
}; class cat : public animal
{
public:
cat(): animal() { }
~cat() { } virtual void print_age(void) {
cout << "Meow, my age = " << age_ << endl;
} virtual void print_kind() {
cout << "I'm a cat" << endl;
} virtual void print_status() {
cout << "I'm sleeping" << endl;
}
}; void print_random_message(void* something) {
cout << "I'm crazy" << endl;
} int main(void)
{
cat kitty;
dog puppy;
animal* pa = &kitty; intptr_t* cat_vptr = *((intptr_t**)(&kitty));
intptr_t* dog_vptr = *((intptr_t**)(&puppy)); intptr_t fake_vtable[] = {
dog_vptr[], // for dog::print_age
cat_vptr[], // for cat::print_kind
(intptr_t) print_random_message
};
*((intptr_t**) pa) = fake_vtable; pa->print_age(); // Woof, my age = 1
pa->print_kind(); // I'm a cat
pa->print_status(); // I'm crazy return ;
}

我们可以看到R大干了什么!!丧心病狂的把vtable自己伪造了一个,然后放到虚表指针后面!简直佩服。看到这个代码我也是才明白,虚表可以这么操作。

虚表地址和虚函数地址

虚函数表的地址(int*)&classname)与虚函数的地址(int*)*(int*)(&classname)实际按照R大的说法,这里的int应该改成intptr_t才更好,这样能够防止在LP64模型下,函数指针是8个字节。而地址获取不全。

虚函数表的地址和虚函数地址的关系类似于: x 和 *x的关系。

C++学习 - 虚表,虚函数,虚函数表指针学习笔记的更多相关文章

  1. 从零开始学C++之虚函数与多态(一):虚函数表指针、虚析构函数、object slicing与虚函数

    一.多态 多态性是面向对象程序设计的重要特征之一. 多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态的实现: 函数重载 运算符重载 模板 虚函数 (1).静态绑定与动态绑 ...

  2. c++ 虚函数多态、纯虚函数、虚函数表指针、虚基类表指针详解

    静态多态.动态多态 静态多态:程序在编译阶段就可以确定调用哪个函数.这种情况叫做静态多态.比如重载,编译器根据传递给函数的参数和函数名决定具体要使用哪一个函数.动态多态:在运行期间才可以确定最终调用的 ...

  3. C++子类虚函数表指针

    最近看剑指offer,记录一下 #include <iostream> #include <string> #include <cctype> #include&l ...

  4. c++基础之虚函数表指针和虚函数表创建时机

    虚函数表指针 虚函数表指针随对象走,它发生在对象运行期,当对象创建的时候,虚函数表表指针位于该对象所在内存的最前面. 使用虚函数时,虚函数表指针指向虚函数表中的函数地址即可实现多态. 虚函数表 虚函数 ...

  5. C++ - 类的虚函数\虚继承所占的空间

    类的虚函数\虚继承所占的空间 本文地址: http://blog.csdn.net/caroline_wendy/article/details/24236469 char占用一个字节, 但不满足4的 ...

  6. 虚函数&&虚继承

    如果说没有虚函数的虚继承只是一个噩梦的话,那么这里就是真正的炼狱.这个C++中最复杂的继承层次在VS上的实现其实我没有完全理解,摸爬滚打了一番也算得出了微软的实现方法吧,至于一些刁钻的实现方式我也想不 ...

  7. C++虚函数表调用学习

    知识点是看 陈皓大哥的博客,代码也参考了他的,不过做了很小的改动. 原文链接:http://blog.csdn.net/haoel/article/details/1948051 #include & ...

  8. C++基础 (6) 第六天 继承 虚函数 虚继承 多态 虚函数

    继承是一种耦合度很强的关系 和父类代码很多都重复的 2 继承的概念 3 继承的概念和推演 语法: class 派生类:访问修饰符 基类 代码: … … 4 继承方式与访问控制权限 相对的说法: 爹派生 ...

  9. 子类父类(虚函数下的 引用指针 对象)->看来没有子类指针这回事

    #include<iostream> using namespace std; class Father { public: Father() { cout << " ...

随机推荐

  1. Poj1012_Joseph

    一.Description The Joseph's problem is notoriously known. For those who are not familiar with the ori ...

  2. MongoDB之二基础入门(window/linux安装启动)

    mongodb中有三元素:数据库,集合,文档,其中“集合”就是对应关系数据库中的“表”,“文档”对应“行”. 一window安装与启动 一. 下载 上MongoDB官网 ,下载页面:https://w ...

  3. 查看,上传crushmap命令

    标签(空格分隔): ceph,ceph运维,crushmap 查看crushmap命令 从mon节点获取crushmap: # ceph osd getcrushmap -o crush.map 反编 ...

  4. System.getProperty()获取系统的相关属性

    我们在编程的过程中有时候需要获取系统的相关属性,今天就让我们一起来学习学习如何获取系统的相关属性 至于System.getProperty(param)中的各个参数的概念请看下表. java.vers ...

  5. JavaScript跳转和打开新窗口

    跳转: window.location.href = "www.baidu.com"  // 跳转到百度首页,不打开新的浏览器窗口 等价于html中的<a href=&quo ...

  6. [51nod1043]幸运号码

    题意:1个长度为2N的数,如果左边N个数的和 = 右边N个数的和,那么就是一个幸运号码. 例如:99.1230.123312是幸运号码. 给出一个N,求长度为2N的幸运号码的数量.由于数量很大,输出数 ...

  7. TCP/IP四层体系结构

    1.数据链路层  2.网络层  3.传输层  4.应用层 , 其中IP是在第二层网络层中,TCP是在第3层传输层中, Internet体系结构最重要的是TCP/IP协议,是实现互联网络连接性和互操作性 ...

  8. 2、Tophat align_summary.txt and samtools flagstat accepted_hits.bam disagree

    ###https://www.biostars.org/p/195758/ Left reads: Input : 49801387 Mapped : 46258301 (92.9% of input ...

  9. HDU 5241 Friends (大数)

    题意:略. 析:答案就是32^n. 代码如下: import java.math.BigInteger; import java.util.Scanner; public class Main{ pu ...

  10. Vue 学习文档

    Vue 学习文档 vue 起步 引包 启动 new Vue(options) options: el 目的地(可以用类名.标签名等,也可以直接用mod元素) #elementId .elementCl ...