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;
}

原文链接: https://www.cnblogs.com/qintangtao/archive/2012/11/10/2763600.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C++ 虚函数与多态

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/68829

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 下午1:30
下一篇 2023年2月9日 下午1:31

相关推荐