课件
10.1 引言
引言
notes
引子 | 为什么需要继承和派生
类真不错!类描述了群体的共性,通过创建类的不同对象,我们实现了代码重用。但是这种重用是不充分的……
假设我们有汽车类
#include <iostream>using namespace std;class Car {public:int seats;void accelerate() { cout << "加速到50迈" << endl; } // 油门void brake() {// 刹车}};int main() {Car c;return 0;}
当 Car 类的功能需要进行扩展为变形金刚类 Transformer,增加“变身”的功能,怎么做?
解决方案一:创建一个新类 Transformers,在其中粘贴 Car 类的代码,再添加新的方法 voidbianshen()
#include <iostream>using namespace std;class Car {public:int seats;void accelerate() { cout << "加速到50迈" << endl; } // 油门void brake() {// 刹车}void bianshen() {// 变身}};int main() {Car c;return 0;}
Car 类增加一个功能:停车 voidstop(),那么我们要同时更改 Car 类和 Transformer 类的代码。这种做法太不优雅啦,会让代码难以维护。
解决方案二:继承与派生
Transofomer 类“使用”了 Car 类的特性,那么不需要复制粘贴,Transformer 类就自动具有 Car 类所有的特性。更好的一点是,任何时候 Car 类进行修改,Transformer类都能应用这种修改。
继承与派生就是本章节要介绍的重点内容。
10.2 派生类的引入与特性
- 一旦指定了某种事物父代的本质特征,那么它的子代将会自动具有那些性质。这就是一种朴素的 可重用的概念
 - 继承就是在一个已经存在的类的基础上建立另一个新的类
 - 已存在的类称为“基类”或“父类”,新建立的类称为“派生类”或“子类”
 
派生:
- 子代可以拥有父代没有的特性,这是 可扩充的概念
 - 派生类的功能主要通过以下方式来体现: 
- 吸收基类成员
 - 添加新成员
 - 改造基类成员
 
 
从编码的角度来看,派生类从基类中以较低的代价换来了较大的灵活性:
- 派生类可以对继承的属性进行扩展、限制或改变。
 - 一旦产生了可靠的基类,只需要调试派生类中所作的修改即可。
 
10.3 单继承
单继承的继承方式
同名成员的访问方式
赋值兼容原则
单继承方式下的构造与析构函数的调用顺序
notes
单继承、多继承
- 单继承:派生类只有一个直接基类
 - 多继承:派生类有多个直接基类
 

单继承派生类的定义语法、访问权限
class 派生类名:<继承方式>基类名{// …… 派生类修改基类的成员// …… 派生类新添加的成员};// 继承方式:public、private、protected
虽然继承了基类的所有成员,但是派生类并非都能访问基类的所有成员,继承方式 会影响派生类对基类中各种成员的使用。
public、private、protected:
- 回顾类成员的访问方式也有 public、private、protected 分别是什么意思?
 - 和我们这里继承方式的 public、private、protected 有区别吗?
 
类成员的 public、private、protected
public类内外都可以访问,是类的对外接口private类内可以访问,内外不能访问protected同 private,类内可以访问,内外不能访问
继承方式的 public、private、protected
public公有派生类private私有派生类protected保护派生类
| 基类 | 公有成员 | 私有成员 | 保护成员 | 
|---|---|---|---|
| 公有派生类 | 公有成员 | 不可访问成员 | 保护成员 | 
| 私有派生类 | 私有成员 | 不可访问成员 | 私有成员 | 
| 保护派生类 | 保护成员 | 不可访问成员 | 保护成员 | 
虽然派生类继承了基类所有的成员,但是对于派生类来说,基类的私有成员是不可见的,因此不能被派生类所访问。
不可访问成员:
- 在类外不能被直接访问
 - 在派生类的类内不能被直接访问
 
class 派生类名: public 基类名 {// …… 派生类新添加的成员}
#include <iostream>using namespace std;class Base {int v1; // 没有使用访问修饰符,则默认使用 private 访问修饰符public:int v2;Base(int a = 0, int b = 0) {v1 = a;v2 = b;}};class Derived : public Base {int v3;public:int v4;Derived(int a = 0, int b = 0) {v3 = a;v4 = b;}void func() {// cout << v1 << endl; // 编译器报错“ v1 是 Base 的 private 成员,不能访问”,private 权限 + 任何继承 = 不能访问cout << v2 << endl; // 成功访问到基类成员v2 ,public 权限 + public 继承 = public 权限,此时 v2 的访问权限是 publiccout << v3 << endl; // 成功访问到自身成员 v3,private 权限,允许自身成员函数访问,不允许自身对象访问。cout << v4 << endl; // 成功访问到自身成员 v4,public 权限,允许自身成员函数,自身对象访问。}};int main() {Derived obj = Derived(5, 6);// obj.v1 = 8; // 编译器报错:“Derived 没有成员 v1”// obj.v3 = 4; // 编译器报错:“v3 是 Derived 的私有成员”obj.v2 = 8; // 成功,通过对象访问公有变量 public 权限 + public 继承 = public 权限obj.v4 = 7; // 成功,通过对象访问公有变量。obj.func();return 0;}/* 运行结果:857*/
class 派生类名: private 基类名 {// …… 派生类新添加的成员}
#include <iostream>using namespace std;class Base {int v1;public:int v2;Base(int a = 0, int b = 0) {v1 = a;v2 = b;}};class Derived : private Base {int v3;public:int v4;Derived(int a = 0, int b = 0) {v3 = a;v4 = b;}void func() {// cout << v1 << endl;cout << v2 << endl;cout << v3 << endl;cout << v4 << endl;}};int main() {Derived obj = Derived(5, 6);// obj.v1 = 8;// obj.v3 = 4;// obj.v2 = 8; // v2 是派生类的私有成员,不能被类外访问obj.v4 = 7;obj.func();return 0;}/* 运行结果:057*/
class 派生类名: protected 基类名 {// …… 派生类新添加的成员}
#include <iostream>using namespace std;class Base {protected:int v1;public:int v2;Base(int a = 0, int b = 0) {v1 = a;v2 = b;}};class Derived : protected Base {int v3;public:int v4;Derived(int a = 0, int b = 0) {v3 = a;v4 = b;}void func() {cout << v1 << endl;cout << v2 << endl;cout << v3 << endl;cout << v4 << endl;}};int main() {Derived obj = Derived(5, 6);// obj.v1 = 8; // error => 'v3' is a private member of 'Derived'// obj.v3 = 4; // error => 'v3' is a private member of 'Derived'// obj.v2 = 8; // error => 'v2' is a protected member of 'Base'obj.v4 = 7;obj.func();return 0;}/* 运行结果(注意:和课件描述不一致):0057*/
派生类与基类同名成员的访问方式
- C++ 允许派生类可以重新定义基类的成员,此时称派生类的成员 覆盖了 基类的同名成员。
 - 如果在派生类中,想使用基类的同名成员,则可以显式地使用 类名 + 限定符 的方式:
基类名::成员 
#include <iostream>using namespace std;class Base {protected:int v1;public:int v2;Base(int a = 0, int b = 0) {v1 = a;v2 = b;}};class Derived : public Base {int v2;public:int v3;Derived(int a = 0, int b = 0) {v2 = a;v3 = b;}void func() {int sum1 = v1 + v2 + v3;int sum2 = v1 + Base::v2 + v3;cout << "v1 = " << v1 << endl;cout << "v2 = " << v2 << endl;cout << "v3 = " << v3 << endl;cout << "Base::v2 = " << Base::v2 << endl;cout << "sum1 = " << sum1 << endl;cout << "sum2 = " << sum2 << endl;}};int main() {Derived obj(5, 6);// obj.v2 = 8; // 错误 这么写使用的是 Derived 中的 v2,它是私有成员obj.Base::v2 = 9; // 正确 使用的是 Base 中的 v2obj.func();return 0;}/* 运行结果:v1 = 0v2 = 5v3 = 6Base::v2 = 9sum1 = 11sum2 = 15*/
赋值兼容规则
必须满足赋值兼容规则才能够正常完成赋值。
#include <iostream>using namespace std;class animal {public:void eat() { cout << "animal is eating" << endl; }};class dog : public animal {public:void eat() { cout << "wang wang" << endl; }};int main() {animal a;dog d;d.eat(); // => wang wanga = d; // 成功赋值,因为"所有的动物都是狗"a.eat(); // => animal is eatinganimal& ref_a = d;ref_a.eat(); // => animal is eatinganimal* pointer_a = &d;pointer_a->eat(); // => animal is eatingreturn 0;}
#include <iostream>using namespace std;class animal {public:void eat() { cout << "animal is eating" << endl; }};class dog : public animal {public:void eat() { cout << "wang wang" << endl; }};int main() {animal a;dog d;d.eat(); // => wang wang// d = a; // 编译错误,不能将 animal 对象 a 转换为 dog 对象 d,因为“不是所有的动物都是狗”return 0;}
#include <iostream>using namespace std;class animal {public:void eat() { cout << "animal is eating" << endl; }};class dog : public animal {public:void eat() { cout << "wang wang" << endl; }};class lion : public animal {public:void eat() { cout << "lion lion" << endl; }};int main() {animal a;dog d;lion l;a = l; // 正确,因为狮子是动物a.eat(); // => animal is eating// l = a; // 错误,动物不一定是狮子l.eat(); // => lion lion// l = (lion)a; // 错误,动物不一定是狮子// d = (dog)a; // 错误,动物不一定是狗l.eat(); // => lion liond.eat(); // => wang wangreturn 0;}
在 公有派生 方式下,派生类对象可以作为基类对象来使用,具体方式如下:
- 派生类的对象可以直接赋值给基类的对象
 - 基类对象的引用可以引用一个派生类对象
 - 基类对象的指针可以指向一个派生类对象
 
这个规则可以简单的总结为:所有的子类对象都是基类的对象。
例:所有的狗都是动物,但不是所有的动物都是狗。
单继承的构造与析构
问:派生类继承了基类成员,对象中既包含派生类的成员又包含基类的成员。当我们生成派生类对象的时候,是怎么进行初始化的呢?
答:通过构造函数!
就像我们自己要建造变形金刚汽车,我们首先要抽象汽车的共性,包括发动机,轮胎,车门等信息,然后在派生类当中加上我们自己的特色,比如我们的汽车具有变形功能等。
所以派生类在构造对象的时候,先要生成自己基类部分,然后生成自己的成员对象,最后才是自己独有的部分。
派生类构造函数(参数表):基类构造函数(参数表),对象成员1(参数表), …… 对象成员n(参数表), {// …… 初始化自定义数据成员}
- 如果没有对象成员,那么在初始化列表中可以省略 
对象成员(参数表)这一项。 - 如果基类使用的是缺省的构造函数或不带参数的构造函数,那么在初始化列表中可以省略 
基类构造函数(参数表)这一项。 
#include <iostream>using namespace std;class Point {protected:float x, y;public:Point(float xx = 0, float yy = 0) : x(xx), y(yy) {}float GetX() { return x; }float GetY() { return y; }void moveto(float xx, float yy) {x = xx;y = yy;}};class Circle {Point center;float radius;public:Circle(float x, float y, float r) : center(x, y) { radius = r; }Point GetCenter() const { return center; }int GetRadius() { return radius; }};class ColorCircle : public Circle {int color;public:ColorCircle(float x, float y, float r, int color) : Circle(x, y, r) {this->color = color;}int GetColor() { return color; }};int main() {ColorCircle cc(1, 2, 3, 4);Point center = cc.GetCenter();int r = cc.GetRadius();cout << "圆心: (" << center.GetX() << ", " << center.GetY() << ")" << endl;cout << "半径: " << r << endl;cout << "颜色: " << cc.GetColor() << endl;return 0;}/* 运行结果:圆心: (1, 2)半径: 3颜色: 4 */
Circle(x,y,r)初始化基类数据this->color=color;初始化自定义的基本数据类型数据
#include <iostream>using namespace std;class Base {public:Base() { cout << "Base obj created" << endl; }~Base() { cout << "Base obj deleted" << endl; }};class Derived : public Base {public:Derived() { cout << "Derived obj created" << endl; }~Derived() { cout << "Derived obj deleted" << endl; }};int main() {Derived d;return 0;}/* 运行结果:Base obj createdDerived obj createdDerived obj deletedBase obj deleted*/
构造函数和析构函数的调用顺序:
- 构造函数的调用顺序如下: 
- 先调用基类构造函数;
 - 再调用对象成员所属类的构造函数;
 - 最后调用派生类构造函数;
 
 - 析构函数的调用顺序如下: 
- 再调用对象成员所属类的析构函数;
 - 先调用派生类的析构函数;
 - 最后调用基类的析构函数;
 
 

