16 继承
继承是面向对象三大特性之一。有些类与类之间存在特殊的关系,例如:动物与猫、狗之间。当定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性。这时就可以利用继承的技术,减少重复代码。
16.1 继承的基本语法
语法:class 子类 : 继承方式 父类
子类也称为派生类,父类也称为基类。
示例:
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
| #include <iostream> using namespace std; #include <string>
class BasePage { public:
void header() { cout << "首页、公开课、登录、注册...(公共头部)" << endl; }
void footer() { cout << "帮助中心、交流合作、站内地图...(公共底部)" << endl; }
void left() { cout << "Java、Python、C++...(公共分类列表)" << endl; } };
class Java : public BasePage { public:
void content() { cout << "Java学科视频" << endl; } };
class Python : public BasePage { public:
void content() { cout << "Python学科视频" << endl; } };
class CPP : public BasePage { public:
void content() { cout << "C++学科视频" << endl; } };
void test01() { cout << "Java下载视频页面如下: " << endl; Java ja; ja.header(); ja.footer(); ja.left(); ja.content();
cout << "--------------------" << endl; cout << "Python下载视频页面如下: " << endl; Python py; py.header(); py.footer(); py.left(); py.content();
cout << "--------------------" << endl; cout << "C++下载视频页面如下: " << endl; CPP cpp; cpp.header(); cpp.footer(); cpp.left(); cpp.content(); }
int main() {
test01();
system("pause");
return 0; }
|
总结:继承的好处是可以减少重复代码,公共部分写在父类,子类只写自己特有的内容。
16.2 继承方式
继承方式一共有三种:
- 公共继承 public
- 保护继承 protected
- 私有继承 private
| 继承方式 | 父类 public 成员 | 父类 protected 成员 | 父类 private 成员 |
|---|
| public | public | protected | 不可访问 |
| protected | protected | protected | 不可访问 |
| private | private | private | 不可访问 |
示例:
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
| #include <iostream> using namespace std;
class Base1 { public: int m_A; protected: int m_B; private: int m_C; };
class Son1 : public Base1 { public: void func() { m_A = 10; m_B = 20; } };
class Son2 : protected Base1 { public: void func() { m_A = 100; m_B = 200; } };
class Son3 : private Base1 { public: void func() { m_A = 1000; m_B = 2000; } };
void test01() { Son1 s1; s1.m_A = 100;
Son2 s2;
Son3 s3; }
int main() {
test01();
system("pause");
return 0; }
|
16.3 继承中的对象模型
问题:从父类继承过来的成员,哪些属于子类对象中?
父类中所有非静态成员属性都会被子类继承下去;父类中私有成员属性,是被编译器给隐藏了,因此是访问不到的,但是确实被继承下去了。
示例:利用 sizeof 验证
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> using namespace std;
class Base { public: int m_A; protected: int m_B; private: int m_C; };
class Son : public Base { public: int m_D; };
void test01() { cout << "sizeof(Son) = " << sizeof(Son) << endl; }
int main() {
test01();
system("pause");
return 0; }
|
提示:在 VS 开发者工具中可以查看类的对象模型,验证父类私有成员确实存在于子类对象的内存中。
16.4 继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数。
问题:父类和子类的构造和析构顺序是谁先谁后?
结论:先构造父类,再构造子类;先析构子类,再析构父类。
示例:
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
| #include <iostream> using namespace std;
class Base { public:
Base() { cout << "Base 构造函数!" << endl; }
~Base() { cout << "Base 析构函数!" << endl; } };
class Son : public Base { public:
Son() { cout << "Son 构造函数!" << endl; }
~Son() { cout << "Son 析构函数!" << endl; } };
void test01() { Son s; }
int main() {
test01();
system("pause");
return 0; }
|
16.5 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据?
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
示例:
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 Base { public:
Base() { m_A = 100; }
void func() { cout << "Base - func() 调用" << endl; }
void func(int a) { cout << "Base - func(int a) 调用" << endl; }
int m_A; };
class Son : public Base { public:
Son() { m_A = 200; }
void func() { cout << "Son - func() 调用" << endl; }
int m_A; };
void test01() { Son s; cout << "Son 下的 m_A = " << s.m_A << endl;
cout << "Base 下的 m_A = " << s.Base::m_A << endl; }
void test02() { Son s; s.func(); s.Base::func();
s.Base::func(100); }
int main() {
test01(); test02();
system("pause");
return 0; }
|
注意:同名静态成员处理方法相同,访问父类静态成员需加作用域(Son::Base::m_A 或 s.Base::m_A)。
16.6 多继承语法
C++ 允许一个类继承多个类。
语法:class 子类 : 继承方式 父类1, 继承方式 父类2 ...
注意:多继承可能会引发父类中有同名成员出现,需要加作用域区分。C++ 实际开发中不建议使用多继承。
示例:
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
| #include <iostream> using namespace std;
class Base1 { public:
Base1() { m_A = 100; }
int m_A; };
class Base2 { public:
Base2() { m_A = 200; }
int m_A; };
class Son : public Base1, public Base2 { public:
Son() { m_C = 300; m_D = 400; }
int m_C; int m_D; };
void test01() { Son s; cout << "sizeof(Son) = " << sizeof(s) << endl;
cout << "Base1::m_A = " << s.Base1::m_A << endl; cout << "Base2::m_A = " << s.Base2::m_A << endl; }
int main() {
test01();
system("pause");
return 0; }
|
16.7 菱形继承
概念:两个派生类继承同一个基类,又有某个类同时继承这两个派生类,这种继承被称为菱形继承,或者钻石继承。
菱形继承问题:
- 羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性
- 羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以
示例:
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
| #include <iostream> using namespace std;
class Animal { public:
int m_Age; };
class Sheep : virtual public Animal { public: };
class Camel : virtual public Animal { public: };
class SheepTuo : public Sheep, public Camel { public: };
void test01() { SheepTuo st;
st.Sheep::m_Age = 18; st.Camel::m_Age = 28;
}
int main() {
test01();
system("pause");
return 0; }
|
总结:
- 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
- 利用虚继承可以解决菱形继承问题,使用虚继承后,共同父类成为虚基类,同名的数据只保留一份