c++ 面对对象程序设计

41
C++ C++ 面面面面面面面面 面面面面面面面面 授授授授 :

Upload: reed

Post on 18-Jan-2016

187 views

Category:

Documents


0 download

DESCRIPTION

C++ 面对对象程序设计. 授课老师 :. 第 10 章 继承. 本章要点 10.1 继承与派生 10.2 支配规则和赋值兼容性 10.3 虚基类. 本章要点 继承与派生的概念 派生类的定义语句格式及使用方法 冲突、支配规则、赋值兼容性的概念 虚基类的概念 虚基类的语句定义格式及使用方法 静态成员的定义格式、初始化方式及作用域. 10.1.1 继承与派生的基本概念. 1. 继承与派生的定义 - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: C++  面对对象程序设计

C++ C++ 面对对象程序设计面对对象程序设计

授课老师 :

Page 2: C++  面对对象程序设计

第 10 章 继承

• 本章要点• 10.1 继承与派生 • 10.2 支配规则和赋值兼容性• 10.3 虚基类

Page 3: C++  面对对象程序设计

• 本章要点• 继承与派生的概念• 派生类的定义语句格式及使用方法• 冲突、支配规则、赋值兼容性的概念• 虚基类的概念• 虚基类的语句定义格式及使用方法• 静态成员的定义格式、初始化方式及作用域

Page 4: C++  面对对象程序设计

10.1.110.1.1 继承与派生的基本概念继承与派生的基本概念

• 1. 继承与派生的定义• 在面相对象程序设计中提供了类的继承机

制,允许程序员在保持原有类特性的基础上,进行更具体、更详细的说明。以原有的类为基础产生新的类,我们就说新类集成了原有类的特性,也可以说从原有类派生出新类。原有类称为基类或父类,产生的新类称为派生类或子类。

• 2. 单一继承

• 3. 多重继承

Page 5: C++  面对对象程序设计

10.1.210.1.2 派生类的定义派生类的定义• 在 C++ 中,定义派生类的一般语法为:• class < 派生类名 > : < 继承方式 1> < 基类名 1>,

﹝,…, <继承方式 n> < 基类名 n> ﹞• { • < 派生类新定义成员 > • }; • 声明中的“基类名”是已有的类的名称,“派生

类名”是继承原有类的特性而生成的新类的名称。

Page 6: C++  面对对象程序设计

10.1.210.1.2 派生类的定义派生类的定义• 在派生过程中,派生出来的新类也可作为基类来

继续派生新的类,此外,一个基类可以同时派生出多个派生类。也就是说,一个类从父类继承来的特征也可以被其他新的类所继承,一个父类的特征,可以同时被多个子类继承。

A

B C

Y

Z

X

Page 7: C++  面对对象程序设计

10.1.310.1.3 派生类的构造函数与析构函数派生类的构造函数与析构函数

• 1.构造函数• 派生类的构造函数需要以合适的初值作为参数,隐

含调用基类和新增对象成员的构造函数,来初始化它们各自的数据成员,然后加入新的语句对新增普通数据成员进行初始化。

• 派生类构造函数的一般语法规则为:• < 派生类名 >::< 派生类名 > ( 形参表 ) : < 基类名

1>( 实参表 1), ﹝,…, <基类名 n>( 实参表 n)﹞• { • < 派生类构造函数体 > // 派生类新增成员的初

始化 • }

Page 8: C++  面对对象程序设计

【例【例 10-110-1 】 派生类构造函数实例】 派生类构造函数实例 • #include "stdafx.h"• #include "stdio.h"• #include "iostream.h"• class A• {• public:• A()• {• a=0;• cout<<" 类 A 的默认构造函数被调用 "<<en

dl;• } //* 程序接下页

Page 9: C++  面对对象程序设计

程序接上页程序接上页 • A(int i)• { a=i;• cout<<" 类 A 的构造函数被调用 "<<e

ndl;• }• ~A()• { cout<<"A 的析构函数被调用 "<<endl;• }• void Print()• const• {• cout<<a<<",";• }• int Geta()• {• return a;• }• private:• int a;• };

Page 10: C++  面对对象程序设计

程序接上页程序接上页• class B: public A• {• public:• B()• {• b=0;• cout<<"B 的默认构造函数被调用 "<<endl;• }• B(int i,int j,int k);• ~B()• {• cout<<"B 的析构函数被调用 "<<endl;• }• void Print();• private:• int b;• A aa;• };

Page 11: C++  面对对象程序设计

程序接上页程序接上页• B::B(int i,int j,int k)• {• b=k;• cout<<"B 的构造函数被调用 "<<endl;• }• void B::Print()• {• A::Print();• cout<<b<<","<<aa.Geta()<<endl;• }• void main()• {• B bb[2];• bb[0]=B(1,2,5);• bb[1]=B(3,4,6);• for(int i=0; i<2; i++)• bb[i].Print();• getchar();• }

Page 12: C++  面对对象程序设计

程序运行结果程序运行结果

Page 13: C++  面对对象程序设计

22 析构函数析构函数• 当对象被删除时,派生类的析构函数被执行。由

于析构函数也不能被继承,因此在执行派生类的折构函数时,基类的析构函数也将被调用。执行顺序是先执行派生类的析构函数,再执行基类的析构函数,其顺序与执行构造函数时的顺序正好相反。

Page 14: C++  面对对象程序设计

【例【例 10-210-2 】 派生类析构函数实例】 派生类析构函数实例 • #include "stdafx.h"• #include "stdio.h"• #include "iostream.h"• class M• {• public:• M()• {• m1=m2=0;• }• M(int i,int j)• {• m1=i;• m2=j;• } // 程序接下页

Page 15: C++  面对对象程序设计

程序接上页程序接上页 • ~M()• {• cout<<"M 的析构函数被调用 "<<endl;• }• void print()• { cout<<m1<<","<<m2<<",";• }• private:• int m1, m2;• };• class N: public M• { public:• N()• { n=0;• }• N(int i,int j,int k);• ~N()• { cout<<"N 的析构函数被调用 "<<endl;• }

Page 16: C++  面对对象程序设计

程序接上页程序接上页• void print()• {• M::print();• cout<<n<<endl;• }• private:• int n;• };• N::N(int i,int j,int k):M(i,j),n(k)• {}• void main()• {• N n1(2,3,4),n2(5,6,7);• n1.print();• n2.print();• n1.~N();• n2.~N();• getchar();• }

Page 17: C++  面对对象程序设计

程序运行结果如图程序运行结果如图

Page 18: C++  面对对象程序设计

10.210.2 冲突、支配规则和赋值兼容性冲突、支配规则和赋值兼容性• 10.2.1 冲突• 为了介绍冲突的概念,先看一个例子。• 【例 10-3 】 定义描述圆的类 Circle ,定义

描述矩形的类 Rectangle ,描述圆的类与描述矩形的类作为基类,多重派生出圆柱体类 Cylinder ,在 Rectangle 类中用矩形中心坐标 (x ,y) 、高 (High) 与宽 (Width) 作为类的数据成员,讨论多重继承中基类成员的冲突问题。

Page 19: C++  面对对象程序设计

程序接上页程序接上页• #include "stdafx.h"• #include "stdio.h"• #include "iostream.h"• class Circle // 定义描述圆的基类• {• protected:• float x, y, r; //(x,y) 为圆心坐标, r 为半径• public:• Circle(float a,float b,float c)• {• x=a;• y=b;• r=c;• }• float Area() // 计算圆的面积• {• return (r*r*3.14159);• }• };

Page 20: C++  面对对象程序设计

程序接上页程序接上页• class Rectangle // 定义描述矩形的基类• {• protected:• float x, y, h, w; //(x,y) 为矩形中心坐标, h,w 为矩形的高与宽• public:• Rectangle(float a,float b,float c,float d)• {• x=a;• y=b;• h=c;• w=d;• }• float Area() // 计算矩形面积• {• return h*w;• }• };

Page 21: C++  面对对象程序设计

程序接上页程序接上页• class Cylinder:public Circle,public Rectangle {• private:• float Volume; // 圆柱体的体积• public:• Cylinder(float a,float b,float c):Circle(a,b,c),Rectangle(10,10,c,c) //A• { Volume=Area()*h;} B∥• float GetV()• {return Volume;}• void Show(void)• { cout<<"x="<<x<<' \ t'<<"y="<<y<<endl ; } C∥• } ;• void main()• {• Cylinder cy(3,3,2);• cy.Show();• cout<<"Volume="<<cy.GetV()<<endl;• getchar();• }

Page 22: C++  面对对象程序设计

程序分析程序分析

• 从上例可以看出,在派生类中使用两个基类中同名函数 Area() 或同名数据成员 (x , y) 时会产出编译性错误,出错的原因是发生了同名冲突。由此,给出如下有关冲突的定义:

• 派生类使用基类中同名成员时出现不唯一称为冲突。• 解决冲突的方法是使用作用域运算符“::”指明同名成

员属于哪一个基类,即:• < 基类名 > :: < 成员名 >

Page 23: C++  面对对象程序设计

10.2.210.2.2 支配规则支配规则

• 在 C++ 中,允许派生类中新增加的成员名与其基类的成员名相同,这种重名并不产生冲突。若不使用作用域运算符,则派生类中定义的成员名优于基类中的成员名,这种关系称为支配规则。

• 在【例 10-3 】的派生类 Cylinder 中,新增数据成员 (x ,y , z) 表示圆柱体中心坐标。并从基类 Rectangle 中删除 (x,y) 。

• 修改后程序如下:

Page 24: C++  面对对象程序设计

程序程序• #include "stdafx.h"• #include "stdio.h"• #include "iostream.h"• class Circle // 定义描述圆的基类• {• protected:• float x, y, r; //(x,y) 为圆心坐标, r 为半径• public:• Circle(float a,float b,float c)• {• x=a;• y=b;• r=c;• }

Page 25: C++  面对对象程序设计

程序程序 • float Area() // 计算圆的面积• {• return (r*r*3.14159);• }• };• class Rectangle // 定义描述矩形的基类• {• protected:• float x, y, h, w; //(x,y) 为矩形中心坐标, h,w 为矩形的高与宽• public:• Rectangle(float c,float d)• {• h=c;• w=d;• }• float Area() // 计算矩形面积• {• return h*w;• }• };

Page 26: C++  面对对象程序设计

程序程序• class Cylinder:public Circle,public Rectangle• {• private:• float x,y,z,Volume; • public:• Cylinder(float a,float b,float c,float d):Circle(a,b,d),Rectangle(d,d) • {• x=a;• y=b;• z=c;• Volume=Circle::Area()*h;• }• float GetV()• {• return Volume;• }

Page 27: C++  面对对象程序设计

• void Show()• {• cout<<"x="<<x<<'\t'<<"y="<<y<<'\t'<<"z="<<z<<endl;• }• };• void main()• {• Cylinder cy(3,3,3,2);• cy.Show();• cout<<"Volume="<<cy.GetV()<<endl;• getchar();• }• 程序执行后输出结果如下:• x=3 y=3 z=3• Volume=25.1327

Page 28: C++  面对对象程序设计

10.2.310.2.3 赋值兼容规则赋值兼容规则

• 赋值兼容规则指的是在需要基类对象的任何地方都可使用公有派生类的对象来替代。通过公有继承,派生类得到基类中除构造函数、析构函数之外的所有成员,并且所有成员的访问控制属性也和基类完全相同。因此,公有派生类已经具备基类的所有功能。

• 赋值兼容规则中所指的替代包括以下 3种情况:• ①派生类的对象可以赋值给基类对象。• ②派生类的对象可以初始化基类的引用。• ⑧派生类对象的地址可以赋给基类类型的指针。

Page 29: C++  面对对象程序设计

【例【例 10-510-5 】 赋值兼容规则应用举例】 赋值兼容规则应用举例 • #include "stdafx.h"• #include "stdio.h"• #include "iostream.h"• class A• {• public:• void f() // 公有函数• {• cout<<"A::f()"<<endl;• }• };• class B:public A //A 的公有派生类 B 的声明• {• public:• void f()• {• cout<<"B::f()"<<endl; // 对 A 中的 f() 进行覆盖• }• };

Page 30: C++  面对对象程序设计

• class D:public B• {• public:• void f()• {• cout<<"D::f()"<<endl; // 对 B 中的 f() 进行覆盖• }• };• void fun(A *p) // 参数为指向基类对象的指针• {• p->f();• }

Page 31: C++  面对对象程序设计

• void main()• {• A a;• B b;• D d;• A *p; // 声明 A 类指针• p=&a; //A 类指针指向 A 类对象• fun(p);• p=&b; //A 类指针指向 B 类对象• fun(p);• p=&d; //A 类指针指向 D 类对象• fun(p);• getchar();• }• 运行结果为:• A::f()• A::f()• A::f()

Page 32: C++  面对对象程序设计

10.2.410.2.4 对基类和对象成员的几点说明对基类和对象成员的几点说明

(1) 任一基类在派生类中只能继承一次。(2) 基类成员与对象成员在使用上的差别。把一个类

作为派生类的基类或将一个类的对象作为另一个类的成员,从程序的执行效果上看是相同的,但在使用上是有区别的:

• 派生类中可直接使用基类的成员 (访问权限允许的话 );

• 使用对象成员时,必须在对象名后加上成员运算符“.”和成员名。

Page 33: C++  面对对象程序设计

10.310.3 虚基类虚基类• 在派生类的对象中,同名成员在内存中同时拥有

多个拷贝,可以使用作用域分辨符来惟一标识并分别访问它们,也可以将共同基类改置为虚基类,这时从不同的路径继承过来的该类成员在内存中只拥有一个拷贝,这样就解决了同名成员的惟一标识问题。

• 虚基类说明形式为:• virtual < 继承方式 > < 基类名 >• 其中, virtual 是虚基类的关键字,关键字 virtu

al 与继承方式的位置无关,但必须位于虚基类名之前,且 virtual 只对紧随其后的基类名起作用。

Page 34: C++  面对对象程序设计

【例【例 10-610-6 】虚基类应用举例】虚基类应用举例 • #include "stdafx.h"• #include "stdio.h"• #include "iostream.h"• class A• {• public:• int a;• void fun()• {• cout<<"a="<<a<<endl;• }• };•

Page 35: C++  面对对象程序设计

• class B:virtual public A //A 为虚基类,派生 B 类• {• public: // 新增外部接口• int b;• };• class C:virtual public A //A 为虚基类,派生 C 类• { public:• int c;• };• class D:public B,public C // 派生类 D 声明• { int d;• void f(int i)• { d=i;• }• void g()• { cout<<"d="<<d<<endl;• }• };

Page 36: C++  面对对象程序设计

• void main()• {• D t;• t.a=3; // 使用直接基类• t.fun();• getchar();• }• 运行结果为:• a=3

• 说明:在 D 类中,通过 B , C 两条派生路径继承来的基类 A 中的成员 a 和 fun() 只有一份拷贝,在D 派生类中只有惟一的数据成员 a 和函数成员 fun() 。

Page 37: C++  面对对象程序设计

10.410.4 静态成员静态成员

• 静态数据成员• 静态数据成员在内存中只有一个拷贝。使用静态数

据成员可以节省内存空间。静态数据成员的值对于每一个对象都是一样的。

• 静态数据成员的定义格式如下:• static < 数据类型 > <变量名 >;• 静态数据成员初始化格式如下:• < 数据类型类名 >:: <静态数据成员名 >=< 初始值

>;• 引用静态数据成员的格式如下:• < 类名 >:: <静态数据成员名 >

Page 38: C++  面对对象程序设计

10.4.210.4.2 静态成员函数静态成员函数

• 静态成员函数也是属于该类的所有对象,而不是属于任何对象。静态成员函数的引用不用对象名,而是类名。

• 静态成员函数一般用于操作该类中的静态数据成员。在静态成员函数的实现中不能直接引用类中的非静态成员,但可以引用类中的静态成员。如果静态成员函数中需要引用非静态成员时,只能通过对象来引用。

• 静态成员函数调用格式为:• < 类名 >:: <静态成员函数名 >(< 参数表 >)

Page 39: C++  面对对象程序设计

【例【例 10-710-7 】 静态成员函数应用举例】 静态成员函数应用举例 • #include "stdafx.h"• #include "stdio.h"• #include "iostream.h"• class Vc• {• int A;• static int B; // 静态数据成员说明• public:• Vc(int a)• {• A=a;• B+=a;• }

Page 40: C++  面对对象程序设计

• static void Display(Vc c)• {• cout<<"A="<<c.A<<",B="<<B<<endl;• }• };• int Vc:: B=20; // 静态数据成员初始化• void main()• {• Vc A(20),B(40);• Vc::Display(A);• Vc::Display(B);• getchar();• }• 程序运行结果是:• A=20 , B=80• A=40 , B=80

Page 41: C++  面对对象程序设计

本章小结本章小结

• 本章介绍了类的继承性。面向对象的三大特性是封装性 , 继承性和多态性。所谓继承是指从已有类出发建立新的类,使新类部分或全部地继承已有类的成员。通过继承已有的一个或多个类产生一个新类称为派生。使用继承与派生可以减少程序编写的工作量,还能使程序更加易于维护。派生类使用基类中同名成员时出现不唯一称为冲突。在多重派生的过程中,欲使公共的基类在派生类中只有一个拷贝,可将此基类说明成虚基类。本章最后还介绍了静态成员,静态数据成员不是属于任何对象,它属于该类的所有对象。静态成员函数和静态数据成员一样,也是属于该类的所有对象,而不是属于任何对象。