我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
我正在学习C++,我刚刚开始学习虚拟函数。
从我(在书中和网上)读到的内容来看,虚拟函数是基类中的函数,可以在派生类中重写。
但在本书的早些时候,当我学习基本继承时,我能够在派生类中重写基函数,而不使用虚函数。
那么我在这里错过了什么?我知道虚拟函数还有很多,它似乎很重要,所以我想清楚它到底是什么。我只是在网上找不到一个直截了当的答案。
当前回答
下面是前两个答案的C++代码的合并版本。
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
#ifdef VIRTUAL
virtual string says() { return "??"; }
#else
string says() { return "??"; }
#endif
};
class Dog: public Animal
{
public:
string says() { return "woof"; }
};
string func(Animal *a)
{
return a->says();
}
int main()
{
Animal *a = new Animal();
Dog *d = new Dog();
Animal *ad = d;
cout << "Animal a says\t\t" << a->says() << endl;
cout << "Dog d says\t\t" << d->says() << endl;
cout << "Animal dog ad says\t" << ad->says() << endl;
cout << "func(a) :\t\t" << func(a) << endl;
cout << "func(d) :\t\t" << func(d) << endl;
cout << "func(ad):\t\t" << func(ad)<< endl;
}
两种不同的结果是:
如果没有#define virtual,它将在编译时绑定。Animal*ad和func(Animal*)都指向Animal的says()方法。
$ g++ virtual.cpp -o virtual
$ ./virtual
Animal a says ??
Dog d says woof
Animal dog ad says ??
func(a) : ??
func(d) : ??
func(ad): ??
使用#define virtual,它在运行时绑定。Dog*d、Animal*ad和func(Animal*)指向/引用Dog的says()方法,因为Dog是它们的对象类型。除非未定义[Dog's says()“woof”]方法,否则它将是在类树中首先搜索的方法,即派生类可能会覆盖其基类的方法[Eanimal's says)]。
$ g++ virtual.cpp -D VIRTUAL -o virtual
$ ./virtual
Animal a says ??
Dog d says woof
Animal dog ad says woof
func(a) : ??
func(d) : woof
func(ad): woof
有趣的是,Python中的所有类属性(数据和方法)都是虚拟的。由于所有对象都是在运行时动态创建的,因此不需要类型声明或关键字virtual。下面是Python的代码版本:
class Animal:
def says(self):
return "??"
class Dog(Animal):
def says(self):
return "woof"
def func(a):
return a.says()
if __name__ == "__main__":
a = Animal()
d = Dog()
ad = d # dynamic typing by assignment
print("Animal a says\t\t{}".format(a.says()))
print("Dog d says\t\t{}".format(d.says()))
print("Animal dog ad says\t{}".format(ad.says()))
print("func(a) :\t\t{}".format(func(a)))
print("func(d) :\t\t{}".format(func(d)))
print("func(ad):\t\t{}".format(func(ad)))
输出为:
Animal a says ??
Dog d says woof
Animal dog ad says woof
func(a) : ??
func(d) : woof
func(ad): woof
这与C++的虚拟定义相同。注意,d和ad是两个不同的指针变量,引用/指向同一个Dog实例。表达式(ad is d)返回True,其值与0xb79f72cc>处的<main.Dog对象相同。
其他回答
虚拟函数用于支持运行时多态性。
也就是说,virtual关键字告诉编译器不要在编译时做出(函数绑定的)决定,而是推迟到运行时”。
您可以通过在函数的基类声明中的关键字virtual之前使其成为虚拟函数。例如类基础{虚虚函数();}当基类具有虚拟成员函数时,从基类继承的任何类都可以使用完全相同的原型重新定义该函数,即只能重新定义功能,而不能重新定义函数的接口。类派生:公共基{void函数();}基类指针可用于指向基类对象和派生类对象。当使用基类指针调用虚拟函数时,编译器在运行时决定要调用函数的哪个版本(即基类版本或重写的派生类版本)。这被称为运行时多态性。
接口设计采用虚拟方法。例如,在Windows中有一个名为IUnknown的界面,如下所示:
interface IUnknown {
virtual HRESULT QueryInterface (REFIID riid, void **ppvObject) = 0;
virtual ULONG AddRef () = 0;
virtual ULONG Release () = 0;
};
这些方法留给界面用户来实现。它们对于创建和销毁必须继承IUnknown的某些对象至关重要。在这种情况下,运行时知道这三个方法,并期望在调用它们时实现它们。所以在某种意义上,它们充当了对象本身和使用该对象的任何事物之间的契约。
你熟悉函数指针吗?虚拟函数也是一个类似的想法,只是您可以轻松地将数据绑定到虚拟函数(作为类成员)。将数据绑定到函数指针并不容易。对我来说,这是主要的概念区别。这里的许多其他答案只是说“因为…多态性!”
为什么我们需要虚拟功能?
虚拟函数避免了不必要的类型转换问题,我们中的一些人会争论,当我们可以使用派生类指针来调用派生类中特定的函数时,为什么需要虚拟函数!答案是,它否定了在大型系统开发中继承的全部思想,因为在大型系统中,非常需要使用单指针基类对象。
让我们比较以下两个简单的程序,以了解虚拟函数的重要性:
无虚拟功能的程序:
#include <iostream>
using namespace std;
class father
{
public: void get_age() {cout << "Fathers age is 50 years" << endl;}
};
class son: public father
{
public : void get_age() { cout << "son`s age is 26 years" << endl;}
};
int main(){
father *p_father = new father;
son *p_son = new son;
p_father->get_age();
p_father = p_son;
p_father->get_age();
p_son->get_age();
return 0;
}
输出:
Fathers age is 50 years
Fathers age is 50 years
son`s age is 26 years
具有虚拟功能的程序:
#include <iostream>
using namespace std;
class father
{
public:
virtual void get_age() {cout << "Fathers age is 50 years" << endl;}
};
class son: public father
{
public : void get_age() { cout << "son`s age is 26 years" << endl;}
};
int main(){
father *p_father = new father;
son *p_son = new son;
p_father->get_age();
p_father = p_son;
p_father->get_age();
p_son->get_age();
return 0;
}
输出:
Fathers age is 50 years
son`s age is 26 years
son`s age is 26 years
通过仔细分析这两个输出,可以理解虚拟函数的重要性。
我以对话的形式给出了答案,以便更好地阅读:
为什么我们需要虚拟功能?
因为多态性。
什么是多态性?
基指针也可以指向派生类型对象。
多态性的定义是如何导致对虚拟函数的需求的?
嗯,通过早期绑定。
什么是早期绑定?
C++中的早期绑定(编译时绑定)意味着在执行程序之前,函数调用是固定的。
所以
因此,如果您使用基类型作为函数的参数,编译器将只识别基接口,如果您用派生类的任何参数调用该函数,它将被截断,这不是您想要的。
如果这不是我们想要的,为什么允许这样做?
因为我们需要多态性!
那么多态性的好处是什么?
您可以使用基类型指针作为单个函数的参数,然后在程序运行时,您可以使用该基指针的解引用来访问每个派生类型接口(例如,它们的成员函数),而不会出现任何问题。
我仍然不知道虚拟函数有什么好处。。。!这是我的第一个问题!
嗯,这是因为你问得太快了!
为什么我们需要虚拟功能?
假设您使用基指针调用了一个函数,该函数具有来自其派生类之一的对象地址。正如我们上面所讨论的,在运行时,这个指针会被取消引用,但是,到目前为止,我们希望“从我们的派生类”执行一个方法(==成员函数)!然而,基类中已经定义了相同的方法(具有相同标头的方法),那么为什么您的程序要费心选择另一个方法呢?换言之,我的意思是,你怎么能把这种情况与我们以前通常看到的情况区分开来?
简单的答案是“基类中的一个虚拟成员函数”,稍长一点的答案是,“在这一步,如果程序在基类中看到一个虚拟函数,它知道(意识到)您正在尝试使用多态性”,因此去到派生类(使用v-table,一种后期绑定形式),发现另一个方法具有相同的头,但预期实现不同。
为什么实施不同?
你这个笨蛋!去读一本好书吧!
好吧,等等,等等,当他/她可以简单地使用派生类型指针时,为什么还要麻烦使用基指针呢?你是法官,所有这些头疼值得吗?看看这两个片段:
//1:
Parent* p1 = &boy;
p1 -> task();
Parent* p2 = &girl;
p2 -> task();
//2:
Boy* p1 = &boy;
p1 -> task();
Girl* p2 = &girl;
p2 -> task();
好吧,虽然我认为1还是比2好,但你也可以这样写1:
//1:
Parent* p1 = &boy;
p1 -> task();
p1 = &girl;
p1 -> task();
此外,你应该意识到,这只是我迄今为止向你解释的所有事情的一种人为使用。相反,假设例如在程序中有一个函数分别使用每个派生类的方法(getMonthBenefit()):
double totalMonthBenefit = 0;
std::vector<CentralShop*> mainShop = { &shop1, &shop2, &shop3, &shop4, &shop5, &shop6};
for(CentralShop* x : mainShop){
totalMonthBenefit += x -> getMonthBenefit();
}
现在,试着重新写一遍,不要让人头疼!
double totalMonthBenefit=0;
Shop1* branch1 = &shop1;
Shop2* branch2 = &shop2;
Shop3* branch3 = &shop3;
Shop4* branch4 = &shop4;
Shop5* branch5 = &shop5;
Shop6* branch6 = &shop6;
totalMonthBenefit += branch1 -> getMonthBenefit();
totalMonthBenefit += branch2 -> getMonthBenefit();
totalMonthBenefit += branch3 -> getMonthBenefit();
totalMonthBenefit += branch4 -> getMonthBenefit();
totalMonthBenefit += branch5 -> getMonthBenefit();
totalMonthBenefit += branch6 -> getMonthBenefit();
事实上,这可能也是一个虚构的例子!