zoukankan      html  css  js  c++  java
  • 【C++】虚函数

    I 动态绑定、多态、虚函数、对象的静态类型与动态类型

      1、基类中有两种函数:

      • 派生类直接继承不做改变
      • 派生类重新定义成适合自身的版本覆盖掉基类的函数

      对于第一种就是普通的基类成员函数,第二种通常通过将该函数定义为虚函数来实现。

      

      2、对于基类对象的引用或指针,由于继承关系会有两种不同的类型:

      • 静态类型:指针或引用定义时的类型
      • 动态类型:指针或引用实际指向的对象类型

      对于对象的引用或者指针,可以实现从派生类向基类的类型转换,以下代码是合法的:

    class Base{
            virtual void print(){...}
    };
    
    class Derive:public Base{
            void print(){...}
    };
    
    Derive d;
    Base &b1 = d;
    Base *b2 = &d;

      但是不存在从基类到派生类的类型转换,因为派生类都包含基类部分,说白了,就是基类有的派生类都有,派生类基类不一定有,可以说苹果是水果,不能说水果是苹果嘛。

      如果表达式既不是指针也不是引用时,则它的动态类型永远与静态类型一致,也不存在基类和派生类之间的类型转换。

      

      3、在使用基类的引用或者指针调用一个虚函数的时候,由于基类的引用或者指针的动态类型可能和静态类型不一样,在编译时并不能确定它调用的虚函数是基类的还是派生类的,直到运行时才能根据其动态类型来确定其虚函数版本的现象,被称作动态绑定,也叫运行时绑定。举个例子,一个基类Person,两个派生类Professor和student都继承自Person:

    class Person {
    public:
        Person() = default;
        Person(string s, int a) : name(s), age(a){}
    
        string name;
        int age;
    
        virtual void getdata(){
            cin >> name;
            cin >> age;
        }    
        virtual void putdata(){
            cout << name << " " << age << endl;
        }
    };
    
    class Professor :public Person{
    public:
        Professor() = default;
        Professor(string s, int a) : Person(s, a){}
    
        int publications;
        int cur_{ id };
    
        static const int id = 1;
    
        void getdata(){
            cin >> name;
            cin >> age;
            cin >> publications;
        }
    
        void putdata(){
            cout << name << " " << age << " " << publications << " " << id << endl;
        }
    };
    
    class Student :public Person{
    public:
        int marks[6];
        int cur_{ id };
    
        static const int id = 2;
    
        void getdata(){
            cin >> name;
            cin >> age;
            for (int i = 0; i < 6; i++){
                cin >> marks[i];
            }
        }
    
        void putdata(){
            int sum = marks[0] + marks[1] + marks[2] + marks[3] + marks[4] + marks[5];
            cout << name << " " << age << " " << sum << " " << id << endl;
        }
    };
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
        //=====================================================================
        // 静态类型为基类,动态类型为派生类
        // 基类指针调用虚函数,发生动态绑定,按照其动态类型进行调用
        // 如果基类没有定义虚函数,则调用基类函数
        //======================================================================
        int val;
        Person *per[2];    //基类指针数组
        for (int i = 0; i < 2; i++){
            cin >> val;
            if (val == 1){
                per[i] = new Professor;    //第一个元素动态类型是professor
            }
            else per[i] = new Student; // 第二个元素动态类型是student
    
            per[i]->getdata(); //第一次调用的是professor::getdata,第二次调用的是student::getdata
        }
        for (int i = 0; i<2; i++)
            per[i]->putdata(); //第一次调用的是professor::putdata,第二次调用的是student::putdata
    
        //============================================================================
    return 0; }

      4、当基类派生类有同名函数,但是基类中该函数不是虚函数(没有virtual),则不能实现覆盖,派生类直接继承基类函数,当基类引用或者指针调用函数时,不会发生动态绑定,调用的是基类函数。派生类和基类对象调用函数时,调用各自版本的函数


    II 虚函数表,参考学习:http://blog.csdn.net/haoel/article/details/1948051

    typedef void(*Fun)(void);
    
    class Base {
    public:
        virtual void f() { cout << "Base::f" << endl; }
        virtual void g() { cout << "Base::g" << endl; }
        virtual void h() { cout << "Base::h" << endl; }
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        Base b;
    
        cout << (int*)(&b) << endl;   //获取虚函数表地址
        cout << (int)*(int*)(&b) << endl; //虚函数表第一个函数地址
        
        Fun pFunB = (Fun)*((int*)*(int*)(&b));
        Fun pFunB1 = (Fun)*((int*)*(int*)(&b) + 1);
        Fun pFunB2 = (Fun)*((int*)*(int*)(&b) + 2);
        pFunB();    //Base::f
        pFunB1();    //Base::g
        pFunB2();    //Base::h
    
            return 0;
    }

    1、无虚函数覆盖,派生类直接继承基类的虚函数,虚函数表访问的仍然是基类的虚函数

    Base::f Base::g Base::h
    class Derive :public Base{
    public:
        void f1() { cout << "Derive::f" << endl; }
        void g1() { cout << "Derive::g" << endl; }
        void h1() { cout << "Derive::h" << endl; }
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        Derive d;
        Fun pFunD = (Fun)*((int*)*(int*)(&d));
        Fun pFunD1 = (Fun)*((int*)*(int*)(&d) + 1);
        Fun pFunD2 = (Fun)*((int*)*(int*)(&d) + 2);
        pFunD();    //Base::f
        pFunD1();   //Base::g
        pFunD2();   //Base::h
    
        return 0;
    }

    2、无虚函数覆盖,但是派生类有自己的虚函数,则派生类的虚函数在虚函数表中按照定义顺序排在基类虚函数后面

    Base::f Base::g Base::h Derive::f Derive::g Derive::h
    class Derive :public Base{
    public:
        virtual void f1() { cout << "Derive::f" << endl; }
        virtual void g1() { cout << "Derive::g" << endl; }
        virtual void h1() { cout << "Derive::h" << endl; }
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        Derive d;
    cout << (int*)(&d) << endl; //获取虚函数表地址 cout << (int)*(int*)(&d) << endl; //虚函数表第一个函数地址 Fun pFunD = (Fun)*((int*)*(int*)(&d)); Fun pFunD1 = (Fun)*((int*)*(int*)(&d) + 1); Fun pFunD2 = (Fun)*((int*)*(int*)(&d) + 2); pFunD(); //Base::f pFunD1(); //Base::g pFunD2(); //Base::h Fun pFunD3 = (Fun)*((int*)*(int*)(&d)+3); Fun pFunD4 = (Fun)*((int*)*(int*)(&d) +4); Fun pFunD5 = (Fun)*((int*)*(int*)(&d) + 5); pFunD3(); //Derive::f pFunD4(); //Derive::g pFunD5(); //Derive::h system("pause"); return 0; }

    3、派生类中有虚函数覆盖了基类的虚函数(如例中f),则在虚函数表中,派生类的虚函数放在了父类虚函数的位置,没被覆盖的保持原样

    Derive::f Base::g Base::h Derive::g Derive::h
    class Derive :public Base{
    public:
        virtual void f() { cout << "Derive::f" << endl; }
        virtual void g1() { cout << "Derive::g" << endl; }
        virtual void h1() { cout << "Derive::h" << endl; }
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        Derive d;
    
        Fun pFunD = (Fun)*((int*)*(int*)(&d));
        Fun pFunD1 = (Fun)*((int*)*(int*)(&d) + 1);
        Fun pFunD2 = (Fun)*((int*)*(int*)(&d) + 2);
        pFunD();    //Derive::f
        pFunD1();    //Base::g
        pFunD2();     //Base::h
    
        Fun pFunD3 = (Fun)*((int*)*(int*)(&d)+3);
        Fun pFunD4 = (Fun)*((int*)*(int*)(&d) + 4);
        pFunD3();    //Derive::g
        pFunD4();    //Derive::h
    
            return 0;
    }

    4、多重继承,无虚函数覆盖:

    • 每个父类都有一张虚函数表,按照父类定义顺序排列
    • 派生类的虚函数排在第一个父类虚函数的后面
    Base1::f Base1::g Base1::h Derive::f Derive::g Derive::h
    Base2::f Base2::g Base2::h
    Base3::f Base3::g Base3::h

    5、多重继承,有虚函数覆盖:

      派生类中的虚函数放在被覆盖虚函数位置,其他不变

  • 相关阅读:
    常用python机器学习库总结
    链接器link.exe 编译器cl.exe 资源编译器rc.exe
    LRESULT与wParam和lParam的问题
    CreateDialog和DialogBox
    如何通俗易懂地解释卷积?
    深度学习在graph上的使用
    一文弄懂神经网络中的反向传播法——BackPropagation
    WM_COMMAND消息
    win32编程中消息循环和WndProc()窗口过程函数
    使用UEditor 的时候,ajax注意使用同步的方法
  • 原文地址:https://www.cnblogs.com/Chilly2015/p/5436885.html
Copyright © 2011-2022 走看看