zoukankan      html  css  js  c++  java
  • 虚函数

    可用于实现多态公有继承的机制:

    • 在派生类中重新定义基类的方法。
    • 使用虚方法

    类Brass和类BrassPlus如下所示:

    class Brass
    {
    private:
    char fullName[MAX];
    long acctNum;
    double balance;
    public:
    virtual void WithDraw(double amt);
    virtual void ViewAcct()const;
    virtual ~Brass(){}
    };
    class BrassPlus: public Brass
    {
    private:
    double rate;
    public:
    virtual void ViewAcct()const;
    virtual void WithDraw(double amt);
    };

    如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。如果ViewAcct()不是虚拟的,则程序的行为如下:

    Brass dom("Dominic Banker",11224,4183.45);
    BrassPlus dot("Dorothy Banker",12118,2592.00);
    Brass & b1_ref = dom;
    Brass & b2_ref = dot;
    b1_ref.ViewAcct();//调用Brass::ViewAcct()
    b2_ref.ViewAcct();//调用Brass::ViewAcct()

    如果ViewAcct是虚拟的,则行为如下:

    1 Brass dom("Dominic Banker",11224,4183.45);
    2 BrassPlus dot("Dorothy Banker",12118,2592.00);
    3 Brass & b1_ref = dom;
    4 Brass & b2_ref = dot;
    5 b1_ref.ViewAcct();//调用Brass::ViewAcct()
    6 b2_ref.ViewAcct();//调用BrassPlus::ViewAcct()

    用途:

    假设要同时管理Brass和BrassPlus账户,我们无法用同一个数组来保存Brass和BrassPlus对象,但是我们可以创建一个指向Brass的指针数组。这样,每个元素的类型都相同,而且Brass指针既可以纸箱Brass对象,也可以纸箱BrassPlus对象。因此,可以用一个数组来表示多种类型的对象。这就是多态性。

     虚函数的工作原理:

    编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(vtbl)。虚函数表中存储了为类对象声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地址。如果派生类定义了新的虚函数,则虚函数的地址也将被添加到vtbl中。注意,无论类中包含多少个虚函数,都只要再对象中添加1个地址成员,只是表的大小不同而已。

    使用虚函数时,在内存和执行速度方面有一定的成本,包括:

    • 每个对象都将增大,增大量为存储地址的空间。
    • 对每个类,编译器都创建一个虚函数地址表。
    • 每个函数调用都需要执行一步额外的操作,即到表中查找地址。

    虽然非虚函数的效率比虚函数稍高,但不具备动态联编功能。

    虚函数要点:

    • 在基类方法的声明中使用关键字virtual可使该方法在基类以及所有的派生类中是虚拟的。
    • 如果使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而使用为引用或指针类型定义的方法。这称为动态联编。这种行为使得基类指针或引用可以指向派生类对象。
    • 如果定义的类将被用做基类,则应将那些要在派生类中重新定义的类方法声明为虚函数。

    注意事项:

    1. 构造函数不能是虚函数。
    2. 析构函数应当是虚函数,除非类不用做基类。
    3. 友元不能是虚函数,因为友元不是类成员,而只有成员才能使虚函数。
    4. 如果派生类没有重新定义函数,将使用该函数的基类版本。
    5. 重新定义隐藏方法。

    *Tip:通常应该给基类提供一个虚拟析构函数,即使它并不需要析构函数。

    析构函数应当是虚函数,除非类不用做基类。例如假设给BrassPlus添加一个char*成员,该成员指向由new分配的内存。当BrassPlus对象过期时,必须调用~BrassPlus析构函数来释放内存。

    请看下面的代码:

    Brass* p = new BrassPlus;
    delete p;

    delete语句将调用~Brass()析构函数。这将释放由BrassPlus对象中的Brass部分指向的内存,但不会释放新的类成员指向的内存。但如果析构函数是虚拟的,则上述代码将先调用~BrassPlus析构函数释放由BrassPlus组件指向的内存,然后调用~Brass()析构函数来释放由Brass组件指向的内存。这意味着,即使基类不需要显示析构函数提供服务,也不应该依赖于默认构造函数,而应提供虚拟析构函数,即使它不执行任何操作:

    virtual ~Brass(){}

    给类定义一个虚拟析构函数并非错误,即使这个类不用做基类;这只是一个效率方面的问题。

     *Tip:重新定义隐藏方法

    假设创建了如下所示的代码:

    class Dwelling
    {
    public:
    virtual void showperks(int a)const;
    ...
    };
    class Hovel: public Dwelling
    {
    public:
    virtual void showperks()const;
    ...
    };

     这可能会出现类似于下面这样的警告:

    Warning: Hovel::showperks(void) hides Dwelling::showperks(int)

    也可能不会出现警告。但不管结果如何,代码将具有如下含义:

    Hove trump;
    trump.showperks();//valid
    trump.showperks(5);//invalid

    重新定义不会生成函数的两个重载版本,而是隐藏了接受一个int参数的基类版本。也就是说重新定义方法并不是重载。派生类重新定义函数,将不是使用相同的函数特征覆盖基类声明,而是隐藏同名的基类函数,不管函数的参数特征如何。

    这引出了两条经验规则:

    第一,如果重新定义继承的方法,应确保与原来的原型完全相同。但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特征被称为返回类型协变。如:

    class Dwelling
    {
    public:
    virtual Dweling& build(int n);
    ...
    };
    class Hovel: public Dwelling
    {
    public:
    virtual Hovel & build(int n);
    ...
    };

     注意,这种例外只适用于返回值,而不适用于参数。

    第二,如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如:

    class Dwelling
    {
    public:
    virtual void showperks(int a)const;
    virtual void showperks(double x)const;
    virtual void showperks()const;
    ...
    };
    class Hovel: public Dwelling
    {
    public:
    virtual void showperks(int a)const;
    virtual void showperks(double x)const;
    virtual void showperks()const;
    ...
    };

     如果只重新定义一个版本,则另外两个版本将被隐藏,派生类对象将无法使用它们。如果不需要修改,则新定义可调用基类版本。

  • 相关阅读:
    Spring boot 集成三种拦截方式
    Spring boot 异常处理配置
    Linux 启动、停止、重启tomcat工具(Shell脚本)
    Java 序列化对象工具类
    Spring boot 集成 Swagger
    Spring boot 项目导出可执行jar
    包扫描自定义注解类并实例化
    Zuul 网关搭建
    Hystrix + Hystrix Dashboard搭建(Spring Cloud 2.X)
    Feign + Hystrix 服务熔断和服务降级
  • 原文地址:https://www.cnblogs.com/happygirl-zjj/p/4641810.html
Copyright © 2011-2022 走看看