C++父子类继承时的隐藏、覆盖、重载

  存在父子类继承关系时,若有同名成员函数同时存在,会发生隐藏、覆盖和重载这几种情况。对于初学者也比较容易混淆,为此,我整理了一下我的个人看法,仅供参考。希望对大家理解有帮助,也欢迎指正。

1.父子类继承关系: 子类复制父类全部成员

  首先,理解父子类的继承关系是怎样发生的。在此基础上就很容易理解它们之间的关系和区别。  

  每一个类有它自己的成员变量和成员函数,是一个独立的空间整体。当子类继承父类时,会将父类的全部成员全部复制一份,作为子类的成员,但是,同时也会标记这些成员是从父类中继承的,与子类本身的成员,还是有区别的。这里认为将子类本身的成员存在子类域,从父类复制过来的存在父类域。

如下图,Childer类中存在两个域,子类域和父类域,相互之间互不干扰。

 1 class Father
 2 {
 3     int f_a;
 4     int f_b;
 5 };
 6
 7 class Childer:public Father
 8 {
 9     int c_a;
10     int f_b;
11 };
12
13 int main()
14 {
15     cout<<"sizeof childer:"<<sizeof(Childer)<<endl;   //-> 16
16     cout<<"sizeof father:"<<sizeof(Father)<<endl;     //-> 8
17 }

运行结果显示,子类大小为16,父类大小为8,也就是说子类的确有4个成员变量,就算是同名成员,也同样复制。

2.隐藏:子类对象优先考虑子类域自身成员(成员变量和成员函数)

  隐藏发生的主要原因,就是当子类有父类的同名成员时,子类对象访问该成员时,会发生冲突。所以编译器的处理方式是,优先考虑子类域中的自身成员。

即,子类对象访问某成员时,如ch.m_m 或者ch.f(),成员变量和成员函数都一样。编译器首先在子类域中检索,如果在子类域中找到该成员,则检索结束,返回该成员进行访问。如果在子类域中找不到该成员,则去父类域中检索。如果父类域中存在,则返回该成员进行访问,如果父类域中也不存在,则编译错误,该成员无效。

  当父子类域都存在同一成员时,编译器优先在子类中检索,就算父类域中也存在该同名成员,也不会被检索到。因此,父类域中的该成员被子类域中的该同名成员隐藏,即访问时完全以为该成员不存在,如果想访问父类域中的该成员,只能通过显示调用的方式,即:ch.Father::m_m;

下面用代码说明,为了对问题有针对性说明,此处成员都采用public,也不涉及构造析构等问题。

 1 class Father
 2 {
 3 public:
 4     int f_a;
 5     int f_b;
 6
 7     void ff1() {cout<<"father ff1"<<endl;}
 8 };
 9
10 class Childer:public Father
11 {
12 public:
13     int c_a;
14     int f_b;
15
16     void cf1() {cout<<"childer cf1"<<endl;}
17     void ff1() {cout<<"childer ff1"<<endl;}
18 };
19
20 int main()
21 {
22     Childer ch;
23
24     cout<<ch.c_a<<endl; //只在子类域中的成员变量
25     cout<<ch.f_b<<endl; //子类域和父类域都存在,优先访问子类域中的
26     cout<<ch.Father::f_b<<endl; //显示访问被隐藏的成员变量
27
28     cout<<"====================\n";
29
30     ch.cf1();
31     ch.ff1();
32     ch.Father::ff1();
33 }

运行结果可以看出,ch.f_b;  和 ch.Father::f_b;  两个同名成员同时存在。但访问时,子类成员将父类成员隐藏,想访问父类成员只能显示调用。

通过成员函数的访问,这一效果更明显,ch.ff1();调用时,调用了子类域中的该同名成员函数。

  且此时编译器检索时,只根据名字,与函数的参数和返回类型无关。

1 int ff1(int a ) {cout<<"childer ff1"<<endl;return 0;}

若将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,其他不进行改变,运行结果也不变。

  下面解释一下,“覆盖”二字的由来。

首先需明白一点,虚函数的提出,是为了实现多态。也就是说,虚函数的目的是为了,在用父类指针指向不同的子类对象时,调用虚函数,调用的是对应子类对象的成员函数,即可以自动识别具体子类对象。所以,上述例子中,直接用子类对象调用虚函数是没有意义的,一般情况也不会这样使用。

 1 class Father
 2 {
 3 public:
 4     virtual void ff1() {cout<<"father ff1"<<endl;}
 5 };
 6
 7 class Childer_1:public Father
 8 {
 9 public:
10     void ff1() {cout<<"childer_1 ff1 "<<endl;}
11 };
12 class Childer_2:public Father
13 {
14 public:
15     void ff1() {cout<<"childer_2 ff1"<<endl; }
16 };
17
18 int main()
19 {
20     Father* fp;
21
22     Childer_1 ch1;
23     fp = &ch1;
24     fp->ff1();
25
26     Childer_2 ch2;
27     fp = &ch2;
28     fp->ff1();
29
30     return 0;
31 }

使用虚函数,都是父类指针的形式,pf->f11() 。例子中的24行和28行,相同的代码,因为fp的指向不同对象,所以调用不同对象的虚函数。但从代码上看,fp是一个Father类的指针,但调用的是子类成员函数,就好像父类的成员被覆盖了一样。这就是覆盖一词的来源。

覆盖的情况下,子类虚函数必须与父类虚函数有相同的参数列表,否则认为是一个新的函数,与父类的该同名函数没有关系。但不可以认为两个函数构成重载。因为两个函数在不同的域中。

举例:

 1 class Father
 2 {
 3 public:
 4     virtual void ff1() {cout<<"father ff1"<<endl;}
 5 };
 6
 7 class Childer_1:public Father
 8 {
 9 public:
10     void ff1(int a) {cout<<"childer_1 ff1 "<<endl; }
11 };
12
13 int main()
14 {
15     Father* fp;
16
17     Childer_1 ch1;
18     fp = &ch1;
19     fp->ff1();
20    //ch1.ff1(); //没有匹配的成员
21     ch1.ff1(2);
22
23     return 0;
24 }

运行结果为:

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中的相同语句,这就是虚函数和多态的意义。

时间: 2024-10-14 20:59:09

C++父子类继承时的隐藏、覆盖、重载的相关文章

Java类的各种成员初始化顺序如:父子类继承时的静态代码块,普通代码块,静态方法,构造方法,等先后顺

class B extends A ,然后A类也就是父类里面有静态代码块,普通代码块,静态方法,静态成员变量,普通成员变量,普通方法.子类也是这样,然后继承之后,关于程序打印输出的结果.涉及到Java类的各种成员的初始化顺序.经测试,得到如下结论: 1.父类[静态成员]和[静态代码块],按在代码中出现的顺序依次执行.2.子类[静态成员]和[静态代码块],按在代码中出现的顺序依次执行.3.父类的[普通成员变量被普通成员方法赋值]和[普通代码块],按在代码中出现的顺序依次执行.4.执行父类的构造方法

【C++】通过基类的指针变量访问派生类中由基类继承来的隐藏对象

//<img src="http://img.blog.csdn.net/20150512213309005?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZG91ZG91d2ExMjM0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /> // 可以将一个派生类的对象的地址赋给其基类的指针变量,但

【java&amp;c++】父子类中同名函数的覆盖问题

java和c++两门语言对于父子类中同名函数具有不同的处理方式. 先上两段代码: C++: class Basic { public: void test(string i){ cout << "basic str" <<endl; } void test(int i){ cout << "basic" <<endl; } }; class Senior : public Basic { public: void te

逆向第十九讲——类继承和成员类、运算符重载、模板逆向20171211

一.类继承逆向 在C++中使用到继承,主要是为了实现多态,那么多态就必须会用到虚函数,即会产生虚表指针. (1)父类和子类中有没用到虚函数的四种情形 1)父类和子类中都没有用到虚函数 如果父类和子类中都没有用到虚函数,那么子类中就只是继承了父类中的成员变量和成员函数,当然还得视父类中成员变量和成员函数的公有私有性质和继承方式而定,在此继承中有一种特殊形式,当父类和子类中含有同名同参数同返回值的函数时,用父类对象指针调该函数,则调的是父类中的该函数,用子类对象指针调该函数时,调的是子类中的而该函数

JS__class 入坑 chrome测试class 父子类 继承关系

emmm...直接通过测试来看父子关系吧: class A{ constructor(){//并非必须 this.name="class_A"; //console.log(this) //A {name: "class_A"} (另起一行)B {name: "class_A"} //父类子类分别执行了一次 } static staticCallNameA(){ return "name_A"; } callNameA(){

类继承时,构造函数和析构函数的调用次序

比如,apple类是fruit类的子类,当执行apple a("红富士");的时候,将先调用fruit类的构造函数,再调用apple类的构造函数 同理,假设在程序里先后声明了两个apple实例: apple a(“红富士"); apple b("青香蕉"); 当函数退出时,要先销毁b,再销毁a(因为b在栈顶). 而调用析构函数时,先调用apple的析构函数,再调用fruit的析构函数. 综上可见,构造函数和析构函数的调用次序恰好反了个个儿. Java和C+

类继承中的 隐藏和重写的 区别

我看先看一个例子: public class A{    public int a = 10;    public virtual void Fun1()    {        Console.WriteLine("1");  -------A1    } public virtual void Fun3()    {        Console.WriteLine(this.a); -------A2 }    public void Fun2()    {        Fun

方法和变量在继承时的覆盖和隐藏

package com.jdk7.chapter2.converhide; public class Parent { public static String kind = "com.jdk7.chapter2.Parent"; //类变量 public static int age = 100; //类变量 public String name = "Parent"; //实例变量 public static String getKind() { //静态方法

JS 类继承 原型继承

参考文档:JS原型继承和类继承 <script src="jquery-2.0.3.js"> </script> <script> /*//类继承 var father=function(){ this.age=53; this.say=function(){ console.log("name:"+this.name+",age:"+this.age); } }; var child=function(){