c++远征04之继承篇

C++远征之继承篇

本课程将带领大家体会C++面向对象三大特性中的继承特性,讲述了基类、派生类的概念,公有继承、保护继承和私有继承、多重继承及多继承,虚析构函数及虚继承的作用,理解课程内容对于面向对象的学习将大有裨益,所有知识均通过编码实践的方式讲解到操作层面,力求即学即会。

1 课程介绍

2 为什么继承

2.1 继承

说明: 父类(基类)和子类(派生类)

  • 被继承的类叫做基类(父类),从其他类继承而来的类叫做派生类(子类)。
  • 子类中不仅继承了父类中的数据成员,也继承了父类的成员函数。
  • c++ 中的继承关系是概念上的父子关系,不是个体的父子关系。

注意:父类和子类之间必须遵循概念上的父子关系,否则将造成定义和使用的混乱。

工人,后者是前者的子集,即工人 is 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
* 定义人的类: Person
* 数据成员姓名: m_strName
* 成员函数: eat()
*/

class Person
{
public:
string m_strName;
void eat()
{

cout << "eat" << endl;
}
};

/**
* 定义士兵类: Soldier
* 士兵类公有继承人类: public
* 数据成员编号: m_strCode
* 成员函数: attack()
*/

class Soldier: public Person
{
public:
string m_strCode;
void attack()
{

cout << "fire!!!" << endl;
}
};

int main(void)
{

// 创建Soldier对象
Soldier soldier;
// 给对象属性赋值
soldier.m_strName = "Jim";
soldier.m_strCode = "592";
// 打印对象属性值
cout << soldier.m_strName << endl;
cout << soldier.m_strCode << endl;
// 调用对象方法
soldier.eat();
soldier.attack();

return 0;
}

2.2 构造过程和析构函数

  • 构造过程:父类构造函数 -> 子类构造函数
  • 析构过程:子类析构函数 -> 父类析构函数

3 继承方式

说明:成员访问修饰符(父类的)和 继承修饰符 决定了子类对继承自父类的成员的 成员访问修饰符

3.1 成员访问修饰符

成员访问修饰符|可以内部访问|可以外部访问(通过实例访问)
—|—|—
public|是|是
protected|是|否
public|否|否

3.2 继承修饰符

3.3 子类访问继承的成员

4 继承中的特殊关系

4.1 隐藏

说明: 当成员函数同名时,子类会 隐藏 父类中同名的成员函数,即,除非明确指明,否则同名成员调用的是子类的。
注意:只要重名就会发生隐藏,属性类型是否相同,函数参数是否一致不相干。
技巧: 好的命名方式,会在一定程度上避免父子类成员重名的情况 ,比如命名中带有类型信息,则在一定程度上降低了重名的概率。

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
#include <iostream>
using namespace std;

// 人
class Person
{
public:
void play()
{

cout << "Person::play" << endl;
}
protected:
string m_strName;
int code;
};

// 士兵
class Soldier:public Person
{
public:
void play()
{

cout << "Soldier::play" << endl;
}
void work()
{

code = "1234"; // 自身的 code
Person::code = 5678; // 父类的 code
cout << "Soldier::work" << endl;
}
protected:
string code;
};

int main(void)
{

Soldier soldier;

// 同名函数
soldier.play(); // 子类的 play (父类的 play 被隐藏)
soldier.Person::play(); // 调用父类的 play

return 0;
}

4.2 is a

说明: 子类和父类之间的关系可以理解成 is a的关系。比如,一个工人 is 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <iostream>
using namespace std;

// 人
class Person
{
public:
Person(string name = "Jim")
{
m_strName = name;
cout << "Person()" << endl;
}
// 虚析构函数
virtual ~Person()
{
cout << "virtual ~Person()" << endl;
}
void play()
{

cout << "Person::play" << endl;
}
protected:
string m_strName;
};

// 士兵
class Soldier:public Person
{
public:
Soldier(string name = "James", int age = 20)
{
m_strName = name;
m_iAge = age;
cout << "Solider()" << endl;
}
virtual ~Soldier()
{
cout << "virtual ~Solider()" << endl;
}
void play()
{

cout << "Soldier::play" << endl;
}
void work()
{

cout << "Soldier::work" << endl;
}
protected:
int m_iAge;
};

// 传入拷贝
void func1(Person p)
{

p.play();
// 该函数结束时会释放拷贝的实例,出发一次內粗怒回收
}

// 传入指针
void func2(Person *p)
{

p->play();
}

// 传入引用
void func3(Person &p)
{

p.play();
}

int main(void)
{

Soldier s1;
Soldier *s2 = new Soldier("Neo", 30);

Person p1 = s1;
Person *p2 = s2; // s1 是在栈中初始化的,不能检验销毁实例对虚函数的调用情况,因此使用了 s2
Person &p3 = s1;

/*
* 同名成员,访问到的是父类的
*/

p1.play(); // Person::play
p2->play(); // Person::play
p3.play(); // Person::play

/*
* 子类独有的成员,不能访问
*/

// p1.work();
// p2->work();
// p3.work();

/*
* 调用虚析构函数
*/

delete p2; // 只执行父类的析构函数
p2 = NULL;

/*
* 作为参数传入(没啥特别的)
*/

func1(p1);
func2(p2);
func3(p3);

return 0;
}

5 多继承与多重继承

5.1 多重继承

说明: 当 B 类从 A 类派生,C 类从 B 类派生,此时成为多重继承

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
using namespace std;

// 人类
class Person
{
public:
Person()
{
m_strName = "Merry";
cout << "Person()" << endl;
}
~Person()
{
cout << "~Person()" << endl;
}
void play()
{

cout << "Person::play()" << endl;
cout << m_strName << endl;
}
protected:
string m_strName;
};

// 士兵类
class Soldier : public Person
{
public:
Soldier()
{
cout << "Soldier()" << endl;
}
~Soldier()
{
cout << "~Soldier()" << endl;
}
void work()
{

cout << "Soldier::work()" << endl;
}
void play()
{

cout << "Soldier::work" << endl;
}
};

// 步兵类
class Infantry
{
public:
Infantry(string name, int age)
{
m_strName = name;
m_iAge = age;
cout << "Infantry()" << endl;
}
~Infantry()
{

}
void Infantry::attack()
{
cout << m_strName << endl;
cout << m_iAge << endl;
cout << "Infantry::attack()" << endl;
}
};

int main(void)
{

Infantry infantry;

return 0;
}

5.2 多继承

说明: 一个子类同时继承多个父类就构成了多继承。多继承对父类的个数没有限制,继承方式可以是公共继承、保护继承和私有继承。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <iostream>
#include <string.h>
using namespace std;

// 农民
class Farmer
{
public:
Farmer(string name = "Jack")
{
cout << "Farmer()" << endl;
}
virtual ~Farmer()
{
cout << "virtual ~Farmer()" << endl;
}
void show()
{

cout << "show()" << endl;
}
protected:
string m_strName;
};

// 工人
class Worker
{
public:
Worker(string code = "001")
{
m_strCode = code;
cout << "Worker()" << endl;
}
virtual ~Worker()
{
cout << "virtual ~Worker()" << endl;
}
void carry()
{

cout << m_strCode << endl;
cout << "Worker::carry()" << endl;
}
protected:
string m_strCode;
};

// 农民工
class MigrantWorker:public Farmer, public Worker
{
public:
// 在初始化类表中调用父类的构造器完成实例中父类部分的初始化
MigrantWorker(string name, string code):Farmer(name), Worker(code)
{
cout << "MigrantWorker()" << endl;
}
~MigrantWorker()
{
cout << "~MigrantWorker()" << endl;
}
};

int main(void)
{

MigrantWorker *mv = new MigrantWorker("Merry", "100");
mv->carry();
mv->show();

delete mv;
mv = NULL;

return 0;
}

6 虚继承

6.1 多继承和多重继承带来的烦恼

说明: 当多继承和多重继承组合在一起的时候,就可能导致一些问题。

例如,农名工的两个父类工人农民派生自同一个类 ,这种情况叫做菱形继承。当实例化农民工的时候,工人农民会分别初始化继承自的数据成员,即,有两份人的数据,造成数据的冗余,程序尝试访问非静态的冗余的成员将导致编译错误。

报错: 报错不是必然的,常见错误包括以下情形

  • 重定义:如果最顶层的基类(比如上面的人类)是通过外部头文件引入的,会导致重定义错误(可以通过在头文件中设置宏防止重复载入)。
  • 重复成员:如果最顶层的基类就在当前文件中定义,当尝试使用使用冗余的数据成员时,计算机会发现有多份数据成员而导致编译错误(可以通过虚继承来解决)。

6.2 虚继承

说明: 通过在继承过程中声明 virtual,菱形继承顶端的类只会被实例化一次,而且是调用默认的构造器。
注意:其派生类中通过初始化列表调用的构造函数将被忽略。

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <iostream>
#include <string.h>
using namespace std;

// 人
class Person
{
public:
Person(string color = "blue")
{
m_strColor = color;
cout << "Person()" << endl;
}
// 虚析构函数
virtual ~Person()
{
cout << "virtual ~Person()" << endl;
}
void printColor()
{

cout << m_strColor << endl;
cout << "Person::printColor()" << endl;
}
protected:
string m_strColor;
};

// 农民
class Farmer:virtual public Person
{
public:
Farmer(string name = "Jack", string color = "green"):Person("Farmer" + color)
{
cout << "Farmer()" << endl;
}
virtual ~Farmer()
{
cout << "virtual ~Farmer()" << endl;
}
protected:
string m_strName;
};

// 工人
class Worker:virtual public Person
{
public:
Worker(string code = "001", string color = "blue"):Person("Worker" + color)
{
m_strCode = code;
cout << "Worker()" << endl;
}
virtual ~Worker()
{
cout << "virtual ~Worker()" << endl;
}
void carry()
{

cout << m_strCode << endl;
cout << "Worker::carry()" << endl;
}
protected:
string m_strCode;
};

// 农民工
class MigrantWorker:public Farmer, public Worker
{
public:
// 在初始化类表中调用父类的构造器完成实例中父类部分的初始化
MigrantWorker(string name, string code, string color):Farmer(name, color), Worker(code, color)
{
cout << "MigrantWorker()" << endl;
}
~MigrantWorker()
{
cout << "~MigrantWorker()" << endl;
}
};

int main(void)
{

MigrantWorker *mw = new MigrantWorker("xiaohai", "12345", "blue");
mw->Worker::printColor();
mw->Farmer::printColor();

delete mw;
mw = NULL;
return 0;
}

output

1
2
3
4
5
6
7
8
9
10
11
12
Person() # 棱形继承顶部类仅被初始化一次
Farmer()
Worker()
MigrantWorker()
blue # 初始化类表调用构造函数没生效
Person::printColor()
blue
Person::printColor()
~MigrantWorker()
virtual ~Worker()
virtual ~Farmer()
virtual ~Person()