2009-12-01 89 views
5

我很难理解C++中关键字virtual的用途。我知道C和Java非常好,但我是新的C++麻烦理解C++`virtual`

维基百科

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

但是我可以重写的方法作为不使用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 ::一个可以成功地与B ::一个重写而无需virtual

复利我的困惑是有关虚拟析构函数这一说法,也从维基百科

,如以下示例所示, 对于C++基类 具有虚拟析构函数以确保 总是调用最多的派生类的析构函数是很重要的。

那么virtual也告诉编译器调用父类的析构函数吗?这似乎是从我原来的virtual理解非常不同,因为“做的函数重写”

回答

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()方法在未标记为虚拟时完全相同。

+2

不需要用new/delete将例子复杂化:'B obj; A * p = &obj;'或'A&ref = obj;' – 2009-12-01 21:52:17

+1

正确 - 我在第一次编辑时实际上忽略了删除操作;但C++中的解引用语义也很复杂...... ;-) – ConsultUtah 2009-12-01 21:55:26

0

尝试((A *)& b).a()然后看看接下来调用的是什么。

虚拟关键字可以抽象地对待一个对象(也就是通过一个基类指针),但仍然叫后裔代码...

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

9

你可以想象如下。

Java中的所有函数都是虚拟的。如果您有一个具有函数的类,并且您在派生类中重写了该函数,那么将调用它,而不管您用来调用它的变量的声明类型。

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

如果你有一个基类基地和派生类派生的,他们都在他们的非虚拟函数命名为“富”,然后

Base * base; 
Derived *derived; 

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

如果foo是虚拟的,那么这两个呼叫派生:: foo中。

+0

写得很好;简洁。 – 2009-12-01 23:56:10

1

正如你可以看到下面的函数A ::被成功地与B ::一个重写,而无需虚拟

它可能会,也可能无法正常工作。在你的例子中它是有效的,但这是因为你直接创建和使用B对象,而不是通过指向A。见C++ FAQ Lite, 20.3

那么虚拟还告诉编译器调用父进程的析构函数?

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

2

虚拟意味着实际的方法是基于实例化哪个类而不是您用来声明变量的类型来确定运行时。 在你的情况,这是一个静态覆盖它会去为B类定义的不管什么是对象的实际类型创建

0

说你实例化B,而持有它作为一个实例的方法:

A *a = new B(); 

和被调用函数a()的一个()的实现将被调用?

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

如果B的构造函数分配吨的内存阵列或打开文件,调用

delete a; 

将确保B的析构函数被调用,无论以它是如何举行,由基类或接口或什么是它。

好的问题的方式。

+3

我认为你打算成为一个指针。 – 2009-12-01 21:32:25

+0

糟糕。谢谢。猜猜谁在VB.net和C#上使用了三年? – 2009-12-01 21:36:35

1

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

虚拟机的缺乏允许保存一个检查来知道它需要调用哪个基类或某个派生类。但是,在这一点上,不要担心表演,只需要正确的行为。

虚拟析构函数非常重要,因为派生类可能会在堆中声明其他变量(即使用关键字'new'),并且您需要能够删除它。你可能注意到,在C++中,你倾向于使用比java中更少的派生(例如,你经常使用模板进行类似的使用),也许你甚至不需要为这个问题烦恼。另外,如果你永远不会在堆上声明你的对象(“A a;”而不是“A * a = new A();”),那么你也不需要担心它。当然,这很大程度上取决于你开发什么/怎么样,以及你是否计划别人会派生你的课程。

+1

这不是关于对象的分配位置,而是关于通过指针和基类的引用来使用它们,包括方法中的隐含“this”。 – 2009-12-01 21:54:10

2

那么虚拟还告诉编译器调用父进程的析构函数?这似乎与我最初对虚拟的理解有很大不同,因为“使功能可以重写”

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

  • 方法(你称之为函数)是总是覆盖。无论是虚拟的,纯粹的,非虚拟的还是其他的。
  • 父母破坏者是总是调用。和构造函数一样。

如果您通过指针类型为pointer-to-baseclass的指针调用方法,那么“Virtual”只会有所作为。由于在你的例子中你根本不使用指针,虚拟根本没有任何区别。

如果您使用的指针为A类型的变量aA* 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. 
+0

+1,你可能想要添加你的声明,即函数**总是**可覆盖。在C++ 0x关键字“最终”应该防止覆盖。 – Tim 2013-02-16 23:45:50

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