3

然后我又有一个问题。像这样的东西:

#include <iostream>
using namespace std;

class Base
{
public:
    void foo()
    {
        cout<<"Base."<<endl;
    }
};

class Derive:public Base
{
public:
    void foo()
    {
        cout<<"Derive."<<endl;
    }
};

int main()
{
    Derive d;
    Base *pb=&d;    //attention here
    pb->foo();      //ateention here
    system("pause");
    return 0;
}

输出是“Base.”。然后功能规则不起作用,我对此感到困惑,您能帮帮我吗?谢谢。

4

5 回答 5

6

因为foo不是虚函数,所以调用的函数是基于静态类型(即,指针被声明指向的类型)而不是动态类型(指针当前引用的对象的类型)。

还有一些更棘手的情况需要考虑。有一点(其他一些答案实际上有些误导)是,重要的不仅仅是函数名称,而是整个函数签名。例如:

#include <iostream>

struct base { 
    virtual void foo() { 
        std::cout << "base::foo";
    }
};

struct derived : base { 
    virtual void foo() const { 
        std::cout << "derived::foo";
    }
};

int main(){ 
    base *b = new derived;

    b->foo();
}

Here在基类和(看似多余的)派生类中都是foo合格virtual的,但调用b->foo() 仍会打印出base::foo.

const添加到 的签名意味着derived::foo它不再匹配 的签名base::foo,因此我们没有覆盖虚函数,而是最终得到两个具有相同名称的独立函数,因此derived::foo隐藏了base::foo,但不覆盖它。尽管有virtual限定,我们得到了静态绑定,因此b->foo();调用基函数而不是派生函数,即使b指向该derived类型的对象。

正如 Tony D 在评论中指出的那样,C++11 为该语言添加了一个新的缺陷,以帮助确保不会发生这种情况。当你想覆盖一个基类函数时,你可以override在派生类的函数中添加标识符:

struct derived : base {
     virtual void foo() const override {
         std::cout << "derived::foo";
     }
};

derived::foo这样,如果函数签名存在差异(如此处所示的情况),编译器将生成一条错误消息,提醒您标记为的事实override,但实际上并未覆盖基类中的函数。但是,这是在 C++11 中添加的,因此如果您使用的是较旧的编译器,则可能无法实现此功能(尽管幸运的是,未实现它的编译器很快就会被遗忘)。

将基类中的签名更正为:

virtual void foo() const // ...

...会让代码编译,并产生正确的结果。

于 2014-08-27T03:08:53.030 回答
2

函数Base::foo是非虚拟的。之所以调用它是因为使用了指向基类的指针。

如果您像这样更改代码:

class Base
{
public:
    virtual void foo() // add virtual
    {
        cout<<"Base."<<endl;
    }
};

输出应该是“派生”。

于 2014-08-27T01:46:55.603 回答
1

您会看到此行为,因为foo()未声明为virtualin Base。在 C++ 中,成员函数默认为非虚函数。您必须显式声明一个函数virtual,以便利用动态调度和多态性。

于 2014-08-27T01:47:05.260 回答
1

从您问题的标题来看,我认为您不完全了解函数何时被隐藏、重载和覆盖。

示例代码1:

struct Base
{
   void foo()
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
};

int main()
{
   Derive d;
   Base *pb=&d;
   d.foo();    // Resolves to Derived::foo()
   pb->foo();  // Resolves to Base::foo()
   return 0;
}

为什么d.foo()调用Derived::foo()pb->foo()调用Base::foo()

该问题的答案在于编译器为解决这些函数绑定而采取的步骤。

给定一个 T 类型的对象和一个函数名f,编译器查找在 中命名的f函数T。如果它只找到一个名为 的函数f,则对函数的搜索将停止。如果它找到多个函数,它会尝试从 中找到的一组函数中进行重载解析T

如果它没有找到任何名为finTT具有基类的函数,它会在 的基类中尝试上述逻辑T。如果T没有任何基类,编译器会报错。

来到示例代码的对象......

在处理函数调用d.foo()时,编译器会查找fooin Derived。它在那里找到一个匹配项并停止。由于Derived::foo()不是virtual函数,因此绑定是在编译时完成的。在运行时,Derived::foo()被调用。

在处理函数调用pb->foo()时,编译器会查找fooin Base。它在那里找到一个匹配项并停止。由于Base::foo()不是virtual函数,因此绑定是在编译时完成的。在运行时,Base::foo()被调用。

示例代码 2:

struct Base
{
   void foo(int i)
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
};

int main()
{
   Derive d;
   Base *pb=&d;
   d.foo();      // Resolves to Derived::foo()
   d.foo(10);    // Compiler error.

   pb->foo(10);  // Resolves to Base::foo(int)
   pb->foo();    // Compiler error.
   return 0;
}

为什么编译器会在这里产生错误?

在处理函数调用d.foo(10)时,编译器会查找fooin Derived。它在那里找到一个匹配项并停止。它尝试使用该函数,但该函数的签名与调用代码不匹配。因此,这是一个编译器错误。

在处理函数调用pb->foo()时,编译器会查找fooin Base。它在那里找到一个匹配项并停止。它尝试使用该函数,但该函数的签名与调用代码不匹配。因此,这是一个编译器错误。

一旦编译器找到 a fooinDerived它就不会去搜索匹配foo的 in Base

在这种情况下,你可以认为Derived::foo是完全隐藏Base::foo

示例代码 3:

struct Base
{
   void foo()
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
   void foo(int )
   {
   }
};

int main()
{
   Derive d;
   d.foo();      // Resolves to Derived::foo()
   d.foo(10);    // Resolves to Derived::foo(int)

   Base *pb=&d;
   pb->foo();    // Resolves to Base::foo()
   pb->foo(10);  // Compiler error.

   return 0;
}

在处理函数调用d.foo()d.foo(10),编译器会查找fooin Derived。它在那里找到了几个匹配项并停止。然后它尝试重载决议。它能够找到两个版本的匹配项。由于Derived::foo()s 都不是virtual函数,因此绑定是在编译时完成的。

在处理函数调用bp->foo()bp->foo(10),编译器会查找fooin Base。它在那里找到了几个匹配项并停止。然后它尝试重载决议。它能够为第一个版本找到匹配项,但不能为第二个版本找到匹配项。它会为第二次调用生成错误。

这里Derived::foo不仅隐藏Base::foo,而且还有两个重载版本的Derived::foo.

示例代码 4:

struct Base
{
   virtual void foo()
   {
   }
   void foo(int)
   {
   }
};

struct Derive: public Base
{
   void foo()
   {
   }
   void foo(int )
   {
   }
};

int main()
{
   Derive d;
   d.foo();      // Resolves to Derived::foo()
                 // But Derived:foo() gets called at run time.

   d.foo(10);    // Resolves to Derived::foo(int)
                 // But Derived:foo(int) gets called at run time.

   Base *pb=&d;
   pb->foo();    // Resolves to Base::foo()
                 // But Derived:foo() gets called at run time due to
                 // function overwritting.

   pb->foo(10);  // Resolves to Base::foo(10)
                 // Base:foo(int) gets called at run time.

   return 0;
}

这包括函数隐藏、函数重载和函数覆盖。

Derived::foo隐藏Base::foo
Derived::foo()并且Derived::foo(int)超载。
Base::foo()并且Base::foo(int)超载。
Base::foo()被 覆盖Derived::foo()

我希望这能消除你的一些疑问。

于 2014-08-27T03:53:47.230 回答
0

此代码中没有发生重载或覆盖。Base::foo被调用,而不是Derive::foo因为程序员没有任何规范来为 name 使用动态绑定foo。如果没有提供virtual-specifier,编译器会根据调用它的对象的静态类型而不是它可能指向的类型来查找函数。这称为静态绑定,它在编译时完成。

如果使用虚拟说明符,则在运行时查找函数名称并根据对象的运行时类型调用。


顺便说一句,您的基类需要一个虚拟析构函数,原因与上述相同。如果您有一个Base指向Derive删除的类指针,则该指针将仅调用基类析构函数,而不是基类和派生类。

于 2014-08-27T02:03:38.720 回答