• C++ 虚函数与多态


    #include <iostream>
    using namespace std;

    /*-----------------------
    2012年11月10日 01:26:17
    ------------------------*/

    /**************************************************************
    虚函数:
        1、在基类用【virtual声明】的成员函数即为虚函数
        2、在派生类中的定义此函数,要求
            函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同
            并根据派生类的需要重新定义函数体
        3、当一个成员函数声明为虚函数后,其派生类中的同名函数都自动成为虚函数,无论是否加virtual关键字
        4、用基类的指针或引用指向派生类的对象
           通过用基类的指针或引用调用虚函数
           实际执行的将使派生类对象中定义的虚函数

    虚函数表:
        1、如果类中包含有虚成员函数,在用该类实例化对象时,
        对象的第一个成员将是一个指向虚函数表的指针[pvftable]。
        2、虚函数表记录运行过程中实际应该调用的虚函数的入口地址
        3、类中所有的虚函数形成虚函数表
            [可以通过调试查看:
                cout<<sizeof(aa3)<<endl;]

    虚析构函数: -- 防止内存泄漏
        1、如果一个基类的指针指向派生类的对象,
        并且想通过该指针delete派生类对象,
        系统将只会执行基类的析构函数
        而不会执行派生类的析构函数
        2、为避免这种情况的发生,往往把析构函数声明为虚的,
        此时,系统将 先执行派生类对象的析构函数
        然后再执行基类的析构函数
        3、如果基类的析构函数声明为虚的,
        派生类的析构函数也将自动成为虚构函数
        无论派生类析构函数声明中是否加virtual
        4、防止内存泄漏
        当派生类中有指针时,对象销毁时,
        也需要释放指针所指向的地址
        5、特别注意:
            5.1、
            基类名*  指针 = new 子类名();
                只有当一个父类类型的指针 指向 子类对象[new 的对象]时
                只有将 基类的析构函数 声明为 虚函数virtual  
            在 delete 指针时-->
                才会:
                    先执行派生类对象的析构函数
                    然后再执行基类的析构函数
                否则:
                    只执行基类的析构函数
            5.2、
            类名*  指针 = new 类名();
                当指针的类型是对象类型的本身时
            在 delete 指针时-->
                    基类的析构函数 【不是】虚函数virtual  
                也会:
                    先执行派生类对象的析构函数
                    然后再执行基类的析构函数
            5.3、栈中分配分配空间时【子类分配空间】
                5.3.1、不需要 delete
                    在 该区域 执行完后 会自动释放 该栈空间
                5.3.2、基类的析构函数 【不是】虚函数virtual  
                也会:
                    先执行派生类对象的析构函数
                    然后再执行基类的析构函数

    纯虚函数和抽象类:
        1、纯虚函数具有如下的一般形式:
            virtual 返回类型 函数名(参数列表)=0;
        2、纯虚函数没有函数体,即只有函数的声明而没有函数的定义
        3、通常在基类中声明纯虚函数,在派生类中定义该虚函数
        如果派生类中也没有定义该纯虚函数,则该函数在派生类中任然为纯虚函数
        4、不能实例化对象的类称为[抽象类],
        具有纯虚函数的类是不能被实例化对象的
        所以具有纯虚函数的类是一种抽象类
        5、虽然抽象类不能实例化对象,但是可以用抽象类的指针指向派生类对象
        并调用派生类的虚函数的实际实现
        6、子类在继承抽象类时:
        必须全部实现抽象类的 纯虚函数,该派生类才能被实例化
        如果只实现部分 纯虚函数,则该派生类还是 抽象类
        7、当一个的所有函数都是 纯虚函数,
        则该类就是 接口【C++没有接口】
        接口是抽象类的一种特例

    子类调用基类的函数:
        基类类名::函数名();

    多态:
        1、父类的函数为 虚函数, 即在 返回类型前加 virtual
            virtual void Print(){.....};
        2、子类对该函数进行重写[返回类型 和 函数名 相同]
            void Print(){......};
        3、父类的[指针]指向父类对象或子类对象
            //通过指针
                void test1(a* temp){
                    temp->Print();
                }
            3.1、在 【栈中】 分配空间
                父类名* 地址变量名 =  &父类名[子类名]();
                    a* aa = &b();
                    //等价于
                        a* aa;
                        b bb;  
                        aa=&bb;
                    
                    test1(aa);
            3.2、在 【堆中】 分配空间
                父类名* 地址变量名 = new 父类名[子类名]();
                    a* cc = new c();
                    test1(cc);

    注意点:
        1、指针调用函数的方式:
            指针名->函数名();
        2、引用调用函数的方式:
            引用名.函数名();
        3、继承方式
            class 子类名:public 父类名{....};
    ***************************************************************/

    class Person{ //抽象类
    public:
        char* name;
    public:
        virtual void Eat()=0;
        virtual void Sleep()=0;
    };

    class Stu:public Person{
    public:
        void Eat(){
            cout<<"Eat......."<<endl;
        }
        void Sleep(){
            cout<<"Sleep.........."<<endl;
        }
    };

    void main(){
        Person* person = new Stu();
        person->Eat();
    }



    class a{
    public:
        virtual void Print(){
            cout<<"a............."<<endl;
        }
        virtual void Prafint(){
            cout<<"a............."<<endl;
        }
    public:
        a(){
            cout<<"new a............"<<endl;    
        }
    public:
        virtual ~a(){
            cout<<"destory a............"<<endl;
        }
    };

    //通过指针
    void test1(a* temp){
        temp->Print();
    }

    //通过引用
    void test2(a& temp){
        temp.Print();
    }

    class b:public a{
    public:
        void Print(){
            cout<<"b............."<<endl;
        }
    public:
        b(){
            cout<<"new b............"<<endl;    
        }
    public:
        ~b(){
            cout<<"destory b............"<<endl;
        }
    };

    class c:public a{
    public:
        void Print(){
            cout<<"c............."<<endl;
        }
    public:
        c(){
            cout<<"new c............"<<endl;    
        }
    public:
        ~c(){
            cout<<"destory c............"<<endl;
        }
    };



    void test22(){
         
         /*
            b bb;
            注意:
                在栈中实例化 派生类
                1、先 调用父类的 构造函数 再调用派生类的构造函数
                2、不需要手动释放内存
                3、在 该 区域执行完后 会自动释放内存空间
            new a............
            new b............
            destory b............
            destory a............
         */
         //a aa = bb;
        //aa.Print();
        a* aa = new b();
        //a* aa = new b();  
        //aa->Print();
        delete aa;
        /*
        a* aa = new b();
        在堆中分配空间:
            1、先 调用父类的 构造函数 再调用派生类的构造函数
            2、先调用派生类的析构函数 再调用基类的析构函数
                2.1、2的满足条件:需要把 基类的析构函数 声明 虚函数
            3、如果基类的 析构函数 不是虚函数
                3.1、在 delete 时只会调用基类的析构函数
        */
        /*
        new a............
        new b............
        b.............
        destory b............
        destory a............
        */
    }

    int test11(){
        /*通过指针*/
        a* aa1 = &b();
        /*等价于
            a* aa1;
            b bb;  
            aa1=&bb;
        */
        test1(aa1);
        a* cc1 = new c(); //动态绑定  就是虚函数中的内容 在实例化的时候动态载入
        test1(cc1);
        
        /*通过引用*/
        a& aa2 = b();
        test2(aa2);
        a* cc2 = new c();
        test1(cc2);

        a aa3;
        cout<<sizeof(aa3)<<endl;

        //cout<<&(a::Print)<<endl;
        /*
        //打印函数的地址
            printf("a::Print=%x\n", &(a::Print));
            printf("b::Print=%x\n", &(b::Print));
            printf("c::Print=%x\n", &(c::Print));
        
        打印结果:
            a::Print=401271
            b::Print=401271
            c::Print=401271    
        分析:
                a
               / \
              b   c
            1、b继承a , c继承a
            2、Print()在a类中是 虚函数
            3、类b类c 重写了a的虚函数,所以该函数也为虚函数
            4、三个类中的函数统一载入一个地址区:
                意味着三个只能有一个存在
            5、具体 401271 里面的内容就由调用的实例在实例化的时候动态载入
        */
        return 0;
    }

  • 相关阅读:
    文件重名问题
    文件上传
    回顾IO流
    Freemarker
    中文乱码问题
    Filter(过滤器)
    Ajax
    jQuery
    普华操作系统,开机无法进入桌面程序; 解决多次source /etc/profile的烦恼
    C++ 文件类型判别错误,将目录文件识别为普通文件
  • 原文地址:https://www.cnblogs.com/qintangtao/p/2763600.html
Copyright © 2020-2023  润新知