笔记:C++虚函数

来源:互联网 发布:数据库输入数据语句 编辑:程序博客网 时间:2024/05/21 10:29

  C++的多态性分为两种:静态多态(编译时多态)和动态多态(运行时多态)。静态多态主要通过重载和模板来实现,动态多态是子类覆盖父类的虚函数,定义一个父类的指针,使指针指向子类实例,这样,父类就可以访问子类的方法,如果把父类的指针指向不同的子类实例,父类就可以访问不同的方法。

class Base {public:    int data;    void myFunc() { cout << "myFunc" << endl; }    virtual void f() { cout << "Base::f()" << endl; }    virtual void g() { cout << "Base::g()" << endl; }    virtual void h() { cout << "Base::h()" << endl; }};class Derive : public Base {public:    virtual void g() { cout << "Derive::g()" << endl; }};Base b;Base *ptr = new Derive();ptr->g();    //Derive::g()

  在创建带有虚函数的类的实例时,会创建一个指向虚函数表的指针,这个指针被存在实例的开始处,虚函数表中保存着虚函数的地址,如图:
 

 这里写图片描述

  我们可以这样获取到虚函数的地址:&b表示类的首地址,由于类内存表中第一个位置存的是指针,所以先把地址强制转换成指针类型,就可以取出第一个值*(int*)(&b),这个值就是指向虚函数的指针vptr(g++中指向虚函数表的指针放在开始处),vptr就是虚函数表的首地址,同理,虚函数表中存的是指针,所以强制转换后取值*(int*)*(int*)(&b)就是函数 f 的地址,*((int*)*(int*)(&b)+1) 就是 g 的地址。要想输出虚函数,需要强制转换成函数指针。

    typedef void(*pfunc)(void);    Base b;    pfunc p;    p = (pfunc)*((int*)*(int*)(&b)+1); //强制转换成函数指针    p();  //Base::g();

注:忽略了一点,64位时指针是8个字节,而不是4个字节,所以,上述int*中的int改为long更恰当。

  
  
  子类继承基类时,虚函数表中会包含子类和基类的全部虚函数

class Derive : public Base{public:    virtual void df() { cout << "Derive::df()" << endl;    virtual void dg() { cout << "Derive::dg()" << endl;    virtual void dh() { cout << "Derive::dh()" << endl;}

这里写图片描述

  
  如果子类的虚函数覆盖了父类的虚函数,虚函数表中父类的虚函数将会被子类的虚函数替代。

class Derive : public Base{public:    virtual void f() { cout << "Derive1::f()" << endl;    virtual void dg() { cout << "Derive1::dg()" << endl;    virtual void dh() { cout << "Derive1::dh()" << endl;}Derive d;Base * b = &d;b->f();  //Derive::f()

这里写图片描述

  所以,前面的例子中,父类的指针Base * ptr = new Derive(),访问 f 方法时(ptr->f()),访问到的是子类的方法。
  多重继承时,有几个含有虚函数的父类,就会创建几个指向虚函数表的指针,每个虚函数表都存着对应的父类的虚函数的地址,其中,子类的虚函数地址保存在第一个父类的虚函数表中。

class Base1 {public:    virtual void f() { cout << "Base1::f()" << endl; }    virtual void g() { cout << "Base1::g()" << endl; }    virtual void h() { cout << "Base1::h()" << endl; }};class Base2 {public:    virtual void f() { cout << "Base2::f()" << endl; }    virtual void g() { cout << "Base2::g()" << endl; }    virtual void h() { cout << "Base2::h()" << endl; }};class Derive : public Base1, public Base2 {public:    virtual void df() { cout << "Derive::df()" << endl; };Derive d;Base1 * ptr1 = &d;Base2 * ptr2 = &d;ptr1->f();  //Base1::f()ptr2->f();  //Base2::f()

这里写图片描述

  两个父类Base1和Base2的指针虽然都指向子类,但是其实地址是不同的,ptr1对应的是vptr1指向的对象,ptr2对应的是vptr2指向的对象。
  
  子类覆盖父类方法时:

class Derive : public Base1, public Base2 {public:    virtual void f() { cout << "Derive::f()" << endl;     }Derive d;Base1 * ptr1 = &d;Base2 * ptr2 = &d;ptr1->f();  //Derive::f()ptr2->f();  //Derive::f()

这里写图片描述

原创粉丝点击