C++虚拟/纯虚拟解释

Jus*_*tin 327 c++ virtual

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

Die*_*ias 321

来自维基百科的虚拟功能 ......

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

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

这与非虚函数形成对比,非虚函数仍然可以在派生类中重写,但"新"版本仅由派生类及其下方使用,但根本不会更改基类的功能.

而..

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

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

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

  • virtual void Function()= 0; 是纯粹的虚拟."= 0"表示纯度. (191认同)
  • 这不是一个好的答案.任何方法都可以被覆盖,而不仅仅是虚拟方法.有关详细信息,请参阅我的回答 (14认同)
  • 我相信Stroustrup说他想添加一个`pure`关键字,但贝尔实验室即将发布C++的主要版本,他的经理不会在最后阶段允许它.添加关键字是一件大事. (13认同)
  • 那么......纯粹是虚拟关键字,还是仅仅是一个使用的术语? (9认同)
  • Justin,'纯虚拟'只是一个术语(不是关键字,请参阅下面的答案),用于表示"此功能无法由基类实现.正如Goz所说,将"= 0"添加到虚拟结尾功能使它"纯粹" (7认同)
  • @AndersonGreen对维基百科的流行派对的糟糕表现.无论如何,维基百科都是一个高度可靠的信息来源.我接受多数为事实,可能有例外的是有动机进行虚假编辑的争议性话题,但无论如何都要由主持人审查,并且几乎总是在短时间内删除.逻辑错误,评论不好. (3认同)
  • @zeFree,唯一的问题是阿西克的答案没有完全回答问题——它告诉了虚拟是什么,但没有提到纯虚拟。 (2认同)

Asi*_*sik 201

我想评论维基百科对虚拟的定义,这里有几个重复.[在写这个答案的时候,]维基百科将虚拟方法定义为可以在子类中重写的方法.[幸运的是,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();
}
Run Code Online (Sandbox Code Playgroud)

这个程序的输出是什么?

Base NonVirtual called.
Base Virtual called.
Base NonVirtual called.
Derived Virtual called.
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • 该定义仍然不正确.根据定义,可以在派生类中重写的方法不是虚拟的; 是否可以覆盖该方法与"虚拟"的定义无关.另外,"重载"通常是指同一个类中具有相同名称和返回类型但参数不同的多个方法; 它与"覆盖"非常不同,"覆盖"意味着完全相同的签名但在派生类中.当它以非多态(非虚拟基础)完成时,它通常被称为"隐藏". (23认同)
  • 仅仅因为维基百科文章(我无法保护)定义了一个虚拟方法"作为可以在子类中重写的方法"并不排除可以声明具有相同名称的其他非虚拟方法的可能性.这称为重载. (6认同)
  • 这应该是公认的答案.那篇特别的维基百科文章[我将花时间在这里链接,因为这个问题的其他任何人都没有这样做](http://en.wikipedia.org/wiki/Virtual_function),是完全垃圾.+1,好先生. (5认同)
  • 现在它是有道理的.谢谢,好先生,正确解释任何方法都可以被派生类覆盖,并且更改是编译器在不同情况下选择调用哪个函数的行为. (2认同)
  • 添加带有相同函数调用的`Derived*`来驱动点回家可能会有所帮助.否则很好的答案 (2认同)
  • `派生* d = 新派生(); d-&gt;NonVirtual();` 将打印“Derived NonVirtual called”。如果您想知道如何称呼它。 (2认同)

JBR*_*son 112

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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

如果我们这样做:

Duck d;
SomeFunction(&d);
Run Code Online (Sandbox Code Playgroud)

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

Horse h;
SomeFunction(&h);
Run Code Online (Sandbox Code Playgroud)

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

Animal a;
SomeFunction(&a);
Run Code Online (Sandbox Code Playgroud)

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

纯虚函数主要用于定义:

a)抽象类

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

b)接口

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

  • 顺便说一句,我没有找到愚蠢的例子. (7认同)

Nic*_*dad 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;
};
Run Code Online (Sandbox Code Playgroud)

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

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

  protected:
    virtual void initShape() // override the Shape::initShape function
    {
      setName("Square");
    }
}
Run Code Online (Sandbox Code Playgroud)

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

因此,如果您想使Shape :: initShape纯虚拟,您将执行以下操作:

class Shape 
{
 ...
    virtual void initShape() = 0; // pure virtual method
 ... 
};
Run Code Online (Sandbox Code Playgroud)

通过向类中添加纯虚方法,可以使类成为一个抽象基类 ,这对于将接口与实现分离非常方便.

  • 并且"getName函数不能由子类实现"是不对的.子类可以实现该方法(具有相同或不同的签名),但该实现不会覆盖该方法.您可以将Circle实现为子类并实现"std :: string Circle :: getName()" - 然后您可以为Circle实例调用任一方法.但是如果通过Shape指针或引用使用,编译器将调用Shape :: getName(). (2认同)

Joh*_*kin 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

int main()
{
    A a;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出:

$ 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()
Run Code Online (Sandbox Code Playgroud)

  • 此外,约翰,以下不太正确:"'纯虚'意味着它是一个没有实现的虚方法." 纯虚方法*可以*实现.但是你无法直接调用它们:你必须覆盖并使用子类中的基类实现.这允许您提供实现的默认部分.这不是一种常见的技术. (5认同)

McM*_*rdo 9

虚拟关键字如何工作?

假设人是基类,印度人是人类.

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

Class Indian : public Man
{
 public: 
   void do_work()
   {}
}
Run Code Online (Sandbox Code Playgroud)

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

假设我这样做,

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

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

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

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

"Justin,'纯虚拟'只是一个术语(不是关键字,请参阅下面的答案),用于表示"这个函数不能由基类实现."

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

  • 不幸的是,一旦答案开始上升,所有其他人将被忽略.即使他们可能会更好. (2认同)

ras*_*dcs 5

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

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