虚成员函数可以在派生类中被重写。 在派生类中重新定义一个函数被称为函数重写。
为什么我们需要虚函数呢?
虚函数/方法简单来说就是一个函数,其行为可以在子类(或者在C++中称作派生类)中被重写,通过重新定义函数如何工作(使用相同的签名)。
想象一个哺乳动物基类有一个speak函数。这个函数是void类型,并且只是显示一个哺乳动物的说话方式。当你从这个类继承时,你可以重写speak方法,使得狗发出“汪汪!”的声音,而猫则发出“喵喵”的声音。
你的问题似乎问的是有什么区别,实际上没有区别,因为虚函数可以重写这些函数的行为。你可能想了解重载函数和覆盖函数之间的区别。
重载函数是指创建一个具有相同名称但不同参数的函数,即不同数量和类型的参数。这里是关于C++中重载的解释,IBM网站上有说明。
重载(仅适用于C ++)如果在同一范围内为函数名称或运算符指定多个定义,则已重载该函数名称或运算符。分别在重载函数(仅限C ++)和重载运算符(仅限C ++)中描述了重载的函数和运算符。
重载声明是使用与先前在同一范围内声明的声明相同的名称声明的声明,但两个声明具有不同的类型。
如果调用重载的函数名称或运算符,则编译器通过比较您用于调用函数或运算符的参数类型与定义中指定的参数类型来确定要使用的最合适的定义。选择最合适的重载函数或运算符的过程称为重载决议,如重载决议(仅限C ++)中所述。
至于需要虚函数的情况的完整理性原因,这篇博客文章给出了一个很好的解释:http://nrecursions.blogspot.in/2015/06/so-why-do-we-need-virtual-functions.html
在多态性方面,函数重载和 virtual
函数的区别变得非常重要。特别是当使用基类的引用或指针时。
在 C++ 中,任何派生类都可以传递给需要基类对象的函数。(另请参见 Slicing 和 LSP)。给定:
struct Base_Virtual
{
virtual void some_virtual_function();
};
struct Base_Nonvirtual
{
void some_function();
};
void Function_A(Base_Virtual * p_virtual_base);
void Function_B(Base_Nonvirtual * p_non_virtual_base);
struct Derived_From_Virtual
: public Base_Virtual
{
void some_virtual_function(); // overrides Base_Virtual::some_virtual_function()
};
结构体Derived_From_Nonvirtual: public Base_Nonvirtual { void some_function(); }
根据C++语言规定,我可以将指向Derived_From_Virtual的指针传递给Function_A,因为Derived_From_Virtual继承自Base_Virtual。我也可以将指向Derived_From_Nonvirtual的指针传递给Function_B。
在Base_Virtual中使用virtual修饰符告诉编译器Function_A将使用Derived_From_Virtual::some_virtual_function()而不是Base_Virtual中的方法。这是因为该方法是虚拟的,最终定义可能位于未来或派生类中。实际定义表示使用包含定义的最终派生类中的方法。
当将指向“Derived_From_Nonvirtual”指针传递给“Function_B”时,编译器将指示函数使用基类“Base_Nonvirtual::some_function()”的方法。派生类中的“some_function()”方法是与基类无关的独立方法。请查看C++ FAQ lite,http://www.parashift.com/c++-faq-lite/。它可能是初学者最好的C++资源之一。它详细介绍了虚函数和覆盖。
我个人发现C++ FAQ是我学习C++的优秀资源。其他人有不同的意见,因人而异。
virtual
是一个请求运行时派发被声明方法的关键字,并同时将该方法声明为重写(除了实现纯虚拟方法)。被声明的方法以及从该类向下继承层次结构中共享精确签名和名称的任何方法都是重写。当你通过父指针或引用调用虚拟方法时,运行时会调用被调用对象在继承层次结构中最具体的重写。struct base {
virtual void override() { std::cout << "base::override" << std::endl; }
void not_override() { std::cout << "base::not_override" << std::endl; }
};
struct derived : base {
void override() { std::cout << "derived::override" << std::endl; }
void not_override() { std::cout << "derived::not_override" << std::endl; }
};
int main() {
derived d;
base & b = d;
b.override(); // derived::override
b.not_override(); // base::not_override
d.not_override(); // derived::not_override
}
d.base::override()
将调用基本实现,即使在派生类中存在其他覆盖。struct base {
void f() {}
};
struct derived : base {
void f(int) {}
};
int main() {
derived d;
// d.f() // error, derived::f requires an argument, base::f is hidden in this context
}
d.base::f()
会调用基本版本,并不是因为它禁用了多态性 - 它并没有,因为该方法未声明为虚拟方法,因此永远不会具有多态行为 - 而是因为完全限定名告诉编译器方法在哪里,即使它被派生类中的另一个方法隐藏。摘要
本文讨论了C++中的虚函数。第零部分解释了如何声明和重载虚函数。第一部分试图(也许会失败)解释虚函数的实现方式。第二部分是一个使用在第零部分和第一部分定义的示例类的程序。第三部分是每个虚函数-多态性教程中都提供的经典动物示例。
第零部分
如果一个类的方法被声明为 virtual,那么它就是虚函数。
class my_base
{
public:
void non_virtual_test() { cout << 4 << endl; } // non-virtual
virtual void virtual_test() { cout << 5 << endl; } // virtual
};
#define virtual
这样的事情。)class my_derived : public my_base
{
public:
void non_virtual_test() { cout << 6 << endl; } // overloaded
void virtual_test() { cout << 7 << endl; } // overriden
};
第一部分
当编译器检测到一个类有虚方法时,它会自动将虚方法表(也称为vtable)添加到类的内存布局中。这个结果类似于从编译以下代码生成的结果:
class my_base
{
//<vtable>
// The vtable is actually a bunch of member function pointers
protected:
void (my_base::*virtual_test_ptr)();
//</vtable>
// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
void virtual_test_impl() { cout << 5 << endl; }
// Initializing the real_virtual_test pointer in the vtable.
public:
my_base() : virtual_test_ptr(&my_base::virtual_test_impl) {}
public:
void non_virtual_test() { cout << 4 << endl; }
// The interface of the virtual function is a wrapper
// around the member function pointer.
inline void virtual_test() { *virtual_test_ptr(); }
};
当编译器检测到一个类已经覆盖了一个虚方法时,它会替换在vtable中与之关联的条目。这个结果类似于从编译以下代码生成的结果:
class my_derived : public my_base
{
// The actual implementation of the virtual function
// is hidden from the rest of the program.
private:
void virtual_test_impl() { cout << 7 << endl; }
// Initializing the real_virtual_test pointer in the vtable.
public:
my_derived() : virtual_test_ptr(&my_derived::virtual_test_impl) {}
public:
void non_virtual_test() { cout << 6 << endl; }
};
第二部分
现在清楚了虚函数是使用vtable实现的,而vtable仅仅是一堆函数指针,那么这段代码的作用就很明显了:
#include <iostream>
using namespace std;
class my_base
{
public:
void non_virtual_test() { cout << 4 << endl; }
virtual void virtual_test() { cout << 5 << endl; }
};
class my_derived : public my_base
{
public:
void non_virtual_test() { cout << 6 << endl; }
void virtual_test() { cout << 7 << endl; }
}
int main()
{
my_base* base_obj = new my_derived();
// This outputs 4, since my_base::non_virtual_test() gets called,
// not my_derived::non_virtual_test().
base_obj->non_virtual_test();
// This outputs 7, since the vtable pointer points to
// my_derived::virtual_test(), not to my_base::virtual_test().
base_obj->virtual_test();
// We shall not forget
// there was an object that was pointed by base_obj
// who happily lived in the heap
// until we killed it.
delete base_obj;
return 0;
}
第三部分
由于没有一个虚函数的例子是完整的,没有动物的例子...
#include <iostream>
using namespace std;
class animal
{
public:
virtual void say_something()
{ cout << "I don't know what to say." << endl
<< "Let's assume I can growl." << endl; }
/* A more sophisticated version would use pure virtual functions:
*
* virtual void say_something() = 0;
*/
};
class dog : public animal
{
public:
void say_something() { cout << "Barf, barf..." << endl; }
};
class cat : public animal
{
public:
void say_something() { cout << "Meow, meow..." << endl; }
};
int main()
{
animal *a1 = new dog();
animal *a2 = new cat();
a1->say_something();
a2->say_something();
}
从Java转到C++时,人们可能会觉得虚拟成员函数和非虚拟成员函数的概念令人困惑。要记住的是,Java方法对应于C++中的虚拟成员函数。
问题不在于我们为什么需要虚拟函数,而在于为什么需要非虚拟函数?我自己的理解(如果我错了,请纠正我)是它们更便宜实现,因为对它们的调用可以在编译时解析。
虚函数存在是为了帮助设计基类的行为。一个只有纯虚函数的基类不能被实例化,被称为抽象类。
由派生类来实现基类中由虚函数描述的方法。 派生类可以被实例化(它们存在并占用内存)。
从派生类派生可以重新定义在父对象中已经定义的函数。这个你已经知道的技巧叫做重载,它允许你自定义这个子对象的行为。
随着你学习更多C++,你会发现继承并不像它被吹嘘的那样好。组合通常是更好的选择。玩得开心。
区别只在于当您通过基类对象的指针调用派生类的方法时。在那一刻,如果您调用的方法在派生类中被覆盖,您将得到基类的执行,而如果是虚拟的,则会执行派生类的方法。
#include <iostream>
class A{
public:
virtual void getA() { std::cout << "A in base" << std::endl;};
};
class B : public A {
public:
void getA() { std::cout << "A in derived class" << std::endl;}
};
int main(int argc, char** argv)
{
A a;
B b;
a.getA();
b.getA();
A* t = new B;
t->getA();
}
t->getA()
打印出 "派生类中的 A"
,但如果基类 A 中没有虚拟修饰符,则会打印出 "基类中的 A"
。直升机和飞机都能飞行,但它们的飞行方式不同——它们都是某个假想对象“飞行器”的实例。您可以要求“飞行器”对象“飞行”,但“飞行器”只是一个接口,它除了应该能够飞行之外,不知道任何关于飞行的事情。
然而,如果直升机和飞机都遵循“飞行器”的接口,那么如果有一个机场对象并且您给它一个“飞行器”,所有机场需要做的就是请求“飞行器”飞行。
例如:
Airplace X=Airplane X("boeing 747");
Airfield::takeoff(&X);
Helicopter Y= Helicopter("Comache");
Airfield::takeof(&Y);
void Airfield::takeOff(Flyer * f)
{
f->fly();
}
C++是一种严格的类型安全语言,只有在启用对象层次结构的RTTI时,通过基类间接调用派生类的函数才有可能实现此类功能,并且限定成员函数为虚函数可以实现这一点。