C++大学基础教程 第9章 继承与派生 北京邮电大学电信工程学院 计算机技术中心 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -2- 第9章 继承与派生 9.1 继承的概念 9.2 继承方式 9.3 派生类构造函数的定义 9.4 多继承 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -3- 第9章 继承与派生 软件重用 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -4- 第9章 继承与派生 类具有封装性、继承性和多态性 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -5- 继承的概念 自行车 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -6- 继承的概念 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -7- 继承的概念 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -8- 继承的概念 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -9- 继承的概念 Bicycle Mountain Bikes Racing Bikes Tandem Bikes is-a relationships 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -10- 第9章 继承与派生 主要介绍: ? 继承和派生的概念 ? 继承方式; ? 派生类的构造函数与析构函数; ? 多继承中的二义性 ? 虚基类。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -11- 9.1 继承的概念 类的继承 是在现有类的基础之上,创建新类的 机制 。 称现有的类为 基类 , 新建立的类为 派生 类 。 ? 新类继承了基类的属性和行为 ? 新类是基类的特殊情况。 不必从 “草稿 ”开始创建特殊的程序对象 继承是处理 “特殊情况 ”的面向对象编程机制 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -12- 派生类的定义格式 class 派生类名:继承方式 基类名 { public: //派生类公有成员 … private: //派生类私有成员 … } 派生类只有 一个直接基 类为 单继承 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -13- 例 : 定义基类 shape class shape { private: int m_x,m_y; //位置 char m_color; //颜色 public: void setposition(int x, int y); void setcolor(char color); int getx(); int gety(); char getcolor(); }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -14- 定义派生类 (等边三角形类 ) class Triangle: public Shape { public: Triangle(int x, int y, char color='R', float slen = 1); float GetSideLength() const; void SetTriangle(int x, int y, char color, float slen); void Draw(); private: float m_SideLength; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -15- 派生新类: circle 圆形 rectangle 矩形 triangle 三角形 基类称为 父类 派生类称为 子类 shape circle rectangle triangle 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -16- 派生类的定义格式 class 派生类名:继承方式 基类名1, … 继承方式 基类名n { public: //派生类公有成员 … private: //派生类私有成员 … } 有多个基类 派生类有多 个基类为 多继承 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -17- 例:已有基类base1,base2,base3,定义派生类deriver class deriver:public base1,public base2, private base3 { private: int m_derdata; public: void derfunction(); }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -18- 注意 每一个 “继承方式 ”,只用于限制对紧随其后之 基类的继承。 类 的继承方式是派生类对基类成员的继承方 式。 类的继承方式指定了类外对象对于派生类从基 类继承来的成员的访问权限。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -19- 直接基类和间接基类 class base {…… }; class deriver1:public base { …… }; class deriver2:public deriver1 {…… } ? 父类被称为子 类的 直接基类 ? 父类的父类或 更高层次的父 类被称为这个 子类的 间接基 类 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -20- 派生与继承的实例 公司人员管理系统 : 小型公司人员分为: 经理、兼职技术人员、销售经理和 兼职推销员. 要求: 存储所有人员的姓名、编号、级别、当月薪水,计算月薪 总额并显示全部信息。 人员编号在生成人员信息时同时生成,每输入一个人员信 息编号顺序加1。 程序能够对不同人员按不同方法提升级别,月薪的计算 方法是: ? 经理拿固定月薪; ? 兼职技术人员按工作小时数领取月薪; ? 兼职推销员的报酬按该推销员当月销售额提成; ? 销售经理既拿固定月薪也领取销售提成。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -21- 派生与继承的实例 分析: ·描述全体职员的共性(基类) ·描述每一类特殊人员(派生类) 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -22- class employee { protected: char *name; //姓名 int individualEmpNo; //个人编号 int grade; //级别 float accumPay; //月薪总额 static int employeeNo; //本公司职员编号目前最大值 public: employee(); //构造函数 ~employee(); //析构函数 void pay(); //计算月薪函数 void promote(int);//升级函数 void displayStatus(); //显示人员信息 }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -23- class technician: public employee //兼职技术人员类 { private: float hourlyRate; //每小时酬金 int workHours; //当月工作时数 public: technician(); //构造函数 void pay(); //计算月薪函数 void displayStatus(); //显示人员信息 }; 新增加的成员 同名覆盖,改造基类成员 派生类的成员: 1. 从基类继承的成员; 2. 改造基类成员; 3. 添加派生类新成员. 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -24- 9.2 继承方式 三种继承方式 public、protected、private 不同继承方式的影响主要体现在: ? 派生类 成员 对基类成员的访问控制。 ? 派生类 对象 对基类成员的访问控制。 定义派生类时要声明继承方式 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -25- 9.2.1. 派生类的定义 派生类的定义形式: class 派生类名:继承方式 基类1, 继承方式 基类2, … ,继承方式 基类n { 派生类成员声明; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -26- 例如: 设已有基类base1和base2,定义派生类 deriver. class deriver: public base1,private base2 { private: int newmember; public: void newfun(); }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -27- 单继承情况,派生类的定义 class 派生类名:继承方式 基类名 { 派生类成员声明 }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -28- 例9.1 图形类及其派生类的声明 class Shape { public: Shape(int x=0, int y=0, char c = 'R'); int GetX() const; void SetX( int x); int GetY() const; void SetY( int x); char GetColor() const; void SetColor(char c); protected: char m_color; int m_x; int m_y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -29- class Circle : public Shape { public: Circle(int x, int y, float r=1, char color='R'); float GetRadius () const; void SetCircle(int x, int y, float r, char color); void Draw(); private: float m_Radius; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -30- class Triangle: public Shape { public: Triangle(int x, int y, char color='R', float slen = 1); float GetSideLength() const; void SetTriangle(int x, int y, char color, float slen); void Draw(); private: float m_SideLength; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -31- class Rectangle: public Shape { public: Rectangle(int x, int y, char color, int length=10, int width=10); int GetWidth() const; int GetHeight() const; void Draw(); void SetRectangle (int x, int y, char color, int length, int width); private: int m_Width; int m_Length; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -32- ? 派生类的成员包括:(1)继承基类的成员,(2)派生 类定义时声明的成员。 ? 派生类自己增加的成员,完成两个需求:(1)修改基类 成员,(2)描述新的特征或方法。 从基类 继承的 成员 m_color; m_x; m_y; GetX();SetX(); GetY();SetY(); GetColor(); SetColor(); m_Radius; GetRadius () SetCircle(); Draw(); m_color; m_x; m_y; GetX();SetX(); GetY();SetY(); GetColor(); SetColor(); m_SideLength; GetSideLength(); SetTriangle(); Draw(); m_color; m_x; m_y; GetX();SetX(); GetY();SetY(); GetColor(); SetColor(); m_Width; m_Length; GetWidth(); GetHeight(); Draw(); SetRectangle(); 派生类 增加的 成员 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -33- 同名覆盖 派生类修改基类的成员,是在派生类中声明了 一个与基类成员同名的新成员。在派生类作用 域内或者在类外通过派生类的对象直接使用这 个成员名,只能访问到派生类中声明的同名新 成员,这个新成员覆盖了从基类继承的同名成 员,这种情况称为 同名覆盖 。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -34- class Shape { public: ┇ void Draw(){}; protected: ┇ }; class Triangle: public Shape { public: Triangle(int x, int y, char color='R', float slen = 1); float GetSideLength() const; void SetTriangle(int x, int y, char color, float slen); void Draw(); private: float m_SideLength; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -35- 例9.2 同名覆盖示例 #include<iostream> using namespace std; class base { public: void function(){cout<<"function of class base"<<endl;} }; class deriver: public base { public: void function(){cout<<"function of class deriver"<<endl;} }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -36- void main() { deriver derobj; derobj.function(); } 输出结果: function of class deriver 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -37- 例9.3 派生类成员函数对基类同名函 数的改进。 //number.h class Number { protected: int m_number; public: int GetNumber(){ return m_number; } void SetNumber(int n){ m_number=n;} void Prime(); }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -38- //number.cpp #include<iostream> #include "number.h" using namespace std; void Number::Prime() { int i; for(i=2; i<m_number; i++) //找 m_number的因数 { if(m_number %i==0) break;} if(m_number ==i) //判断 m_number是否被小于 m_number的数整除 cout <<m_number <<" is prime"<<endl; else cout <<m_number <<" isn't prime"<<endl; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -39- //DerNumber.h class DerNumber: public Number { public: void Prime(); }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -40- //DerNumber.cpp #include<iostream> #include<cmath> #include "number.h" #include "DerNumber.h" using namespace std; void DerNumber::Prime() { double sqrtm=sqrt(m_number); //用到 math.h int i; for(i=2; i<=sqrtm; i++) { if(m_number %i==0) break;} if(sqrtm<i) cout <<m_number <<" is prime.\n"; else cout <<m_number <<" isn't prime.\n"; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -41- //使用模块 #include<iostream> #include<ctime> #include "number.h" #include "DerNumber.h" using namespace std; void main() { Number aNum; DerNumber aDerNum; clock_t start, finish; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -42- double elapsed_time1,elapsed_time2; int i; unsigned int max(100000); time( &start ); for(i=10000;i<=max;i++) { aNum.SetNumber(i); cout<<aNum.GetNumber()<<" "<<endl; aNum.Prime(); } time( &finish ); 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -43- elapsed_time1 = difftime( finish, start ); time( &start ); for(i=10000;i<=max;i++) { aDerNum.SetNumber(i); cout<<aDerNum.GetNumber()<<" "<<endl; aDerNum.Prime(); } time( &finish ); elapsed_time2 = difftime( finish, start ); 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -44- cout<<"Delay for using Number class: "<<elapsed_time1 <<" seconds"<<endl; cout<<"Delay for using DerNumber class: " <<elapsed_time2<<" seconds"<<endl; } 输出结果(部分): Delay for using Number class: 157 seconds Delay for using DerNumber class: 151 seconds 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -45- 9.2.2. 继承的访问控制 派生类继承了基类中除构造函数和析 构函数之外的所有成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -46- 9.2.2. 继承的访问控制 三种继承方式 : ? 公有继承 (public) ? 私有继承 (private) ? 保护继承 (protected) 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -47- 9.2.2. 继承的访问控制 不同的继承方式使得派生类从基类继承的成员 具 有 不同的访问控制权限,以实现数据的安全性和共享 性控制。 不同继承方式决定的不同访问控制权限体现在: ? 派生类的成员函数 对其继承的基类成员的访问控制; ? 其它模块通过派生类对象 对其继承的基类成员的访问控 制。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -48- 1. 公有继承 公有继承的派生类定义形式: class 派生类名: public 基类名 { 派生类新成员定义; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -49- 公有继承 public是定义公有继承方式的关键字 公有继承方式定义的派生类,继承了基类中 除构造 函数和析构函数外 的其余成员:公有成员、保护成 员和私有成员 被继承的基类成员在派生类中仍将保持其原来的访 问属性。 派生类的成员函数 可以访问基类的公有成员和保护 成员,不能访问基类的私有成员; 派生类以外的其它函数 可以通过派生类的对象,访 问从基类继承的公有成员, 但不能访问从基类继承 的保护成员和私有成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -50- class Point //基类 Point类的定义 { public: //公有函数成员 void InitP(float xx=0, float yy=0) {X=xx;Y=yy;} void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;} float GetX() {return X;} float GetY() {return Y;} private: //私有数据成员 float X,Y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -51- class Rectangle: public Point //派生类声明部分 { public: //新增公有函数成员 void InitR(float x, float y, float w, float h) { InitP(x,y); //访问基类公有成员函数 W=w;H=h;} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据成员 float W,H; }; z派生类中的 成员函 数 可以直接访问基类 中的public和 protected成员 ,但不 能访问基类的private 成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -52- main() { Rectangle rect; cout<<rect.X; //? cout<<rect.GetX();//?? } z使用派生类的 对象 只能访问基类的 public成员 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -53- class Rectangle: public Point //派生类声明部分 { public: //新增公有函数成员 void InitR(float x, float y, float w, float h) {X=x; Y=y; //?访问基类私有成员 W=w;H=h;} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据成员 float W,H; }; z派生类中的 成员函 数 可以直接访问基类 中的public和 protected成员, 但不 能访问基类的private 成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -54- class Point //基类 Point类的定义 { public: void InitP(float xx=0, float yy=0) {X=xx;Y=yy;} void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;} float GetX() {return X;} float GetY() {return Y;} protected: float X,Y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -55- class Rectangle: public Point //派生类声明部分 { public: //新增公有函数成员 void InitR(float x, float y, float w, float h) {X=x; Y=y; //??访问基类的保护成员 W=w;H=h;} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据成员 float W,H; }; z派生类中的 成员函 数 可以直接访问基类 中的public和 protected成员, 但不 能访问基类的private 成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -56- main() { Rectangle rect; cout<<rect.X; //? cout<<rect.GetX();//?? } z使用派生类的 对象 只能访问基类的 public成员 依然错误 ! 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -57- 2.私有继承 私有继承的派生类定义形式: class 派生类名: private 基类名 { 派生类新成员定义; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -58- 私有继承 private是定义私有继承方式的关键字 以私有继承方式定义的派生类,继承了基类中可以 继承的成员:公有成员、保护成员和私有成员,这些 成员在派生类中的访问属性都是私有的。 派生类的成员函数 可以访问基类的公有成员和保护 成员,不能访问基类的私有成员。 派生类以外的其它函数 则不能通过派生类的对象访 问从基类继承的任何成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -59- class Point //基类声明 { public: void InitP(float xx=0, float yy=0) {X=xx;Y=yy;} void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;} float GetX() {return X;} float GetY() {return Y;} private: float X,Y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -60- class Rectangle: private Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) {InitP(x,y);W=w;H=h;} //派生类访问基类公有成员 void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX() {return Point::GetX();} float GetY() {return Point::GetY();} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据 float W,H; }; 派生类中的 成员函数 可以直接访问基类中 的public和protected 成员, 但不能访问基 类的private成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -61- class Rectangle: private Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) { X=x; Y=y;//? W=w;H=h; } void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX() {return Point::GetX();} float GetY() {return Point::GetY();} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据 float W,H; }; 派生类中的 成员函数 可以直接访问基类中的 public和protected成 员, 但不能访问基类的 private成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -62- class Point //基类声明 { public: void InitP(float xx=0, float yy=0) {X=xx;Y=yy;} void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;} float GetX() {return X;} float GetY() {return Y;} protected: float X,Y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -63- class Rectangle: private Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) { X=x; Y=y;//?? W=w;H=h; } void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX() {return Point::GetX();} float GetY() {return Point::GetY();} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据 float W,H; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -64- main() { Rectangle rect; cout<<rect.X; //? cout<<rect.GetX();//? } z使用派生类的 对象 不能访问基类中的任 何成员。 错误 ! 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -65- 3. 保护继承 保护继承的派生类定义形式: class 派生类名: protected 基类名 { 派生类新成员定义; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -66- 保护继承 protected是定义保护继承方式的关键字 以保护继承方式定义的派生类,继承了基类中可 以继承的成员:公有成员、保护成员和私有成 员。其中基类的公有成员和保护成员在派生类中 访问控制属性变成保护类型的,基类的私有成员 保持原来属性。 派生类的成员函数 可以访问基类的公有成员和保 护成员,不能访问基类的私有成员。 派生类以外的其它函数 则不能通过派生类的对象 访问从基类继承的任何成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -67- class Point //基类声明 { public: void InitP(float xx=0, float yy=0) {X=xx;Y=yy;} void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;} float GetX() {return X;} float GetY() {return Y;} private: float X,Y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -68- class Rectangle: protected Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) {InitP(x,y);W=w;H=h;} //派生类访问基类公有成员 void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX() {return Point::GetX();} float GetY() {return Point::GetY();} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据 float W,H; }; 派生类中的 成员函数 可以直接访问基类中 的public和protected 成员, 但不能访问基 类的private成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -69- class Rectangle: protected Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) { X=x; Y=y;//? W=w;H=h; } void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX() {return Point::GetX();} float GetY() {return Point::GetY();} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据 float W,H; }; 派生类中的 成员函数 可以直接访问基类中的 public和protected成 员, 但不能访问基类的 private成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -70- class Point //基类声明 { public: void InitP(float xx=0, float yy=0) {X=xx;Y=yy;} void Move(float xOff, float yOff) {X+=xOff;Y+=yOff;} float GetX() {return X;} float GetY() {return Y;} protected: float X,Y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -71- class Rectangle: protected Point //派生类声明 {public: //新增外部接口 void InitR(float x, float y, float w, float h) { X=x; Y=y;//?? W=w;H=h; } void Move(float xOff, float yOff) {Point::Move(xOff,yOff);} float GetX() {return Point::GetX();} float GetY() {return Point::GetY();} float GetH() {return H;} float GetW() {return W;} private: //新增私有数据 float W,H; }; 派生类中的 成员函数 可以直接访问基类中的 public和protected成 员, 但不能访问基类的 private成员。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -72- main() { Rectangle rect; cout<<rect.X; //? cout<<rect.GetX();//? } z使用派生类的 对象 不能访问基类中的任 何成员。 错误 ! 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -73- private private private private private protected protected protected private protected public public private protected public 存取方式 继承类型 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -74- 9.3 派生类构造函数的定义 派生类继承了基类中除构造函数和析 构函 数之外 的所有成员。 基 类 的 构造函数和析构函数不能被派生类所继 承,派生类需要自己定义的构造函数和析构函数。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -75- 9.3.2 派生类的构造函数 派生类构造函数的一般形式 : 派生 类名: :派生 类名( 基类 所需 的形 参,本类成员所需的形参) : 基类1(基类参数表1), … ,基类n(基类参数表n), 对象成员1(对象参数表1), … ,对象成员m(对象参数表m) { 本类基本类型数据成员初始化; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -76- 派生类的构造函数 单继承时的构造函数 派生类名::派生类名(基类所需的形参,本类成员所需的形参) : 基类名(参数) { 本类成员初始化赋值语句; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -77- class Point //基类 Point类的定义 { public: //公有函数成员 Point(int xx=0, int yy=0) {X=xx;Y=yy;} void InitP(int xx=0, int yy=0) {X=xx;Y=yy;} void Move(int xOff, int yOff) {X+=xOff;Y+=yOff;} int GetX() {return X;} int GetY() {return Y;} private: //私有数据成员 int X,Y; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -78- class Rectangle: public Point { public: Rectangle(int x, int y, int w, int h); void InitR(int x, int y, int w, int h) {InitP(x,y);W=w;H=h;} //派生类访问基类公有成员 void Move(int xOff, int yOff) {Point::Move(xOff,yOff);} int GetX() {return Point::GetX();} int GetY() {return Point::GetY();} int GetH() {return H;} int GetW() {return W;} private: //新增私有数据 int W,H; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -79- Rectangle:: Rectangle(int x, int y, int w, int h): Point(x,y) { W=w; H=h; }; 本类成员所需的形参 基类所需的形参 基类构 造函数 本类成员初始 化赋值语句; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -80- 例9.5 定义一个派生类deriver,它是基类 base1和base2的多继承。Deriver类还有两 个私有的内嵌对象成员。定义派生类 deriver的构造函数。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -81- class base1 { private: int m_base_data; public: base1(int data){m_base_data=data;} //… }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -82- class base2 { private: int m_base_data; public: base2(int data){m_base_data=data;} //… }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -83- class Abc { private: float m_abc_data; public: Abc(float data){ m_abc_data=data; } //… }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -84- class deriver:public base1, public base2 { private: Abc m_member1, m_member2; double m_deriver_data; public: deriver(int bd1, int bd2, float id1, float id2, double dd); }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -85- deriver:: deriver(int bd1, int bd2, float id1, float id2, double dd): base1(bd1),base2(bd2), m_member1(id1), m_member2(id2) { m_deriver_data=dd; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -86- 使用基类无参构造函数 deriver:: deriver(float id1, float id2, double dd): m_member1(id1), m_member2(id2) { m_deriver_data=dd; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -87- 使用对象数据成员的无参构造函数 deriver:: deriver(int bd1, int bd2, double dd): base1(bd1),base2(bd2) { m_deriver_data=dd; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -88- 派生类的构造函数 如果基类和对象数据成员的构造函数都无参 数,派生类构造函数形参表中将只包含用于初 始化它自己的基本类型数据成员的参数。 如果这个派生类恰好没有基本类型的数据成 员,则其构造函数的形参表为空,可以不定义 构造函数,而使用系统提供的默认构造函数。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -89- 使用系统提供的默认构造函数 #include<iostream> using namespace std; class base { private: int m_data; public: void SetData(int data){m_data=data;} int GetData(){return m_data;} }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -90- class deriver:public base { private: int m_member; public: void SetMember(int m){ m_member=m;} int GetMember(){return m_member;} }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -91- void main() { int n(10); deriver obj; obj.SetMember(n); cout<<obj.GetMember()<<endl; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -92- 派生类的构造函数 基类的构造函数不被继承,需要在派生类中自行 定义 。 定义构造函数时,以合适的初值为参数,初始化 本类中新增成员。 利用 成员初始化表 隐含调用 基类 和 新增对象数据 成员 的构造函数,初始化它们各自的数据成员。 构造函数的调用次序 ? 系统会使用派生类构造函数的形参表的参数调 用基类和内嵌对象成员的构造函数。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -93- 派生类的构造函数 系统在 建立派生类 对象时,首先调用基类的构造 函数,再调用派生类的构造函数。 系统在 建立组合类 对象时,先调用内嵌子对象的 构造函数,在调用组合类的构造函数。 如果 一个派生类又是组合类 ,则系统先调用其基 类的构造函数,再调用其内嵌子对象的构造函 数,再后系统才调用派生类的构造函数。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -94- 派生类的构造函数 如果是 多继承 ,系统 调用基类构造函数的顺序 是 按照定义派生类时这些基类被继承的顺序进行 的,与这些基类构造函数在派生类构造函数成员 初始化列表的先后次序无关。 如果派生类有 多个对象数据成员 ,则系统 调用这 些对象数据成员的构造函数的顺序 是依据派生类 定义这些成员的顺序进行的,与派生类成员初始 化列表中对象数据成员构造函数排列先后次序无 关。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -95- deriver:: deriver(int bd1, int bd2, float id1, float id2, double dd): base2(bd2),base1(bd1), m_member2(id2) , m_member1(id1) { m_deriver_data=dd; } 构造函数的 调用次序 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -96- 9.3.2 派生类的析构函数 派生类不能继承基类的析构函数,需要自己 定义析构函数,以便在派生类对象消亡之前 进行必要的清理工作。 派生类的析构函数只负责清理它新定义的非 对象数据成员,对象数据成员由对象成员所 属类的析构函数负责析构。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -97- #include<iostream> using namespace std; class base { private: int m_base_data; public: base(int data){m_base_data=data;} ~base(){cout<<"base object deconstruction"<<endl;} //… }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -98- class Abc { private: float m_abc_data; public: Abc(float data){ m_abc_data=data; } ~Abc(){cout<<"Object member deconstruction"<<endl;} //… }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -99- class deriver:public base { private: double m_deriver_data; Abc m_member1; int *m_ptr; public: deriver(int bd, float id, double dd); ~deriver(); void function(); }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -100- deriver:: deriver(int bd, float id, double dd):base(bd), m_member1(id) { m_deriver_data=dd; m_ptr=new int[256]; if(m_ptr==NULL) cout<<"memory error in deriver obj"<<endl; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -101- deriver::~deriver() { if(m_ptr!=NULL) delete [] m_ptr; cout<<"Deriver obj deconstruction."<<endl; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -102- void deriver::function() { cout<<"Maybe you want to do something with m_ptr in this function. "<<endl; cout<<"Do as you like."<<endl; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -103- void main() { int n(1); float x(2.0f); double d(3.0); deriver obj(n,x,d); obj.function(); cout<<"The end of main function"<<endl; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -104- 输出结果: Maybe you want to do something with m_ptr in this function. Do as you like. The end of main function Deriver obj deconstruction. Object member deconstruction base object deconstruction 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -105- 派生类的析构函数 如果没有特殊指针数据成员需要清理,可以使 用由系统提供的默认析构函数。 当派生类对象消亡时,系统调用析构函数的顺 序与建立派生类对象时调用构造函数的顺序正 好相反,即先调用派生类的析构函数,再调用 其对象数据成员的析构函数,最后调用基类的 析构函数。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -106- 9.4 多继承 9.4.1 多继承与二义性 多继承类结构中,派生类可能有多个直接基类 或间接基类,充分体现了软件重用的优点,但 也可能会引起成员访问的二义性或不确定性问 题。 例9.7 多继承时的二义性 #include<iostream> using namespace std; class base {private: int m_data; public: base(int m) { m_data=m; cout<<"base construction"<<endl; } ~base(){cout<<"base deconstruction"<<endl;} void setdata(int data){m_data=data;} int getdata(int data){ return m_data;} }; class Fderiver1: public base { private: int m_value; public: Fderiver1(int value,int data):base(data) { m_value=value; cout<<"Fderiver1 construction"<<endl; } ~Fderiver1(){cout<<"Fderiver1 deconstruction"<<endl;} void setvalue(int value){ m_value=value;} int getvalue(){ return m_value; } void fun(){}; }; class Fderiver2: public base { private: int m_number; public: Fderiver2(int number,int data):base(data) { m_number=number; cout<<"Fderiver2 construction"<<endl; } ~Fderiver2(){cout<<"Fderiver2 deconstruction"<<endl;} void setnumber(int number){ m_number=number;} int getnumber(){ return m_number; } void fun(){}; }; class Sderiver: public Fderiver1, public Fderiver2 {private: int m_attrib; public: Sderiver(int attrib,int number,int value,int data): Fderiver1(value,data),Fderiver2(number,data) { m_attrib=attrib; cout<<"Sderiver construction"<<endl; } ~Sderiver(){cout<<"Sderiver deconstruction"<<endl;} void setattrib(int attrib){m_attrib=attrib;} int getattrib(){return m_attrib;} void newfun1(){}; int newfun2(){}; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -111- void main() { Sderiver object(3,4,5,6); object.setdata(7); } 产生二义性 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -112- m_data m_value m_data m_number m_attrib 从 Fderiver1 继承 从 Fderiver2 继承 Sdreriver 自己声明的部分 图9 -4 多继承的派生类对象内存使用 base Fderiver1 Fderiver2 Sderiver 图9- 3 多 继承类结构 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -113- 9.4.2 虚基类 为解决二义性问题,将共同基类设置为虚基类,创 建派生类对象时,虚基类的构造函数只会调用一 次,虚基类的成员在第三层派生类对象中就只有一 份拷贝,不会再引起二义性问题。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -114- 虚基类 语法形式: class 派生类名: virtual 继承方式 基类名 { //…… } 在多继承情况下,虚基 类关键字的作用范围和继承 方式关键字相同,只对紧随其后的基类起作用。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -115- 虚基类 在多继承类结构中,说明虚基类之后,虚基类的成 员在派生类中将不会因继承关系对虚基类的多次继 承而形成多份拷贝,只为最远的派生类提供唯一的 基类成员,消除了多继承结构中的二义性问题。 需要注意的是在第一级继承时就要将共同基类设计 为虚基类。 class Fderiver1: virtual public base { private: int m_value; public: Fderiver1(int data,int value):base(data) { m_value=value; cout<<"Fderiver1 construction"<<endl; } ~Fderiver1(){cout<<"Fderiver1 deconstruction"<<endl;} void setvalue(int value){ m_value=value;} int getvalue(){ return m_value; } void fun(){}; }; class Fderiver2: virtual public base { private: int m_number; public: Fderiver2(int data, int number):base(data) { m_number=number; cout<<"Fderiver2 construction"<<endl; } ~Fderiver2(){cout<<"Fderiver2 deconstruction"<<endl;} void setnumber(int number){ m_number=number;} int getnumber(){ return m_number; } void fun(){}; }; class Sderiver: public Fderiver1, public Fderiver2 { private: int m_attrib; public: Sderiver(int data, int value, int number, int attrib): base(data),Fderiver1(data,value), Fderiver2(data,number) { m_attrib=attrib; cout<<"Sderiver construction"<<endl;} ~Sderiver(){cout<<"Sderiver deconstruction"<<endl;} void setattrib(int attrib){m_attrib=attrib;} int getattrib(){return m_attrib;} void newfun1(){}; int newfun2(){}; }; 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -119- 多继承派生类构造函数的定义 在包含虚基类的继承结构中,系统在建立派生类的 对象时,调用构造函数的顺序是: (1)首先按照虚拟基类被继承的顺序,调用它们的 构造函数; (2)其次按照非虚拟基类被继承的顺序,调用它 们 的构造函数; (3)再次按照对象数据成员声明的顺序,调用它 们 的构造函数; (4)最后调用派生类自己的构造函数。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -120- 多继承派生类构造函数的定义 如果基类使用带参的构造函数,则派生类需要在其 构造函数的形式参数表中提供相应的参数给基类,对 其对象数据成员亦如此。 如果基类和对象数据成员都使用默认构造函数,派 生类也没有需要初始化的基本类型数据成员,也可以 使用默认构造函数。 析构派生类的对象时,析构函数的调用顺序正好与 构造函数的调用顺序相反。 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -121- #include<iostream> using namespace std; void main() { int d (1), v (2), n (3), a (4); Sderiver(d, v, n, a); //add some function,… cout<<"the end of main"<<endl; } 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -122- 输出结果: base construction Fderiver1 construction Fderiver2 construction Sderiver construction Sderiver deconstruction Fderiver2 deconstruction Fderiver1 deconstruction base deconstruction the end of main 2005-4-27 北京邮电大学电信工程学院计算机技术中心 -123- 作业