鉴于以下代码片段,函数调用有何不同?什么是隐藏功能?什么是功能重写?它们如何与函数重载相关?两者有什么区别?我在一个地方找不到这些的好描述,所以我在这里问我所以我可以巩固这些信息。
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();
}
答案 0 :(得分:23)
...是隐藏名称的一种形式。一个简单的例子:
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
}
};
这与虚拟功能的概念有关。 [class.virtual] / 2
如果在类
vf
和类Base
中声明虚拟成员函数Derived
,直接或间接地从Base
派生,则成员函数{{1声明同名,参数类型列表,cv-qualification和ref-qualifier(或缺少相同),vf
也是虚拟的(无论是否为如此声明)并且它覆盖Base::vf
。
Derived::vf
调用虚函数时,最后的覆盖变得相关:[class.virtual] / 2
类对象
的成员函数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) };
是最终覆盖,除非C::vf
是基类子对象(如果有)的最派生类声明或继承另一个覆盖S
。
即。如果你有一个S
类型的对象,那么最后的覆盖是你在vf
的类层次结构遍历到它的基类时看到的第一个覆盖。重要的一点是函数调用表达式的动态类型用于确定最终的覆盖:
S
基本上,基类中的函数总是被派生类中的同名函数隐藏;无论派生类中的函数是否覆盖基类的虚函数:
S
要查找函数名称,请使用表达式的静态类型:
Base* p = new Derived;
p -> vf(); // dynamic type of `*p` is `Derived`
Base& b = *p;
b . vf(); // 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)
答案 1 :(得分:5)
调用虚拟成员函数和调用非虚拟成员函数之间的区别在于,根据定义,在前一种情况下,目标函数是根据使用调用中使用的对象表达式的 dynamic 类型,而在后一种情况下使用 static 类型。
这就是它的全部内容。您的示例通过p2->doA()
和p2->doB()
调用清楚地说明了这种差异。 *p2
表达式的静态类型为Parent
,而同一表达式的动态类型为Child
。这就是p2->doA()
调用Parent::doA
和p2->doB()
调用Child::doB
的原因。
在这种差异很重要的情况下,名字隐藏根本不会出现在图片中。
答案 2 :(得分:2)
我们将从简单的开始。
p1
是Parent
指针,因此它始终会调用Parent
的成员函数。
cp
是指向Child
的指针,因此它始终会调用Child
的成员函数。
现在越困难了。 p2
是Parent
指针,但它指向Child
类型的对象,因此只要匹配Child
函数,它就会调用Parent
的函数是虚拟的,或者该函数仅存在于Child
内,而不存在于Parent
中。换句话说,Child
会将Parent::doA()
隐藏为doA()
,但会覆盖Parent::doB()
。函数隐藏有时被认为是函数重载的一种形式,因为具有相同名称的函数被赋予不同的实现。由于隐藏功能与隐藏功能属于不同的类,因此它具有不同的签名,这使得可以清楚地使用哪个。
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;
}
在cp->doX()
通过输出
doA in Child
doB in Child
doA in Parent
doB in Parent
doX in Child
此外,cp->Parent::doA()
会调用Parent
的{{1}}版本。
doA()
无法引用p2
,因为它是doX()
,而Parent*
不知道Parent
中的任何内容。但是,Child
可以投放到p2
,因为它已初始化为1,然后可以用来调用Child*
。
答案 3 :(得分:2)
一个更简单的例子,它们与所有这些都有不同。
class Base {
public:
virtual int fcn();
};
class D1 : public Base {
public:
// D1 inherits the definition of Base::fcn()
int fcn(int); // parameter list differs from fcn in Base
virtual void f2(); // new virtual function that does not exist in Base
};
class D2 : public D1 {
public:
int fcn(int); // nonvirtual function hides D1::fcn(int)
int fcn(); // overrides virtual fcn from Base
void f2(); // overrides virtual f2 from D1
}
答案 4 :(得分:1)
您在问题中编写的示例代码在运行时基本上给出了答案。
调用非虚函数将使用与指针类型相同的类中的函数,而不管该对象是否实际创建为其他派生类型。而调用虚函数将使用原始分配对象类型中的函数,而不管您使用的是哪种指针。
因此,在这种情况下,您的程序输出将是:
doA in Parent
doA in Parent
doA in Child
doB in Parent
doB in Child
doB in Child