我是荔园微风,作为一名在IT界整整25年的老兵,今天来聊聊Visual C++中函数的覆盖和函数的隐藏。
Visual C++中函数的覆盖和函数的隐藏与JAVA有很大不同,有些容易搞错的点和概念,我们要特别注意。
首先,先满足一下急性子的同学,因为有的同学是因为急于了解虚函数和纯虚函数才来看这篇帖子的,那你可以先这样理解:
C++中的虚函数就是JAVA中的普通函数, C++ 中的纯虚函数就是JAVA中的抽象函数, C++ 中的抽象类就是JAVA中的抽象类, C++ 中的虚基类就是JAVA中的接口。
这就是本人的学习方法和别人不一样的地方,我年轻时学C++始终不得要领,于是我把JAVA学会后再去学C++,就全都搞明白了。所以我的帖子都是用这种方法去学C++的。希望那些学C++很吃力的同学可以来借鉴我的这个学习方法。
好了,我们先来看一个程序,一个父亲和一个儿子,希望父亲的学习本领能被儿子继承下去。我们在儿子类中重新定义学习方法。我们希望如果对象是儿子,就调用 儿子类的学习方法,如果对象是父亲,那么就调用父亲类的学习方法。
#include <iostream>
using namespace std;
class Father
{
public:
void eat()
{
cout<<"eat"<<endl;
}
void run()
{
cout<<"run"<<endl;
}
virtual void study()
{
cout<<"study"<<endl;
}
};
class Son : public Father
{
public:
void study()
{
cout<<"new study"<<endl;
}
};
void fn(Father *p)
{
p->study();
}
int main()
{
Father *p;
Son boy;
p=&boy;
fn(p);
return 0;
}
在基类父亲类的study函数前添加了virtual关键字,声明该函数为虚函数。在派生类儿子类中重写了 study函数,我们注意到, 儿子类的study函数和父亲类的study函数完全一样,无论是函数名,还是参数列表都是一样的,这称为函数的覆盖(override)。
我们必须知道构成函数覆盖的条件为:(1)基类函数必须是虚函数(使用 virtual关键字进行声明)。(2)发生覆盖的两个函数要分别位于派生类和基类中。(3)函数名称与参数列表必须完全相同。由于C++的多态性是通过虚函数来实现的,所以函数的覆盖总和多态关联在一起(这一点和JAVA十分相似, 但是JAVA不需要virtual这种关键词)。在函数覆盖的情况下,编译器会在运行时根据对象的实际类型来确定要调用的函数。
我们再看代码:
#include <iostream>
using namespace std;
class Father
{
public:
void eat()
{
cout<<"eat"<<endl;
}
void run()
{
cout<<"run"<<endl;
}
void study()
{
cout<<"study"<<endl;
}
};
class Son : public Father
{
public:
void study()
{
cout<<"new study"<<endl;
}
};
void fn(Father *p)
{
p->study();
}
int main()
{
Father *p;
Son boy;
p=&boy;
fn(p);
return 0;
}
这个其实又回到我之前一个帖子的内容了,大家有兴趣可以看看那个帖子:
Visual C++中的虚函数和纯虚函数的原理
大家应该可以看出来这段代码前面代码的区别。在这段代码中,派生类儿子类中的study函数和基类父亲类中的study函数是完全一样的,不同的是study函数不是虚函数,这种情况称为函数的隐藏。所谓隐藏,是指派生类中具有与基类同名的函数(不考虑参数列表是否相同),从而在派生类中隐藏了基类的同名函数。
初学VC者很容易把函数的隐藏与函数的覆盖、重载相混淆,我们看下面两种函数隐藏的情况:
(1)派生类的函数与基类的函数完全相同(函数名和参数列表都相同),只是基类的函数没有使用virtual关键字。此时基类的函数将被隐藏,而不是被覆盖。
(2)派生类的函数与基类的函数同名,但参数列表不同,在这种情况下,不管基类的函数声明是否有 virtual关键字,基类的函数都将被隐藏。注意这种情况与函数重载的区别,重载发生在同一个类中。
下面我们给出一个例子,以帮助大家加深理解函数的覆盖和隐藏
class Father
{
public:
virtual void fn();
};
class Son:public Father
{
public:
void fn(int);
};
class Grandson:public Son
{
public:
void fn();
};
在上面代码中, 儿子类的 fn(int)函数隐藏了父亲类的fn()函数, 儿子类 fn(int)函数不是虚函数(注意和覆盖相区别)。 孙子类的 fn()函数隐藏了 儿子类的 fn(int)函数,由于 孙子类的 fn()函数与 父亲类的fn()函数具有同样的函数名和参数列表,因此 孙子类的 fn()函数是一个虚函数,覆盖了父亲 类的 fn()函数。注意,在孙子类中, 父亲类的 fn()函数是不可见的,但这并不影响 fn 函数的覆盖。
当隐藏发生时,如果在派生类的同名函数中想要调用基类的被隐藏函数,那么可以使用“基类名::函数名(参数)”的语法形式。例如,要在儿子类的fn(int)方法中调用父亲类的fn()方法,可以使用Father::fn()语句。
有的初学者可能会想,怎样才能更好地区分覆盖和隐藏呢?实际上只要记住一点:
函数的覆盖发生在派生类与基类之间,两个函数必须完全相同,并且都是虚函数。那么不属于这种情况的就是隐藏了。
作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。