5

我无法理解virtualC++ 中关键字的用途。我非常了解 C 和 Java,但我是 C++ 新手

来自维基百科

在面向对象的编程中,虚函数或虚方法是一种函数或方法,其行为可以在继承类中被具有相同签名的函数覆盖。

virtual但是我可以在不使用关键字的情况下覆盖如下所示的方法

#include <iostream>

using namespace std;

class A {
    public:
        int a();
};

int A::a() {
    return 1;   
}

class B : A { 
    public:
        int a();
};

int B::a() {
    return 2;
}

int main() {
    B b;
    cout << b.a() << endl;
    return 0;
}

//output: 2

正如您在下面看到的,函数 A::a 被 B::a 成功覆盖,而不需要virtual

让我更加困惑的是这个关于虚拟析构函数的声明,也来自维基百科

如以下示例所示,对于 C++ 基类,具有虚拟析构函数非常重要,以确保始终调用来自最派生类的析构函数。

那么virtual还告诉编译器调用父级的析构函数吗?这似乎与我最初对virtual“使函数可覆盖”的理解有很大不同

4

9 回答 9

16

进行以下更改,您将了解原因:

#include <iostream>

using namespace std;

class A {
    public:
        int a();
};

int A::a() {
    return 1;   
}

class B : public A { // Notice public added here
    public:
        int a();
};

int B::a() {
    return 2;
}

int main() {
    A* b = new B(); // Notice we are using a base class pointer here
    cout << b->a() << endl; // This will print 1 instead of 2
    delete b; // Added delete to free b
    return 0;
}

现在,让它像你想要的那样工作:

#include <iostream>

using namespace std;

class A {
    public:
        virtual int a(); // Notice virtual added here
};

int A::a() {
    return 1;   
}

class B : public A { // Notice public added here
    public:
        virtual int a(); // Notice virtual added here, but not necessary in C++
};

int B::a() {
    return 2;
}

int main() {
    A* b = new B(); // Notice we are using a base class pointer here
    cout << b->a() << endl; // This will print 2 as intended
    delete b; // Added delete to free b
    return 0;
}

您包含的关于虚拟析构函数的注释是完全正确的。在您的示例中,没有什么需要清理的,但是说 A 和 B 都有析构函数。如果它们没有被标记为虚拟,哪一个会被基类指针调用?提示:它的工作方式与未标记为虚拟时的 a() 方法完全相同。

于 2009-12-01T21:26:17.190 回答
9

你可以这样想。

Java 中的所有函数都是虚函数。如果您有一个带有函数的类,并且您在派生类中重写了该函数,则无论您用来调用它的变量的声明类型如何,它都会被调用。

另一方面,在 C++ 中,它不一定会被调用。

如果你有一个基类 Base 和一个派生类 Derived,并且它们都有一个名为 'foo' 的非虚函数,那么

Base * base;
Derived *derived;

base->foo(); // calls Base::foo
derived->foo(); // calls Derived::foo

如果 foo 是虚拟的,那么两者都调用 Derived::foo。

于 2009-12-01T21:27:12.567 回答
2

virtual 表示实际方法是根据实例化的类而不是用于声明变量的类型来确定运行时的。在您的情况下,这是一个静态覆盖,无论创建的对象的实际类型是什么,它将适用于为 B 类定义的方法

于 2009-12-01T21:28:40.193 回答
2

所以 virtual 还告诉编译器调用父级的析构函数?这似乎与我最初对 virtual 的理解非常不同,即“使函数可覆盖”

你原来的和你新的理解都是错误的。

  • 方法(你称它们为函数)总是可以覆盖的。不管是虚拟的、纯粹的、非虚拟的还是什么。
  • 总是调用父析构函数。构造函数也是如此。

只有当您通过指向基类的指针类型的指针调用方法时,“虚拟”才会产生影响。由于在您的示例中您根本不使用指针,因此 virtual 根本没有任何区别。

如果使用apointer-to-A类型的变量,即A* a;,你不仅可以将pointer-to-A类型的其他变量赋值给它,还可以将pointer-to-B类型的变量赋值给它,因为B派生自A .

A* a; 
B* b;

b = new B(); // create a object of type B. 
a = b;       // this is valid code. a has still the type pointer-to-A, 
             // but the value it holds is b, a pointer to a B object.

a.a();       // now here is the difference. If a() is non-virtual, A::a()
             // will be called, because a is of type pointer-to-A. 
             // Whether the object it points to is of type A, B or
             // something entirely different doesn't matter, what gets called
             // is determined during compile time from the type of a.

a.a();       // now if a() is virtual, B::a() will be called, the compiler
             // looks during runtime at the value of a, sees that it points
             // to a B object and uses B::a(). What gets called is determined
             // from the type of the __value__ of a.
于 2009-12-01T23:42:34.393 回答
1

正如您在下面看到的,函数 A::a 被 B::a 成功覆盖,而不需要 virtual

它可能起作用,也可能不起作用。在您的示例中它有效,但这是因为您B直接创建和使用对象,而不是通过指向A. 请参阅C++ FAQ Lite,20.3

所以 virtual 还告诉编译器调用父级的析构函数?

如果删除指向派生类对象的基类指针,并期望基类和派生析构函数都运行,则需要虚拟析构函数。请参阅C++ FAQ Lite,20.7

于 2009-12-01T21:28:33.797 回答
1

如果您使用基类指针作为consultutah(以及我输入时的其他人;)),您需要虚拟。

缺少虚拟允许保存检查以了解它需要调用的方法(基类之一或某些派生类)。但是,此时不要担心表现,只关注正确的行为。

虚拟析构函数特别重要,因为派生类可能会在堆上声明其他变量(即使用关键字“new”)并且您需要能够将其删除。

但是,您可能会注意到,在 C++ 中,您往往比在 java 中使用更少的派生(例如,您经常将模板用于类似用途),也许您甚至不需要为此烦恼。此外,如果您从未在堆上声明您的对象(“A a;”而不是“A * a = new A();”),那么您也不必担心它。当然,这在很大程度上取决于您开发什么/如何开发以及您是否计划其他人将派生您的课程。

于 2009-12-01T21:34:34.700 回答
0

试试 ((A*)&b).a() 看看会调用什么。

virtual 关键字允许您以抽象方式处理对象(IE 通过基类指针),但仍然调用后代代码......

换句话说,虚拟关键字“让旧代码调用新代码”。您可能已经编写了对 A 进行操作的代码,但是通过虚函数,该代码可以调用 B 的较新的 a()。

于 2009-12-01T21:27:07.743 回答
0

假设您实例化了 B,但将其作为 A 的实例保存:

A *a = new B();

并调用函数 a(),其 a() 的实现将被调用?

如果 a() 不是虚拟 A,将被调用。如果 a() 是虚拟的,则无论您如何持有它,都会调用 a() 的实例化子类版本。

如果 B 的构造函数为数组或打开的文件分配了大量内存,则调用

delete a;

将确保调用 B 的析构函数,而不管它是如何被持有的,无论是由基类或接口还是其他任何东西。

顺便问下好问题。

于 2009-12-01T21:29:47.637 回答
0

我总是把它想象成棋子(我对 OO 的第一次实验)。

棋盘上有指向所有棋子的指针。空方格是 NULL 指针。但它只知道每个指针指向一个棋子。董事会不需要知道更多信息。但是当棋子移动时,棋盘并不知道这是有效的棋步,因为每个棋子的移动方式都不同。因此,如果移动有效,董事会需要检查该棋子。

Piece*    board[8][8];

CheckMove(Point const& from,Point const& too)
{
    Piece*  piece = board[from.x][from.y];
    if (piece != NULL)
    {
        if (!piece->checkValidMove(from,too))
        {    throw std::exception("Bad Move");
        }
        // Other checks.
    }
}

class Piece
{
    virtual bool checkValidMove(Point const& from,Point const& too)  = 0;
};

class Queen: public Piece
{
    virtual bool checkValidMove(Point const& from,Point const& too) 
    {
         if (CheckHorizontalMove(from,too) || CheckVerticalMoce(from,too) || CheckDiagonalMove(from,too))
         {
             .....
         }
    }
}
于 2009-12-02T00:31:16.243 回答