当前位置: 首页 > 知识库问答 >
问题:

重写虚函数和隐藏非虚函数有什么区别?

上官扬
2023-03-14

给定下面的代码片段,函数调用有什么不同?什么是函数隐藏?什么是函数覆盖?它们与函数重载有什么关系?两者有什么区别?我在一个地方找不到关于这些的很好的描述,所以我在这里询问,这样我可以整合信息。

class Parent {
  public:
    void doA() { cout << "doA in Parent" << endl; }
    virtual void doB() { cout << "doB in Parent" << endl; }
};

class Child : public Parent {
  public:
    void doA() { cout << "doA in Child" << endl; }
    void doB() { cout << "doB in Child" << endl; }
};

Parent* p1 = new Parent();
Parent* p2 = new Child();
Child* cp = new Child();

void testStuff() {
  p1->doA();
  p2->doA();
  cp->doA();

  p1->doB();
  p2->doB();
  cp->doB();
}

共有3个答案

祁高格
2023-03-14
匿名用户

我们从简单的开始。

< code>p1是一个< code>Parent指针,因此它将始终调用< code>Parent的成员函数。

cp 是指向 Child 的指针,因此它将始终调用 Child 的成员函数。

现在是更困难的一个p2Parentpointer,但它指向Child类型的对象,因此只要匹配的 函数是虚拟的,或者该函数只存在于 中,而不存在于 中,它就会调用 0.05 Child的函数。换句话说,Child使用自己的doA()隐藏Parent::doA(。函数隐藏有时被认为是函数重载的一种形式,因为同名的函数被赋予不同的实现。因为隐藏函数与隐藏函数位于不同的类中,所以它确实具有不同的签名,这使得使用哪一个更为明确。

testStuff() 的输出将是

doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child

在任何情况下,Parent::doA()Parent::doB()都可以使用名称解析在Child中调用,而不考虑函数的“虚拟性”。函数

void Child::doX() {
  doA();
  doB();
  Parent::doA();
  Parent::doB();
  cout << "doX in Child" << endl;
}

由< code>cp-调用时演示这一点

doA in Child
doB in Child
doA in Parent
doB in Parent
doX in Child

此外,< code>cp-

p2不能引用doX(),因为它是 Parent不知道 p2 可以被强制转换为Child*,因为它被初始化为一个,然后可以用来调用doX()

奚和光
2023-03-14

调用虚成员函数和调用非虚成员函数的区别在于,根据定义,在前一种情况下,目标函数是根据调用中使用的对象表达式的动态类型选择的,而在后一种情况下,使用的是静态类型。

这就是一切。您的示例通过p2清楚地说明了这种差异-

在这种差异很重要的情况下,姓名隐藏根本不会出现。

商和雅
2023-03-14

...是名称隐藏的一种形式。一个简单的例子:

void foo(int);
namespace X
{
    void foo();
    
    void bar()
    {
        foo(42); // will not find `::foo`
        // because `X::foo` hides it
    }
}

这也适用于基类中的名称查找:

class Base
{
public:
    void foo(int);
};

class Derived : public Base
{
public:
    void foo();
    void bar()
    {
        foo(42); // will not find `Base::foo`
        // because `Derived::foo` hides it
    }
};

这与虚拟功能的概念有关。[类.虚拟]/2

如果虚拟成员函数vf在类Base中声明,并且在直接或间接从Base派生的类中声明,则声明具有与Base::vf相同的名称、参数类型列表、cv资格和参考限定符(或不存在相同)的成员函数vf,则ed::vf也是虚拟的(无论是否如此声明)并且它覆盖Base::vf

class Base
{
private:
    virtual void vf(int) const &&;
    virtual void vf2(int);
    virtual Base* vf3(int);
};

class Derived : public Base
{
public: // accessibility doesn't matter!
    void vf(int) const &&; // overrides `Base::vf(int) const &&`
    void vf2(/*int*/);     // does NOT override `Base::vf2`
    Derived* vf3(int);     // DOES override `Base::vf3` (covariant return type)
};

调用虚拟函数时,最后一个重写器变得相关:[class.virtual]/2

类对象S的虚拟成员函数C::vf是最终的覆盖器,除非最底层的派生类S是基类子对象(如果有)声明或继承另一个覆盖vf的成员函数。

也就是说,如果你有一个 S 类型的对象,则最终的覆盖器是你在遍历 S 的类层次结构回到其基类时看到的第一个覆盖器。重要的一点是,函数调用表达式的动态类型用于确定最终覆盖器:

Base* p = new Derived;
p -> vf(42);    // dynamic type of `*p` is `Derived`

Base& b = *p;
b  . vf(42);    // dynamic type of `b` is `Derived`

本质上,基类中的函数总是被派生类中同名的函数隐藏;无论派生类中的函数是否重写基类的虚函数:

class Base
{
private:
    virtual void vf(int);
    virtual void vf2(int);
};

class Derived : public Base
{
public:
    void vf();     // doesn't override, but hides `Base::vf(int)`
    void vf2(int); // overrides and hides `Base::vf2(int)`
};

要查找函数名,使用表达式的静态类型:

Derived d;
d.vf(42);   // `vf` is found as `Derived::vf()`, this call is ill-formed
            // (too many arguments)

因为“函数隐藏”是名称隐藏的一种形式,所以如果函数的名称被隐藏,所有重载都会受到影响:

class Base
{
private:
    virtual void vf(int);
    virtual void vf(double);
};

class Derived : public Base
{
public:
    void vf();     // hides `Base::vf(int)` and `Base::vf(double)`
};

对于函数重写,只有基类中具有相同参数的函数才会被重写;当然,您可以重载虚拟函数:

class Base
{
private:
    virtual void vf(int);
    virtual void vf(double);
    void vf(char);  // will be hidden by overrides in a derived class
};

class Derived : public Base
{
public:
    void vf(int);    // overrides `Base::vf(int)`
    void vf(double); // overrides `Base::vf(double)`
};

 类似资料:
  • 本文向大家介绍C++ 虚函数和纯虚函数的区别分析,包括了C++ 虚函数和纯虚函数的区别分析的使用技巧和注意事项,需要的朋友参考一下 首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数。 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。 定义一个函数为纯虚函数,才代表函数没有被实现。 定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函

  • 本文向大家介绍虚函数与纯虚函数之间的区别,包括了虚函数与纯虚函数之间的区别的使用技巧和注意事项,需要的朋友参考一下 在本文中,我们将了解虚拟和纯虚拟功能之间的区别。 虚函数 它在类中有自己的定义。 基类可以覆盖虚拟函数。 它没有派生类。 声明 纯虚函数 没有定义。 如果一个类至少具有一个虚函数,则可以将其声明为抽象。 派生类必须重写纯虚函数才能使用它。 通过在声明中放置“ = 0”来指定纯虚函数

  • 本文向大家介绍c++中虚函数和纯虚函数的作用与区别,包括了c++中虚函数和纯虚函数的作用与区别的使用技巧和注意事项,需要的朋友参考一下 虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数。 纯

  • 本文向大家介绍什么是虚函数?什么是抽象函数?相关面试题,主要包含被问及什么是虚函数?什么是抽象函数?时的应答技巧和注意事项,需要的朋友参考一下 答: 虚函数:没有实现的,可由子类继承并重写的函数。Virtual CallSomeOne(); 抽象函数:规定其非虚子类必须实现的函数,必须被重写。public abstract void CallSomeOne();  

  • 为什么一个虚函数在派生类中默认是虚的,甚至不可能在派生类中完全移除虚性,这有什么好的理由吗? 我希望这种行为的原因与我不希望默认情况下每个函数都是的原因相同。编译器可能会生成牺牲性能的vtables。 基类应该有一个vtable(我想要多态性在那里),但派生类没有vtable(我不希望多态性在那里,为什么我要那样,只是因为它派生了一个具有多态性的类?)。 我想解决的问题是更好地理解虚函数。我只是没

  • 其实,类似的问题这里问过,那里问过,但回答都不满意。代码示例是 输出为 关于 由于是虚函数,派生类不实现它,因此程序将调用; 则程序将调用; 是一个常规函数,程序将调用; 也是一个常规函数,程序将调用; 我不明白的部分来了,不是虚函数,是属于的指针,基本上只能访问中的函数!但输出是 相比之下,会像我想的那样调用。 在基类指针只能访问基类和虚函数中定义的函数的原则与实际输出之间似乎存在矛盾。原因也无