zoukankan      html  css  js  c++  java
  • C++语言基础(12)-虚函数

    一.虚函数使用的注意事项

    1.只需要在虚函数的声明处加上 virtual 关键字,函数定义处可以加也可以不加。

    2.为了方便,你可以只将基类中的函数声明为虚函数,这样所有子类中具有遮蔽(覆盖)关系的同名函数都将自动成为虚函数。

    3. 当在基类中定义了虚函数时,如果派生类没有定义新的函数来遮蔽此函数,那么将使用基类的虚函数。

    4.只有子类的虚函数遮蔽基类的虚函数(函数原型相同)才能构成多态(通过基类指针访问子类函数)。例如基类虚函数的原型为virtual void func();,派生类虚函数的原型为virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句p -> func(100);将会出错,而语句p -> func();将调用基类的函数。

    5.构造函数不能是虚函数。对于基类的构造函数,它仅仅是在派生类构造函数中被调用,这种机制不同于继承。也就是说,派生类不继承基类的构造函数,将构造函数声明为虚函数没有什么意义。.

    6.析构函数可以声明为虚函数,而且有时候必须要声明为虚函数。

    二.具体用法

    #include <iostream>
    using namespace std;
    
    //基类Base
    class Base{
    public:
        virtual void func();
        virtual void func(int);
    };
    void Base::func(){
        cout<<"void Base::func()"<<endl;
    }
    void Base::func(int n){
        cout<<"void Base::func(int)"<<endl;
    }
    
    //派生类Derived
    class Derived: public Base{
    public:
        void func();
        void func(char *);
    };
    void Derived::func(){
        cout<<"void Derived::func()"<<endl;
    }
    void Derived::func(char *str){
        cout<<"void Derived::func(char *)"<<endl;
    }
    
    int main(){
        Base *p = new Derived();
        p -> func();  //输出void Derived::func()
        p -> func(10);  //输出void Base::func(int)
        p -> func("http://c.biancheng.net");  //compile error
    
        return 0;
    }

    注意:p -> func("http://c.biancheng.net");会出现编译错误,因为子类Derived中的 void func(char *)并未对父类中的virtual void func(int)造成覆盖,两个函数的函数原型不同,所以无法构造多态,自然也不能通过基类的指针来访问子类函数。

    三.将基类的析构函数定义成虚函数的必要性

    先看下面的例子:

    #include <iostream>
    using namespace std;
    
    //基类
    class Base{
    public:
        Base();
        ~Base();
    protected:
        char *str;
    };
    Base::Base(){
        str = new char[100];
        cout<<"Base constructor"<<endl;
    }
    Base::~Base(){
        delete[] str;
        cout<<"Base destructor"<<endl;
    }
    
    //派生类
    class Derived: public Base{
    public:
        Derived();
        ~Derived();
    private:
        char *name;
    };
    Derived::Derived(){
        name = new char[100];
        cout<<"Derived constructor"<<endl;
    }
    Derived::~Derived(){
        delete[] name;
        cout<<"Derived destructor"<<endl;
    }
    
    int main(){
       Base *pb = new Derived();
       delete pb;
    
       cout<<"-------------------"<<endl;
    
       Derived *pd = new Derived();
       delete pd;
    
       return 0;
    }

    运行结果:
    Base constructor
    Derived constructor
    Base destructor
    -------------------
    Base constructor
    Derived constructor
    Derived destructor
    Base destructor

    在本例中,不调用派生类的析构函数会导致 name 指向的 100 个 char 类型的内存空间得不到释放;除非程序运行结束由操作系统回收,否则就再也没有机会释放这些内存。这是典型的内存泄露

    注意:

    delete pb; 不调用子类的析构函数是因为:这里的析构函数是非虚函数,通过指针访问非虚函数时,编译器会根据指针类型来确定要调用的函数;也就是说,指针指向哪个类
    就调用哪个类的函数。pb是基类的指针,所以不管它指向的是基类的对象还是子类的对象,始终都是调用基类的析构函数

    delete pd 会同时调用子类和基类的析构函数是因为:pd是子类的指针,编译器会根据它的类型匹配到子类的析构函数,在执行子类的析构函数的过程中,又会调用基类的析构函数
    子类的析构函数始终会调用基类的析构函数,且这个过程是隐式完成的

    更改上面的代码,将基类的析构函数声明为虚函数:

    class Base{
    public:
        Base();
        virtual ~Base();
    protected:
        char *str;
    };

    将基类的析构函数声明为虚函数后,派生类的析构函数也会自动成为虚函数。这个时候编译器会忽略指针的类型,而根据指针的指向来选择函数;也就是说,指针指向哪个类的对象就调用哪个类的函数。pb、pd 都指向了派生类的对象,所以会调用派生类的析构函数,继而再调用基类的析构函数。如此一来也就解决了内存泄露的问题。

    当然了,这里强调的是基类,如果一个类是最终的类,那就没必要再声明为虚函数了。

  • 相关阅读:
    css3实现文本渐变
    元组--购物车实战
    js事件冒泡
    openssl生成v3版自签证书
    linux中可以在哪些地方增加环境变量
    linux下如何找到USB转串口
    linux下通过shell命令测试串口
    CANopen协议
    ubuntu使用虚拟can(vcan)
    移植python3到flash有限的arm
  • 原文地址:https://www.cnblogs.com/yongdaimi/p/7088222.html
Copyright © 2011-2022 走看看