信息科学技术学院 程序设计实习 郭炜微博 http://weibo.com/guoweiofpku http://blog.sina.com.cn/u/3266490431 刘家瑛微博 http://weibo.com/pkuliujiaying 1
信息科学技术学院 程序设计实习 郭炜刘家瑛 继承和派生 ( 教材 P215) 2
继承和派生的概念 继承 : 在定义一个新的类 B 时, 如果该类与某个已有的类 A 相似 ( 指的是 B 拥有 A 的全部特点 ), 那么就可以把 A 作为一个基类, 而把 B 作为基类的一个派生类 ( 也称子类 ) 3
继承和派生的概念 派生类是通过对基类进行修改和扩充得到的 在派生类中, 可以扩充新的成员变量和成员函数 派生类一经定义后, 可以独立使用, 不依赖于基类 4
继承和派生的概念 派生类拥有基类的全部成员函数和成员变量, 不论是 private protected public 在派生类的各个成员函数中, 不能访问基类中的 private 成员 5
需要继承机制的例子 所有的学生都有的共同属性 : 姓名学号性别成绩所有的学生都有的共同方法 ( 成员函数 ): 是否该留级是否该奖励 6
需要继承机制的例子 而不同的学生, 又有各自不同的属性和方法研究生导师系大学生系中学生竞赛特长加分 7
需要继承机制的例子 如果为每类学生都从头编写一个类, 显然会有不少重复的代码, 浪费 比较好的做法是编写一个 学生 类, 概括了各种学生的共同特点, 然后从 学生 类派生出 大学生 类, 中学生 类, 研究生类 8
需要继承机制的例子 CStudent CHighSchoolStudent CUndergraduateStudent CGraduatedStuden
派生类的写法 class 派生类名 :public 基类名 { 10
class CStudent { private: string sname; int nage; public: bool IsThreeGood() { void SetName( const string & name ) { sname = name; } //... class CUndergraduateStudent: public CStudent { private: int ndepartment; public: bool IsThreeGood() {... // 覆盖 bool CanBaoYan() {... // 派生类的写法是 : 类名 : public 基类名 11
class CGraduatedStudent:public CStudent { private: int ndepartment; char szmentorname[20]; public: int CountSalary() {... 12
派生类对象的内存空间 派生类对象的体积, 等于基类对象的体积, 再加上派生类对象自己的成员变量的体积 在派生类对象中, 包含着基类对象, 而且基类对象的存储位置位于派生类对象新增的成员变量之前 class CBase { int v1,v2; class CDerived:public CBase { int v3; 13
继承实例程序 : 学籍管理 (P228) #include <iostream> #include <string> using namespace std; class CStudent { private: string name; string id; // 学号 char gender; // 性别,'F' 代表女,'M' 代表男 int age; public: void PrintInfo(); void SetInfo( const string & name_,const string & id_, int age_, char gender_ ); string GetName() { return name; } 14
class CUndergraduateStudent:public CStudent {// 本科生类, 继承了 CStudent 类 private: string department; // 学生所属的系的名称 public: void QualifiedForBaoyan() { // 给予保研资格 cout << qualified for baoyan << endl; } void PrintInfo() { CStudent::PrintInfo(); // 调用基类的 PrintInfo cout << Department: << department <<endl; } void SetInfo( const string & name_,const string & id_, int age_,char gender_,const string & department_) { CStudent::SetInfo(name_,id_,age_,gender_); // 调用基类的 SetInfo department = department_; } 15
void CStudent::PrintInfo() { cout << "Name:" << name << endl; cout << "ID:" << id << endl; cout << "Age:" << age << endl; cout << "Gender:" << gender << endl; } void CStudent::SetInfo( const string & name_,const string & id_, int age_,char gender_ ) { name = name_; id = id_; age = age_; gender = gender_; } 16
int main() { } CUndergraduateStudent s2; s2.setinfo( Harry Potter, 118829212,19, M, Computer Science ); cout << s2.getname() << ; s2.qualifiedforbaoyan (); s2.printinfo (); return 0; 17
输出结果 : Harry Potter qualified for baoyan Name:Harry Potter ID:118829212 Age:19 Gender:M Department:Computer Science 18
信息科学技术学院 程序设计实习 郭炜微博 http://weibo.com/guoweiofpku http://blog.sina.com.cn/u/3266490431 刘家瑛微博 http://weibo.com/pkuliujiaying 1
信息科学技术学院 程序设计实习 郭炜刘家瑛 继承关系和复合关系 (P231) 2
类之间的两种关系 继承 : 是 关系 基类 A,B 是基类 A 的派生类 逻辑上要求 : 一个 B 对象也是一个 A 对象 复合 : 有 关系 类 C 中 有 成员变量 k,k 是类 D 的对象, 则 C 和 D 是复合关系 一般逻辑上要求 : D 对象是 C 对象的固有属性或组成部分 3
继承关系的使用 写了一个 CMan 类代表男人 后来又发现需要一个 CWoman 类来代表女人 CWoman 类和 CMan 类有共同之处 CMan 就让 CWoman 类从 CMan 类派生而来, 是否合适? 是不合理的! 因为 一个女人也是一个男人 从逻辑上不成立! CWoman 4
继承关系的使用 好的做法是概括男人和女人共同特点, 写一个 CHuman 类, 代表 人, 然后 CMan 和 CWoman 都从 CHuman 派生 CHuman CWoman CMan 5
复合关系的使用 几何形体程序中, 需要写 点 类, 也需要写 圆 类 class CPoint { double x,y; class CCircle:public CPoint { double r; 6
复合关系的使用 几何形体程序中, 需要写 点 类, 也需要写 圆 类, 两者的关系就是复合关系 ---- 每一个 圆 对象里都包含 ( 有 ) 一个 点 对象, 这个 点 对象就是圆心 class CPoint { double x,y; friend class CCircle; // 便于 Ccirle 类操作其圆心 class CCircle { double r; CPoint center; 7
复合关系的使用 如果要写一个小区养狗管理程序, 需要写一个 业主 类, 还需要写一个 狗 类 而狗是有 主人 的, 主人当然是业主 ( 假定狗只有一个主人, 但一个业主可以有最多 10 条狗 ) 8
复合关系的使用 class CDog; class CMaster { CDog dogs[10]; class CDog { CMaster m; 9
10
11
复合关系的使用 class CDog; class CMaster { CDog dogs[10]; class CDog { CMaster m; 循环定义! 12
复合关系的使用 另一种写法 : 为 狗 类设一个 业主 类的成员对象 ; 为 业主 类设一个 狗 类的对象指针数组 class CDog; class CMaster { CDog * dogs[10]; class CDog { CMaster m; 13
复合关系的使用 凑合的写法 : 为 狗 类设一个 业主 类的对象指针 ; 为 业主 类设一个 狗 类的对象数组 class CMaster; //CMaster 必须提前声明, 不能先 // 写 CMaster 类后写 Cdog 类 class CDog { CMaster * pm; class CMaster { CDog dogs[10]; 14
复合关系的使用 正确的写法 : 为 狗 类设一个 业主 类的对象指针 ; 为 业主 类设一个 狗 类的对象指针数组 class CMaster; //CMaster 必须提前声明, 不能先 // 写 CMaster 类后写 Cdog 类 class CDog { CMaster * pm; class CMaster { CDog * dogs[10]; 15
1 基类 / 派生类同名成员 与 Protected 关键字 郭炜刘家瑛 北京大学程序设计实习
基类和派生类有同名成员的情况 class base { int j; public: int i; void func(); class derived : public base{ public: int i; void access(); void func(); 2
基类和派生类有同名成员的情况 class base { int j; public: int i; void func(); class derived : public base{ public: int i; void access(); void func(); 3
void derived::access() { j = 5; i = 5; //error // 引用的是派生类的 i base::i = 5; // 引用的是基类的 i func(); // 派生类的 base::func(); // 基类的 } derived obj; obj.i = 1; obj.base::i = 1; Obj 占用的存储空间 Base::j Base::i i Note: 一般来说, 基类和派生类不定义同名成员变量 4
访问范围说明符 基类的 private 成员 : 可以被下列函数访问 基类的成员函数 基类的友员函数 基类的 public 成员 : 可以被下列函数访问 基类的成员函数 基类的友员函数 派生类的成员函数 派生类的友员函数 其他的函数 5
访问范围说明符 : protected 基类的 protected 成员 : 可以被下列函数访问 基类的成员函数 基类的友员函数 派生类的成员函数可以访问当前对象的基类的保护成员 6
保护成员 class Father { private: int nprivate; // 私有成员 public: int npublic; // 公有成员 protected: int nprotected; // 保护成员 class Son : public Father { void AccessFather () { npublic = 1; // ok; nprivate = 1; // wrong nprotected = 1; // OK, 访问从基类继承的 protected 成员 Son f; f.nprotected = 1; //wrong, f 不是当前对象 } 7
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; } 8
1 派生类的构造函数 郭炜刘家瑛 北京大学程序设计实习
派生类的构造函数 派生类对象包含基类对象执行派生类构造函数之前, 先执行基类的构造函数派生类交代基类初始化, 具体形式 : 构造函数名 ( 形参表 ): 基类名 ( 基类构造函数实参表 ) { } 2
派生类的构造函数 class Bug { private : int nlegs; 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); 3
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 nwings = wings; } // 正确的 FlyBug 构造函数 : FlyBug::FlyBug (int legs, int color, int wings):bug(legs, color) { nwings = wings; } 表达式中可以出现 : FlyBug 构造函数的参数 4
int main() { FlyBug fb ( 2,3,4); fb.printbug(); fb.ntype = 1; fb.nlegs = 2 ; // error.nlegs is private return 0; } 5
FlyBug fb (2,3,4); 在创建派生类的对象时, 需要调用基类的构造函数 : 初始化派生类对象中从基类继承的成员 在执行一个派生类的构造函数之前, 总是先执行基类的构造函数
调用基类构造函数的两种方式 显式方式 : 派生类的构造函数中 基类的构造函数提供参数 derived::derived(arg_derived-list):base(arg_base-list) 隐式方式 : 派生类的构造函数中, 省略基类构造函数时派生类的构造函数, 自动调用基类的默认构造函数 派生类的析构函数被执行时, 执行完派生类的析构函数后, 自动调用基类的析构函数
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" << endl; } ~Derived() { cout << "Derived destructed" << endl; } int main() { Derived Obj(3); return 0; } 8
输出结果 : Base 3 constructed Derived constructed Derived destructed Base 3 destructed 9
包含成员对象的派生类的构造函数 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 构造函数的 参数, 常量 10
创建派生类的对象时, 执行派生类的构造函数之前 : 调用基类的构造函数 初始化派生类对象中从基类继承的成员 调用成员对象类的构造函数 初始化派生类对象中成员对象 执行完派生类的析构函数后 : 调用成员对象类的析构函数 调用基类的析构函数 析构函数的调用顺序与构造函数的调用顺序相反
信息科学技术学院 程序设计实习 郭炜微博 http://weibo.com/guoweiofpku http://blog.sina.com.cn/u/3266490431 刘家瑛微博 http://weibo.com/pkuliujiaying 1
public 继承的赋值兼容规则 7
public 继承的赋值兼容规则 class base { class derived : public base { base b; derived d; 1) 派生类的对象可以赋值给基类对象 b = d; 2) 派生类对象可以初始化基类引用 base & br = d; 3) 派生类对象的地址可以赋值给基类指针 base * pb = & d; 如果派生方式是 private 或 protected, 则上述三条不可行 8
直接基类和间接基类 9
直接基类与间接基类 类 A 派生类 B, 类 B 派生类 C, 类 C 派生类 D, 类 A 是类 B 的直接基类 类 B 是类 C 的直接基类, 类 A 是类 C 的间接基类 类 C 是类 D 的直接基类, 类 A B 是类 D 的间接基类 A B C D 10
直接基类与间接基类 在声明派生类时, 只需要列出它的直接基类 派生类沿着类的层次自动向上继承它的间接基类 A B C D 派生类的成员包括 派生类自己定义的成员 直接基类中的所有成员 所有间接基类的全部成员 11
#include <iostream> using namespace std; class Base { public: int n; Base(int i):n(i) { cout << "Base " << n << " constructed" << endl; } ~Base() { cout << "Base " << n << " destructed" << endl; } 12
class Derived:public Base { public: Derived(int i):base(i) { cout << "Derived constructed" << endl; } ~Derived() { cout << "Derived destructed" << endl; } 13
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; } 14
输出结果 : Base 4 constructed Derived constructed More Derived constructed More Derived destructed Derived destructed Base 4 destructed 15