C++ note (8)
Eiaton

继承和派生 Ⅰ

继承/派生

概念

派生类(derived class)是通过对基类(base class)进行扩充和修改得到的。基类的所有成员自动成为派生类的成员。在基类中除了自己定义的成员之外,还自动包括了派生类中定义的数据成员与成员函数,这些自动继承下来的成员称为基类的继承成员

base class / parent class / super class

derived class / sub class

继承代表了从属(is-a)关系

提出继承/派生概念的原因

  • 是通常所说的oop的基础
  • 继承提供了一种通过修改(演化)一个或多个现有类来构造新类的方法
  • 如果只有一个类的概念,软件的可重用性、演化和相关的概念表示存在严重的不灵活问题。继承机制为软件可重用性、IS-A 概念表示和易于修改提供了解决方案

图解

1
2
3
4
        A
↗ ↖
B C
单继承:派生类只有一个直接基类
1
2
3
4
    A       B
↖ ↗
C
多重继承:派生类具有两个及以上直接基类
1
2
3
4
5
6
        A
↗ ↖
B C
↖ ↗
D
菱形继承:派生类两次或两次以上重复继承某个祖先类

语法与访问控制

继承关系语法

单重继承的定义形式

1
2
3
4
class derivedClass_name : inheritanceControl baseClass_name {
objectControl:
objectDeclaration_list;
};

继承中的访问控制

继承访问控制和成员访问控制均由保留public、protected、private来定义,缺省均为private

  • private

    私有成员只能通过本类的成员函数来访问

  • public

    公有成员用于描述一个类与外部世界的接口,类的外部(程序的其它部分的代码)可以访问公有成员

  • protected

    受保护成员具有private与public的双重角色:

    对派生类的成员函数而言,它为public,而对类的外部而言,它为private。即:protected成员只能由本类及其后代类的成员函数访问

影响继承成员(派生类从基类中继承而来的成员)访问控制方式的两个因素:

  1. 定义派生类时指定的继承访问控制
  2. 该成员在基类中所具有的成员访问控制

  • 无论采用什么继承方式,基类的私有成员在派生类中都是不可访问的。
  • “私有”和“不可访问”有区别:私有成员可以由派生类本身访问,不可访问成员即使是派生类本身也不能访问。
  • 大多数情况下均使用 public 继承

举例:

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
class base {

public:
base();
void get_ij();

protected:
int i, j;

private:
int temp;
};

//公有派生:在y1类中,i、j是受保护成员
class y1 : public base {

public:
void increment(); // get_ij()是公有成员,temp不可访问

private:
float member;
};

base::base() : i(0), j(0), temp(0) {}

void base::get_ij() {
cout << i << " " << j << endl;
}

void y1::increament() {
++i;
++j;
}
1
2
3
4
5
6
7
8
int main() {
base obj1;
y obj2;
obj2.increament();
obj2.get_ij();
obj1.get_ij();
return (0);
}
1
2
1 1
0 0

保护派生:在y2类中,i、j是受保护成员。get_ij()变成受保护成员,temp不可访问

class y2 : protected BASE{ … };

私有派生:在y3类中,i、j、 get_ij()都变成私有成员,temp不可访问

class y3 : private BASE{ … };

在大多数情况下,使用public的继承方式;private和protected很少使用

派生类对象的存储

  • 派生类的对象不仅存放了在派生类中定义的非静态数据成员,而且也存放了从基类中继承下来的非静态数据成员
  • 可以认为派生类对象中包含了基类子对象

构造与析构顺序

继承时的构造函数

基类的构造函数不被继承,派生类中需要声明自己的构造函数

派生类的构造函数中只需要对本类中新增成员进行初始化即可。对继承来的基类成员的初始化是通过编译在派生类构造函数初始化器中自动生成默认构造函数(默认拷贝构造)完成。

如果基类没有默认构造(包括 = delete),则编译错误

派生类的构造函数需要使用基类的有参构造函数时,必须显式地在初始化器列表中申明。

注意:不能在构造函数内调用!

构造函数的调用次序(创建派生类对象时)

  • 首先调用其基类的构造函数(调用顺序按照基类被继承时的声明顺序(从左向右))。
  • 然后调用本类对象成员的构造函数(调用顺序按照对象成员在类中的声明顺序)。
  • 最后调用本类的构造函数。

继承时的析构函数

撤销派生类对象时析构函数的调用次序与构造函数的调用次序相反

  • 首先调用本类的析构函数
  • 然后调用本类对象成员的析构函数
  • 最后调用其基类的析构函数
1
2
3
4
5
6
7
8
9
10
11
12
//demo.h 
class C {
public:
C( );
~C( );
};

class base {
public:
base( );
~base( );
}; 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include “demo.h”
//demo.cpp
C::C( ) {
cout << "Constructing C object." << endl;
}

C::~C( ) {
cout << "Destructing C object." << endl;
}

base::base( ) {
cout << "Constructing base object." << endl;
}

base::~base( ) {
cout << "Destructing base object." << endl;
}
1
2
3
4
5
6
7
8
// Derived.h
class derived : public base {
public:
derived()
~derived()
private:
C newObj;
}; 
1
2
3
4
5
6
7
8
9
#include “Derived.h”
// Derived.cpp
derived::derived() {
cout << "Constructing derived object." << endl;
}

derived::~derived() {
cout << "Destructing derived object." << endl;
}
1
2
3
4
5
6
7
#include “Derived.h”  
// Client.cpp
int main() {
derived obj; // 声明一个派生类的对象
// 什么也不做,仅完成对象obj的构造与析构
return (0);
}
1
2
3
4
5
6
7
Constructing base object.
Constructing C object.
Constructing derived object.
Destructing derived object.
Destructing C object.
Destructing base object.