关于C++:继承是如何工作的?

How does inheritance actually work?

本问题已经有最佳答案,请猛点这里访问。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#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。虽然aclass A的私有成员变量,但它似乎没有继承,class B的每个对象都有一个a的副本。你能告诉我这里发生了什么事吗?

  • 主报表中的第6条将class Ba_p设置为25。displayB()函数表示class Ba_p的值,displayAP()函数表示class Aa_p的值。但是,两者的输出都是25。你能解释一下这部分吗?


简短的回答:继承就像一个Matryoshka玩偶,每个类都完全包含它的所有基类(如果有的话)。

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

1
2
3
4
5
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;它看起来像这样(在联机x64环境中使用编译器选项/d1reportSingleClassLayoutE通过msvc生成)。

1
2
3
4
5
6
7
8
9
10
11
12
13
class E size(1):
        +---
        | +--- (base class D)
        | | +--- (base class B)
        | | | +--- (base class A)
        | | | +---
        | | +---
        | | +--- (base class C)
        | | | +--- (base class A)
        | | | +---
        | | +---
        | +---
        +---

注意,对于virtual基类,这个类比稍微有点偏离,它往往位于最派生类的"主体"之后(缺少更好的术语;分配给内存中所有非virtual基类和数据成员的内存)。

1
2
3
4
5
class A {};
class B : public virtual A {};
class C : public virtual A {};
class D : public B, public C {};
class E : public D {};

E包含D,其中包含BCE有一个单独的A的实例粘在它的背面。

1
2
3
4
5
6
7
8
9
10
11
12
13
class E size(16):
        +---
        | +--- (base class D)
        | | +--- (base class B)
 0      | | | {vbptr}
        | | +---
        | | +--- (base class C)
 8      | | | {vbptr}
        | | +---
        | +---
        +---
        +--- (virtual base A)
        +---

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

1
2
3
4
5
6
7
8
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个int),B(其中包含int)和int。clang、gcc或msvc都不会发出Size mismatch.错误。使用/d1reportSingleClassLayoutD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class D size(20):
        +---
        | +--- (base class C)
        | | +--- (base class A)
 0      | | | a
 4      | | | b
 8      | | | c
        | | +---
        | | +--- (base class B)
12      | | | d
        | | +---
16      | | e
        | +---
        +---

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

  • private成员仅在声明的类中可见。AA中可见,但在CD中不可见。
  • 遇到protected成员后,在整个继承层次结构中都可见。BACD中可见(但在B中不可见,因为它不从A继承)。ECD中可见。
  • 江户十一〔39〕人赤身露体,供世人观看。CD随处可见。

类中声明的所有成员都可以看到其包含类可见的任何成员。用你的例子,即使在调用派生类B的实例时,A::displayA()总是可以看到A::a;但是,如果B声明了一个隐藏A::displayA()的成员displayA(),那么B::displayA()就不能看到A::a,必须依赖publicprotected的成员A::a。〔3〕如果它想与A::a合作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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(); }
};

第一个要点你是对的。派生类对象具有一个称为"基类子对象"的区域。因此,派生类对象包含基类子对象的方式与包含任何非静态数据成员的成员子对象的方式完全相同。(但情况并非总是这样,因为编译器试图优化事物,结果对象可能不会为非常简单的示例显式地区分成员变量)。

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

为了帮助您理解,请考虑以下代码,其中我将在中隐藏变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#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。

请注意,在本例中,为了引用的成员,必须显式指定要这样做。


displayAP() function shows the value of a_p of class A.

你怎么能确定?,b1.displayAP();不是叫A::displayA(),而是叫B::displayA(),因为b1B型。(B继承了你所有的A的财产。)即使是这一个b1.a_p=25;25的价值转让B::a_p

Can you let me know what is happening here?

它不是一个拷贝,C++中的继承(也在所有语言中)继承了它的父成员的所有成员,不管成员是什么指定的,并且它们的访问说明符保持不变。

所以,它给你的输出是正确的,特别是在你的"主项下的前5个陈述"上。

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