C ++虚拟/纯虚拟解释

如果一个函数被定义为虚拟的并且与纯虚拟相同,究竟意味着什么呢?

从维基百科的虚拟function …

虚函数或虚方法是一种函数或方法,其行为可以通过具有相同签名的函数在inheritance类中重写

而..

纯虚函数或纯虚方法是一个虚函数,需要由一个不是抽象的派生类来实现“ – 维基百科

所以,虚拟function可以被覆盖,纯粹的虚拟必须被执行。

我想评论维基百科对虚拟的定义,在这里重复几个。 [在写这个答案的时候,]维基百科定义了一个可以在子类中被覆盖的虚拟方法。 [幸运的是,维基百科已经被编辑了,现在它正确地解释了这个。]这是不正确的:任何方法,不只是虚拟的,可以在子类中被覆盖。 虚拟是为了给你多态性,也就是能够在运行时select一个方法的派生最多的覆盖

考虑下面的代码:

 #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类上做了parsing。

但是,调用Virtual调用Derived类实现。 由于虚拟关键字,方法的select发生在运行时 ,而不是编译时。 编译时在这里发生的事情是编译器发现这是一个Base *,并且调用了一个虚方法,所以它调用了vtable而不是class Base。 这个vtable在运行时被实例化,因此运行时parsing到最多派生的覆盖。

我希望这不是太混乱。 简而言之,任何方法都可以被覆盖,但是只有虚拟方法才能为你提供多态性,也就是运行时select最多派生的覆盖。 然而,在实践中,重写非虚拟方法被认为是不好的做法,很less使用,所以很多人(包括撰写维基百科文章的人)认为只有虚拟方法可以被覆盖。

虚拟关键字赋予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'。 它并不关心什么types的动物物体通过它,只要它来自动物。 编译器会自动将任何动物派生类转换为Animal,因为它是基类。

如果我们这样做:

 Duck d; SomeFunction(&d); 

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

 Horse h; SomeFunction(&h); 

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

 Animal a; SomeFunction(&a); 

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

纯虚函数主要用于定义:

a)抽象类

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

b)接口

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

在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纯虚拟,你会做到以下几点:

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

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

“虚拟”意味着该方法可以在子类中被覆盖,但在基类中具有可直接调用的实现。 “纯虚拟”意味着它是一个虚拟的方法,没有可直接调用的实现。 这样的方法在inheritance层次结构中必须至less覆盖一次 – 如果一个类有任何未实现的虚拟方法,则该类的对象不能被构造,编译将失败。

@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中至less不会编译:

 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() 

虚拟关键字如何工作?

假设人是基类,印度是从人类派生的。

 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. 

如果不使用虚拟,则静态确定或由编译器静态绑定,具体取决于调用的对象。 所以如果Man的一个对象调用do_work(),那么Man的do_work()即使被称为它也指向一个印度对象

我相信顶尖的投票答案是误导 – 任何虚拟的方法可以在派生类中重写实现。 具体参考C ++,正确的区别是运行时(当使用虚拟时)绑定和编译时(当虚拟不被使用,但是一个方法被覆盖,一个基指针指向一个派生的对象)绑定相关的函数。

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

“Justin,”纯粹的虚拟“只是一个术语(不是关键字,请参阅下面的答案),意思是”这个函数不能被基类实现“。

这是错误的! 纯粹的虚拟function也可以有一个机构,可以实现! 事实是,抽象类的纯虚函数可以静态调用! 两个非常好的作家是Bjarne Stroustrup和Stan Lippman ….因为他们写了这个语言。

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

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

封装允许抽象的实现细节隐藏在简单的接口后面。

inheritance允许将一个新的抽象定义为对某些现有抽象的扩展或改进,自动获得其某些或全部特征。

dynamic方法绑定允许新抽象显示其新行为,即使在需要旧抽象的上下文中使用。

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

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

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

纯虚拟方法对应抽象类中抽象方法的行为。 而一个只包含纯虚方法和常量的类将是接口的cpp-pendant。

纯虚拟function

试试这个代码

 #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并运行代码。 你会得到错误!因为当一个类包含一个纯虚函数时,没有任何对象可以从这个类中创build,并且它被inheritance,那么它的派生类必须实现这个函数。

虚拟function

尝试另一个代码

 #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函数在基类中被标记为虚拟的。它说编译器试图在派生类中search函数并执行函数。如果没有find,那么执行基类。谢谢

“虚拟函数或虚拟方法是一种函数或方法,其行为可以通过具有相同签名的函数在inheritance类中重写” – 维基百科,自由的百科全书

这不是虚拟function的好解释。 因为,即使成员不是虚拟的,inheritance类也可以覆盖它。 你可以尝试自己看看。

当一个函数把一个基类作为一个参数的时候,这个区别就performance出来了 当您将inheritance类作为input时,该函数将使用overriden函数的基类实现。 但是,如果该函数是虚拟的,则使用派生类中实现的函数。

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

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

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

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

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

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