我'd like to comment on Wikipedia'的虚拟定义,在这里重复几次 . [在写这个答案的时候,]维基百科将虚拟方法定义为可以在子类中重写的方法 . [幸运的是,Wikipedia已经被编辑过,现在它正确地解释了这一点 . ]这是不正确的:任何方法,而不仅仅是虚方法,都可以在子类中重写 . 虚拟的功能是为您提供多态性,即 ability to select at run-time the most-derived override of a method .
请考虑以下代码:
#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.
#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()
0
虚拟关键字如何工作?
假设人是基类,印度人是人类 .
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.
#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();
}
A virtual function is a member function that is declared in a base class and that is redefined by derived class. 虚函数按继承顺序分层 . 当派生类不重写虚函数时,将使用其基类中定义的函数 .
A pure virtual function is one that contains no definition relative to the base class. 它在基类中没有实现 . 任何派生类都必须覆盖此函数 .
13 回答
从Wikipedia's Virtual function ...
与非虚函数不同,当重写虚函数时,最常导出的版本用于类层次结构的所有级别,而不仅仅是创建它的级别 . 因此,如果基类的一个方法调用虚方法,则将使用派生类中定义的版本而不是基类中定义的版本 .
这与非虚函数形成对比,非虚函数仍然可以在派生类中重写,但“新”版本仅由派生类及其下方使用,但根本不会更改基类的功能 .
而..
当存在纯虚方法时,该类为"abstract"且无法自行实例化 . 相反,必须使用实现纯虚方法的派生类 . pure-virtual根本没有在基类中定义,因此派生类必须定义它,或者派生类也是抽象的,并且不能实例化 . 只能实例化没有抽象方法的类 .
虚拟提供了一种覆盖基类功能的方法,而纯虚拟需要它 .
我'd like to comment on Wikipedia'的虚拟定义,在这里重复几次 . [在写这个答案的时候,]维基百科将虚拟方法定义为可以在子类中重写的方法 . [幸运的是,Wikipedia已经被编辑过,现在它正确地解释了这一点 . ]这是不正确的:任何方法,而不仅仅是虚方法,都可以在子类中重写 . 虚拟的功能是为您提供多态性,即 ability to select at run-time the most-derived override of a method .
请考虑以下代码:
这个程序的输出是什么?
派生会覆盖Base的每个方法:不仅是虚拟方法,还包括非虚拟方法 .
我们看到当你有一个Base-pointer-to-Derived(bDerived)时,调用NonVirtual调用Base类实现 . 这在编译时解决:编译器发现bDerived是Base *,NonVirtual不是虚拟的,因此它在类Base上执行解析 .
但是,调用Virtual会调用Derived类实现 . 由于关键字virtual,方法的选择发生在运行时,而不是编译时 . 在编译时发生的事情是编译器看到这是一个Base *,并且它正在调用一个虚方法,所以它插入一个调用vtable而不是类Base . 此vtable在运行时实例化,因此运行时解析为最派生的覆盖 .
我希望这不会太混乱 . 简而言之,任何方法都可以被覆盖,但只有虚方法才能为您提供多态性,即运行时选择最多派生的覆盖 . 然而,在实践中,覆盖非虚拟方法被认为是不好的做法并且很少使用,因此许多人(包括撰写维基百科文章的人)认为只能覆盖虚拟方法 .
virtual关键字赋予C“支持多态性的能力” . 当你有一个指向某个类的对象的指针,例如:
在这个(愚蠢的)示例中,GetNumberOfLegs()函数根据调用它的对象的类返回适当的数字 .
现在,考虑函数'SomeFunction' . 它不关心什么类型的动物对象传递给它,只要它来自动物 . 编译器会自动将任何Animal派生类转换为Animal,因为它是基类 .
如果我们这样做:
它输出'2' . 如果我们这样做:
它输出'4' . 我们不能这样做:
因为GetNumberOfLegs()虚函数是纯函数,它不会编译,这意味着它必须通过派生类(子类)来实现 .
纯虚函数主要用于限定:
a)抽象类
这些是基类,您必须从它们派生,然后实现纯虚函数 .
b)接口
这些是“空”类,其中所有函数都是纯虚函数,因此您必须派生并实现所有函数 .
在C类中,virtual是指定该关键字的关键字,可以覆盖(即由子类实现)方法 . 例如:
在这种情况下,子类可以覆盖initShape函数来执行一些专门的工作:
术语“纯虚拟”是指需要由子类实现但尚未由基类实现的虚函数 . 通过使用virtual关键字并在方法声明的末尾添加= 0,可以将方法指定为纯虚方法 .
因此,如果您想使Shape :: initShape纯虚拟,您将执行以下操作:
通过向类中添加纯虚方法,可以使类成为abstract base class,这对于将接口与实现分离非常方便 .
"Virtual"表示可以在子类中重写该方法,但在基类中具有可直接调用的实现 . "Pure virtual"表示它是一个没有可直接调用的实现的虚方法 . 必须在继承层次结构中至少覆盖一次这样的方法 - 如果一个类有任何未实现的虚方法,则无法构造该类的对象,编译将失败 .
@quark指出纯虚方法可以有一个实现,但是由于必须重写纯虚方法,所以不能直接调用默认实现 . 以下是具有默认值的纯虚方法示例:
根据评论,编译是否会失败是特定于编译器的 . 至少在GCC 4.3.3中,它不会编译:
输出:
虚拟关键字如何工作?
假设人是基类,印度人是人类 .
将do_work()声明为虚拟只是意味着:只在运行时确定要调用的do_work() .
假设我这样做,
如果未使用virtual,则由编译器静态确定或静态绑定,具体取决于调用的对象 . 因此,如果Man的一个对象调用do_work(),那么Man的do_work()被称为即使它指向一个印度对象
我认为最高投票的答案是误导性的 - 任何方法,无论虚拟是否可以在派生类中具有重写的实现 . 具体参考C,正确的差异是运行时(当使用虚拟时)绑定和编译时(当不使用虚拟但是方法被覆盖并且基指针指向派生对象时)关联函数的绑定 .
似乎有另一个误导性的评论说,
这是错的!纯粹的虚拟功能也可以有一个身体并且可以实现!事实是,抽象类的纯虚函数可以静态调用!两位非常优秀的作家是Bjarne Stroustrup和Stan Lippman ....因为他们写了这门语言 .
Simula,C和C#,默认情况下使用静态方法绑定,程序员可以通过将它们标记为虚拟来指定特定方法应该使用动态绑定 . 动态方法绑定是面向对象编程的核心 .
面向对象编程需要三个基本概念:封装,继承和动态方法绑定 .
可以通过派生类来覆盖虚方法,但需要在基类中实现(将被覆盖的实现)
纯虚方法没有实现基类 . 它们需要由派生类定义 . (因此技术上被覆盖的不是正确的术语,因为没有什么可以覆盖) .
当派生类重写基类的方法时,Virtual对应于默认的java行为类 .
纯虚方法对应于抽象类中抽象方法的行为 . 并且只包含纯虚方法和常量的类将是接口的cpp-pendant .
Pure Virtual Function
试试这段代码
在类 anotherClass 中删除函数sayHellow并运行代码 . 你会得到错误!因为当一个类包含一个纯虚函数时,不能从该类创建任何对象并且它是继承的,那么它的派生类必须实现该函数 .
Virtual function
尝试另一个代码
这里sayHellow函数在基类中被标记为虚拟 . 它说编译器尝试在派生类中搜索函数并实现该函数 . 如果没有找到则执行基本函数 . 谢谢
“虚函数或虚方法是一种函数或方法,其行为可以通过具有相同签名的函数在继承类中重写” - 维基百科
这对虚拟功能来说不是一个好的解释 . 因为,即使成员不是虚拟成员,继承类也可以覆盖它 . 你可以尝试自己看看 .
当函数将基类作为参数时,差异显示出来 . 当您将继承类作为输入时,该函数使用overriden函数的基类实现 . 但是,如果该函数是虚函数,则它使用派生类中实现的函数 .
虚函数必须在基类中以及派生类中有定义但不是必需的,例如ToString()或toString()函数是虚拟的,因此您可以通过在用户定义的类中重写它来提供自己的实现 . .
虚函数在普通类中声明和定义 .
纯虚函数必须以“= 0”结尾,并且只能在抽象类中声明 .
具有纯虚函数的抽象类不能具有纯虚函数的定义,因此它暗示必须在从该抽象类派生的类中提供实现 .
A virtual function is a member function that is declared in a base class and that is redefined by derived class. 虚函数按继承顺序分层 . 当派生类不重写虚函数时,将使用其基类中定义的函数 .
A pure virtual function is one that contains no definition relative to the base class. 它在基类中没有实现 . 任何派生类都必须覆盖此函数 .
在面向对象的编程中,虚函数或虚方法是一种函数或方法,其行为可以通过具有相同签名的函数在继承类中重写,以提供多态行为 .
换句话说,假设有一个Base类,它有一个函数名print(),还有另一个Child类,它继承自Base类,并且还有一个函数名print() . 现在,如果Base类的函数,即Base :: print()是非虚函数,则子类的函数,即Child :: print()在运行时不会覆盖Base类的函数 . 如果Base :: print()是虚拟的,那么Child :: print()函数将覆盖Base类函数 . 与这些相关的示例将在以下部分中显示 .