从 Wikipedia's Virtual function ...
在面向对象的编程中,在 C++ 和 Object Pascal 等语言中,虚函数或虚方法是一种可继承和可覆盖的函数或方法,可促进动态分派。这个概念是面向对象编程 (OOP) 的(运行时)多态性部分的重要组成部分。简而言之,虚函数定义了要执行的目标函数,但在编译时可能不知道目标函数。
与非虚函数不同,当虚函数被覆盖时,派生最多的版本将用于类层次结构的所有级别,而不仅仅是创建它的级别。因此,如果基类的一个方法调用了虚方法,则将使用派生类中定义的版本,而不是基类中定义的版本。
这与非虚函数形成对比,非虚函数仍然可以在派生类中被覆盖,但“新”版本只会被派生类及其以下使用,而根本不会改变基类的功能。
然而..
纯虚函数或纯虚方法是如果派生类不是抽象的,则需要由派生类实现的虚函数。
当纯虚方法存在时,类是“抽象的”,不能单独实例化。相反,必须使用实现纯虚拟方法的派生类。纯虚拟根本没有在基类中定义,所以派生类必须定义它,或者派生类也是抽象的,不能被实例化。只有没有抽象方法的类才能被实例化。
虚拟提供了一种覆盖基类功能的方法,而纯虚拟需要它。
我想评论一下维基百科对虚拟的定义,这里有几个人重复了。 [在编写此答案时,] Wikipedia 将虚拟方法定义为可以在子类中覆盖的方法。 [幸运的是,Wikipedia 已经被编辑过,现在它正确地解释了这一点。] 这是不正确的:任何方法,不仅仅是虚拟方法,都可以在子类中被覆盖。 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 会调用 Base 类的实现。这是在编译时解决的:编译器看到 bDerived 是 Base*,NonVirtual 不是虚拟的,因此它对 Base 类进行解析。
但是,调用 Virtual 会调用 Derived 类的实现。由于关键字 virtual,方法的选择发生在运行时,而不是编译时。在编译时发生的情况是编译器看到这是一个 Base*,并且它正在调用一个虚方法,因此它插入了对 vtable 的调用而不是类 Base。这个 vtable 在运行时被实例化,因此运行时解析到最派生的覆盖。
我希望这不会太混乱。简而言之,任何方法都可以被覆盖,但只有虚拟方法才能为您提供多态性,即运行时选择最派生的覆盖。然而,在实践中,重写非虚拟方法被认为是不好的做法,并且很少使用,所以很多人(包括撰写该 Wikipedia 文章的人)认为只有虚拟方法可以被重写。
Derived*
可能会有所帮助。否则很好的答案
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,因为它是基类。
如果我们这样做:
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
...
};
通过向您的类添加一个纯虚拟方法,您可以使该类成为 abstract base class,这对于将接口与实现分开非常方便。
m_name
。 m_
是什么意思?
“虚拟”意味着该方法可以在子类中被覆盖,但在基类中具有可直接调用的实现。 “纯虚拟”意味着它是一个没有直接可调用实现的虚拟方法。这样的方法必须在继承层次结构中至少被覆盖一次——如果一个类有任何未实现的虚方法,那么该类的对象就不能被构造并且编译将失败。
@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()
虚函数是在基类中声明并由派生类重新定义的成员函数。虚函数是按继承顺序分层的。当派生类不覆盖虚函数时,将使用其基类中定义的函数。
纯虚函数是不包含与基类相关的定义的函数。它在基类中没有实现。任何派生类都必须重写此函数。
virtual 关键字是如何工作的?
假设 Man 是一个基类, Indian 是从 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()
我认为投票最多的答案具有误导性 - 任何方法,无论是否 virtual 都可以在派生类中具有覆盖的实现。对于 C++ 的具体引用,正确的区别在于关联函数的运行时(使用 virtual 时)绑定和编译时(未使用 virtual 但方法被覆盖并且基指针指向派生对象时)绑定。
似乎还有另一个误导性的评论说,
“贾斯汀,'纯虚拟'只是一个术语(不是关键字,请参见下面的答案),用于表示“此功能无法由基类实现”。
这是错误的!纯虚函数也可以有实体并且可以实现!事实上,抽象类的纯虚函数可以被静态调用!两位非常优秀的作者是 Bjarne Stroustrup 和 Stan Lippman.... 因为他们编写了语言。
Simula、C++ 和 C# 默认使用静态方法绑定,程序员可以通过将它们标记为虚拟来指定特定方法应使用动态绑定。动态方法绑定是面向对象编程的核心。
面向对象编程需要三个基本概念:封装、继承和动态方法绑定。
封装允许抽象的实现细节隐藏在一个简单的接口后面。继承允许将新的抽象定义为一些现有抽象的扩展或改进,自动获得其部分或全部特征。动态方法绑定允许新抽象显示其新行为,即使在需要旧抽象的上下文中使用时也是如此。
虚拟方法可以被派生类覆盖,但需要在基类中实现(将被覆盖的那个)
纯虚方法没有实现基类。它们需要由派生类定义。 (所以技术上覆盖不是正确的术语,因为没有什么可以覆盖的)。
当派生类覆盖基类的方法时,虚拟对应于默认的 java 行为。
纯虚方法对应于抽象类中抽象方法的行为。一个只包含纯虚方法和常量的类将是接口的 cpp-pendant。
纯虚函数
试试这个代码
#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();
}
在另一个类中删除函数 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 函数在基类中被标记为虚拟。它表示编译器尝试在派生类中搜索函数并实现该函数。如果找不到,则执行基类。谢谢
“虚函数或虚方法是一种函数或方法,其行为可以在继承类中被具有相同签名的函数覆盖” - 维基百科
这不是对虚函数的一个很好的解释。因为,即使成员不是虚拟的,继承类也可以覆盖它。你可以自己试试看。
当函数将基类作为参数时,差异就会显现出来。当您将继承类作为输入时,该函数使用覆盖函数的基类实现。但是,如果该函数是虚函数,它会使用派生类中实现的函数。
虚函数必须在基类和派生类中有定义,但不是必需的,例如 ToString() 或 toString() 函数是一个 Virtual,因此您可以通过在用户定义的类中覆盖它来提供自己的实现。
虚函数在普通类中声明和定义。
纯虚函数必须声明以“= 0”结尾,并且只能在抽象类中声明。
具有纯虚函数的抽象类不能具有该纯虚函数的定义,因此这意味着必须在派生自该抽象类的类中提供实现。
pure
关键字,但贝尔实验室即将发布 C++ 的主要版本,而他的经理在后期不允许这样做。添加关键字是一件大事。