2012-01-11 103 views
30

可能重复:
Can someone explain C++ Virtual Methods?为什么使用虚拟功能?

我有一个关于在C++虚函数的问题。

为什么以及何时做,我们使用虚拟函数?任何人都可以给我一个实时的实现或使用虚拟功能?

+1

我为此创建了一篇博文,因为当一个小孩问我“为什么虚拟功能”时,我很难解释它。 http://nrecursions.blogspot.in/2015/06/so-why-do-we-need-virtual-functions.html – Nav 2015-06-07 11:23:17

回答

45

当你想覆盖某种行为(读法)为您的派生类而不是基类中实现了一个可以使用的虚拟功能,你想在运行时通过指针基类这样做。

典型的例子是,当你有一个基类叫做Shape和具体形状(类),从它派生。每个具体类覆盖(实现一个虚拟方法),称为Draw()

的类层次结构如下:

Class hierarchy

如下片段展示的实例中的使用情况;它创建一个Shape类指针数组,其中每个指向一个不同的派生类对象。在运行时,调用Draw()方法会导致调用由该派生类覆盖的方法,并且绘制(或呈现)特定的Shape

Shape *basep[] = { &line_obj, &tri_obj, 
        &rect_obj, &cir_obj}; 
for (i = 0; i < NO_PICTURES; i++) 
    basep[i] -> Draw(); 

上述程序只是使用指向基类的指针来存储派生类对象的地址。这提供了一个松耦合,因为如果随时添加一个新的具体派生类shape,程序不需要剧烈改变。原因是有实际使用(取决于)具体的Shape类型的最小代码段。

以上是著名SOLID设计原则Open Closed Principle的一个很好的例子。

+7

如果我们不使用'virtual'并且只是在子类中重新定义它们,例如'Line '和'三角形'。那里会有什么不同? – bluejamesbond 2013-11-27 05:31:32

+4

如果该函数在基类中不是虚拟的,则无法通过基类指针访问派生类版本。 – radensb 2014-07-08 06:03:27

+0

对不起,这里还是有点困惑。如果说虚拟函数用于在运行时不知道需要哪个版本的函数时,是否正确,但在此之前还需要基类的实例化?因为看起来我仍然可以在没有虚函数的情况下获得它,并且只要创建子类并实例化恰当的类就可以知道我需要什么,直到运行时为止。所以看起来你*还需要在基础类的实例化之前的一段时间*是关键的,对吧?又名:通过基址指针调用派生函数 – krb686 2015-11-03 01:42:51

1

你会使用虚拟函数来实现“多态”,特别是当你有一个对象,不知道实际的基础类型是什么,但知道你要在其上执行什么操作,并且执行这(它是如何做的)根据你实际拥有的类型而不同。

本质上通常所说的“里氏替换原则”芭芭拉里氏谁谈到这个1983年左右

,你需要使用动态运行时决定,其中,在该点调用该函数的代码被称为得名,您不知道现在或将来可能通过哪些类型,这是一个很好的使用模式。

虽然这不是唯一的方法。有各种各样的“回调”可能会带来“斑点”的数据,并且您可能有回调表取决于数据中的头块,例如,一个消息处理器。为此,不需要使用虚函数,实际上你可能会用到的只是一个v表如何只用一个入口(例如只有一个虚函数的类)来实现。

20

思考的动物类的,以及它的衍生是猫,狗和牛。动物类有一个

virtual void SaySomething() 
{ 
    cout << "Something"; 
} 

函数。

Animal *a; 
a = new Dog(); 
a->SaySomething(); 

而不是打印“东西”,狗应该说“树皮”,猫应该说“喵”。在这个例子中,你看到a是一只狗,但有时候你有一只动物指针,不知道它是哪个动物。你不想知道它是哪个动物,你只是想让动物说些什么。所以你只是称虚拟功能,猫会说“喵”,狗会说“树皮”。

当然,SaySomething函数应该是纯虚拟的以避免可能的错误。

+2

谢谢你的回答令人满意.... – haris 2012-01-11 18:28:10

+0

我不太关注你的最后一句“SaySomething函数应该是纯虚拟的,以避免可能的错误。”请你举个例子吧。 – user454083 2013-12-17 09:33:06

+3

对不起,延迟回复。 考虑到这一点,开发人员创建一个继承我们动物类的新类(比如'Fox'),并忘记重写SaySomething方法。如果你使用我提供的虚拟方法,'Fox'实例会说“Something”,这是不对的。如果我们将SaySomething声明为纯虚函数,我们将无法实例化Fox,包含“new Fox(...)”的代码将引发错误。这样,创建Fox类的开发人员将在编译时通知他/她的错误。编译时间错误是很好的,因为它们不会浪费时间:) – holgac 2014-06-13 05:16:29

20

当您需要以相同的方式处理不同的对象时,您可以使用虚函数。它叫做多态。让我们想象一下,你有一些基类 - 有点像经典外形:

class Shape 
    { 
     public: 
      virtual void draw() = 0; 
      virtual ~Shape() {} 
    }; 

    class Rectange: public Shape 
    { 
     public: 
      void draw() { // draw rectangle here } 
    }; 


    class Circle: public Shape 
    { 
     public: 
      void draw() { // draw circle here } 
    }; 

现在你可以有不同形状的载体:

vector<Shape*> shapes; 
    shapes.push_back(new Rectangle()); 
    shapes.push_back(new Circle()); 

,你能画各种形状是这样的:

for(vector<Shape*>::iterator i = shapes.begin(); i != shapes.end(); i++) 
    { 
      (*i)->draw(); 
    } 

通过这种方式,您可以使用一种虚拟方法绘制不同的形状 - draw()。方法的正确版本是根据关于指针后面的对象类型的运行时间信息来选择的。

通知 当使用虚拟函数可以声明为纯虚(类形状,只是代替“= 0”的方法的原之后等)。在这种情况下,您将无法使用纯虚函数创建对象实例,并将其称为Abstract类。

也注意析构函数之前的“虚拟”。如果您计划通过指向其基类的指针来处理对象,则应该声明析构函数为虚拟的,因此,当您为基类指针调用“delete”时,将调用所有析构函数链,并且不会有内存泄漏。