程序设计实习 (I): C++ 程序设计 第六讲继承
上节内容回顾 三种运算符重载的实现方式 重载为普通函数 重载为成员函数 重载为友元 常见的运算符重载 流运算符 (>> <<) 自增 / 自减运算符 (++ --) 赋值号 (=) 下标运算符 ([]) 类型转换运算符 2
[ 补充 ] 类型转换运算符重载 类型转换 Operator type(); 必须为成员函数, 不指定返回类型, 形参为空 一般不改变被转换对象, 因此常定义为 const 类型转换自动调用 3
主要内容 基本概念 : 继承 基类 派生类 派生类的成员组成 可见性 派生类的构造 析构 派生类与基类的指针类型转换 4
继承机制 : 从例子开始 姓名 姓名 学号 学号 学生 性别成绩 中学生 性别成绩 是否该奖励? 姓名 竞赛获奖是否该奖励? 姓名学号 学号 性别 大学生 性别成绩 研究生 成绩院系 院系 导师 是否该奖励? 是否该奖励? 5 是否保研? 是否发 RA?
继承 继承与派生的概念 在定义一个新的类 B 时, 如果该类与某个已有的类 A 相似 ( 指的是 B 拥有 A 的全部特点 ) A-- 基类 ( 父类 ),B-- 基类的派生类 ( 子类 ) 派生类是通过对基类进行修改和扩充得到的 在派生类中, 可以扩充新的成员变量和成员函数 派生类一经定义后, 可以独立使用, 不依赖于基类 派生类拥有基类的全部成员, 包括 private, protected, public 成员变量和 private, protected, public 方法 在派生类的各个成员函数中, 不能访问基类中的 private 成员 6
需要继承机制的例子 所有的学生都有一些共同属性和方法 属性 : 姓名, 学号, 性别, 成绩等 方法 : 判断是否该留级, 判断是否该奖励等 但不同的学生, 比如中学生, 大学生, 研究生, 又有各自 不同的属性和方法 e.g. 大学生有系的属性, 而中学生没有, 研究生有导师的属性, 中学生竞赛 特长加分之类的属性 如果为每类学生都编写一个类 不少重复的代码, 浪费 比较好的做法 : 编写一个 学生 类, 概括了各种学生的共同特点 从 学生 类派生出 大学生 类, 中学生 类, 研究生类 7
class CStudent { private: char szname[20]; int nage; int nsex; public: bool IsThreeGood() { int SetSex( int nsex_ ) { nsex = nsex_ ; } void SetName( char * szname_ ) { strcpy( szname, szname_); } //... // 派生类的写法是 : 类名 : public 基类名 class CUndergraduateStudent : public CStudent { private: int ndepartment; public: bool IsThreeGood() {... // 覆盖 bool CanBaoYan() {... // 派生类的写法是 : 类名 : public 基类名 8
class CGraduateStudent : public CStudent { private: int ndepartment; char szsupervisorname[20]; public: int CountSalary() {... 9
继承 : 是 关系 基类 A B 是基类 A 的派生类 复合与继承 逻辑上要求 : 一个 B 对象也是一个 A 对象 复合 : 有 关系 复合, 即一个类的对象拥有作为其成员的其它类的对象其象 d 是类 C 的成员 d 是类 D 的一个对象 复合关系满足 :C 类中 有 成员对象 d 10
继承的使用 写了一个 CMan 类代表男人 又发现需要一个 CWoman 类来代表女人 仅仅因为 CWoman 类和 CMan 类有共同之处, 就让 CWoman 类从 CMan 类派生而来, 是不合理的 因为 一个女人也是一个男人 从逻辑上不成立 好的做法是概括男人和女人共同特点, 写一个 CHuman 类, 代表 人, 然后 CMan 和 CWoman 都从 CHuman 派生 11
复合关系的使用 几何形体程序中, 需要写 点 类, 也需要写 圆 类, 两者的关系就是复合关系 每一个 圆 对象里都包含 ( 有 ) 一个 点 对象, 这个 点 对象就是圆心 12 class CPoint { double x, y; friend class CCircle; // 便于 CCirle 类操作其圆心 class CCircle { double r; CPoint center;
写一个小区养狗管理程序 复合关系的使用 需要写一个 业主 类, 还需要写一个 狗 类 而狗是有 主人 的, 主人当然是业主 ( 假定狗只有一个主人, 但一个业主可以有最多 10 条狗 ) 13
class CDog; class CMaster { CDog dogs[10]; class CDog { CMaster m; 这段程序有问题吗? 复合关系的使用 14
正确写法 : 复合关系的使用 为 狗 类设一个 业主 类的成员对象指针 为 业主 类设一个 狗 类的对象指针数组 class CDog; class CMaster { CDog * dogs[10]; class CDog { CMaster * m; 15
继承 派生类可以定义一个和基类成员同名的成员, 这叫 覆盖 在派生类中访问这类成员时, 缺省的情况是访问派 生类中定义的成员 要在派生类中访问由基类定义的同名成员时, 要使 用作用域符号 :: 16
基类和派生类有同名成员的情况 class base { class derived d: public base{ int j; public: public: int i; int i; void access(); void func(); void func(); void derived::access(){ Obj 占用的存储空间 j = 5; //error Base::j i = 5; // 引用的是派生类的 i base::i =5;// 引用的是基类的 i Base::i func(); // 派生类的 base::func(); // 基类的 i } 一般来说, 基类和派生类 derived obj; 不定义同名成员变量 obj.i = 1; obj.base::i = 1; 17
另一种存取权限说明符 :protected 基类的 private 成员 : 可以被下列函数访问 基类的成员函数 基类的友员函数 基类的 public 成员 : 可以被下列函数访问 基类的成员函数 基类的友员函数 派生类的成员函数 派生类的友员函数 其他的函数 基类的 protected 成员 : 可以被下列函数访问 18 基类的成员函数 基类的友员函数 派生类的成员函数可以访问当前对象的基类的保护成员
保护成员 class Father { private: int nprivate; // 私有成员 public: int npublic; // 公有成员 protected: int nprotected; // 保护成员 class Son : public Father{ void AccessFather () { npublic = 1; // ok; nprivate Pi = 1; // wrong nprotected = 1; // OK, 访问从基类继承的 protected 成员 Fth Father f; f.nprotected = 1; //wrong, f 不是函数所作用的当前对象 } 19
int main() { Father f; Son s; f.npublic = 1; // Ok s.npublic = 1; // Ok f.nprotected = 1; // error f.nprivate = 1; // error s.nprotected = 1; //error s.nprivate = 1; // error return 0; } 20
派生类的构造函数 class Bug { private : int nlegs; g; int ncolor; public: int ntype; Bug (int legs, int color); void PrintBug(){ class FlyBug: public Bug // FlyBug 是 Bug 的派生类 { int nwings; public: FlyBug(int legs, int color, int wings); 21
Bug::Bug(int legs, int color){ nlegs = legs; ncolor = color; } // 错误的 FlyBug 构造函数 FlyBug::FlyBug (int legs, int color, int wings){ nlegs = legs; // 不能访问 ncolor = color; // 不能访问表达式中可以出现 : ntype = 1; // ok FlyBug 构造函数 nwings = wings; 的参数 常量 } // 正确的 FlyBug 构造函数 FlyBug::FlyBug (int legs, int color, int wings):bug( legs, color){ nwings = wings; } 22
int main() { } FlyBug fb (2,3,4); fb.printbug(); fb.ntype = 1; fb.nlegs = 2 ; // error. nlegs is private return 0; 23
FlyBug fb (2,3,4); 在创建派生类的对象时, 需要调用基类的构造函数 : 初始化派生类对象中从基类继承的成员 在执行一个派生类的构造函数之前, 总是先执行基类的构造函数 调用基类构造函数的两种方式 显式方式 : 在派生类的构造函数中, 为基类的构造函数提供参数 derived::derived(arg_derived-list):base(arg_base-list) 隐式方式 : 在派生类的构造函数中, 省略基类构造函数时, 派生类的构造函数则自动调用基类的默认构造函数 派生类的析构函数被执行时, 执行完派生类的析构函数后, 自动调用基类的析构函数 24
class Base { public: int n; Base(int i):n(i) { cout << "Base " << n << " constructed" << endl; } ~Base() { cout << "Base " << n << " destructed" << endl;} class Derived:public Base { public: Derived(int i):base(i) { cout << "Derived constructed" t << endl; } ~Derived() { cout << "Derived destructed" << endl; } int main() { Derived Obj(3); return 0; } 25
输出结果 : Base 3 constructed Derived constructed Derived destructed Base 3 destructed 26
包含成员对象的派生类的构造函数 class Skill{ public: Skill(int n) { } class FlyBug: public Bug { int nwings; Skill sk1, sk2; public: FlyBug( int legs, int color, int wings); FlyBug::FlyBug( int legs, int color, int wings): Bug(legs, color), sk1(5), sk2(color) { nwings = wings; } 表达式中可以出现 :FlyBug 构造函数的参数 常量 27
在创建派生类的对象时, 在执行一个派生类的构造函数之前 : 调用基类的构造函数 : 初始化派生类对象中从基类继承的成员 调用成员对象类的构造函数 : 初始化派生类对象中成员对象 派生类的析构函数被执行时, 执行完派生类的析构函数后 : 调用成员对象类的析构函数 ; 调用基类的析构函数 析构函数的调用顺序与构造函数的调用顺序相反 28
public 继承的赋值兼容规则 class base { class derived : public base { base b; derived d; base derived 派生类的对象可以赋值给基类对象 b = d; 派生类对象可以初始化基类引用 base & br = d; 派生类对象的地址可以赋值给基类指针 base * pb = & d; 29
基类与派生类的指针强制转换 派生类对象的指针可以直接赋值给基类指针 Base * ptrbase = &objderived; ptrbase tb 指向的是一个 Derived 类的对象 *ptrbase 可以看作一个 Base 类的对象, 访问它的 public 成员 直接通过 ptrbase, tb 不能够访问 objderived 由 Derived 类扩 展的成员 通过强制指针类型转换, 可以把 ptrbase 转换成 Derived 类的指针 Base * ptrbase = &objderived; Derived *ptrderived = (Derived * ) ptrbase; 程序员要保证 ptrbase 指向的是一个 Derived 类的对象, 否则 很容易会出错 30
#include <iostream> using namespace std; class Base { protected: int n; public: Base(int i):n(i){ cout << "Base " << n << " constructed" << endl; } ~Base() { cout << "Base " << n << " destructed" << endl; } void Print() { cout << "Base:n=" << n << endl;} 31
class Derived:public Base { public: int v; Derived(int i):base(i),v(2 * i) { cout << "Derived constructed" << endl; } ~Derived() { cout << "Derived destructed" << endl; } void Func() { } ; void Print() { } cout << "Derived:v=" << v << endl; cout << "Derived:n=" << n << endl; 32
int main() { Base objbase(5); Derived objderived(3); Base * pbase = & objderived ; //pbase->func(); //err; Base 类没有 Func() 成员函数 //pbase->v = 5; //err; Base 类没有 v 成员变量 pbase->print(); //Derived * pderived = & objbase; //error Derived * pderived = (Derived *)(& objbase); pderived->print(); // 慎用, 可能出现不可预期的错误 pderived->v v = 128; // 往别人的空间里写入数据, 会有问题 objderived.print(); return 0; } 33
Derived * pderived = (Derived *)(& objbase); pderived n v }objbase n v } objderived 34
输出结果 : Base 5 constructed Base 3 constructed Derived constructed Base:n=3 Derived:v=0 Derived:n=5 Derived:v=6 Derived:n=3 Derived destructed Base 3 destructed Base 5 destructed 35
直接基类与间接基类 类 A 派生类 B, 类 B 可再派生类 C, 类 C 派生类 D, 类 A 是类 B 的直接基类 类 B 是类 C 的直接基类, 类 A 是类 C 的间接基类 类 C 是类 D 的直接基类, 类 A B 是类 D 的间接基类 在声明派生类时, 派生类的首部只需要列出它的直接基类 派生类的首部不要列出它的间接基类 派生类沿着类的层次自动向上继承它的间接基类 派生类的成员包括 派生类自己定义的成员 直接基类中定义的成员 间接基类的全部成员 36
#include <iostream> using namespace std; class Base { public: int n; Base(int ti) i):n(i) { cout << "Base " << n << " constructed" << endl; } ~Base() { cout << "Base " << n << " destructed" << endl; } 37
class Derived:public Base { public: Derived(int i):base(i) { cout << "Derived constructed" << endl; } ~Derived() { cout << "Derived destructed" << endl; } 38
class MoreDerived:public Derived { public: MoreDerived():Derived(4) { cout << "More Derived constructed" <<endl; } ~MoreDerived() { cout << "More Derived destructed" << endl; } int main() { MoreDerived Obj; return 0; } 39
输出结果 : Base 4 constructed Derived constructed More Derived constructed More Derived destructed Derived destructed Base 4 destructed 40
总结 基本概念 : 继承 基类 派生类 合理派生 派生类的成员组成 可见性 private/protected 成员的继承性 在派生类的各个成员函数中, 不能访问基类中的 private 成员 派生类的构造 析构 构造顺序 : 基类 对象成员 派生 析构反之 派生类与基类的指针类型转换 f( 派生类 ) y( 基类对象 ):f: 对象 / 对象地址 ;y: 对象 / 引用 / 指针 基类指针 ( 强制指针类型转换 ) 派生类指针 41
42