无法理解C ++`virtual`

时间:2009-12-01 21:21:43

标签: c++ virtual-functions

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

来自维基百科

  

在面向对象的编程中,a   虚函数或虚方法是   一种行为的函数或方法   可以在继承中重写   通过具有相同功能的类   签名。

但是,如果不使用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的原始理解非常不同,因为“使函数可以覆盖”

9 个答案:

答案 0 :(得分: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()方法完全相同。

答案 1 :(得分:9)

您可以将其想象如下。

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

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

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

Base * base;
Derived *derived;

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

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

答案 2 :(得分:2)

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

答案 3 :(得分:2)

  

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

您的原创和新理解都是错误的。

  • 方法(您称之为函数)始终可以覆盖。无论是虚拟的,纯粹的,非虚拟的还是什么。
  • 父级析构函数始终被调用。和构造函数一样。

如果通过指向类型指针的指针调用方法,“虚拟”只会产生影响。因为在你的例子中你根本不使用指针,所以虚拟根本没有任何区别。

如果使用指向A的类型的变量a,即A* a;,则不仅可以为其指定其他类型的指针变量,还可以指定变量指向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.

答案 4 :(得分:1)

  

如下所示,函数A :: a被B :: a成功覆盖而不需要虚拟

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

  

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

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

答案 5 :(得分:1)

如果你使用基类指针作为consultutah(以及我正在键入时的其他人),你需要虚拟。))说。

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

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

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

答案 6 :(得分:0)

尝试((A *)&amp; b).a()并查看当时调用的内容。

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

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

答案 7 :(得分:0)

假设你实例化了B但是把它作为A:

的一个实例
A *a = new B();

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

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

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

delete a;

将确保B的析构函数被调用,无论它是如何被保存的,无论是基类还是接口等等。

顺便说一句好问题。

答案 8 :(得分:0)

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

棋盘上有指向所有棋子的指针。空方块是NULL指针。但它只知道每个指针指向一个棋子。董事会无需了解更多信息。但是当一块被移动时,棋盘不知道它是一个有效的移动,因为每个pice具有不同的特征,即它是如何移动的。因此,如果移动有效,董事会需要检查该部分。

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))
         {
             .....
         }
    }
}