zoukankan      html  css  js  c++  java
  • 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<<"====================
    ";
    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中的相同语句,这就是虚函数和多态的意义。

    路是一步一步走的
  • 相关阅读:
    LeetCode 453 Minimum Moves to Equal Array Elements
    LeetCode 112 Path Sum
    LeetCode 437 Path Sum III
    LeetCode 263 Ugly Number
    Solutions and Summay for Linked List Naive and Easy Questions
    AWS–Sysops notes
    Linked List
    All About Linked List
    datatable fix error–Invalid JSON response
    [转]反编译c#的相关问题
  • 原文地址:https://www.cnblogs.com/Lalafengchui/p/3994340.html
Copyright © 2011-2022 走看看