Presentation is loading. Please wait.

Presentation is loading. Please wait.

C++语言程序设计(第4版) 第七章 继承与派生 数学与统计科学学院 胡凤珠.

Similar presentations


Presentation on theme: "C++语言程序设计(第4版) 第七章 继承与派生 数学与统计科学学院 胡凤珠."— Presentation transcript:

1 C++语言程序设计(第4版) 第七章 继承与派生 数学与统计科学学院 胡凤珠

2 目录 7.1 类的继承与派生 7.2 访问控制 7.3 类型兼容规则 7.4 派生类的构造、析构函数 7.5 派生类成员的标识与访问

3 7.1类的继承与派生

4 4 7.1 类的继承与派生

5 7.1 类的继承与派生 动画角色扮演游戏开发 巴巴爸爸和巴巴妈妈 巴巴祖  巴巴拉拉 巴巴利波 巴巴伯 巴巴贝尔  巴巴布莱特 巴巴布莱伯

6 7.1 类的继承与派生 #include “string.h” class Barbapapa {
6 7.1 类的继承与派生 #include “string.h” class Barbapapa { string name; // private私有数据成员 string color; float x; float y; public://公有函数成员 void move( float x2,float y2) { x += x2; y += y2; } Barbapapa(string name1, string color1, float x1=0, float y1=0) {name = name1; color = color1; x = x1; y = y1; } }; 属性: 姓名:巴巴爸爸 颜色:粉红色 横坐标x :0 纵坐标y :0 行为(动作): 移动位置

7 7.1 类的继承派生 #include “test.h” #include “string.h” class Barbulaite{
string name; // private私有数据成员 string color; float x; float y; public://公有函数成员 void move( float x2,float y2) { x += x2; y += y2; } Barbulaite(string name1, string color1, float x1=0, float y1=0) {name = name1; color = color1; x = x1; y = y1; } void test(); }; 属性: 姓名:巴巴布莱特 颜色:蓝色 横坐标x :0 纵坐标y :0 行为(动作): 移动位置 实验

8 7.1 类的继承与派生----引例 void Barbulaite::test( ); {int n;
8 7.1 类的继承与派生----引例 void Barbulaite::test( ); {int n; cout<<“请输入1-5整数实验编号”<<endl; cin>>n; switch(n) {case 1:test1( );break; case 2:test2( );break; case 3:test3( );break; case 4:test4( );break; case 5:test5( );break; } 属性: 姓名:巴巴布莱特 颜色:蓝色 横坐标x :0 纵坐标y :0 行为(动作): 移动位置 实验

9 可不可以利用父亲的类型定义孩子的类型 7.1 类的继承与派生 class Barbapapa class Barbulaite 属性:
姓名:巴巴布莱特 颜色:蓝色 横坐标x :0 纵坐标y :0 行为(动作): 移动位置 实验 属性: 姓名:巴巴爸爸 颜色:粉红色 横坐标x :0 纵坐标y :0 行为(动作): 移动位置 可否实现代码重用

10 利用父亲的类型定义孩子的类型 7.1 类的继承与派生——1、 派生与继承的定义 保持已有类的特性而构造新类的过程称为继承。
在已有类的基础上新增自己的特性而产生新类的过程称为 派生。 被继承的已有类称为基类(或父类)。 派生出的新类称为派生类。 利用父亲的类型定义孩子的类型 class Barbapapa class Barbulaite

11 派生类的声明格式 7.1 类的继承与派生 —— 2、派生类的定义 class 派生类名:继承方式 基类名 { 成员声明; } 三种继承方式:
公有继承 私有继承 保护继承 例如: class D: public B1, private B2 { int m;//新增数据成员 public: D ( );//构造函数 ~D ( );//析构函数 void print(){cout<<“子类新增函数跟踪”;}//新增成员 函数 }; 派生类从基类继承(除了构造和析构函数)成员之外的所有成员,还可以新增及改造成员。

12 不采用继承 7.1 类的继承与派生 {name = name1; color = color1; x = x1; y = y1; }
13 7.1 类的继承与派生 不采用继承 class Barbapapa { string name; string color; float x; float y; public: void move( float x2,float y2) { x += x2; y += y2; } Barbapapa(string name1, string color1,float x1=0,float y1=0) {name = name1; color = color1; x = x1; y = y1; } }; #inlude “test.h” class Barbulaite{ string name; string color; float x; float y; public: void move( float x2,float y2) { x += x2; y += y2; } Barbulaite(string name1, string color1,float x1=0,float y1=0) {name = name1; color = color1; x = x1; y = y1; } void test( ); };//实现部分略

13 采用继承 7.1 类的继承与派生 Barbulaite ( );//构造函数 class Barbapapa {
14 7.1 类的继承与派生 采用继承 class Barbapapa { string name; string color; float x; float y; public: Barbapapa(string name1, string color1, float x1=0, float y1=0) {name = name1; color = color1; x = x1; y = y1; } void move( float x2,float y2) { x += x2; y += y2; } }; #inlude “test.h” class Barbulaite: public Barbapapa {//新增数据成员无,可以省略 public: Barbulaite ( );//构造函数 ~ Barbulaite( );//析构函数 void test( ); //添加新的成员函数 void move( float x2,float y2) //改造的成员 { x += x2*2; y += y2*2; } };//实现部分略

14 7.1 类的继承与派生—— 3、继承的种类 一个派生类,可以同时有多个基类,这种情况称为多继承。 class Barbapapa { ……
}; class Barbamama { class Barbalala:public Barbapapa ,public Barbamama { …… };

15 7.1 类的继承与派生—— 3、继承的种类 一个派生类只有一个直接基类的情况,称为单继承。 直接参与派生出某类的基类称为直接基类。
16 7.1 类的继承与派生—— 3、继承的种类 一个派生类只有一个直接基类的情况,称为单继承。 直接参与派生出某类的基类称为直接基类。 class Barbapapa { …… }; class Barbulaite: public Barbapapa { …… };

16 7.1 类的继承与派生—— 3、继承的种类 基类的基类甚至更高层的基类称为间接基类。 class Barbapapa { …… };
class Barbamama: public Barbapapa { 巴巴爸爸和巴巴妈妈 class Barbalala:public Barbamama { …… }; 巴巴贝尔

17 7.1 类的继承与派生—— 4、派生类生成过程 派生新类经历了三个步骤: 吸收基类成员
吸收基类成员之后,派生类实际上就包含了它的全部 基类中除构造和析构函数之外的所有成员。 改造基类成员 如果派生类声明了一个和某基类成员同名的新成员( 如果是成员函数,则参数表也要相同,参数不同的情 况属于重载),派生的新成员就覆盖了外层同名成员 。 添加新的成员 派生类新成员的加入是继承与派生机制的核心,是保 证派生类在功能上有所发展。

18 class 派生类名:继承方式 基类名 { 成员声明; }; class Barbalala:public Barbapapa ,public Barbamama {…… }; 派生类可以吸收基类中除构造和析构函数之外的所有成员。 声明了一个和基类成员同名的新成员(如果是成员函数,则参数表也要相同),派生的新成员就覆盖了外层同名成员。 派生类中添加新成员,也可以是其他成员的重载函数。

19 问题: 1、三种继承方式对派生类有何影响? 2、派生类如何改造基类成员和添加新成员?

20 7.2 访问控制

21 7.2.1 公有继承(public) 7.2.2 私有继承(private) 7.2.3 保护继承(protected)
不同继承方式的影响主要体现在: 派生类成员函数(类体内)对基类成员的访问权限 通过派生类对象(类体外)对基类成员的访问权限

22 ? 以7.1 类的继承与派生 继承举例为例。 Barbulaite ( );//构造函数 class Barbapapa {
23 以7.1 类的继承与派生 继承举例为例。 class Barbapapa { string name; string color; float x; float y; public: Barbapapa(string name1, string color1, float x1=0, float y1=0) {name = name1; color = color1; x = x1; y = y1; } void move( float x2,float y2) { x += x2; y += y2; } }; #inlude “test.h” class Barbulaite: public Barbapapa {//新增数据成员无,可以省略 public: Barbulaite ( );//构造函数 ~ Barbulaite( );//析构函数 void test( ); //添加新的成员函数 void move( float x2,float y2) //改造的成员 { x += x2*2; y += y2*2; } };//实现部分略 void main( ) {Barbulaite t; cout<<t.x; t.test( ); } ?

23 公有继承(public) 基类 派生类 public protected private 不可直接访问
24 公有继承(public) 基类 派生类 public protected private 不可直接访问 派生类中的成员函数(类体内)可以直接访问基类中的public和 protected成员,但不能直接访问基类的private成员。 通过派生类的对象(类体外)只能访问基类的public成员。

24 以点(point)类为基类,重新定义矩形类。点为直角坐标点,矩形水平放置,由左下方的顶点和长宽定义.
float w, h;

25 26 //Point.h #ifndef _POINT_H #define _POINT_H 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; }; #endif //_POINT_H //Rectangle.h #ifndef _RECTANGLE_H #define _RECTANGLE_H #include "Point.h" 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; }; #endif //_RECTANGLE_H

26 对象rect x y w h 2 3 20 10 x y w h 2 3 x y #include <iostream>
27 #include <iostream> #include <cmath> using namespace std; int main( ) { Rectangle rect; //执行默认的构造函数 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;} 对象rect x y w h 2 随机 随机 3 随机 20 随机 10 2 3 20 10 x y w h 2 3 x y 调用: void initRectangle(float x, float y, float w, float h) {initPoint(x, y); //调用基类公有成员函数 this->w = w; this->h = h; } 调用 void initPoint(float x = 0, float y = 0) { this->x = x; this->y = y;}

27 对象rect x y w h 3 2 offX offY #include <iostream>
#include <cmath> using namespace std; int main( ) { Rectangle rect; //执行默认的构造函数 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;} 对象rect x y w h 5 2 3 5 20 10 3 2 offX offY 调用: void move(float offX, float offY) { x += offX; y += offY; }

28 float getX( ) const { return x; } float getY( ) const { return y; }
#include <iostream> #include <cmath> using namespace std; int main( ) { Rectangle rect; //执行默认的构造函数 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;} 对象rect x y w h 5 5 20 10 float getX( ) const { return x; } float getY( ) const { return y; } float getH( ) const { return h; } float getW( ) const { return w; }

29 没有调用派生类对象继承基类的公有成员函数,编译报错。

30 私有继承(private) 基类 派生类 public private protected 不可直接访问
32 私有继承(private) 基类 派生类 public private protected 不可直接访问 派生类中的成员函数(类体内)可以直接访问基类中的public和 protected成员,但不能直接访问基类的private成员。 通过派生类的对象(类体外)不能直接访问基类中的任何成员。

31 public: initPoint、 move、 getX、 getY
33 基类 public: initPoint、 move、 getX、 getY protected:无 private: x, y //Point.h #ifndef _POINT_H #define _POINT_H 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; }; #endif //_POINT_H

32 派生类拥有多少个函数成员? 派生类拥有10个函数成员!
34 //Rectangle.h #ifndef _RECTANGLE_H #define _RECTANGLE_H #include "Point.h" 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; }; #endif //_RECTANGLE_H 从基类处继承的三个成员函数: void move(float offX, float offY) { x += offX; y += offY; } float getX( ) const { return x; } float getY( ) const { return y; } 派生类拥有多少个函数成员? 改造了从基类处继承的成员函数 派生类拥有10个函数成员!

33 #include <iostream> #include <cmath> using namespace std;
35 #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; }

34 保护继承(protected) 基类 派生类 public protected private 不可直接访问
37 保护继承(protected) 基类 派生类 public protected private 不可直接访问 派生类中的成员函数可以直接访问基类中的public和protected成员,但 不能直接访问基类的private成员。 通过派生类的对象不能直接访问基类中的任何成员。

35 protected 成员的特点与作用 对建立其所在类对象的模块来说,它与 private 成员的性 质相同。
38 protected 成员的特点与作用 对建立其所在类对象的模块来说,它与 private 成员的性 质相同。 对于其派生类来说,它与 public 成员的性质相同。 既实现了数据隐藏,又方便继承,实现代码重用。

36 class A { protected: int x; }; int main() { A a;
39 class A { protected: int x; }; int main() { A a; a.x = 5; //类体外访问保护成员是错误的 }

37 改动 class A { protected: int x; }; class B: public A{ public:
40 改动 class A { protected: int x; }; class B: public A{ public: void function(); void B:function() { x = 5; //类B公有继承A,则A的保护成员在类体内可以访问,在类体外不可访问 }

38 ① 派生类B中成员函数f2()能否访问基类A中的成员f1(),i1,j1?
41 补充例1: 分析下列代码,回答提出的问题。 程序内容如下: #include <iostream.h> class A { public: void f1(); protected: int j1; private: int i1; }; class B: public A {public: void f2(); int j2; int i2; }; 回答下述问题: ① 派生类B中成员函数f2()能否访问基类A中的成员f1(),i1,j1? ② 派生类B中对象b能否访问基类A中的成员f1(),i1和j1?

39 7.2.1 公有继承(public) 7.2.2 私有继承(private) 7.2.3 保护继承(protected)
1、派生类有几个成员,从基类处继承几个?新增几个? 2、派生类的成员的访问属性? 在派生类体内、类体外可否访问? 3、派生类改造和增加成员的方法。 4、函数重载也是有作用域的,基类中的成员函数可以重载,派生类中的成员函数可以重载。

40 #include <iostream> using namespace std; // 基类 class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派生类 class Rectangle: public Shape int getArea() { return (width * height); } }; int main(void) { Rectangle Rect; Rect.setWidth(5); Rect.setHeight(7); // 输出对象的面积 cout << "Total area: " << Rect.getArea() << endl; return 0; }

41 在派生类中可以定义一个与基类成员函数同名的函数,这样派生类中的函数就会覆盖掉基类的成员函数。
在派生类中可以定义一个与基类成员函数同名的函数,这样派生类中的函数就会覆盖掉基类的成员函数。  谭浩强的C++程序设计这本书第十一章,351页最下面有这么一段话: 可在派生类中声明一个与基类成员同名的成员函数,则派生类中的新函数会覆盖基类的同名成员,但应注意:如果是成员函数,不仅应是函数名相同,而且函数的参数表(参数的个数和类型)也应相同,如果不相同,就会成为函数重载而不是覆盖了、用这样的方法可以用新成员取代基类的成员。

42 C++语言程序设计(第4版) 目录 7.1 类的继承与派生 7.2 访问控制 7.3 类型兼容规则 7.4 派生类的构造、析构函数 7.5 派生类成员的标识与访问

43 派生类的类体内可以直接访问从基类处继承的哪些成员?
复习 46 公有继承 私有继承 保护继承 基类 派生类 public private protected 不可直接访问 派生类的类体内可以直接访问从基类处继承的哪些成员?

44 派生类的类体外可以直接访问从基类处继承的哪些成员?
复习 47 公有继承 私有继承 保护继承 基类 派生类 public private protected 不可直接访问 派生类的类体外可以直接访问从基类处继承的哪些成员? 公有派生类继承了基类所有对外的功能,凡是基类能解决的问题,公有派生类都可以解决。

45 C++语言程序设计(第4版) 目录 7.1 类的继承与派生 7.2 访问控制 7.3 类型兼容规则 7.4 派生类的构造、析构函数 7.5 派生类成员的标识与访问

46 公有派生类继承了基类所有对外的功能,凡是基类能解决的问题,公有派生类都可以解决。
49 公有派生类继承了基类所有对外的功能,凡是基类能解决的问题,公有派生类都可以解决。 一个公有派生类的对象在使用上可以被当作基类 的对象,反之则禁止。

47 一个公有派生类的对象在使用上可以被当作基类的对象
50 生活中举例 距离100km 一个公有派生类的对象在使用上可以被当作基类的对象 基类汽车 派生类汽车

48 一、兼容规则 派生类的对象可以隐含转换为基类对象。 派生类的对象可以初始化基类的引用。 派生类的指针可以隐含转换为基类的指针。
51 一、兼容规则 派生类的对象可以隐含转换为基类对象。 派生类的对象可以初始化基类的引用。 派生类的指针可以隐含转换为基类的指针。 但是通过基类对象名、指针只能使用从基类继承 的成员。

49 二、兼容规则举例

50 对象b1 对象a1 a 1 a 1 b 2 用派生类对象b1给基类对象a1赋值 a 1 基类对象m
53 对象b1 对象a1 #include <iostream> using namespace std; class A //基类A { int a; public: void print1() {a=1; cout << "执行基类A中的print1函数" <<"a="<<a<<endl;} }; class B: public A//公有派生类B { int b; void print2( ) {print1(); b=2; cout << "执行派生类B中的print2函数" <<"b="<<b<<endl; cout << "****************************************\n"; } void show(A m) {cout << "执行派生类B中的show函数" <<endl; m.print1();} }; void main( ) {B b1; //声明B类对象 b1.print2(); A a1=b1; a1.print1(); b1.show(b1); } a 1 a 1 b 2 用派生类对象b1给基类对象a1赋值 a 1 基类对象m

51 对象b1 a 1 引用a1 b 2 用派生类对象b1给基类对象引用a1初始化 基类对象引用m
54 对象b1 #include <iostream> using namespace std; class A //基类A { int a; public: void print1() {a=1; cout << "执行基类A中的print1函数" <<"a="<<a<<endl;} }; class B: public A//公有派生类B { int b; void print2( ) {print1(); b=2; cout << "执行派生类B中的print2函数" <<"b="<<b<<endl; cout << "****************************************\n"; } void show(A &m) {cout << "执行派生类B中的show函数" <<endl; m.print1();} }; void main( ) {B b1; //声明B类对象 b1.print2(); A &a1=b1; a1.print1(); b1.show(b1); } a 1 引用a1 b 2 用派生类对象b1给基类对象引用a1初始化 基类对象引用m

52 期望传的实参对象类型不同,调用不同的 建议不要重新定义继承而来的非虚函数
55 期望传的实参对象类型不同,调用不同的 display #include <iostream> using namespace std; class B1 //基类B1 {public:void display() const{cout << "执行B1的display函数" << endl;} }; class B2: public B1 //公有派生类B2 {public:void display() const {cout << "执行B2的display函数" << endl;} class D: public B2 { //公有派生类D public:void display() const {cout << "执行D的display函数" << endl;} void fun(B1 *p) { //参数为指向基类对象的指针 p->display(); } int main() { B1 b1; B2 b2; D d1; fun(&b1); //用B1类对象的指针调用fun函数 fun(&b2); //用B2类对象的指针调用fun函数 fun(&d1); //用D类对象的指针调用fun函数 return 0; } 建议不要重新定义继承而来的非虚函数

53 56 基类对象不能隐含的转换为派生类对象

54 57 基类对象不能为派生类对象的引用初始化

55 基类对象指针不能隐含的转换为派生类对象指针
58 基类对象指针不能隐含的转换为派生类对象指针

56 一、兼容规则 二、兼容规则举例 派生类的对象可以隐含转换为基类对象。 派生类的对象可以初始化基类的引用。
59 一、兼容规则 派生类的对象可以隐含转换为基类对象。 派生类的对象可以初始化基类的引用。 派生类的指针可以隐含转换为基类的指针。 但是通过基类对象名、指针只能使用从基类继承 的成员。 二、兼容规则举例 建议不要重新定义继承而来的非虚函数。

57 7.4 派生类的构造、析构 函数

58 单继承 派生类只从一个基类派生。 多继承 派生类从多个基类派生。 多重派生 由一个基类派生出多个不同的派生类。 多层派生
61 单继承 派生类只从一个基类派生。 多继承 派生类从多个基类派生。 多重派生 由一个基类派生出多个不同的派生类。 多层派生 派生类又作为基类,继续派生新的类。

59 基类的构造函数不被继承,派生类中需要声明自 己的构造函数。 定义构造函数时: 需要对本类中新增成员进行初始化;
62 基类的构造函数不被继承,派生类中需要声明自 己的构造函数。 定义构造函数时: 需要对本类中新增成员进行初始化; 对继承来的基类成员的初始化,自动调用基类构 造函数完成。 派生类的构造函数需要给基类的构造函数传递参 数。

60 派生类名::派生类构造函数名(基类所需的形参, 本类成员所需的形参):基类名(参数表) { 本类成员初始化赋值语句; };
63 派生类名::派生类构造函数名(基类所需的形参, 本类成员所需的形参):基类名(参数表) { 本类成员初始化赋值语句; };

61 单一继承时的构造函数举例 64 #include <iostream> using namespace std;
class B { public: B(); B(int i); ~B(); void print() const; private:int b; }; 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;} class C: public B { public:C(); C(int i, int j); ~C(); private:int c; }; 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; } void main() { C obj(5, 6); obj.print();

62 65 派生类名::派生类构造函数名(参数表):基类名1(基类 1初始化参数表), 基类名2(基类2初始化参数表), ...基 类名n(基类n初始化参数表) { 本类成员初始化赋值语句; };

63 当需要执行基类中带形参的构造函数来初始化基类数据 时,派生类构造函数应在初始化列表中为基类构造函数提 供参数。
66 当基类中声明有缺省构造函数或未声明构造函数时,派生 类构造函数可以不向基类构造函数传递参数,也可以不声 明,构造派生类的对象时,基类的缺省构造函数将被调 用。 当需要执行基类中带形参的构造函数来初始化基类数据 时,派生类构造函数应在初始化列表中为基类构造函数提 供参数。

64 派生类名::派生类名(形参表):基类名1(参数), 基类名2(参 数), ...基类名n(参数),新增成员对象的初始化 {
67 派生类名::派生类名(形参表):基类名1(参数), 基类名2(参 数), ...基类名n(参数),新增成员对象的初始化 { 本类成员初始化赋值语句; };

65 Point::Point(Point &p) { //拷贝构造函数的实现 x = p.x; y = p.y;
cout << "Calling the copy constructor of Point" << endl; } //类的组合 class Line {//Line类的定义 public://外部接口 Line(Point xp1, Point xp2); Line(Line &l); double getLen() { return len; } private://私有数据成员 Point p1, p2; //Point类的对象p1,p2 double len;}; //组合类的构造函数 Line::Line(Point xp1, Point xp2) : p1(xp1), p2(xp2) { cout << "Calling constructor of Line" << endl; double x = static_cast<double>(p1.getX() - p2.getX()); double y = static_cast<double>(p1.getY() - p2.getY()); len = sqrt(x * x + y * y); } Line::Line (Line &l): p1(l.p1), p2(l.p2) {//组合类的拷贝构造函数 cout << "Calling the copy constructor of Line" << endl; len = l.len; int main() {//主函数 Point myp1(1, 1), myp2(4, 5); //建立Point类的对象 Line line(myp1, myp2); //建立Line类的对象 Line line2(line); //利用拷贝构造函数建立一个新对象 cout << "The length of the line is: "; cout << line.getLen() << endl; cout << "The length of the line2 is: "; cout << line2.getLen() << endl; return 0;} 68

66 调用基类构造函数,调用顺序按照它们被继承时 声明的顺序(从左向右)。 对成员对象进行初始化,初始化顺序按照它们在 类中声明的顺序。
69 调用基类构造函数,调用顺序按照它们被继承时 声明的顺序(从左向右)。 对成员对象进行初始化,初始化顺序按照它们在 类中声明的顺序。 执行派生类的构造函数体中的内容。

67 70 #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; }}; class D: public Base2, public Base1, public Base3 { //派生新类D,注意基类名的顺序 public: //派生类的公有成员 D(int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) { }//注意基类名的个数与顺序,注意成员对象名的个数与顺序 private: //派生类的私有成员对象 Base1 member1; Base2 member2; Base3 member3;}; void main() {D obj(1, 2, 3, 4); }

68 若编写派生类的拷贝构造函数,则需要为基类相 应的拷贝构造函数传递参数。 例如: C::C(const C &c1): B(c1) {…}
71 若建立派生类对象时没有编写拷贝构造函数,编 译器会生成一个隐含的拷贝构造函数,该函数先 调用基类的拷贝构造函数,再为派生类新增的成 员对象执行拷贝。 若编写派生类的拷贝构造函数,则需要为基类相 应的拷贝构造函数传递参数。 例如: C::C(const C &c1): B(c1) {…}

69 声明方法与一般(无继承关系时)类的析构函数 相同。 不需要显式地调用基类的析构函数,系统会自动 隐式调用。
72 析构函数也不被继承,派生类自行声明。 声明方法与一般(无继承关系时)类的析构函数 相同。 不需要显式地调用基类的析构函数,系统会自动 隐式调用。 析构函数的调用次序与构造函数相反。

70 73 73 #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; }}; class D: public Base2, public Base1, public Base3 { //派生新类D,注意基类名的顺序 public:D (int a, int b, int c, int d): Base1(a), member2(d), member1(c), Base2(b) { } //注意基类名的个数与顺序,注意成员对象名的个数与顺序 private: Base1 member1; Base2 member2; Base3 member3;}; void main() { D obj(1, 2, 3, 4);}

71 同名隐藏规则 当派生类与基类中有相同成员时: 若未强行指名,则通过派生类对象使用的是派生类 中的同名成员。
74 7.5 派生类成员的标识与访问 —— 作用域分辨 同名隐藏规则 当派生类与基类中有相同成员时: 若未强行指名,则通过派生类对象使用的是派生类 中的同名成员。 如要通过派生类对象访问基类中被隐藏的同名成 员,应使用基类名限定。

72 7.5 派生类成员的标识与访问 —— 7.5.1 作用域分辨 75 #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; }//同名函数成员 }; void 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基类成员 }

73 7.5 派生类成员的标识与访问 —— 作用域分辨 76 二义性问题 多继承 派生类从多个基类派生。 在多继承时,基类与派生类之间,或基类之间出 现同名成员时,将出现访问时的二义性(不确定 性)——采用虚函数或同名隐藏规则来解决。 当派生类从多个基类派生,而这些基类又从同一 个基类派生,则在访问此共同基类中的成员时, 将产生二义性——采用虚基类来解决。

74 二义性问题举例(1) 7.5 派生类成员的标识与访问 —— 7.5.1 作用域分辨 class A { public: void f();
77 7.5 派生类成员的标识与访问 —— 作用域分辨 二义性问题举例(1) class A { public: void f(); }; class B { void f(); void g() class C: public A, piblic B { public: void g(); void h(); }; 如果定义:C c1; 则 c1.f() 具有二义性 而 c1.g() 无二义性(同名隐藏)

75 二义性的解决方法 解决方法一:用类名来限定 c1.A::f() 或 c1.B::f()
78 7.5 派生类成员的标识与访问 —— 作用域分辨 二义性的解决方法 class A { public: void f(); }; class B { void f(); void g() class C: public A, piblic B { public: void g(); void h(); }; 如果定义:C c1; 则 c1.f() 具有二义性 而 c1.g() 无二义性(同名隐藏) 解决方法一:用类名来限定 c1.A::f() 或 c1.B::f()

76 二义性的解决方法 解决方法二:同名隐藏 在C 中声明一个同名成员函数f(),f()再根据需要 调用 A::f() 或 B::f()
79 7.5 派生类成员的标识与访问 —— 作用域分辨 二义性的解决方法 class A { public: void f(); }; class B { void f(); void g() class C: public A, piblic B { public: void g(); void h(); }; 如果定义:C c1; 则 c1.f() 具有二义性 而 c1.g() 无二义性(同名隐藏) 解决方法二:同名隐藏 在C 中声明一个同名成员函数f(),f()再根据需要 调用 A::f() 或 B::f()

77 7.5 派生类成员的标识与访问 —— 7.5.1 作用域分辨 例7-7 多继承同名隐藏举例(2) 80 //7_7.cpp
例7-7 多继承同名隐藏举例(2) //7_7.cpp #include <iostream> using namespace std; class Base0 { //定义基类Base0 public: int var0; void fun0() { cout << "Member of Base0" << endl; } }; class Base1: public Base0 { //定义派生类Base1 public: //新增外部接口 int var1; class Base2: public Base0 { //定义派生类Base2 int var2; class Derived: public Base1, public Base2 {//定义派生类Derived public: //新增外部接口 int var; void fun() { cout << "Member of Derived" << endl; } }; int main() { //程序主函数 Derived d; //定义 Derived类对象d d.Base1::var0 = 2; //使用直接基类 d.Base1::fun0(); d.Base2::var0 = 3; //使用直接基类 d.Base2::fun0(); return 0; } 无二义性: d.Base1::var0 d.Base2::var0

78 有二义性: Derived d; d.var0

79 有二义性: Derived d; d.Base0::var0

80 虚继承(Virtual Inheritance)
7.5 派生类成员的标识与访问 虚继承(Virtual Inheritance) 为了解决多继承时的命名冲突和冗余数据问题,C++ 提出 了虚继承,使得在派生类中只保留一份间接基类的成员。 在继承方式前面加上 virtual 关键字就是虚继承。 类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。 图1:菱形继承

81 使用虚继承,在派生类 D 中就只保留了一份成员变量 m_a,直接访问不会有歧义。
图2:使用虚继承解决菱形继承中的命名冲突问题 使用虚继承,在派生类 D 中就只保留了一份成员变量 m_a,直接访问不会有歧义。 //间接基类A class A{ protected: int m_a;}; //直接基类B class B: virtual public A{ //虚继承 int m_b;}; //直接基类C class C: virtual public A{ //虚继承 int m_c;}; //派生类D class D: public B, public C{ public: void seta(int a){ m_a = a; } //正确 void setb(int b){ m_b = b; } //正确 void setc(int c){ m_c = c; } //正确 void setd(int d){ m_d = d; } //正确 private: int m_d;}; int main(){ D d; return 0;} 虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。 这个被共享的基类就称为虚基类(Virtual Base Class), A 是一个虚基类。 在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

82 7.5 派生类成员的标识与访问 C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。 图3:虚继承在C++标准库中的实际应用

83 7.5.2 虚基类---强调 7.5 派生类成员的标识与访问 虚基类的引入 用于有共同基类的场合 声明
86 7.5 派生类成员的标识与访问 7.5.2 虚基类---强调 虚基类的引入 用于有共同基类的场合 声明 以virtual修饰说明基类 例:class B1:virtual public B 作用 主要用来解决多继承时可能发生的对同一基类继承多次 而产生的二义性问题. 为最远的派生类提供唯一的基类成员,而不重复产生多 次拷贝 注意: 在第一级继承时就要将共同基类设计为虚基类。

84 例7-8 7.5 派生类成员的标识与访问 —— 7.5.2 虚基类 为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝 87
#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 int var2; 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; 为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝 87

85 使用多继承经常会出现二义性问题,必须十分小心。如果 继承的层次多一些,关系更复杂一些,程序的编写、调试 和维护工作都会变得更加困难,因此不提倡在程序中使用 多继承,只有在比较简单和不易出现二义性的情况或实在 必要时才使用多继承。 也正是由于这个原因,C++ 之后的很多面向对象的编程语 言,例如 Java、PHP 等,都不支持多继承。

86 7.5.3 虚基类及其派生类构造函数 7.5 派生类成员的标识与访问 建立对象时所指定的类称为最(远)派生类。
89 7.5 派生类成员的标识与访问 7.5.3 虚基类及其派生类构造函数 建立对象时所指定的类称为最(远)派生类。 虚基类的成员是由最(远)派生类的构造函数通过调用虚 基类的构造函数进行初始化的。 在整个继承结构中,直接或间接继承虚基类的所有派生类, 都必须在构造函数的成员初始化表中给出对虚基类的构造 函数的调用。如果未列出,则表示调用该虚基类的默认构 造函数。 在建立对象时,只有最远派生类的构造函数调用虚基类的 构造函数,该派生类的其他基类对虚基类构造函数的调用 被忽略。

87 有虚基类时的构造函数举例 7.5 派生类成员的标识与访问 —— 7.5.3 虚基类及其派生类构造函数 90
#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 Base2(int var) : Base0(var) { } int var2; 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; } 90

88 构造一个类的对象的一般顺序是: (1)如果该类中有直接或间接的虚基类,则先执行虚基类的构造函数。 (2)如果该类有其他基类,则按照他们在继承声明列表中出现的次序,分别执行他们的构造函数,但执行过程中,不在执行他们的虚基类的构造函数。 (3)按照在类定义中出现的顺序,对派生类中新增的成员对象进行初始化,对于类类型的成员对象,如果出现在构造函数初始化列表中,则以其中指定的参数执行构造函数,如未出现,则执行默认构造函数,对于基本数据类型的成员对象,如果出现在构造函数的初始化列表中,则使用其中指定的值为其赋值,否则什么都不做。

89 小结 主要内容 类的继承、类成员的访问控制、单继承与多继承、派生 类的构造和析构函数、类成员的标识与访问 达到的目标
理解类的继承关系,学会使用继承关系实现代码的重用。

90 //1) 将非成员函数声明为友元函数。 #include <iostream> using namespace std; class Student{ public: Student(char *name, int age, float score); friend void show(Student *pstu); //将show()声明为友元函数 private: void print() { cout<<"执行Student类中的私有成员函数"<<endl;} char *m_name; int m_age; float m_score; };

91 //1) 将非成员函数声明为友元函数。 Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ } //非成员函数 void show(Student *pstu){ cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl; pstu->print(); } int main(){ Student stu("张军", 15, 90.6); show(&stu); //调用友元函数 Student *pstu = new Student("梦圆", 16, 80.5); show(pstu); //调用友元函数 return 0;

92 //2) 将其他类的成员函数声明为友元函数 #include <iostream> using namespace std; class Address; //提前声明Address类 //声明Student类 class Student{ public: Student(char *name, int age, float score); void show(Address *addr); private: char *m_name; int m_age; float m_score; };

93 class Address{ private: void print2() { cout<<"执行 Address 类中的私有成员函数"<<endl; } char *m_province; //省份 char *m_city; //城市 char *m_district; //区(市区) public: Address(char *province, char *city, char *district); //将Student类中的成员函数show()声明为友元函数 friend void Student::show(Address *addr); };

94 //实现Student类 Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ } void Student::show(Address *addr){ cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl; cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl; addr->print2();} //实现Address类 Address::Address(char *province, char *city, char *district){ m_province = province; m_city = city; m_district = district;}

95 int main(){ Student stu("张凯", 16, 95.5f); Address addr("山东", "威海", "环翠"); stu.show(&addr); Student *pstu = new Student("胡兵", 16, 80.5); Address *paddr = new Address("山东", "烟台", "芝罘"); pstu -> show(paddr); return 0; }


Download ppt "C++语言程序设计(第4版) 第七章 继承与派生 数学与统计科学学院 胡凤珠."

Similar presentations


Ads by Google