zoukankan      html  css  js  c++  java
  • C++学习

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

    虚函数

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

    虚表

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

    虚函数表指针

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

    下面看一段代码:

     1 //
     2 //  main.cpp
     3 //  VirtualTable
     4 //
     5 //  Created by Alps on 15/4/14.
     6 //  Copyright (c) 2015年 chen. All rights reserved.
     7 //
     8 
     9 #include <iostream>
    10 using namespace std;
    11 
    12 class Base{
    13 public:
    14     virtual void func(){
    15         printf("Base
    ");
    16     }
    17     virtual void hunc(){
    18         printf("HBase
    ");
    19     }
    20 private:
    21     virtual void gunc(){
    22         printf("Base Private
    ");
    23     }
    24 };
    25 
    26 class Derive: public Base{
    27 public:
    28     virtual void func(){
    29         printf("Derive
    ");
    30     }
    31 };
    32 
    33 class DeriveSecond: public Base{
    34 public:
    35     void func(){
    36         printf("Second!
    ");
    37     }
    38 };
    39 
    40 class DeriveThird: public Base{
    41 };
    42 
    43 class DeriveForth: public Base{
    44 public:
    45     void gunc(){
    46         printf("Derive Forth
    ");
    47     }
    48 };
    49 
    50 int main(int argc, const char * argv[]) {
    51     Derive d;
    52     Base *pb = &d;
    53     pb->func();
    54     // 1  输出:Derive
    55 
    56     DeriveSecond sec;
    57     pb = &sec;
    58     pb->func();
    59     // 2 输出:Derive Second
    60 
    61     DeriveThird thi;
    62     pb = &thi;
    63     pb->func();
    64     //3 输出:Base
    65 
    66     DeriveForth forth;
    67     pb = &forth;
    68 //    pb->gunc();
    69     // 4 报错
    70     return 0;
    71 }

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

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

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

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

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

    虚表详解

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

     1 #include <string>
     2 #include <iostream>
     3 
     4 class Object {
     5   int identity_hash_;
     6 
     7 public:
     8   Object(): identity_hash_(std::rand()) { }
     9 
    10   int IdentityHashCode() const     { return identity_hash_; }
    11 
    12   virtual int HashCode()           { return IdentityHashCode(); }
    13   virtual bool Equals(Object* rhs) { return this == rhs; }
    14   virtual std::string ToString()   { return "Object"; }
    15 };
    16 
    17 class MyObject : public Object {
    18   int dummy_;
    19 
    20 public:
    21   int HashCode() override           { return 0; }
    22   std::string ToString() override   { return "MyObject"; }
    23 };
    24 
    25 int main() {
    26   Object o1;
    27   MyObject o2;
    28   std::cout << o2.ToString() << std::endl
    29             << o2.IdentityHashCode() << std::endl
    30             << o2.HashCode() << std::endl;
    31 }
    32 
    33 /*
    34               Object                      vtable
    35                                -16 [ offset to top     ]  __si_class_type_info
    36                                -8  [ typeinfo Object   ] --> +0 [ ... ]
    37 --> +0  [ vptr           ] --> +0  [ &Object::HashCode ]
    38     +8  [ identity_hash_ ]     +8  [ &Object::Equals   ]
    39     +12 [ (padding)      ]     +16 [ &Object::ToString ]
    40 
    41              MyObject                     vtable
    42                                -16 [ offset to top       ]  __si_class_type_info
    43                                -8  [ typeinfo MyObject   ] --> +0 [ ... ]
    44 --> +0  [ vptr           ] --> +0  [ &MyObject::HashCode ]
    45     +8  [ identity_hash_ ]     +8  [ &Object::Equals     ]
    46     +12 [ dummy_         ]     +16 [ &MyObject::ToString ]
    47 
    48 */

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

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

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

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

    虚表和虚函数地址

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

     1 #include <iostream>
     2 using namespace std;
     3 
     4 class animal
     5 {
     6 protected:
     7   int age_;
     8   animal(int age): age_(age) { }
     9 
    10 public:
    11   virtual void print_age(void) = 0;
    12   virtual void print_kind() = 0;
    13   virtual void print_status() = 0;
    14 };
    15 
    16 class dog : public animal
    17 {
    18 public:
    19   dog(): animal(2) { }
    20   ~dog() { }
    21 
    22   virtual void print_age(void) {
    23     cout << "Woof, my age = " << age_ << endl;
    24   }
    25 
    26   virtual void print_kind() {
    27     cout << "I'm a dog" << endl;
    28   }
    29 
    30   virtual void print_status() {
    31     cout << "I'm barking" << endl;
    32   }
    33 };
    34 
    35 class cat : public animal
    36 {
    37 public:
    38   cat(): animal(1) { }
    39   ~cat() { }
    40 
    41   virtual void print_age(void) {
    42     cout << "Meow, my age = " << age_ << endl;
    43   }
    44 
    45   virtual void print_kind() {
    46     cout << "I'm a cat" << endl;
    47   }
    48 
    49   virtual void print_status() {
    50     cout << "I'm sleeping" << endl;
    51   }
    52 };
    53 
    54 void print_random_message(void* something) {
    55   cout << "I'm crazy" << endl;
    56 }
    57 
    58 int main(void)
    59 {
    60   cat kitty;
    61   dog puppy;
    62   animal* pa = &kitty;
    63 
    64   intptr_t* cat_vptr = *((intptr_t**)(&kitty));
    65   intptr_t* dog_vptr = *((intptr_t**)(&puppy));
    66 
    67   intptr_t fake_vtable[] = {
    68     dog_vptr[0],         // for dog::print_age
    69     cat_vptr[1],         // for cat::print_kind
    70     (intptr_t) print_random_message
    71   };
    72   *((intptr_t**) pa) = fake_vtable;
    73 
    74   pa->print_age();    // Woof, my age = 1
    75   pa->print_kind();   // I'm a cat
    76   pa->print_status(); // I'm crazy
    77 
    78   return 0;
    79 }

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

    虚表地址和虚函数地址

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

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

  • 相关阅读:
    Exaple2_1(显示转换)
    Example2_4(数据的输入Scanner)
    安装jdk遇到的问题
    Java应用程序,用户从键盘只能输入整数,程序输出这些整数的乘积
    Hello.Java//Tom and Jerry
    Example2_3(数据输出System.out.printf)
    Example2_2(基本类型转换)
    c++与java的区别
    大龄屌丝自学笔记Java零基础到菜鸟004
    大龄屌丝自学笔记Java零基础到菜鸟003
  • 原文地址:https://www.cnblogs.com/shihuvini/p/8516911.html
Copyright © 2011-2022 走看看