C++中virtual关键字的困惑

6

我在研究C++中virtual关键字的影响,并编写了以下代码。

#include<iostream>

using namespace std;

class A {
public:
    virtual void show(){
        cout << "A \n";
    }
};

class B : public A {
public:
    void show(){
        cout << "B \n";
    }
};

class C : public B {
public: 
    void show(){
        cout << "C \n"; 
    }
};

int main(){

    A *ab = new B;
    A *ac = new C;
    B *bc = new C;
    ab->show();
    ac->show();
    bc->show();

}

预期输出为:
B
C
B

由于B中的show函数是非虚函数。但是当您编译时的结果是:

B
C
C

它的行为就像B类中的show函数是虚函数一样。为什么会这样呢?B类被覆盖了吗?如果我将C类指向B类,为什么还能指向A类?


B 中的 show() 函数是虚函数,因为它继承自 A,而 Ashow() 函数也是虚函数。 - Peter
3个回答

7
根据C++ 2017标准(10.1.2函数说明符):
2 virtual关键字只能在非静态类成员函数的初始声明中使用;请参见13.3。
而且(13.3虚函数):
2 如果在类Base和派生自Base的直接或间接类Derived中声明了一个名为vf的虚成员函数,其参数类型列表(11.3.5)、cv限定符和引用限定符(或缺乏它们)与Base::vf相同,则声明Derived::vf时会覆盖111 Base::vf,并且Derived::vf也是虚的(无论是否这样声明)。为了方便起见,我们说任何虚函数都是覆盖它本身。类对象S的虚成员函数C::vf是最终覆盖者,除非S所属的最派生类(4.5)声明或继承了另一个覆盖vf的成员函数。在派生类中,如果基类子对象的虚成员函数有多个最终覆盖者,则程序是非法的。
因此,在类B中,函数show是虚函数,因为它具有与类A中声明的函数相同的签名。
考虑一个更有趣的例子,当在类B中将限定符const添加到成员函数show时。
#include<iostream>

using namespace std;

class A {
public:
    virtual void show(){
        cout << "A \n";
    }
};

class B : public A {
public:
    void show() const{
        cout << "B \n";
    }
};

class C : public B {
public: 
    void show() {
        cout << "C \n"; 
    }
};

int main(){

    A *ab = new B;
    A *ac = new C;
    B *bc = new C;
    ab->show();
    ac->show();
    bc->show();

}

在这种情况下,输出将类似于:
A 
C 
B 

在这个表达式语句中
    ab->show();

在类A中声明了一个名为show的虚函数。在这个语句中,保留了html标签。
    ac->show();

在类C中覆盖了一个叫做相同的虚函数。编译器使用类A中的虚函数声明,因为指针ac的静态类型是A *。

在这个语句中:

    bc->show();

存在一个被称为非虚成员函数show,其有限定符const。这是因为指针bc的静态类型为B *,编译器在类B中找到了隐藏在类A中声明的虚函数。

对于原始程序,您可以使用关键字override来使类定义更加清晰。例如:

#include<iostream>

using namespace std;

class A {
public:
    virtual void show(){
        cout << "A \n";
    }
};

class B : public A {
public:
    void show() override{
        cout << "B \n";
    }
};

class C : public B {
public: 
    void show() override{
        cout << "C \n"; 
    }
};

int main(){

    A *ab = new B;
    A *ac = new C;
    B *bc = new C;
    ab->show();
    ac->show();
    bc->show();

}

7
如果在基类中指定了函数为virtual,则在派生类中不需要将其指定为virtual。

那么,它只是从A类中获取虚拟行为?有没有一种方法可以覆盖它?覆盖的意思是,我不想使用超类指定的派生类行为。 - user4709059
2
@Helicopter 不,一个虚方法不能在派生类中变为非虚方法。但是你可以使用final关键字阻止虚方法被覆盖。 - Rotem
“我不想让超类指定派生类的行为” - 这正是你重写虚方法的原因。 - Rotem

0

这种行为是正确的。由于show函数是虚函数,所以调用的版本将是附加到您调用它的实例上的版本,而不是该实例类型描述的版本(该实例的真实类型的基础可以是该实例的基础)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接