虚拟/纯虚拟解释

如果一个函数被定义为虚函数,它究竟意味着什么?它与纯虚函数是相同的吗?

297161 次浏览

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

@夸克指出,纯虚拟方法可以有一个实现,但由于纯虚拟方法必须被覆盖,所以不能直接调用默认实现。下面是一个带默认值的纯虚方法的例子:

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

From 维基百科的虚拟函数 …< / p >

在面向对象编程中,在c++和Object Pascal等语言中,虚函数或虚方法是一种可继承和可覆盖的函数或方法,可以方便地进行动态分派。这个概念是面向对象编程(OOP)的(运行时)多态性部分的重要组成部分。简而言之,虚函数定义了要执行的目标函数,但在编译时可能不知道目标。

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

这与非虚函数相反,非虚函数仍然可以在派生类中被重写,但“新”版本只会被派生类及其以下类使用,但根本不会改变基类的功能。

而. .

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

当纯虚方法存在时,类是“抽象的”,不能单独实例化。相反,必须使用实现纯虚方法的派生类。纯虚类根本没有在基类中定义,所以派生类必须定义了它,或者该派生类也是抽象的,不能实例化。只有没有抽象方法的类才能被实例化。

虚类提供了一种重写基类功能的方法,而纯虚需要提供了这种方法。

在c++类中,虚拟是关键字,它指出方法可以被子类重写(即由子类实现)。例如:

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");
}
}

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

所以,如果你想让Shape::initShape纯虚拟,你会做以下事情:

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

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

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派生的。编译器会自动地将任何Animal派生类强制转换为Animal,因为它是一个基类。

如果我们这样做:

Duck d;
SomeFunction(&d);

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

Horse h;
SomeFunction(&h);

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

Animal a;
SomeFunction(&a);

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

纯虚函数主要用于定义:

A)抽象类

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

b)接口

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

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

考虑下面的代码:

#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.

Derived重写Base的每个方法:不仅是虚方法,而且是非虚方法。

我们看到,当你有一个基类指向派生类的指针(bDerived)时,调用NonVirtual调用基类实现。这是在编译时解析的:编译器看到bDerived是一个Base*,而NonVirtual不是virtual,所以它在类Base上进行解析。

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

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

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

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

封装允许对象的实现细节 抽象要隐藏在背后 简单的接口。< / p >

继承允许一个新的抽象定义为 某些的扩展或改进 现有的抽象,获得一些 或者说它的所有特征 自动。< / p >

动态方法绑定允许new抽象显示其new 行为,即使在上下文中使用

  • 虚函数必须在基类和派生类中有定义,但不是必需的,例如ToString()或ToString()函数是一个虚函数,因此您可以通过在用户定义的类中重写它来提供自己的实现。

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

  • 纯虚函数必须以“= 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类中,删除sayhello函数并运行代码。你会得到错误!因为当一个类包含一个纯虚函数时,不能从该类创建对象,并且它是继承的,那么它的派生类必须实现该函数。

虚函数

试试其他代码

#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!


}

这里sayhello函数在基类中被标记为虚函数。它表示编译器尝试在派生类中搜索函数并实现该函数。如果没有找到,则执行基本的一个。谢谢

虚函数或虚方法是一种函数或方法,其行为可以在继承类中被具有相同签名的函数覆盖

这并不是对虚函数的一个很好的解释。因为,即使成员不是虚成员,继承类也可以重写它。你可以自己去看看。

当函数以基类作为参数时,这种差异就会显现出来。当您将继承类作为输入时,该函数将使用被覆盖函数的基类实现。但是,如果该函数是虚函数,则使用派生类中实现的函数。

virtual关键字是如何工作的?

假设人是一个基类,那么印度人就是由人演变而来的。

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


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

将do_work()声明为virtual仅仅意味着:调用哪个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++,正确的区别是相关函数的运行时(使用virtual时)绑定和编译时(不使用virtual但重写方法且基指针指向派生对象时)绑定。

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

“贾斯汀,‘纯虚拟’只是一个术语(不是关键字,请看我的回答 下面)用来表示“此函数不能由基实现。 类。" < / p >

这是错误的! 纯虚函数也可以有一个体并且可以实现!事实上,抽象类的纯虚函数可以被静态调用!两位非常优秀的作者是Bjarne Stroustrup和Stan Lippman....

虚方法可以被派生类覆盖,但需要基类中的实现(将要被覆盖的基类)。

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

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

纯虚方法对应于抽象类中的抽象方法的行为。而只包含纯虚方法和常量的类则是接口的cppp -pendant。

虚函数是按继承顺序分层的。 当派生类不重写虚函数时,将使用在其基类中定义的函数

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