• 6、多态性-3、虚函数


    虚函数是动态绑定的基础。虚函数必须是非静态的成员函数,虚函数经过派生之后,在类族中就可以实现运行过程中的多态。

    根据类型兼容规则,可以使用派生类的对象代替基类对象。如果用基类类型的指针指向派生类对象,就可以通过这个指针来访问该对象,问题是访问到的只是从基类继承来的同名成员。解决这一问题的办法是:如果需要通过基类的指针指向派生类的对象,并访问某个与基类同名的成员,那么首先在基类中将这个同名函数说明为虚函数这样,通过基类类型的指针,就可以使属于不同派生类的不同对象产生不同的行为,从而实现了运行过程的多态。

    1、一般虚函数成员

     一般虚函数成员的声明语法是:

    virtual 函数类型 函数名(形参表)

    {

      函数体

    }

    虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。

    运行过程中的多态需要满足三个条件首先类之间满足类型兼容规则,第二是要声明虚函数,第三是要由成员函数来调用或者是通过指针、引用来访问虚函数。如果是使用对象名来访问虚函数,则绑定在编译过程中就可以进行(静态绑定),而无需在运行过程中进行。

    例子:虚函数成员;在使用基类类型的指针时,它指向哪个派生类的对象,就可以通过它访问哪个对象的同名虚成员函数

    #include<iostream>
    using namespace std;
    class B0
    {
    public:
    virtual void display(){cout<<"B0::display()"<<endl;}//虚成员函数
    };
    class B1:public B0
    {
    public:
    virtual void display(){cout<<"B1::display()"<<endl;}//虚成员函数
    };
    class D1:public B1
    {
    public:
    virtual void display(){cout<<"D1::display()"<<endl;}//虚成员函数
    };
    void fun(B0 *ptr)
    {
    ptr->display();
    }
    int main()
    {
    B0 b0,*p;
    B1 b1;
    D1 d1;
    p=&b0;
    fun(p);
    p=&b1;
    fun(p);
    p=&d1;
    fun(p);
    getchar();
    }

    程序中的类B0、B1、D1属于同一个类族,而且是通过公有派生而来的,因此满足类型兼容规则,基类B0的函数成员display()声明为虚函数,程序中使用对象指针来访问函数成员,这样绑定过程就是在运行中完成,实现了运行中的多态。通过基类类型的指针就可以访问到正在指向的对象的成员,这样就能够对统一类族中的对象进行统一的处理,抽象程度更高,程序更简洁、更高效。

    在本程序中,派生类并没有显式给出虚函数声明,这时系统就会遵循以下规则来判断派生类的一个函数成员是不是虚函数:

    a、该函数是否与基类的虚函数有相同的名称;

    b、该函数是否与基类的虚函数有相同的参数个数以及相同的对应参数类型。

    c、该函数是否与基类的虚函数有相同的返回值或者满足类型兼容规则的指针、引用型的返回值。

    如果从名称、参数以及返回值三个方面检查之后,派生类的函数满足了上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数。不仅如此,派生类中的虚函数还会隐藏基类中同名函数的所有其他重载形式。

    只有虚函数是动态绑定的,如果派生类需要修改基类的行为(即重写与基类函数同名的函数),就应该在基类中将相应的函数声明为虚函数。而基类中声明的非虚函数,通常代表那些不希望被派生类改变的功能,也是不能实现多态的。

    在重写继承来的虚函数时,如果函数有默认形参值,千万不要重新定义不同的值。原因是:虽然虚函数是动态绑定的,但默认形参值是静态绑定的。也就是说,通过一个指向派生类对象的基类指针,可以访问到派生类的虚函数,但默认形参值却只能来自基类的定义

    2、虚析构函数

    在c++中,不能声明虚构造函数,但是可以声明虚析构函数。析构函数没有类型,也没有参数,和普通成员函数相比,虚析构函数略微简单些:

    虚析构函数的声明语法:

    virtual ~类名();

    如果一个类的析构函数是虚函数,那么,由它派生而来的所有子类的析构函数也是虚函数。析构函数设置为虚函数之后,在使用指针引用时可以动态绑定,实现运行时的多态,保证使用基类类型的指针就能够调用适当的析构函数针对不同的对象进行清理工作。

    简单来说,如果有可能通过基类指针调用对象的析构函数(通过delete),并且被析构的对象是有重要的析构函数的派生类的对象,就需要让基类的析构函数成为虚函数。

    #include<iostream>
    using namespace std;
    class Base
    {
    public:
    ~Base(){cout<<"Base destructor ";}
    };
    class Derived:public Base
    {
    public:
    Derived();
    ~Derived();
    private:
    int *i_pointer;
    };
    Derived::Derived()
    {
    i_pointer=new int(0);
    }
    Derived::~Derived()
    {
    cout<<"Derived destructor ";
    delete i_pointer;
    }
    void fun(Base *b)
    {
    delete b;
    }
    int main()
    {
    Base *b=new Derived();
    fun(b);
    getchar();
    }

    运行结果:

    Base destructor

    上面是没有使用虚析构函数的,说明通过基类指针删除派生类对象时调用的是基类的析构函数。修改如下

    class Base

    {

      public:

        virtual ~Base(){cout<<"Base destructor ";}

    }

    运行结果:

    Derived destructor

    Base destructor

  • 相关阅读:
    cmd 新建空文件
    查看Linux版本
    centos7时间调整
    正确卸载vs2015及以前版本方式
    vs2017,vs2019 无法连接到Web服务器“IIS Express”
    .netcore开发环境和服务器注意事项
    .netcore 网站启动后 502.5
    CentOS7开机报错piix4_smbus ****host smbus controller not enabled
    centos7 升级系统后,启动界面出现多个选项
    .gitkeep文件
  • 原文地址:https://www.cnblogs.com/gary-guo/p/6258228.html
Copyright © 2020-2023  润新知