第 七 章 继 承 与 派 生 1
本 章 主 要 内 容 的 继 承 成 员 的 访 问 控 制 单 继 承 与 多 继 承 派 生 的 构 造 析 构 函 数 成 员 的 标 识 与 访 问 深 度 探 索 2
的 继 承 与 派 生 的 继 承 与 派 生 保 持 已 有 的 特 性 而 构 造 新 的 过 程 称 为 继 承 在 已 有 的 基 础 上 新 增 自 己 的 特 性 而 产 生 新 的 过 程 称 为 派 生 被 继 承 的 已 有 称 为 基 ( 或 父 ) 派 生 出 的 新 称 为 派 生 ( 或 子 ) 3
的 继 承 与 派 生 继 承 与 派 生 问 题 举 例 交 通 工 具 汽 车 小 汽 车 卡 车 旅 行 车 工 具 车 轿 车 面 包 车 4
的 继 承 与 派 生 继 承 与 派 生 问 题 举 例 猴 子 动 物 猫 科 鸟 狮 子 虎 猎 豹 5
的 继 承 与 派 生 继 承 与 派 生 问 题 举 例 几 何 形 状 圆 矩 形 6
的 继 承 与 派 生 继 承 与 派 生 问 题 举 例 兼 职 技 术 人 员 雇 员 管 理 人 员 销 售 人 员 销 售 经 理 7
的 继 承 与 派 生 的 层 次 结 构 由 上 到 下, 是 一 个 具 体 化 特 殊 化 的 过 程 由 下 到 上, 是 一 个 抽 象 化 的 过 程 8
的 继 承 与 派 生 继 承 与 派 生 的 目 的 继 承 的 目 的 : 实 现 代 码 重 用 派 生 的 目 的 : 当 新 的 问 题 出 现, 原 有 程 序 无 法 解 决 ( 或 不 能 完 全 解 决 ) 时, 需 要 对 原 有 程 序 进 行 改 造, 实 现 代 码 扩 充 9
的 继 承 与 派 生 派 生 新 的 过 程 吸 收 基 成 员 基 的 全 部 数 据 成 员 和 除 了 构 造 析 构 函 数 外 的 全 部 函 数 成 员 改 造 基 成 员 基 成 员 的 访 问 控 制 对 基 数 据 或 函 数 成 员 的 隐 藏 添 加 新 的 成 员 一 般 成 员 的 添 加 构 造 函 数 和 析 构 函 数 10
的 继 承 与 派 生 派 生 的 声 明 class 派 生 名 : 继 承 方 式 基 名 { 派 生 成 员 声 明 ; 11
成 员 的 访 问 控 制 继 承 方 式 不 同 继 承 方 式 的 影 响 主 要 体 现 在 : 派 生 成 员 对 基 成 员 的 访 问 权 限 通 过 派 生 对 象 对 基 成 员 的 访 问 权 限 三 种 继 承 方 式 公 有 继 承 私 有 继 承, 默 认 保 护 继 承 12
成 员 的 访 问 控 制 公 有 继 承 (public) 基 的 public 和 protected 成 员 的 访 问 属 性 在 派 生 中 保 持 不 变, 但 基 的 private 成 员 不 可 直 接 访 问 派 生 中 的 成 员 函 数 可 以 直 接 访 问 基 中 的 public 和 protected 成 员, 但 不 能 直 接 访 问 基 的 private 成 员 通 过 派 生 的 对 象 只 能 访 问 基 的 public 成 员 13
成 员 的 访 问 控 制 例 7-1 公 有 继 承 举 例 class Point { // 基 Point 的 定 义 public: // 公 有 函 数 成 员 void initpoint(float x = 0, float y = 0) { this->x = x; this->y = y;} void move(float offx, float offy) { x += offx; y += offy; } float getx() const { return x; } float gety() const { return y; } private: // 私 有 数 据 成 员 float x, y; 14
class Rectangle: public Point { // 派 生 定 义 部 分 public: // 新 增 公 有 函 数 成 员 void initrectangle(float x, float y, float w, float h) { initpoint(x, y); // 调 用 基 公 有 成 员 函 数 this->w = w; this->h = h; } float geth() const { return h; } float getw() const { return w; } private: // 新 增 私 有 数 据 成 员 float w, h; 15
#include <iostream> #include <cmath> using namespace std; int main() { Rectangle rect; // 定 义 Rectangle 的 对 象 // 设 置 矩 形 的 数 据 rect.initrectangle(2, 3, 20, 10); rect.move(3,2); // 移 动 矩 形 位 置 cout << "The data of rect(x,y,w,h): " << endl; // 输 出 矩 形 的 特 征 参 数 cout << rect.getx() <<", " << rect.gety() << ", " << rect.getw() << ", " << rect.geth() << endl; return 0; } 16
成 员 的 访 问 控 制 私 有 继 承 (private) 基 的 public 和 protected 成 员 都 以 private 身 份 出 现 在 派 生 中, 但 基 的 private 成 员 不 可 直 接 访 问 派 生 中 的 成 员 函 数 可 以 直 接 访 问 基 中 的 public 和 protected 成 员, 但 不 能 直 接 访 问 基 的 private 成 员 通 过 派 生 的 对 象 不 能 直 接 访 问 基 中 的 任 何 成 员 17
成 员 的 访 问 控 制 例 7-2 私 有 继 承 举 例 class Rectangle: private Point { // 派 生 定 义 部 分 public:// 新 增 公 有 函 数 成 员 void initrectangle(float x, float y, float w, float h) { initpoint(x, y); // 调 用 基 公 有 成 员 函 数 this->w = w; this->h = h; } void move(float offx, float offy) { Point::move(offX, offy); } float getx() const { return Point::getX(); } float gety() const { return Point::getY(); } float geth() const { return h; } float getw() const { return w; } private: // 新 增 私 有 数 据 成 员 float w, h; 18
#include <iostream> #include <cmath> using namespace std; int main() { Rectangle rect; // 定 义 Rectangle 的 对 象 rect.initrectangle(2, 3, 20, 10); // 设 置 矩 形 的 数 据 rect.move(3,2); // 移 动 矩 形 位 置 cout << "The data of rect(x,y,w,h): " << endl; cout << rect.getx() <<", " // 输 出 矩 形 的 特 征 参 数 << rect.gety() << ", " << rect.getw() << ", " << rect.geth() << endl; return 0; } 19
成 员 的 访 问 控 制 保 护 继 承 (protected) 基 的 public 和 protected 成 员 都 以 protected 身 份 出 现 在 派 生 中, 但 基 的 private 成 员 不 可 直 接 访 问 派 生 中 的 成 员 函 数 可 以 直 接 访 问 基 中 的 public 和 protected 成 员, 但 不 能 直 接 访 问 基 的 private 成 员 通 过 派 生 的 对 象 不 能 直 接 访 问 基 中 的 任 何 成 员 20
成 员 的 访 问 控 制 protected 成 员 的 特 点 与 作 用 对 建 立 其 所 在 对 象 的 模 块 来 说, 它 与 private 成 员 的 性 质 相 同 对 于 其 派 生 来 说, 它 与 public 成 员 的 性 质 相 同 既 实 现 了 数 据 隐 藏, 又 方 便 继 承, 实 现 代 码 重 用 21
成 员 的 访 问 控 制 例 protected 成 员 举 例 class A { protected: int x; int main() { } A a; a.x = 5; // 错 误 22
class A { protected: int x; class B: public A{ public: void function(); void B:function() { x = 5; // 正 确 } 23
型 兼 容 规 则 型 兼 容 一 个 公 有 派 生 的 对 象 在 使 用 上 可 以 被 当 作 基 的 对 象, 反 之 则 禁 止 具 体 表 现 在 : 派 生 的 对 象 可 以 隐 含 转 换 为 基 对 象 派 生 的 对 象 可 以 初 始 化 基 的 引 用 派 生 的 指 针 可 以 隐 含 转 换 为 基 的 指 针 通 过 基 对 象 名 指 针 只 能 使 用 从 基 继 承 的 成 员 24
例 7-3 型 兼 容 规 则 举 例 型 兼 容 //7_3.cpp #include <iostream> using namespace std; class Base1 { // 基 Base1 定 义 public: void display() const { cout << "Base1::display()" << endl; } 25
class Base2: public Base1 { // 公 有 派 生 Base2 定 义 public: void display() const { cout << "Base2::display()" << endl; } class Derived: public Base2 { // 公 有 派 生 Derived 定 义 public: void display() const { cout << "Derived::display()" << endl; } void fun(base1 *ptr) { // 参 数 为 指 向 基 对 象 的 指 针 ptr->display(); //" 对 象 指 针 -> 成 员 名 " } 26
int main() { // 主 函 数 Base1 base1; // 声 明 Base1 对 象 Base2 base2; // 声 明 Base2 对 象 Derived derived; // 声 明 Derived 对 象 // 用 Base1 对 象 的 指 针 调 用 fun 函 数 fun(&base1); // 用 Base2 对 象 的 指 针 调 用 fun 函 数 fun(&base2); // 用 Derived 对 象 的 指 针 调 用 fun 函 数 fun(&derived); } return 0; 运 行 结 果 : Base1::display() Base1 ::display() Base1 ::display() 27
单 继 承 与 多 继 承 基 与 派 生 的 对 应 关 系 单 继 承 派 生 只 从 一 个 基 派 生 多 继 承 派 生 从 多 个 基 派 生 多 重 派 生 由 一 个 基 派 生 出 多 个 不 同 的 派 生 多 层 派 生 派 生 又 作 为 基, 继 续 派 生 新 的 28
单 继 承 与 多 继 承 多 继 承 时 派 生 的 声 明 class 派 生 名 : 继 承 方 式 1 基 名 1, 继 承 方 式 2 基 名 2, { 派 生 成 员 声 明 ; 注 意 : 每 一 个 继 承 方 式, 只 用 于 限 制 对 紧 随 其 后 之 基 的 继 承 29
单 继 承 与 多 继 承 多 继 承 举 例 class A { public: void seta(int); void showa() const; private: int a; class B { public: void setb(int); void showb() const; private: int b; class C : public A, private B { public: void setc(int, int, int); void showc() const; private const: int c; 30
void A::setA(int x) { } a=x; void B::setB(int x) { } b=x; void C::setC(int x, int y, int z) { } // 派 生 成 员 直 接 访 问 基 的 // 公 有 成 员 seta(x); setb(y); c = z; // 其 他 函 数 实 现 略 int main() { C obj; obj.seta(5); obj.showa(); obj.setc(6,7,9); obj.showc(); // obj.setb(6); 错 误 // obj.showb(); 错 误 return 0; } 31
派 生 的 构 造 析 构 函 数 继 承 时 的 构 造 函 数 基 的 构 造 函 数 不 被 继 承, 派 生 中 需 要 声 明 自 己 的 构 造 函 数 定 义 构 造 函 数 时, 只 需 要 对 本 中 新 增 成 员 进 行 初 始 化, 对 继 承 来 的 基 成 员 的 初 始 化, 自 动 调 用 基 构 造 函 数 完 成 派 生 的 构 造 函 数 需 要 给 基 的 构 造 函 数 传 递 参 数 32
派 生 的 构 造 析 构 函 数 单 一 继 承 时 的 构 造 函 数 派 生 名 :: 派 生 名 ( 参 数 表 ) : 基 名 ( 基 初 始 化 参 数 表 ), 成 员 对 象 名 1( 成 员 对 象 1 初 始 化 参 数 表 ),, 成 员 对 象 名 m( 成 员 对 象 m 初 始 化 参 数 表 ) { 其 他 初 始 化 操 作 ; 33
派 生 的 构 造 析 构 函 数 单 一 继 承 时 的 构 造 函 数 举 例 #include<iostream> using namecpace std; class B { public: B(); B(int i); ~B(); void print() const; private: int b; 34
B::B() { b=0; cout << "B's default constructor called." << endl; } B::B(int i) { b=i; cout << "B's constructor called." << endl; } B::~B() { cout << "B's destructor called." << endl; } void B::print() const { cout << b << endl; } 35
class C: public B { public: C(); C(int i, int j); ~C(); void print() const; private: int c; 36
C::C() { c = 0; cout << "C's default constructor called." << endl; } C::C(int i,int j): B(i) { c = j; cout << "C's constructor called." << endl; } C::~C() { cout << "C's destructor called." << endl; } void C::print() const { B::print(); cout << c << endl; } int main() { C obj(5, 6); obj.print(); return 0; } 37
派 生 的 构 造 析 构 函 数 多 继 承 时 的 构 造 函 数 派 生 名 :: 派 生 名 ( 参 数 表 ) : 基 名 1( 基 1 初 始 化 参 数 表 ), 基 名 2( 基 2 初 始 化 参 数 表 ),... 基 名 n( 基 n 初 始 化 参 数 表 ), 成 员 对 象 名 1( 成 员 对 象 1 初 始 化 参 数 表 ),, 成 员 对 象 名 m( 成 员 对 象 m 初 始 化 参 数 表 ) { 其 他 初 始 化 操 作 ; 38
派 生 的 构 造 析 构 函 数 派 生 与 基 的 构 造 函 数 当 基 中 声 明 有 缺 省 构 造 函 数 或 未 声 明 构 造 函 数 时, 派 生 构 造 函 数 可 以 不 向 基 构 造 函 数 传 递 参 数, 也 可 以 不 声 明 构 造 派 生 的 对 象 时, 基 的 缺 省 构 造 函 数 将 被 调 用 当 需 要 执 行 基 中 带 形 参 的 构 造 函 数 来 初 始 化 基 数 据 时, 派 生 构 造 函 数 应 在 初 始 化 列 表 中 为 基 构 造 函 数 提 供 参 数 39
派 生 的 构 造 析 构 函 数 构 造 函 数 的 执 行 顺 序 1. 调 用 基 构 造 函 数, 调 用 顺 序 按 照 它 们 被 继 承 时 声 明 的 顺 序 ( 从 左 向 右 ) 2. 对 派 生 新 增 的 成 员 对 象 进 行 初 始 化, 初 始 化 顺 序 按 照 它 们 在 中 声 明 的 顺 序 3. 执 行 派 生 的 构 造 函 数 体 中 的 内 容 41
派 生 的 构 造 析 构 函 数 拷 贝 构 造 函 数 若 建 立 派 生 对 象 时 没 有 编 写 拷 贝 构 造 函 数, 编 译 器 会 生 成 一 个 隐 含 的 拷 贝 构 造 函 数, 该 函 数 先 调 用 基 的 拷 贝 构 造 函 数, 再 为 派 生 新 增 的 成 员 对 象 执 行 拷 贝 若 编 写 派 生 的 拷 贝 构 造 函 数, 一 般 需 要 为 基 相 应 的 拷 贝 构 造 函 数 传 递 参 数 例 如 : C::C(C &c1): B(c1) { } 42
派 生 的 构 造 析 构 函 数 例 7-4 派 生 构 造 函 数 举 例 //7_4.cpp #include <iostream> using namespace std; class Base1 { // 基 Base1, 构 造 函 数 有 参 数 public: Base1(int i) { cout << "Constructing Base1 " << i << endl; } class Base2 { // 基 Base2, 构 造 函 数 有 参 数 public: Base2(int j) { cout << "Constructing Base2 " << j << endl; } class Base3 { // 基 Base3, 构 造 函 数 无 参 数 public: Base3() { cout << "Constructing Base3 *" << endl; } 43
class Derived: public Base2, public Base1, public Base3 { // 派 生 新 Derived, 注 意 基 名 的 顺 序 public: // 派 生 的 公 有 成 员 Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) { } // 注 意 基 名 的 个 数 与 顺 序,// 注 意 成 员 对 象 名 的 个 数 与 顺 序 private: Base1 member1; Base2 member2; Base3 member3; // 派 生 的 私 有 成 员 对 象 int main() { Derived obj(1, 2, 3, 4); return 0; } 运 行 结 果 : constructing Base2 2 constructing Base1 1 constructing Base3 * constructing Base1 3 constructing Base2 4 constructing Base3 * 44
派 生 的 构 造 析 构 函 数 继 承 时 的 析 构 函 数 析 构 函 数 也 不 被 继 承, 派 生 自 行 声 明 声 明 方 法 与 一 般 ( 无 继 承 关 系 时 ) 的 析 构 函 数 相 同 不 需 要 显 式 地 调 用 基 的 析 构 函 数, 系 统 会 自 动 隐 式 调 用 析 构 函 数 的 调 用 次 序 与 构 造 函 数 相 反 45
派 生 的 构 造 析 构 函 数 例 7-5 派 生 析 构 函 数 举 例 //7_5.cpp #include <iostream> using namespace std; class Base1 { // 基 Base1, 构 造 函 数 有 参 数 public: Base1(int i) { cout << "Constructing Base1 " << i << endl; } ~Base1() { cout << "Destructing Base1" << endl; } class Base2 { // 基 Base2, 构 造 函 数 有 参 数 public: Base2(int j) { cout << "Constructing Base2 " << j << endl; } ~Base2() { cout << "Destructing Base2" << endl; } class Base3 { // 基 Base3, 构 造 函 数 无 参 数 public: Base3() { cout << "Constructing Base3 *" << endl; } ~Base3() { cout << "Destructing Base3" << endl; } 46
class Derived: public Base2, public Base1, public Base3 { // 派 生 新 Derived, 注 意 基 名 的 顺 序 public: // 派 生 的 公 有 成 员 Derived(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) { } // 注 意 基 名 的 个 数 与 顺 序, 注 意 成 员 对 象 名 的 个 数 与 顺 序 private: // 派 生 的 私 有 成 员 对 象 Base1 member1; Base2 member2; Base3 member3; int main() { Derived obj(1, 2, 3, 4); return 0; } 47
例 7-5 运 行 结 果 Constructing Base2 2 Constructing Base1 1 Constructing Base3 * Constructing Base1 3 Constructing Base2 4 Constructing Base3 * Destructing Base3 Destructing Base2 Destructing Base1 Destructing Base3 Destructing Base1 Destructing Base2 48
派 生 成 员 的 标 识 与 访 问 派 生 成 员 的 访 问 派 生 成 员 的 访 问 属 性 不 可 访 问 的 成 员 私 有 成 员 保 护 成 员 公 有 成 员 两 个 问 题 唯 一 标 识 问 题 可 见 性 问 题 49
派 生 成 员 的 标 识 与 访 问 同 名 隐 藏 规 则 当 派 生 与 基 中 有 相 同 成 员 时 : 若 未 强 行 指 明, 则 通 过 派 生 对 象 使 用 的 是 派 生 中 的 同 名 成 员 如 要 通 过 派 生 对 象 访 问 基 中 被 隐 藏 的 同 名 成 员, 应 使 用 作 用 域 分 辨 符 和 基 名 来 限 定 50
派 生 成 员 的 标 识 与 访 问 例 7-6 多 继 承 同 名 隐 藏 举 例 (1) //7_6.cpp #include <iostream> using namespace std; class Base1 { // 定 义 基 Base1 public: int var; void fun() { cout << "Member of Base1" << endl; } class Base2 { // 定 义 基 Base2 public: int var; void fun() { cout << "Member of Base2" << endl; } class Derived: public Base1, public Base2 { // 定 义 派 生 Derived public: int var; // 同 名 数 据 成 员 void fun() { cout << "Member of Derived" << endl; } // 同 名 函 数 成 员 51
int main() { Derived d; Derived *p = &d; d.var = 1; // 对 象 名. 成 员 名 标 识 d.fun(); // 访 问 Derived 成 员 d.base1::var = 2; // 作 用 域 分 辨 符 标 识 d.base1::fun(); // 访 问 Base1 基 成 员 p->base2::var = 3; p->base2::fun(); // 作 用 域 分 辨 符 标 识 // 访 问 Base2 基 成 员 } return 0; 52
派 生 成 员 的 标 识 与 访 问 二 义 性 问 题 在 多 继 承 时, 基 与 派 生 之 间, 或 基 之 间 出 现 同 名 成 员 时, 将 出 现 访 问 时 的 二 义 性 ( 不 确 定 性 ) 采 用 虚 函 数 ( 参 见 第 8 章 ) 或 同 名 隐 藏 规 则 来 解 决 当 派 生 从 多 个 基 派 生, 而 这 些 基 又 从 同 一 个 基 派 生, 则 在 访 问 此 共 同 基 中 的 成 员 时, 将 产 生 二 义 性 采 用 虚 基 来 解 决 53
派 生 成 员 的 标 识 与 访 问 二 义 性 问 题 举 例 ( 一 ) class A { public: void f(); class B { public: void f(); void g() class C: public A, piblic B { public: void g(); void h(); 如 果 定 义 :C c1; 则 c1.f() 具 有 二 义 性 而 c1.g() 无 二 义 性 ( 同 名 隐 藏 ) 54
派 生 成 员 的 标 识 与 访 问 二 义 性 的 解 决 方 法 解 决 方 法 一 : 用 基 名 来 限 定 c1.a::f() 或 c1.b::f() 解 决 方 法 二 : 同 名 隐 藏 在 C 中 声 明 一 个 同 名 成 员 函 数 f(),f() 再 根 据 需 要 调 用 A::f() 或 B::f() 55
派 生 成 员 的 标 识 与 访 问 二 义 性 问 题 举 例 ( 二 ) class B { public: int b; } class B1: public B { private: int b1; class B2: public B { private: int b2; class C : public B1,public B2 { public: 有 二 义 性 : int f(); C c; private: c.b int d; c.b::b } 无 二 义 性 : c.b1::b c.b2::b 56
虚 基 虚 基 的 引 入 用 于 有 共 同 基 的 场 合 声 明 以 virtual 修 饰 说 明 基 例 :class B1:virtual public B 作 用 主 要 用 来 解 决 多 继 承 时 可 能 发 生 的 对 同 一 基 继 承 多 次 而 产 生 的 二 义 性 问 题. 为 最 远 派 生 提 供 唯 一 的 基 成 员, 而 不 重 复 产 生 多 次 拷 贝 注 意 : 在 第 一 级 继 承 时 就 要 将 共 同 基 设 计 为 虚 基 57
虚 基 虚 基 举 例 class B { public: int b; class B1: virtual public B { public: int b1; class B2: virtual public B { public: int b2; class C: public B1, public B2 { public: float d; 下 面 的 访 问 是 正 确 的 : C cobj; cobj.b; 58
虚 例 7-8 虚 基 举 例 基 + var0 : int <<virtual>> Base0 + fun0() : void Derived Base1 + var1 : int Base2 + var2 : int Base0::var0:int Base1::var1:int Base2::var2:int var :int Base0::fun0():void fun():void Derived + var : int + fun() : void 59
//7_8.cpp #include <iostream> using namespace std; class Base0 { // 定 义 基 Base0 public: int var0; void fun0() { cout << "Member of Base0" << endl; } class Base1: virtual public Base0 { // 定 义 派 生 Base1 public: // 新 增 外 部 接 口 int var1; class Base2: virtual public Base0 { // 定 义 派 生 Base2 public: // 新 增 外 部 接 口 int var2; 60
class Derived: public Base1, public Base2 { // 定 义 派 生 Derived public: // 新 增 外 部 接 口 int var; void fun() { cout << "Member of Derived" << endl; } int main() { // 程 序 主 函 数 Derived d; // 定 义 Derived 对 象 d d.var0 = 2; // 直 接 访 问 虚 基 的 数 据 成 员 d.fun0(); // 直 接 访 问 虚 基 的 函 数 成 员 return 0; } 61
虚 虚 基 及 其 派 生 构 造 函 数 基 建 立 对 象 时 所 指 定 的 称 为 最 远 派 生 虚 基 的 成 员 是 由 最 远 派 生 的 构 造 函 数 通 过 调 用 虚 基 的 构 造 函 数 进 行 初 始 化 的 在 整 个 继 承 结 构 中, 直 接 或 间 接 继 承 虚 基 的 所 有 派 生, 都 必 须 在 构 造 函 数 的 成 员 初 始 化 表 中 给 出 对 虚 基 的 构 造 函 数 的 调 用 如 果 未 列 出, 则 表 示 调 用 该 虚 基 的 默 认 构 造 函 数 在 建 立 对 象 时, 只 有 最 远 派 生 的 构 造 函 数 调 用 虚 基 的 构 造 函 数, 该 派 生 的 其 他 基 对 虚 基 构 造 函 数 的 调 用 被 忽 略 62
虚 基 有 虚 基 时 的 构 造 函 数 举 例 #include <iostream> using namespace std; class Base0 { // 定 义 基 Base0 public: Base0(int var) : var0(var) { } int var0; void fun0() { cout << "Member of Base0" << endl; } class Base1: virtual public Base0 { // 定 义 派 生 Base1 public: // 新 增 外 部 接 口 Base1(int var) : Base0(var) { } int var1; class Base2: virtual public Base0 { // 定 义 派 生 Base2 public: // 新 增 外 部 接 口 Base2(int var) : Base0(var) { } int var2; 63
class Derived: public Base1, public Base2 { // 定 义 派 生 Derived public: // 新 增 外 部 接 口 Derived(int var) : Base0(var), Base1(var), Base2(var) { } int var; void fun() { cout << "Member of Derived" << endl; } int main() { // 程 序 主 函 数 Derived d(1); // 定 义 Derived 对 象 d d.var0 = 2; // 直 接 访 问 虚 基 的 数 据 成 员 d.fun0(); // 直 接 访 问 虚 基 的 函 数 成 员 return 0; } 64
组 合 与 继 承 深 度 探 索 组 合 与 继 承 : 通 过 已 有 来 构 造 新 的 两 种 基 本 方 式 组 合 :B 中 存 在 一 个 A 型 的 内 嵌 对 象 有 一 个 (has-a) 关 系 : 表 明 每 个 B 型 对 象 有 一 个 A 型 对 象 A 型 对 象 与 B 型 对 象 是 部 分 与 整 体 关 系 B 型 的 接 口 不 会 直 接 作 为 A 型 的 接 口 65
has-a 举 例 深 度 探 索 class Engine { // 发 动 机 public: void work(); // 发 动 机 运 转 class Wheel { // 轮 子 public: void roll(); // 轮 子 转 动 class Automobile { // 汽 车 public: void move(); // 汽 车 移 动 private: Engine engine; // 汽 车 引 擎 Wheel wheels[4];//4 个 车 轮 意 义 一 辆 汽 车 有 一 个 发 动 机 一 辆 汽 车 有 四 个 轮 子 接 口 作 为 整 体 的 汽 车 不 再 具 备 发 动 机 的 运 转 功 能, 和 轮 子 的 转 动 功 能, 但 通 过 将 这 些 功 能 的 整 合, 具 有 了 自 己 的 功 能 移 动 66
公 有 继 承 的 意 义 深 度 探 索 公 有 继 承 :A 是 B 的 公 有 基 是 一 个 (is-a) 关 系 : 表 明 每 个 B 型 对 象 是 一 个 A 型 对 象 A 型 对 象 与 B 型 对 象 是 一 般 与 特 殊 关 系 回 顾 的 兼 容 性 原 则 : 在 需 要 基 对 象 的 任 何 地 方, 都 可 以 使 用 公 有 派 生 的 对 象 来 替 代 B 型 对 象 包 括 A 型 的 全 部 接 口 67
is-a 举 例 深 度 探 索 class Truck: public Automobile{ // 卡 车 public: void load( ); // 装 货 void dump( ); // 卸 货 private: class Pumper: public Automobile { // 消 防 车 public: void water(); // 喷 水 private: 意 义 卡 车 是 汽 车 消 防 车 是 汽 车 接 口 卡 车 和 消 防 车 具 有 汽 车 的 通 用 功 能 ( 移 动 ) 它 们 还 各 自 具 有 自 己 的 功 能 ( 卡 车 : 装 货 卸 货 ; 消 防 车 : 喷 水 ) 68
派 生 对 象 的 内 存 布 局 深 度 探 索 派 生 对 象 的 内 存 布 局 因 编 译 器 而 异 内 存 布 局 应 使 型 兼 容 规 则 便 于 实 现 一 个 基 指 针, 无 论 其 指 向 基 对 象, 还 是 派 生 对 象, 通 过 它 来 访 问 一 个 基 中 定 义 的 数 据 成 员, 都 可 以 用 相 同 的 步 骤 不 同 情 况 下 的 内 存 布 局 单 继 承 : 基 数 据 在 前, 派 生 新 增 数 据 在 后 多 继 承 : 各 基 数 据 按 顺 序 在 前, 派 生 新 增 数 据 在 后 虚 继 承 : 需 要 增 加 指 针, 间 接 访 问 虚 基 数 据 69
单 继 承 情 形 深 度 探 索 class Base { class Derived: public Base { Derived *pd = new Derived(); Base *pb = pd; pb, pd Base 数 据 成 员 Derived 新 增 数 据 成 员 Derived 对 象 Derived 型 指 针 pd 转 换 为 Base 型 指 针 时, 地 址 不 需 要 改 变 70
多 继 承 情 形 深 度 探 索 class Base1 { class Base2 { class Derived: public Base1, public Base2 { Derived *pd = new Derived(); Base1 *pb1 = pd; Base2 *pb2 = pd; pb1, pd pb2 Base1 数 据 成 员 Base2 数 据 成 员 Derived 新 增 数 据 成 员 Derived 对 象 Derived 型 指 针 pd 转 换 为 Base2 型 指 针 时, 原 地 址 需 要 增 加 一 个 偏 移 量 71
虚 拟 继 承 情 形 深 度 探 索 class Base0 { class Base1: virtual public Base0 { class Base2: virtual public Base0 { class Derived: public Base1, public Base2 { Derived *pd = new Derived(); Base1 *pb1 = pd; Base2 *pb2 = pd; Base0 *pb0 = pb1; pb1, pd pb2 pb0 Base0 指 针 Base1 新 增 数 据 成 员 Base0 指 针 Base2 新 增 数 据 成 员 Base0 数 据 成 员 Derived 对 象 通 过 指 针 间 接 访 问 虚 基 的 数 据 成 员 72
基 向 派 生 的 转 换 深 度 探 索 基 向 派 生 的 转 换 基 指 针 可 以 转 换 为 派 生 指 针 基 引 用 可 以 转 换 为 派 生 引 用 需 要 用 static_cast 显 式 转 换 例 : Base *pb = new Derived(); Derived *pd = static_cast<derived *>(pb); Derived d; Base &rb = d; Derived &rd = static_cast<derived &>(rb); 73
型 转 换 时 的 注 意 事 项 (1) 深 度 探 索 基 对 象 一 般 无 法 被 显 式 转 换 为 派 生 对 象 对 象 到 对 象 的 转 换, 需 要 调 用 构 造 函 数 创 建 新 的 对 象 若 派 生 有 构 造 函 数 接 收 基 对 象 ( 或 基 引 用 ) 的 参 数, 才 可 行 Base b; Derived d = static_cast<derived>(b); 执 行 基 向 派 生 的 转 换 时, 一 定 要 确 保 被 转 换 的 指 针 和 引 用 所 指 向 或 引 用 的 对 象 符 合 转 换 的 目 的 型 : 对 于 Derived *pd = static_cast<derived *>(pb); 一 定 要 保 证 pb 所 指 向 的 对 象 具 有 Derived 型, 或 者 是 Derived 型 的 派 生 74
型 转 换 时 的 注 意 事 项 (2) 深 度 探 索 如 果 A 型 是 B 型 的 虚 基,A 型 指 针 无 法 通 过 static_cast 显 式 转 换 为 B 型 的 指 针 可 以 结 合 虚 继 承 情 况 下 的 对 象 内 存 布 局, 思 考 为 什 么 不 允 许 这 种 转 换 void 指 针 参 加 的 转 换, 可 能 导 致 不 可 预 期 的 后 果 : 例 :Derived 公 共 继 承 自 Base1 和 Base2 Derived *pd = new Derived(); void *pv = pd; // 将 Derived 指 针 转 换 为 void 指 针 Base2 *pb = static_cast<base2 *>(pv); 转 换 后 pb 与 pd 有 相 同 的 地 址, 而 正 常 的 转 换 下 应 有 一 个 偏 移 量 结 论 : 有 void 指 针 参 与 的 转 换, 兼 容 性 规 则 不 适 用 更 安 全 更 灵 活 的 基 向 派 生 转 换 方 式 dynamic_cast, 将 在 下 一 章 介 绍 75
小 结 与 复 习 建 议 主 要 内 容 的 继 承 成 员 的 访 问 控 制 单 继 承 与 多 继 承 派 生 的 构 造 和 析 构 函 数 成 员 的 标 识 与 访 问 达 到 的 目 标 理 解 的 继 承 关 系, 学 会 使 用 继 承 关 系 实 现 代 码 的 重 用 76