C++父子类继承时的隐藏、覆盖、重载
存在父子类继承关系时,若有同名成员函数同时存在,会发生隐藏、覆盖和重载这几种情况。对于初学者也比较容易混淆,为此,我整理了一下我的个人看法,仅供参考。希望对大家理解有帮助,也欢迎指正。
1.父子类继承关系: 子类复制父类全部成员
首先,理解父子类的继承关系是怎样发生的。在此基础上就很容易理解它们之间的关系和区别。
每一个类有它自己的成员变量和成员函数,是一个独立的空间整体。当子类继承父类时,会将父类的全部成员全部复制一份,作为子类的成员,但是,同时也会标记这些成员是从父类中继承的,与子类本身的成员,还是有区别的。这里认为将子类本身的成员存在子类域,从父类复制过来的存在父类域。
如下图,Childer类中存在两个域,子类域和父类域,相互之间互不干扰。
class Father
{
int f_a;
int f_b;
}; class Childer:public Father
{
int c_a;
int f_b;
}; int main()
{
cout<<"sizeof childer:"<<sizeof(Childer)<<endl; //-> 16
cout<<"sizeof father:"<<sizeof(Father)<<endl; //-> 8
}
运行结果显示,子类大小为16,父类大小为8,也就是说子类的确有4个成员变量,就算是同名成员,也同样复制。
2.隐藏:子类对象优先考虑子类域自身成员(成员变量和成员函数)
隐藏发生的主要原因,就是当子类有父类的同名成员时,子类对象访问该成员时,会发生冲突。所以编译器的处理方式是,优先考虑子类域中的自身成员。
即,子类对象访问某成员时,如ch.m_m 或者ch.f(),成员变量和成员函数都一样。编译器首先在子类域中检索,如果在子类域中找到该成员,则检索结束,返回该成员进行访问。如果在子类域中找不到该成员,则去父类域中检索。如果父类域中存在,则返回该成员进行访问,如果父类域中也不存在,则编译错误,该成员无效。
当父子类域都存在同一成员时,编译器优先在子类中检索,就算父类域中也存在该同名成员,也不会被检索到。因此,父类域中的该成员被子类域中的该同名成员隐藏,即访问时完全以为该成员不存在,如果想访问父类域中的该成员,只能通过显示调用的方式,即:ch.Father::m_m;
下面用代码说明,为了对问题有针对性说明,此处成员都采用public,也不涉及构造析构等问题。
class Father
{
public:
int f_a;
int f_b; void ff1() {cout<<"father ff1"<<endl;}
}; class Childer:public Father
{
public:
int c_a;
int f_b; void cf1() {cout<<"childer cf1"<<endl;}
void ff1() {cout<<"childer ff1"<<endl;}
}; int main()
{
Childer ch; cout<<ch.c_a<<endl; //只在子类域中的成员变量
cout<<ch.f_b<<endl; //子类域和父类域都存在,优先访问子类域中的
cout<<ch.Father::f_b<<endl; //显示访问被隐藏的成员变量 cout<<"====================\n"; ch.cf1();
ch.ff1();
ch.Father::ff1();
}
运行结果可以看出,ch.f_b; 和 ch.Father::f_b; 两个同名成员同时存在。但访问时,子类成员将父类成员隐藏,想访问父类成员只能显示调用。
通过成员函数的访问,这一效果更明显,ch.ff1();调用时,调用了子类域中的该同名成员函数。
且此时编译器检索时,只根据名字,与函数的参数和返回类型无关。
int ff1(int a ) {cout<<"childer ff1"<<endl;return ;}
若将Childer中的函数,改为上述类型。主函数中调用时,ch.ff1();编译错误。因为子类的int ff1(int a);会将父类的void ff1();隐藏。所以它们之间不存在重载。
应该改为 ch.ff1(10); 这样会匹配子类域中的该成员。或者ch.Father::ff1();显示调用父类域中的成员。
3.覆盖:虚函数,成员函数类型一摸一样,父类指针调用子类对象成员
覆盖只发生在有虚函数的情况下,且父子类成员函数类型必须一摸一样,即参数和返回类型都必须一致。子类对象调用时,会直接调用子类域中的成员函数,父类域中的该同名成员就像不存在一样,(可以显示调用)即父类该成员被子类成员覆盖。这里很多人会感觉疑惑,认为是隐藏,因为父类的成员函数依然存在,依然可以调用,只是优先调用子类的,也就是“隐藏”了。而“覆盖”两个字的意思,应该是一个将另一个替代了,也就是另一个不存在了。
举个小例子可以很明显的看出,覆盖的情况下,父子类的成员函数也是同时存在的。
virtual void ff1() {cout<<"father ff1"<<endl; }
将上面的例子Father类中的ff1函数加上virtual,其他不进行改变,运行结果也不变。
下面解释一下,“覆盖”二字的由来。
首先需明白一点,虚函数的提出,是为了实现多态。也就是说,虚函数的目的是为了,在用父类指针指向不同的子类对象时,调用虚函数,调用的是对应子类对象的成员函数,即可以自动识别具体子类对象。所以,上述例子中,直接用子类对象调用虚函数是没有意义的,一般情况也不会这样使用。
class Father
{
public:
virtual void ff1() {cout<<"father ff1"<<endl;}
}; class Childer_1:public Father
{
public:
void ff1() {cout<<"childer_1 ff1 "<<endl;}
};
class Childer_2:public Father
{
public:
void ff1() {cout<<"childer_2 ff1"<<endl; }
}; int main()
{
Father* fp; Childer_1 ch1;
fp = &ch1;
fp->ff1(); Childer_2 ch2;
fp = &ch2;
fp->ff1(); return ;
}
使用虚函数,都是父类指针的形式,pf->f11() 。例子中的24行和28行,相同的代码,因为fp的指向不同对象,所以调用不同对象的虚函数。但从代码上看,fp是一个Father类的指针,但调用的是子类成员函数,就好像父类的成员被覆盖了一样。这就是覆盖一词的来源。
覆盖的情况下,子类虚函数必须与父类虚函数有相同的参数列表,否则认为是一个新的函数,与父类的该同名函数没有关系。但不可以认为两个函数构成重载。因为两个函数在不同的域中。
举例:
class Father
{
public:
virtual void ff1() {cout<<"father ff1"<<endl;}
}; class Childer_1:public Father
{
public:
void ff1(int a) {cout<<"childer_1 ff1 "<<endl; }
}; int main()
{
Father* fp; Childer_1 ch1;
fp = &ch1;
fp->ff1();
//ch1.ff1(); //没有匹配的成员
ch1.ff1(); return ;
}
运行结果为:
father ff1
childer_1 ff1
从19行 fp->ff1();的运行结果可以看出,fp虽然指向子类对象,并且调用的是虚函数。但是该虚函数,在子类中没有对应的实现,只好使用父类的该成员。
即第10行的带参ff1 并没有覆盖从父类中继承的无参ff1. 而是认为是一个新函数。
4.重载:相同域的同名不同参函数
重载必须是发生在同一个域中的两个同名不同形参之间的。如果一个在父类域一个在子类域,是不会存在重载的,属于隐藏的情况。调用时,只会在子类域中搜索,如果形参不符合,会认为没有该函数,而不会去父类域中搜索。
5.总结
重载是在同一域下的函数关系,在父子类情况下时,一般不予考虑。
隐藏,是子类改写、重写了父类的代码。而覆盖认为,子类实现了父类的虚函数。父类的虚函数可以没有实现体,成为纯虚函数,等着子类去实现。而隐藏时,父类的函数也必须有实现体的。隐藏还是覆盖,只是说法不同,只要明白编译器在调用时,如果检索、匹配相应的函数即可。
综上所述,总结为以下几点:
1.子类是将父类的所有成员都复制一份,并且保存在不同的域中。如果同名,子类中会有两份,分别在子类域和父类域。
2.调用时,是从调用对象(或指针)的类型开始检索的,先从自己域中检索,如果找到,判断是否为虚函数,不为虚函数直接调用,若为虚函数,通过运行时类型识别,调用真正对象的函数。如果没找到,去其父类域中检索,重复刚刚的判断。直到调用函数或者没有匹配的成员。而不会去子类中检索,所以如果是父类指针,即使指向子类对象,但调用的函数也只能是父类中的函数,除非是虚函数,才会根据子类对象去检索函数。
明白调用过程:
2.1 一般情况下,哪种类型的,就调哪种类型对于自己域中的成员。
Father f; f.a; f.ff1(); 由于f是Father类型的,所以调用的都是Father自己域中的成员。
Childer c; c.a; c.ff1(); 由于c是Chiler类型的,所以调用的都是Childer自己域中的成员。
指针也一样。Father*fp; fp->a; fp->ff1(); 由于fp是Father类型的指针,所以调用的都是Father自己域中的成员。
就算fp = new Childer. fp->ff1(); 指向的是子类对象,依然调用父类自己的成员。因为fp是Father类型的。
Childer *cp; cp->a; cp->ff1(); 由于cp是Childer类型的指针,所以调用的都是Childer自己域中的成员。
2.2 .而有一种情况特殊,则是,当成员函数为虚函数时,虽然是父类类型的指针,但会根据指针指向的具体对象,调用该函数。
即,如果ff1为虚函数,Father*fp; fp = new Childer; fp->ff1(); 虽然fp是Father类型的指针,但由于ff1是虚函数,所以调用的是具体对象,Childer类的成员。
对比2中的相同语句,这就是虚函数和多态的意义。
C++父子类继承时的隐藏、覆盖、重载的更多相关文章
- 【java&c++】父子类中同名函数的覆盖问题
java和c++两门语言对于父子类中同名函数具有不同的处理方式. 先上两段代码: C++: class Basic { public: void test(string i){ cout <&l ...
- python类继承时构造函数的运行问题
假设子类定义了自己的__init__构造方法函数.当子类的实例对象被创建时,子类仅仅会运行自己的__init__方法函数.假设子类没有定义自己的构造方法函数.会沿着搜索树找到父类的构造方法函数去运行父 ...
- C++类中的一些细节(重载、重写、覆盖、隐藏,构造函数、析构函数、拷贝构造函数、赋值函数在继承时的一些问题)
1 函数的重载.重写(重定义).函数覆盖及隐藏 其实函数重载与函数重写.函数覆盖和函数隐藏不是一个层面上的概念.前者是同一个类内,或者同一个函数作用域内,同名不同参数列表的函数之间的关系.而后三者是基 ...
- C++中的虚继承 & 重载隐藏覆盖的讨论
虚继承这个东西用的真不多.估计也就是面试的时候会用到吧.. 可以看这篇文章:<关于C++中的虚拟继承的一些总结> 虚拟基类是为解决多重继承而出现的. 如:类D继承自类B1.B2,而类B1. ...
- 【C++】继承中的隐藏与覆盖
没有访问控制符时默认为私有继承. 当基类中的某个函数有若干个重载版本,继承类中也实现了该函数的某个重载版本时,参数完全相同的基类版本被覆盖,基类的其他版本被隐藏. 1.若要在继承类中使用基类的被覆盖方 ...
- Java:面向对象(继承,方法的重写(overide),super,object类及object类中方法的重写,父子类代码块执行顺序)
继承: 1.继承是对某一匹类的抽象,从而实现对现实世界更好的建模. 2.提高代码的复用性. 3.extends(扩展),子类是父类的扩展. 4.子类继承父类可以得到父类的全部属性和方法.(除了父类的构 ...
- 类属性与对象实现,init方法的作用,绑定方法,绑定方法与普通函数的区别,继承,抽象与继承,派生与覆盖
今日内容: 1.类属性与对象属性 2.init方法的作用 3.绑定方法 4.绑定方法与普通函数的区别(非绑定方法) 5.继承 6.抽象与继承 7.派生与覆盖 1.类属性与对象属性 类中应该进存储所有对 ...
- C++类有继承时,析构函数必须为虚函数
C++类有继承时,析构函数必须为虚函数.如果不是虚函数,则使用时可能存在内在泄漏的问题. 假设我们有这样一种继承关系: 如果我们以这种方式创建对象: SubClass* pObj = new SubC ...
- 项目里出现两个配置类继承WebMvcConfigurationSupport时,为什么只有一个会生效(源码分析)
为什么我们的项目里出现两个配置类继承WebMvcConfigurationSupport时,只有一个会生效.我在网上找了半天都是说结果的,没有人分析源码到底是为啥,博主准备讲解一下,希望可以帮到大家! ...
随机推荐
- 客户端Git的常用命令
(1)git clone 服务器用户名@服务器IP:~/Git目录/.git 功能:下载服务器端Git仓库中的文件或目录到本地当前目录. (2)git status 功能:查看Git仓库中的文件状态. ...
- JSON相关
- [Android Memory] Shallow Heap大小计算释疑
转载自:http://blog.csdn.net/sodino/article/details/24186907 查看Mat文档时里面是这么描述Shallow Heap的:Shallow heap i ...
- CSS3 @media 查询,根据屏幕screen大小调节前端显示;媒体查询方法的使用
------------------- 1.媒体查询方法在 css 里面这样写 -------------------- @media screen and (min-width: 320px) an ...
- phpstorm不安装apache就可以本地测试PHP
最近再搞个PHP的项目,找了很多发现phpstorm这个非常小巧而且很好用,,顺便推荐一下idea开发android非常不错,这2个IDE都是一家公司的.本文由智动软件(zdexe.com)原创,转载 ...
- RenderMonkey 练习 第二天 【opengl 光照模型】
光照模型 3D渲染中, 物体表面的光照计算公式为: I = 环境光(Iambient) + 漫反射光(Idiffuse) + 镜面高光(Ispecular); 其中,环境光(ambient)计算公式为 ...
- 使用虚拟机运行Ubuntu时,主机与宿主机共享文件的方法。
简介: 首先设置虚拟机: 虚拟机 -> 设置-> Hardware -> Network Adapter,在网络连接处设置为 “桥接:直接连接到物理网络”,“NAT:使用已共享的主机 ...
- Java经典算法汇总之冒泡排序
冒泡排序基本思想:在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒.即:每当两相邻的数比较后发现它们的排序与排序要求相反时 ...
- 【甘道夫】Win7环境下Eclipse连接Hadoop2.2.0
准备: 确保hadoop2.2.0集群正常执行 1.eclipse中建立javaproject,导入hadoop2.2.0相关jar包 2.在src根文件夹下拷入log4j.properties,通过 ...
- Netty Client和Server端实现
本文基于Nett4.0.26.Final版本浅析Client与Server端通讯,先看服务器端: public class Server { public static void run(int po ...