zoukankan      html  css  js  c++  java
  • 虚函数表

    (https://www.cnblogs.com/vipchenwei/p/7466018.html?utm_source=debugrun&utm_medium=referral)

    一、

    VPTR指针:虚指针(含有虚函数的类对象中都有一个需指针)

    虚函数表:含有虚函数的类对象中都有一个虚函数表

      这里我们主要来探究一下,编译器在什么地方动了手脚,从而支持了多态?

      从一段代码来分析:

      下面代码,我把C++编译器可能动手脚地方标注出来,看看编译器到底是在什么时候就实现不同对象能调用同名函数绑定关系。

    class Parent{
        public:
            Parent(int a=0){
                this->a = a;}
            virtual void print(){ //地方1  
                cout<<"parent"<<endl;}  
        private:
            int a;
    };
    
    class Son:public Parent{
        public:
           Son(int a=0,int b=0):Parent(a){
               this->b = b;}
           void print(){
               cout<<"Son"<<endl;}
        private:
            int b;
        };
    
      void play(Parent *p){ //地方2  
            p->print();}
    
        void main(int argc, char const *argv[])
        {
            Parent p; //地方3  
            Son s;
            play(&s)
            return 0;
        }

     在地方1处,我们知道,既然函数被声明了virtual,编译器会做特殊处理,会不会是这里确定了绑定关系呢?

      在地方2处,我们知道,当传来子类对象, 执行子类函数, 传来父类对象,执行父类对象,多态就是在这里发生的,那会不会在这里呢?

      但是,恰恰我们最容易忽视就是在地方3处,真正确定绑定关系的地方,就是创建对象的时候!!这时候C++编译器会偷偷的给对象添加一个vptr指针。

      只要我们在类中定义了virtual函数,那么我们在定义对象的时候,C++编译器会在对象中存储一个vptr指针,类中创建的虚函数的地址会存放在虚函数表中vptr指针就是指向这个表的首地址

     

     

    在发生多态的地方,编译器根本不会去区分,传进来的是子类对象还是父类对象。而是关心print()是否为虚函数,

    如果是虚函数,就根据不同对象的vptr指针找属于自己的函数。

    而且父类对象和子类对象都有vptr指针,传入对象不同,编译器会根据vptr指针,到属于自己虚函数表中找自己的函数。即:vptr--->虚函数表------>函数的入口地址,从而实现了迟绑定(在运行的时候,才会去判断)。

      如果不是虚函数,那么这种绑定关系在编译的时候就已经确定的,也就是静态联编!

      这里,关于虚函数表要说明两点:

      说明1:通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,(运行时多态)

    因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。

      说明2:出于效率考虑,没有必要将所有成员函数都声明为虚函数

      说明3 :C++编译器,执行非虚函数,不需要区分是子类对象还是父类对象

      最后,我们来总结一下多态的实现原理:   

    • 当类中声明虚函数时,编译器会在类中生成一个虚函数表
    • 虚函数表是一个存储类成员函数指针的数据结构
    • 虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中

     

    二、

    问题:构造函数中能调用虚函数,实现多态么?

      等价于:对象中的vptr指针什么时候初始化?

    class Parent{
    public:
        Parent(int a=0){
                this->a = a;
                print();}
        virtual void print(){cout<<"Parent"<<endl;}
    private:
        int a;
    };
    class Son:public Parent{
        Son(int a=0,int b=0):Parent(a){
            this->b = b;
            print();}
        virtual void print(){cout<<"Son"<<endl;}
    };
    void main(int argc, char const *argv[]){
            Son s;
            return 0;
    }

    当我们定义对象的时候,会执行构造函数,但是,在构造函数里面,我们调用了虚函数print(),那么这里的print()会执行哪个?会发生多态么??

      测试发现:两个类中构造函数中,都只会调用自己类中的print()函数。

      为什么会这样?为什么没有发生多态?(注:子类对象在构造时,是从基类开始,先调用基类的构造函数,再调用子类的构造函数)

     

    https://www.cnblogs.com/hushpa/p/5707475.html

    1. 基础知识:
    (1) 32位os 指针长度为4字节, 64位os 指针长度为8字节, 下面的分析环境为64位 linux & g++ 4.8.4.
    (2) new一个对象时, 只为类中成员变量分配空间, 对象之间共享成员函数(通过this参数,区分不同的对象)。

    2 虚函数表
        包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr.

    _vptr存放在对象内存的开始, vs和g++都是。


        虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。

    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 Derive : public Base{
    public:
        void g() {cout<<"derive::g"<<endl;}
    };

     5、多继承

  • 相关阅读:
    MySQL-基本sql命令
    Java for LeetCode 203 Remove Linked List Elements
    Java for LeetCode 202 Happy Number
    Java for LeetCode 201 Bitwise AND of Numbers Range
    Java for LeetCode 200 Number of Islands
    Java for LeetCode 199 Binary Tree Right Side View
    Java for LeetCode 198 House Robber
    Java for LeetCode 191 Number of 1 Bits
    Java for LeetCode 190 Reverse Bits
    Java for LeetCode 189 Rotate Array
  • 原文地址:https://www.cnblogs.com/pjl1119/p/9707905.html
Copyright © 2011-2022 走看看