C ++虚拟/纯虚拟解释

时间:2009-08-20 15:00:36

标签: c++ virtual

如果将函数定义为虚拟并且与纯虚拟函数相同,这究竟是什么意思?

12 个答案:

答案 0 :(得分:309)

来自Wikipedia's Virtual function ...

  

在面向对象的编程中,在诸如C ++和Object Pascal之类的语言中,虚函数或虚方法是一种可继承和可覆盖的函数或方法,为其提供动态调度。这个概念是面向对象编程(OOP)的(运行时)多态部分的重要部分。简而言之,虚函数定义了要执行的目标函数,但在编译时可能不知道目标。

与非虚函数不同,当重写虚函数时,最派生的版本用于类层次结构的所有级别,而不仅仅是创建它的级别。因此,如果基类的一个方法调用虚拟方法,则将使用派生类中定义的版本而不是基类中定义的版本。

这与非虚函数形成对比,非虚函数仍可在派生类中重写,但“新”版本仅由派生类及其下方使用,但不会更改基类的功能一点都不。

而..

  

纯虚函数或纯虚方法是一个虚函数,如果派生类不是抽象类,则需要由派生类实现。

当存在纯虚方法时,该类是“抽象的”,并且不能单独实例化。相反,必须使用实现纯虚方法的派生类。根本没有在基类中定义pure-virtual,因此派生类必须定义它,或者派生类也是抽象的,并且不能实例化。只能实例化没有抽象方法的类。

虚拟提供了一种覆盖基类功能的方法,而纯虚拟需要

答案 1 :(得分:193)

我想评论一下维基百科对虚拟的定义,这里有几个重复。 [在写这个答案的时候,]维基百科将虚拟方法定义为可以在子类中重写的方法。 [幸运的是,Wikipedia已经被编辑过,现在它正确地解释了这一点。]这是不正确的:任何方法,而不仅仅是虚方法,都可以在子类中重写。虚拟的做法是为您提供多态性,即在运行时选择方法的最派生覆盖的能力。

请考虑以下代码:

#include <iostream>
using namespace std;

class Base {
public:
    void NonVirtual() {
        cout << "Base NonVirtual called.\n";
    }
    virtual void Virtual() {
        cout << "Base Virtual called.\n";
    }
};
class Derived : public Base {
public:
    void NonVirtual() {
        cout << "Derived NonVirtual called.\n";
    }
    void Virtual() {
        cout << "Derived Virtual called.\n";
    }
};

int main() {
    Base* bBase = new Base();
    Base* bDerived = new Derived();

    bBase->NonVirtual();
    bBase->Virtual();
    bDerived->NonVirtual();
    bDerived->Virtual();
}

这个程序的输出是什么?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.

派生会覆盖Base的每个方法:不仅是虚拟方法,还包括非虚拟方法。

我们看到当你有一个Base-pointer-to-Derived(bDerived)时,调用NonVirtual调用Base类实现。这在编译时解决:编译器发现bDerived是Base *,NonVirtual不是虚拟的,因此它在类Base上执行解析。

但是,调用Virtual会调用Derived类实现。由于关键字为virtual,方法的选择发生在运行时,而不是编译时。在编译时发生的事情是编译器看到这是一个Base *,并且它正在调用一个虚方法,所以它插入一个调用vtable而不是类Base。此vtable在运行时实例化,因此运行时解析为最派生的覆盖。

我希望这不会太混乱。简而言之,任何方法都可以被覆盖,但只有虚方法才能为您提供多态性,即运行时选择最多派生的覆盖。然而,在实践中,覆盖非虚拟方法被认为是不好的做法并且很少使用,因此许多人(包括撰写维基百科文章的人)都认为只能覆盖虚拟方法。

答案 2 :(得分:111)

virtual关键字为C ++提供了支持多态的能力。如果有指向某个类的对象的指针,例如:

class Animal
{
  public:
    virtual int GetNumberOfLegs() = 0;
};

class Duck : public Animal
{
  public:
     int GetNumberOfLegs() { return 2; }
};

class Horse : public Animal
{
  public:
     int GetNumberOfLegs() { return 4; }
};

void SomeFunction(Animal * pAnimal)
{
  cout << pAnimal->GetNumberOfLegs();
}

在这个(愚蠢的)示例中,GetNumberOfLegs()函数根据调用它的对象的类返回适当的数字。

现在,考虑函数'SomeFunction'。它不关心什么类型的动物对象传递给它,只要它来自动物。编译器会自动将任何Animal派生类转换为Animal,因为它是基类。

如果我们这样做:

Duck d;
SomeFunction(&d);

输出'2'。如果我们这样做:

Horse h;
SomeFunction(&h);

输出'4'。我们不能这样做:

Animal a;
SomeFunction(&a);

因为GetNumberOfLegs()虚函数是纯函数而无法编译,这意味着它必须通过派生类(子类)来实现。

纯虚函数主要用于定义:

a)抽象类

这些是基类,您必须从它们派生,然后实现纯虚函数。

b)接口

这些是“空”类,其中所有函数都是纯虚函数,因此您必须派生并实现所有函数。

答案 3 :(得分:30)

在C ++类中, virtual 是指定该关键字的关键字,可以覆盖(即由子类实现)方法。例如:

class Shape 
{
  public:
    Shape();
    virtual ~Shape();

    std::string getName() // not overridable
    {
      return m_name;
    }

    void setName( const std::string& name ) // not overridable
    {
      m_name = name;
    }

  protected:
    virtual void initShape() // overridable
    {
      setName("Generic Shape");
    }

  private:
    std::string m_name;
};

在这种情况下,子类可以覆盖 initShape 函数来执行一些专门的工作:

class Square : public Shape
{
  public: 
    Square();
    virtual ~Square();

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}

术语纯虚拟指的是需要由子类实现但尚未由基类实现的虚函数。您可以使用 virtual 关键字将方法指定为纯虚拟方法,并在方法声明的末尾添加 = 0

因此,如果您想使Shape :: initShape为pure virtual,您可以执行以下操作:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};

通过向您的班级添加纯虚拟方法,您可以将班级设为abstract base class 这对于将接口与实现分离非常方便。

答案 4 :(得分:15)

“Virtual”表示该方法可以在子类中重写,但在基类中具有可直接调用的实现。 “纯虚拟”意味着它是一个虚拟方法,没有可直接调用的实现。这样的方法必须在继承层次结构中至少被覆盖一次 - 如果一个类有任何未实现的虚方法,那么该类的对象就无法构造,编译就会失败。

@quark指出纯虚方法可以有一个实现,但由于必须重写纯虚方法,因此无法直接调用默认实现。以下是具有默认值的纯虚方法的示例:

#include <cstdio>

class A {
public:
    virtual void Hello() = 0;
};

void A::Hello() {
    printf("A::Hello\n");
}

class B : public A {
public:
    void Hello() {
        printf("B::Hello\n");
        A::Hello();
    }
};

int main() {
    /* Prints:
           B::Hello
           A::Hello
    */
    B b;
    b.Hello();
    return 0;
}

根据评论,编译是否会失败是特定于编译器的。至少在GCC 4.3.3中,它不会编译:

class A {
public:
    virtual void Hello() = 0;
};

int main()
{
    A a;
    return 0;
}

输出:

$ g++ -c virt.cpp 
virt.cpp: In function ‘int main()’:
virt.cpp:8: error: cannot declare variable ‘a’ to be of abstract type ‘A’
virt.cpp:1: note:   because the following virtual functions are pure within ‘A’:
virt.cpp:3: note:   virtual void A::Hello()

答案 5 :(得分:9)

虚拟关键字如何运作?

假设Man是基类,印度人是源于人类。

Class Man
{
 public: 
   virtual void do_work()
   {}
}

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}

将do_work()声明为虚拟只是意味着:只能在运行时确定要调用的do_work()。

假设我这样做,

Man *man;
man = new Indian();
man->do_work(); // Indian's do work is only called.

如果未使用virtual,则编译器将静态确定或静态绑定,具体取决于调用的对象。因此,如果Man的一个对象调用do_work(),那么Man的do_work()被称为即使它指向一个印度对象

我认为最高投票的答案是误导性的 - 任何方法,无论虚拟是否可以在派生类中具有重写的实现。具体参考C ++,正确的差异是运行时(当使用虚拟时)绑定和编译时(当不使用虚拟但是方法被覆盖并且基指针指向派生对象时)关联函数的绑定。

似乎有另一种误导性的评论说,

  

“Justin,'纯虚拟'只是一个术语(不是关键词,请参阅我的回答   以下)用于表示“此功能不能由基地实施   类“。

这是错的! 纯粹的虚拟功能也可以有一个身体并且可以实现!事实是,抽象类的纯虚函数可以静态调用!两位非常优秀的作家是Bjarne Stroustrup和Stan Lippman ....因为他们写了这种语言。

答案 6 :(得分:2)

Simula,C ++和C#,默认使用静态方法绑定,程序员可以通过将它们标记为虚拟来指定特定方法应该使用动态绑定。 动态方法绑定是面向对象编程的核心。

面向对象编程需要三个基本概念:封装,继承和动态方法绑定。

  

封装允许实现详细信息   抽象隐藏在一个   简单的界面。

     

继承允许将新抽象定义为   某些人的延伸或完善   现有的抽象,获得一些   或其所有特征   自动。

     

动态方法绑定允许新抽象显示其新内容   甚至在上下文中使用时的行为   期待旧的抽象。

答案 7 :(得分:1)

虚拟方法可以通过派生类来覆盖,但需要在基类中实现(将被覆盖的实现)

纯虚方法没有实现基类。它们需要由派生类定义。 (因此技术上被覆盖的不是正确的术语,因为没有什么可以覆盖的。)

Virtual对应于默认的java行为,当派生类重写基类的方法时。

Pure Virtual方法对应于抽象类中抽象方法的行为。并且只包含纯虚方法和常量的类将是接口的cpp-pendant。

答案 8 :(得分:0)

  • 虚函数必须在基类中以及派生类中有一个定义,但不是必需的,例如ToString()或toString()函数是一个虚拟的,所以你可以通过覆盖用户来提供你自己的实现 - 已定义的类。

  • 虚拟函数在普通类中声明和定义。

  • 纯虚函数必须以“= 0”结尾,并且只能在抽象类中声明。

  • 具有纯虚函数的抽象类不能具有纯虚函数的定义,因此它意味着必须在从该抽象类派生的类中提供实现。

答案 9 :(得分:0)

纯虚拟功能

试试这段代码

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()=0;

};

class anotherClass:aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"hellow World";
    }

};
int main()
{
    //aClassWithPureVirtualFunction virtualObject;
    /*
     This not possible to create object of a class that contain pure virtual function
    */
    anotherClass object;
    object.sayHellow();
}

在课程 anotherClass 中删除函数sayHellow并运行代码。你会得到错误!因为当一个类包含一个纯虚函数时,不能从该类创建任何对象并且它是继承的,那么它的派生类必须实现该函数。

虚拟功能

尝试另一个代码

#include <iostream>
using namespace std;
class aClassWithPureVirtualFunction
{

public:

    virtual void sayHellow()
    {
        cout<<"from base\n";
    }

};

class anotherClass:public aClassWithPureVirtualFunction
{

public:

    void sayHellow()
    {

        cout<<"from derived \n";
    }

};
int main()
{
    aClassWithPureVirtualFunction *baseObject=new aClassWithPureVirtualFunction;
    baseObject->sayHellow();///call base one

    baseObject=new anotherClass;
    baseObject->sayHellow();////call the derived one!

}

这里sayHellow函数在基类中被标记为虚拟。它说编译器尝试在派生类中搜索函数并实现该函数。如果没有找到则执行基函数。谢谢

答案 10 :(得分:0)

“虚函数或虚方法是一种函数或方法,其行为可以通过具有相同签名的函数在继承类中重写” - 维基百科

这不是虚拟功能的好解释。因为,即使成员不是虚拟成员,继承类也可以覆盖它。你可以亲自尝试看看。

当函数将基类作为参数时,差异显示出来。当您将继承类作为输入时,该函数使用overriden函数的基类实现。但是,如果该函数是虚函数,则它使用在派生类中实现的函数。

答案 11 :(得分:0)

虚函数是在基类中声明的成员函数,由派生类重新定义。虚函数按继承顺序分层。 当派生类不覆盖虚函数时,将使用其基类中定义的函数。

纯虚函数是指不包含相对于基类的定义的函数。 它在基类中没有实现。任何派生类都必须覆盖此函数。