【问题标题】:How does inheritance actually work? [duplicate]继承实际上是如何工作的? [复制]
【发布时间】:2017-07-05 22:58:54
【问题描述】:
#include<iostream>
using namespace std;

class A
{
  int a;
public:
  int a_p;
  void displayA()
  {
    cout<<"A::a "<<a<<endl;
  }
  void displayAP()
  {
    cout<<"A::a_p "<<a_p<<endl;
  }
  void get_a(int x)
  {
    a=x;
  }
};

class B:public A
{
  int b;
public:
  void displayB()
  {
    cout<<"A::a_p "<<a_p<<endl;
  }
};

int main()
{
  B b1,b2;
  b1.get_a(5);
  b2.get_a(10);

  b1.displayA();
  b2.displayA();

  cout<<"......"<<endl;

  b1.a_p=25;
  b1.displayB(); 
  b1.displayAP();
}

我需要澄清以下内容:

  • main 下的前 5 个语句将输出为 5,10。尽管a(class A 的私有成员变量)没有被继承,但似乎class B 的每个对象都有a 的副本。你能告诉我这里发生了什么吗?

  • 主集合中的第 6 条语句将 class B 的 a_p 设置为 25。displayB() 函数显示 class B 的 a_p 的值,displayAP() 函数显示 @987654333 的值@class A。但是,两者的输出都是 25。你能解释一下这部分吗?

【问题讨论】:

    标签: c++ inheritance


    【解决方案1】:

    简答:继承就像套娃,每个类都完全包含它的所有基类(如果有的话)。

    长答案:当一个类从一个或多个其他类继承时,派生类包含其父类,而父类又包含其父类,直到您到达派生最少的类(没有自己的父类的类)。因此,例如,使用此设置:

    class A {};
    class B : public A {};
    class C : public A {};
    class D : public B, public C {};
    class E : public D {};
    

    E 包含一个D,其中包含一个B(其中包含一个A)和一个C(其中包含另一个A);它看起来像这样(使用 MSVC 生成,在 an online x64 environment 中使用 compiler option /d1reportSingleClassLayoutE)。

    class E size(1):
            +---
            | +--- (base class D)
            | | +--- (base class B)
            | | | +--- (base class A)
            | | | +---
            | | +---
            | | +--- (base class C)
            | | | +--- (base class A)
            | | | +---
            | | +---
            | +---
            +---
    

    请注意,virtual 基类的类比略有偏差,基类往往位于最派生类的“主体”之后(因为没有更好的术语;分配给所有非 @ 的内存) 987654333@ 基类和数据成员)在内存中。

    class A {};
    class B : public virtual A {};
    class C : public virtual A {};
    class D : public B, public C {};
    class E : public D {};
    

    E 包含一个D,其中包含一个B 和一个C。 E 背面有一个 A 实例。

    class E size(16):
            +---
            | +--- (base class D)
            | | +--- (base class B)
     0      | | | {vbptr}
            | | +---
            | | +--- (base class C)
     8      | | | {vbptr}
            | | +---
            | +---
            +---
            +--- (virtual base A)
            +---
    

    由于每个派生类都包含其整个继承层次结构,因此它还包含在其任何基类中声明的所有变量。

    class A { private: int a; protected: int b; public: int c; };
    class B { public: int d; };
    class C : public A, public B { protected: int e; };
    class D : public C {};
    
    static_assert(sizeof(C) == sizeof(A) + sizeof(B) + sizeof(int), "Size mismatch.");
    static_assert(sizeof(D) == sizeof(C),                           "Size mismatch.");
    static_assert(sizeof(D) == sizeof(int) * 5,                     "Size mismatch.");
    

    D 包含C,其中包含A(包含 3 个ints)、B(包含一个int)和一个int。 Clang、GCC 或 MSVC 都不会发出 Size mismatch. 错误。使用/d1reportSingleClassLayoutD...

    class D size(20):
            +---
            | +--- (base class C)
            | | +--- (base class A)
     0      | | | a
     4      | | | b
     8      | | | c
            | | +---
            | | +--- (base class B)
    12      | | | d
            | | +---
    16      | | e
            | +---
            +---
    

    因此,访问说明符实际上不会影响继承或不继承的内容。然而,它们所做影响的是派生类可见的内容。

    • private 成员仅在声明它们的类中可见。 a 在 A 中可见,但在 C 或 D 中不可见
    • protected 成员在遇到后在整个继承层次结构中都是可见的。 b 在A、C 和D 中可见(但在B 中不可见,因为它不是从A 继承的)。 e 在 C 和 D 中可见。
    • public 成员赤裸裸地让全世界看到。 c 和 d 随处可见。

    类中声明的所有成员都可以看到对其包含类可见的任何成员。使用您的示例,A::displayA() 始终可以看到A::a,即使在派生类B 的实例上调用时也是如此;然而,如果B 声明了一个隐藏A::displayA() 的成员displayA(),那么B::displayA() 将无法看到A::a,并且将不得不依赖public 或protected 的@987654381 成员@如果它想与A::a一起工作。

    class A {
        int a;
    
      public:
        void displayA() { std::cout << "A::a " << a << std::endl; }
    };
    
    class B : public A {
      public:
        // Will emit some variation on "A::a is private, you can't access it here."
        // Note that no compiler will claim that it doesn't exist.
        // void displayA() { std::cout << "A::a " << a << std::endl; }
    
        // This works, though, since it goes through A::displayA(), which can see A::a.
        void displayA() { return A::displayA(); }
    };
    

    【讨论】:

      【解决方案2】:

      第一个要点你是对的。派生类对象,有一个称为“基类子对象”的区域。因此,派生类对象包含一个基类子对象,其方式与它包含任何非静态数据成员的成员子对象完全相同。 (但情况并非总是如此,因为编译器会尝试优化事物,而生成的对象可能不会明确区分非常简单的示例的成员变量)。

      第二个要点你错了。它们都指向 A::a_p(包含在类 A 中的属性)。由于 B 不包含属性 a_p,它会隐式指向 A 中的属性。

      为了帮助您理解下面的代码,我在 A 中隐藏了一个变量:

      #include<iostream>
      
      class A {
      
      public:
          int a;
      };
      
      class B : public A {
      public:
          int a;
      
          B() = delete;
          B(int a_a, int b_a) {
              A::a = a_a;
              a = b_a;
          } 
      
          void displayA_A() {
              std::cout << "A::a: " << A::a << std::endl;
          }
      
          void displayB_A() {
              std::cout << "B::a " << B::a << std::endl;
          }
      };
      
      int main() {
          B b(10,20);
      
          b.displayA_A();
          b.displayB_A();
      
          return 0;
      }
      

      在main中构造B时,首先会构造对象A并将其成员设置为10,然后将B中的成员设置为20。

      请注意,在此示例中,为了引用 A 的成员,您必须明确指定您要这样做。

      【讨论】:

        【解决方案3】:

        displayAP() 函数显示 A 类的 a_p 的值。

        你怎么能确定呢?b1.displayAP(); 不是调用A::displayA() 而是B::displayA() 因为b1 是B 类型。 (B 继承了您的所有 A 属性。)。甚至这个b1.a_p=25; 也将B::a_p 赋值为25。

        你能告诉我这里发生了什么吗?

        这不是副本,C++ 中的继承(也适用于所有语言)继承其父级的所有成员,无论该成员在什么说明符下,并且它们的访问说明符保持不变。

        所以,它给你的输出是正确的,特别是在“main 下的前 5 个语句”中

        你应该阅读更多关于继承http://www.learncpp.com/cpp-tutorial/112-basic-inheritance-in-c/

        【讨论】:

          猜你喜欢
          • 2023-03-23
          • 2010-12-21
          • 2011-09-19
          • 1970-01-01
          • 2019-01-01
          • 1970-01-01
          • 2014-11-03
          • 1970-01-01
          相关资源
          最近更新 更多