资源预览内容
第1页 / 共407页
第2页 / 共407页
第3页 / 共407页
第4页 / 共407页
第5页 / 共407页
第6页 / 共407页
第7页 / 共407页
第8页 / 共407页
第9页 / 共407页
第10页 / 共407页
亲,该文档总共407页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述
面向对象的设计思想 面面向向对对象象的的思思想想最最初初出出现现于于挪挪威威奥奥斯斯陆陆大大学学和和挪挪威威计计算算机机中中心心共共同同研研制制的的Simula 67语语言言中中,随随着着的的Smalltalk-76和和8080语语言言推推出出,面面向向对对象象的的的的程程序序设设计计方法得到了比较完善的实现。方法得到了比较完善的实现。 此此后后,面面向向对对象象的的概概念念和和应应用用已已超超越越了了程程序序设设计计和和软软件件开开发发,扩扩展展到到如如数数据据库库系系统统,交交互互式式界界面面,分分布式系统,网络管理结构和人工智能等领域。布式系统,网络管理结构和人工智能等领域。 面向对象的发展面向对象面向对象思想思想数据抽象数据抽象 一种设计方法一种设计方法 把一组数据及作用与其上的操作组成一把一组数据及作用与其上的操作组成一个设计实体或单位。接口说明外部可见,其个设计实体或单位。接口说明外部可见,其实现是隐蔽的,不可见的。实现是隐蔽的,不可见的。发展的三个阶段发展的三个阶段01010101100100IntegerRealBoolean用户用户定义定义抽象数抽象数据类型据类型面向对象面向对象思想思想抽象数据类型抽象数据类型 抽象数据类型是指抽象数据类型是指“一个值集和作用在一个值集和作用在该值集上的操作集该值集上的操作集”。抽象数据类型的定义。抽象数据类型的定义只决定于其逻辑特性,与其具体的计算机内只决定于其逻辑特性,与其具体的计算机内部实现无关。一个抽象数据类型可以分解为部实现无关。一个抽象数据类型可以分解为语法、语义、表示和算法语法、语义、表示和算法4个部分。个部分。功能抽象功能抽象模块模块数据抽象数据抽象模块模块面向对象面向对象思想思想抽象数据类型的实现抽象数据类型的实现实现抽象数据类型需要借助于高级程序设计语实现抽象数据类型需要借助于高级程序设计语言言;在面向过程的语言中,用户可以自己定义数据在面向过程的语言中,用户可以自己定义数据类型类型;在面向对象的程序设计语言中,借助于对象描在面向对象的程序设计语言中,借助于对象描述抽象数据类型。述抽象数据类型。(Class)面向对象面向对象思想思想问题求解问题求解面向对象设计方法:实现问题空间和问题求解面向对象设计方法:实现问题空间和问题求解空间的近似和直接模拟。空间的近似和直接模拟。意识部分意识部分教室教室物质部分物质部分303303教室教室抽象部分抽象部分教室类教室类具体部分具体部分一个对象:教室实例一个对象:教室实例现实问题空间现实问题空间解空间解空间面向对象的特点面向对象的特点封装机制封装机制基于消息的通信基于消息的通信继承机制继承机制多态机制多态机制 理解面向对象的基本概念对于学习和掌握面向理解面向对象的基本概念对于学习和掌握面向对象的开发方法是十分重要的。对象的开发方法是十分重要的。面向对象的基本概念 类类(ClassClass)对象对象( (Object)Object)实例实例( (Instance)Instance)多态性多态性多态性多态性( (Polymorphism)Polymorphism)继承继承(InheritanceInheritance)消息消息( (Message)Message)封装封装(Encapsulation)(Encapsulation)面向对象的世界观面向对象的世界观把客观世界从概念上看成是一个由相互配合而协把客观世界从概念上看成是一个由相互配合而协作的对象所组成的系统作的对象所组成的系统面向对象面向对象=对象对象+分类分类+继承继承+通信通信一个实例一个实例椅子椅子对象:桌子对象:桌子价格价格尺寸尺寸重量重量位置位置颜色颜色类:家具类:家具对象:椅子对象:椅子一个面向对象的实例一个面向对象的实例椅子椅子价格价格尺寸尺寸重量重量位置位置颜色颜色买卖移动买买卖卖移动移动买买卖卖移动移动价格价格尺寸尺寸重量重量位置位置颜色颜色对象对象对象对象( (Object)Object)对象是用来描述客观存在的事物,它是构成系对象是用来描述客观存在的事物,它是构成系统的基本单位,是对客观世界中事物的抽象统的基本单位,是对客观世界中事物的抽象描述。描述。 对象对象 行为(功能、方法、服务)行为(功能、方法、服务) 属性(数据)属性(数据)属性属性行为行为接口接口对象对象对象对象对象对象面向对象的基本概念面向对象的基本概念对象是由私有数据(属性)及作用于其上的一组操作(行对象是由私有数据(属性)及作用于其上的一组操作(行为)所构成的一个封闭整体为)所构成的一个封闭整体由于对象的数据是私有的,所以要想访问其数据的正确方法是向该对象发送消息,让对象自身选择其内部相应的操作以完成对该项数据的访问对象的动作取决于外界给对象的刺激,这就是消息,消息告诉对象所要求它完成的功能。对象具有一定的智能功能,即“知道”如何选择相应的操作来处理对象所接收的消息从设计人员的角度看,对象是一个完成特定功能的程序块从设计人员的角度看,对象是一个完成特定功能的程序块从用户的角度看,对象为他们提供了所希望的行为从用户的角度看,对象为他们提供了所希望的行为面向对象的基本概念面向对象的基本概念对象对象(Object)对象标识对象标识对象生命周期对象生命周期面向对象的基本概念面向对象的基本概念类(类(类(类(ClassClass)类类又又称称对对象象类类(Object Object ClassClass),是是是是一一组组具具有有相相同属性和相同操作的对象的集合。同属性和相同操作的对象的集合。在在一一个个类类中中,每每个个对对象象都都是是类类的的实实例例(instance) ,它们都可以使用类中提供的函数。,它们都可以使用类中提供的函数。类具有属性类具有属性,用数据结构来描述类的属性,用数据结构来描述类的属性,类类具具有有操操作作,它它是是对对象象的的行行为为的的抽抽象象,操操作作实实现现的的过过程程称称为为方方法法(method) ,方方法法有有方方法法名名,方方法法体体和参数。和参数。 由于对象是类的实例,在进行分析和设计时,由于对象是类的实例,在进行分析和设计时,由于对象是类的实例,在进行分析和设计时,由于对象是类的实例,在进行分析和设计时,通常把注意力集中在类上,而不是具体的对象上。通常把注意力集中在类上,而不是具体的对象上。通常把注意力集中在类上,而不是具体的对象上。通常把注意力集中在类上,而不是具体的对象上。几何对象几何对象颜色颜色位置位置移动(移动(delta:矢量)矢量)选择(选择(P:指针型)指针型):布尔型布尔型旋转旋转(角度角度)图1 对象类的描述人人姓姓 名名:字符串字符串年年 龄龄: 整整 型型改换工作改换工作改换地址改换地址文件文件文件名文件名文件大小文件大小最近更新日期最近更新日期打印打印(人人)张红兵张红兵28绘图员绘图员人民路人民路8号号(人人)李军李军24程序员程序员无无图2 对象的描述对象和类的描述对象和类的描述对象和类的描述对象和类的描述类类类类和和和和对对对对象象象象一一一一般般般般采采采采用用用用“名名名名字字字字”、“属属属属性性性性”和和和和“运运运运算算算算”来来来来描描描描述。述。述。述。类名类名属性属性运算运算 对象对象 类类属于某类的具体对象就是该类的属于某类的具体对象就是该类的实例实例。一个类的不同实例必定具有:一个类的不同实例必定具有:相同的操作(或行为)的集合相同的操作(或行为)的集合相同的信息结构或属性定义,但可以有不同的属性值相同的信息结构或属性定义,但可以有不同的属性值不同的对象标识不同的对象标识面向对象的基本概念面向对象的基本概念面向对象的基本概念面向对象的基本概念消息消息(Message)对象之间的联系是通过传递消息来实现的。对象之间的联系是通过传递消息来实现的。消息消息就是向对象发出的服务请求(互相联系、就是向对象发出的服务请求(互相联系、协同工作等)。协同工作等)。是对象之间进行通讯的一种数据结构。是对象之间进行通讯的一种数据结构。消息统一了消息统一了“数据流数据流”和和“控制流控制流”。数据数据方法方法消息到达面向对象的基本概念面向对象的基本概念消息消息 - - 消息传送与函数调用的区别消息传送与函数调用的区别(1)函数调用可以带或不带参数,但是消息至)函数调用可以带或不带参数,但是消息至少带一个参数,它表明接收该消息的对象,消少带一个参数,它表明接收该消息的对象,消息中告诉对象做什么的部分称为消息操作;息中告诉对象做什么的部分称为消息操作;(2)消息操作名类似于函数名,其本质区别在)消息操作名类似于函数名,其本质区别在于:函数名代表一段可执行的代码,但消息操于:函数名代表一段可执行的代码,但消息操作名具体功能的选定还取决于接收消息的对象作名具体功能的选定还取决于接收消息的对象本身本身(3)函数调用是过程式的(如何做),消息传)函数调用是过程式的(如何做),消息传送是说明式的(做什么),具体如何做,由对送是说明式的(做什么),具体如何做,由对象根据收到的消息自行确定。象根据收到的消息自行确定。继承继承继承继承 (InheritanceInheritance) 继继承承是是使使用用现现存存的的定定义义作作为为基基础础,建建立立新新定定义义的的技技术术。是是父父类类和和子子类类之之间间共共享享数数据据结结构构和和方方法法的的机机制制,这是类之间的一种关系。这是类之间的一种关系。 继承性分:继承性分: 单单重重继继承承:一一个个子子类类只只有有一一个个父父类类。即即子子类类只只继继承承一个父类的数据结构和方法。一个父类的数据结构和方法。 多多重重继继承承:一一个个子子类类可可有有多多个个父父类类。继继承承多多个个父父类类的数据结构和方法。的数据结构和方法。基类基类子类子类A子类子类B图 4 继承性描述现存类定义现存类定义父类父类( (基类基类) )新类定义新类定义子类子类( (派生类派生类) )继继 承承图 3 继承性继承继承单继承单继承CheckingSavingsSuperclass (parent)SubclassesInheritance RelationshipAncestorDescendents继承继承多继承多继承Use multiple inheritance only when needed and always with caution!Multiple Inheritance封装封装(Encapsulation) 封封装装是是一一种种信信息息隐隐蔽蔽技技术术,就就是是把把对对象象的的属属性性和和行行为为相相结结合合构构成成一一个个独独立立的的基基本本单单位位,用用户户只只能能见见到到对对象象封封装装界界面面上上的的信信息息,对对象象内内部部对对用用户户是是隐隐蔽蔽的的。封封装装的的目目的的在在于于将将对对象象的的使使用用者者和和对对象象的的设设计计者者分分开开,使使用用者者不不必必知知道道行行为为实实现现的的细细节节,只只需需使使用用设设计计者提供的消息访问对象者提供的消息访问对象面向对象的基本概念面向对象的基本概念 封装是把过程和数据包围起来,对数据的封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。访问只能通过已定义的界面。面向对象的基本概念面向对象的基本概念PosColor 公有公有消息消息私有私有消息消息存储结构方法协.议一个对象面向对象的基本概念面向对象的基本概念封装的定义为:封装的定义为:(1)一个限定所有对象内部软件的一个清楚界面;一个限定所有对象内部软件的一个清楚界面;(2)一个描述这个对象和其它对象之间相互作用的接口一个描述这个对象和其它对象之间相互作用的接口(3)受保护的内部实现,这个实现给出了由软件对象提受保护的内部实现,这个实现给出了由软件对象提供的功能细节,实现细节不能在定义这个对象的类的供的功能细节,实现细节不能在定义这个对象的类的外面访问外面访问封装封装 vs 继承继承矛盾吗?矛盾吗?No!封装性主要指的是对象的封装性,引入继承机制封装性主要指的是对象的封装性,引入继承机制后,对象仍然是封装地很好的实体,其它对象后,对象仍然是封装地很好的实体,其它对象与它通信只能发送消息。与它通信只能发送消息。相似性:共享代码!相似性:共享代码!继承:静态共享代码封装:动态共享代码面向对象的基本概念面向对象的基本概念多态多态(Polymorphism)即一个即一个名字具有多种语义名字具有多种语义。同一对象接收到不同消息采用不同的行为方式同一对象接收到不同消息采用不同的行为方式不同对象收到相同消息时产生不同的动作不同对象收到相同消息时产生不同的动作重载(重载(overload)动态绑定动态绑定类属类属多态性和动态绑定多态性和动态绑定多态性和动态绑定多态性和动态绑定 多多多多态态态态性性性性( (Polymorphism)Polymorphism)是是是是指指指指相相相相同同同同的的的的操操操操作作作作或或或或函函函函数数数数、过程作用于不同的对象上并获得不同的结果。过程作用于不同的对象上并获得不同的结果。过程作用于不同的对象上并获得不同的结果。过程作用于不同的对象上并获得不同的结果。 即即即即相相相相同同同同的的的的操操操操作作作作的的的的消消消消息息息息发发发发送送送送给给给给不不不不同同同同的的的的对对对对象象象象时时时时,每每每每个个个个对对对对象象象象将将将将根根根根据据据据自自自自己己己己所所所所属属属属类类类类中中中中定定定定义义义义的的的的操操操操作作作作去去去去执执执执行行行行,产产产产生生生生不不不不同的结果。同的结果。同的结果。同的结果。 例例如如: “绘绘图图”操操作作,作作用用在在“椭椭圆圆” 和和“矩矩形形” 上上,画出不同的图形。画出不同的图形。 动动动动态态态态绑绑绑绑定定定定( (dynamic dynamic binding)binding)是是是是在在在在运运运运行行行行时时时时根根根根据据据据对对对对象象象象接收的消息动态地确定要连接的服务代码。接收的消息动态地确定要连接的服务代码。接收的消息动态地确定要连接的服务代码。接收的消息动态地确定要连接的服务代码。使使用用虚虚函函数数可可实实现现动动态态联联编编,不不同同联联编编可可以以选选择择不同的实现,这便是多态性。不同的实现,这便是多态性。 继继承承是是动动态态联联编编的的基基础础,虚虚函函数数是是动动态态联联编编的的关关键。键。多态性的实现举例多态性的实现举例 实现多态性的基本步骤实现多态性的基本步骤实现多态性的基本步骤实现多态性的基本步骤( (以以以以C+C+为例为例为例为例) ):(1)1)在基类中,定义成员函数为虚函数在基类中,定义成员函数为虚函数(virtual);(2)(2)定义基类的公有定义基类的公有(public)派生类;派生类;(3)(3)在基类的公有派生类中在基类的公有派生类中“重载重载”该虚函数;该虚函数;(4)(4)定定义义指指向向基基类类的的指指针针变变量量,它它指指向向基基类类的的公公有派生类的对象。有派生类的对象。注注注注意意意意:重重载载虚虚函函数数不不是是一一般般的的重重载载函函数数,它它要要求求函函数数名、返回类型、参数个数、参数类型和顺序完全相同。名、返回类型、参数个数、参数类型和顺序完全相同。多态多态figureCircleTriangleSquarearea()area()area()area()类与对象类与对象C+语言程序设计C+语言程序设计本章主要内容本章主要内容l面向对象的思想面向对象的思想lOOP的基本特点的基本特点l类概念和声明类概念和声明l对象对象l构造函数构造函数l析构函数析构函数l内联成员函数内联成员函数l拷贝构造函数拷贝构造函数l类的组合类的组合C+语言程序设计回顾:面向过程的设计方法回顾:面向过程的设计方法l重点重点:如何实现细节过程,将数据与函数分开。l形式:形式:主模块+若干个子模块(main()+子函数)。l特点:特点:自顶向下,逐步求精功能分解。l缺点:缺点:效率低,程序的可重用性差。面向对象的思想C+语言程序设计面向对象的方法面向对象的方法l目的:目的:实现软件设计的产业化。l观点观点:解决问题属于自然界的。解决问题属于自然界的。自然界是由实体(对象)所组成。l程序设计方法:程序设计方法:使用面向对象的观点来描述模仿并处理现实问题。l要求:要求:高度概括、分类、和抽象。面向对象的思想C+语言程序设计(1)抽象)抽象抽象是对具体对象(问题)进行概括,抽象是对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述抽出这一类对象的公共性质并加以描述的过程。(如学生,教师的过程。(如学生,教师, 课程)课程)先注意问题的本质及描述,其次是实现过程或细节。数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。行为抽象:描述某类对象的共有的行为特征或具有的功能。抽象的实现:通过类的声明。OOP的基本特点C+语言程序设计抽象实例抽象实例钟表钟表l数据抽象:数据抽象:int Hour, int Minute, int Secondl行为抽象:行为抽象:SetTime(), ShowTime()OOP的基本特点C+语言程序设计抽象实例抽象实例钟表类钟表类class Clock public: void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour,Minute,Second;OOP的基本特点C+语言程序设计抽象实例抽象实例人人l数据抽象:数据抽象:char *name,char *sex, int age, int idl行为抽象:行为抽象:生物属性角度:GetCloth(), Eat(), 社会属性角度:Work(), Promote() ,注意:同一个问题可能有不同的抽象结果:根据解决问题的要求不同,产生的抽象成员可能不同OOP的基本特点C+语言程序设计(2) 封装封装将抽象出的数据成员、行为成员相结将抽象出的数据成员、行为成员相结合,将它们视为一个整体,即类。合,将它们视为一个整体,即类。目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。实现封装:类声明中的OOP的基本特点C+语言程序设计封装封装l实例:实例:class Clock public: void SetTime(int NewH,int NewM, int NewS); void ShowTime(); private: int Hour,Minute,Second;边界特定的访问权限OOP的基本特点外部接口C+语言程序设计(3)继承与派生继承与派生为了重用引出了继函的概念。为了重用引出了继函的概念。是是C+中支持层次分类的一种机制,允许程序员中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体的说明。在保持原有类特性的基础上,进行更具体的说明。实现:声明派生类实现:声明派生类第第11章章昆虫的分类树昆虫的分类树OOP的基本特点C+语言程序设计多态性多态性l多态:同一名称,不同的功能实现方式。多态:同一名称,不同的功能实现方式。l目的:达到行为标识统一,减少程序中标目的:达到行为标识统一,减少程序中标识符的个数。识符的个数。l实现:重载函数和虚函数实现:重载函数和虚函数第第12章章OOP的基本特点C+语言程序设计c+中的类中的类l类是具有相同属性和行为的一组对象类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属供了统一的抽象描述,其内部包括属性和行为两个主要部分。性和行为两个主要部分。l利用类可以实现数据的封装、隐藏、利用类可以实现数据的封装、隐藏、继承与派生。继承与派生。l利用类易于编写大型复杂程序,其模利用类易于编写大型复杂程序,其模块化程度比块化程度比C中采用函数更高。中采用函数更高。类 和 对 象C+语言程序设计类与函数类与函数l函数是将逻辑上相关的函数是将逻辑上相关的语句与数据语句与数据封封装,用于完成特定的功能。装,用于完成特定的功能。l而类则是逻辑上相关的而类则是逻辑上相关的函数与数据函数与数据的的封装,它是对所要处理的问题的描述。封装,它是对所要处理的问题的描述。类 和 对 象C+语言程序设计类的声明形式类的声明形式 类是一种用户自定义类型,声明形式:类是一种用户自定义类型,声明形式:class 类名称 public: 公有成员(外部接口) private: 私有成员 protected: 保护型成员类 和 对 象C+语言程序设计公有类型成员公有类型成员在关键字在关键字public后面声明,它们是类后面声明,它们是类与外部的接口,任何外部函数都可以访与外部的接口,任何外部函数都可以访问公有类型数据和函数。问公有类型数据和函数。类 和 对 象C+语言程序设计私有类型成员私有类型成员在关键字在关键字private后面声明,后面声明,只允许只允许本类中的函数访问,而类外部的任何函本类中的函数访问,而类外部的任何函数都不能访问。数都不能访问。如果如果紧跟在类名称的后面声明私有成员,紧跟在类名称的后面声明私有成员,则则关键字关键字privateprivate可以可以省略。省略。类 和 对 象C+语言程序设计保护类型保护类型与与private类似,其差别表现在继承与类似,其差别表现在继承与派生时对派生类的影响不同,派生时对派生类的影响不同,第七章讲第七章讲。类 和 对 象C+语言程序设计类的成员类的成员class Clock public: void SetTime(int NewH, int NewM, int NewS); void ShowTime(); private: int Hour, Minute, Second;类 和 对 象成员数据成员函数void Clock : SetTime(int NewH, int NewM, int NewS) Hour=NewH; Minute=NewM; Second=NewS;void Clock : ShowTime() coutHour:Minute:Second;19C+语言程序设计成员数据成员数据l与一般的变量声明相同,但需要将它放在类的声明与一般的变量声明相同,但需要将它放在类的声明体中。体中。lclass complex private: double real; double imag; public: void init(double r,double i)real=r;imag=i; double realcomplex()return real; double imagcomplex()return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); 类 和 对 象C+语言程序设计成员函数成员函数l在类中说明原形,可以在在类中说明原形,可以在类外给出函数类外给出函数体实现,并在函数名前使用类名加以限体实现,并在函数名前使用类名加以限定。定。也可以也可以直接在类中给出函数体,形直接在类中给出函数体,形成内联成员函数成内联成员函数。l允许声明重载函数和带默认形参值的函允许声明重载函数和带默认形参值的函数数类 和 对 象C+语言程序设计带缺省形参值的函数带缺省形参值的函数lVoid clock:setTime(int NewH=0,int NewM=0,int NewS=0) Hour=NewH; Minute=NewM; Second=NewS; C+语言程序设计内联成员函数内联成员函数l为了提高运行时的效率,对于较简单为了提高运行时的效率,对于较简单的函数可以声明为内联形式。的函数可以声明为内联形式。l内联函数体中不要有复杂结构(如循内联函数体中不要有复杂结构(如循环语句和环语句和switch语句)。语句)。l在类中声明内联成员函数的方式:在类中声明内联成员函数的方式:将函数体放在类的声明中-隐式使用inline关键字-显式类 和 对 象C+语言程序设计内联成员函数举例内联成员函数举例(隐式隐式)class Point public: void Init(int initX,int initY) X=initX; Y=initY; int GetX() return X; int GetY() return Y; private: int X,Y;类 和 对 象C+语言程序设计内联成员函数举例内联成员函数举例(显式显式)class Point public: void Init(int initX,int initY); int GetX(); int GetY(); private: int X,Y;类 和 对 象inline void Point: Init(int initX,int initY) X=initX; Y=initY;inline int Point:GetX() return X;inline int Point:GetY() return Y;25C+语言程序设计对象对象l类的对象是该类的某一特定实体,即类的对象是该类的某一特定实体,即类类型的变量。类类型的变量。l声明形式:声明形式: 类名类名 对象名;对象名;l例:例: Clock myClock;类 和 对 象C+语言程序设计类中成员的访问方式类中成员的访问方式l类中成员互访类中成员互访直接使用成员名l类外访问类外访问使用“对象名.成员名”方式访问 public 属性的成员类 和 对 象C+语言程序设计例例 类的应用举例类的应用举例#includeusing namespace std;class Clock public: void SetTime(int NewH,int NewM,int NewS); void ShowTime(); private: int Hour,Minute,Second;void Clock:SetTime(int NewH,int NewM,int NewS) Hour=NewH; Minute=NewM; Second=NewS;C+语言程序设计void Clock:ShowTime() coutHour“:”Minute“:”Second; void main(void) Clock myClock; myClock.SetTime(8,30,30); myClock.ShowTime();例例 类的应用举例类的应用举例C+语言程序设计结构与类结构与类Struct Savings unsigned accountNumber; float balance;void fn() Savings a; Savings b; a.accountNumber=1; b.accountNumber=2;结构与类的区别:类中成员的缺省存储属性为私有的结构体中的缺省存储属性为共有的C+语言程序设计构造函数构造函数l由于类的封装性,不能像普通变量一样初始化由于类的封装性,不能像普通变量一样初始化struct Savingsunsigned accountNumber; float balance;Savings A=1,2000.0;Savings B(2,3000.0);构造函数和析构函数C+语言程序设计构造函数构造函数l构造函数的作用是在对象被创建时使用特定的构造函数的作用是在对象被创建时使用特定的值去构造对象,或者说将对象值去构造对象,或者说将对象初始化初始化为一个特为一个特定的状态。定的状态。l在对象创建时在对象创建时由系统自动调用由系统自动调用。l如果程序中未声明,则系统自动产生出一个如果程序中未声明,则系统自动产生出一个默默认形式认形式的构造函数的构造函数l允许为允许为内联内联函数、函数、重载重载函数、函数、带默认形参值带默认形参值的的函数函数构造函数和析构函数C+语言程序设计构造函数举例构造函数举例class Clockpublic:Clock (int NewH, int NewM, int NewS);/构造函数构造函数void SetTime(int NewH, int NewM, int NewS);void ShowTime();private:int Hour,Minute,Second;构造函数和析构函数构造函数的实现:构造函数的实现:Clock:Clock(int NewH, int NewM, int NewS)Hour= NewH;Minute= NewM;Second= NewS;建立对象时构造函数的作用:建立对象时构造函数的作用:void main() Clock c (0,0,0); /隐含调用构造函数,将初始值作为实参。隐含调用构造函数,将初始值作为实参。 c.ShowTime();31C+语言程序设计重载构造函数:重载构造函数:/ex.h#include class Demo int x,y; public: Demo(int a,int b) x=a; y=b; cout“Constructor demo(int ,int)be calledn”; Demo() cout“Constructor demo() be calledn”; void show() cout“X=”xt“Y=”yendl; ; C+语言程序设计#include “ex.h”void main() Demo d1(3,5); /A d1.Show(); Demo d2; /B d2.Show();该程序的输出为:该程序的输出为:Constructor Demo(int ,int) be calledX=3 Y=5Constructor Demo( ) be calledX=946 Y=928 (随机值随机值)C+语言程序设计拷贝构造函数拷贝构造函数拷贝构造函数是一种特殊的构造函数,其形参为本类的对拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。象引用。作用:使用一个对象(参数指定的对象),去初始化一个作用:使用一个对象(参数指定的对象),去初始化一个正在被建立的同类型对象。正在被建立的同类型对象。class 类名 public : 类名(形参);/构造函数 类名(类名 &对象名);/拷贝构造函数 .;类名: 类名(类名 &对象名)/拷贝构造函数的实现 函数体 构造函数和析构函数C+语言程序设计例例 拷贝构造函数举例拷贝构造函数举例class Point public: Point(int xx=0,int yy=0); Point( Point &p); int GetX() return X; int GetY() return Y; private: int X,Y;构造函数和析构函数Point:Point(int xx,int yy) X=xx; Y=yy;Point:Point (Point& p) X=p.X; Y=p.Y; cout拷贝构造函数被调用拷贝构造函数被调用endl;Point b(1,1);Point a(b);34C+语言程序设计拷贝构造函数被调用的时机拷贝构造函数被调用的时机l(1)当用类的一个对象去初始化该类的)当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数另一个对象时系统自动调用拷贝构造函数实现拷贝赋值。实现拷贝赋值。void main(void) Point A(1,2); Point B(A); /拷贝构造函数被调用拷贝构造函数被调用 coutB.GetX()endl;构造函数和析构函数C+语言程序设计l(2)若函数的形参为类对象,调用函)若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调数时,实参赋值给形参,系统自动调用拷贝构造函数。例如:用拷贝构造函数。例如:void fun1(Point p) coutp.GetX()endl; void main() Point A(1,2); fun1(A); /调用拷贝构造函数调用拷贝构造函数 构造函数和析构函数拷贝构造函数被调用的时机拷贝构造函数被调用的时机C+语言程序设计l(3)当函数的返回值是类对象时,系统自)当函数的返回值是类对象时,系统自动调用拷贝构造函数。例如:动调用拷贝构造函数。例如:Point fun2() Point A(1,2); return A; /调用拷贝构造函数调用拷贝构造函数void main() Point B; B=fun2(); 构造函数和析构函数拷贝构造函数被调用的时机拷贝构造函数被调用的时机C+语言程序设计拷贝构造函数拷贝构造函数如果程序员没有为类声明拷贝初始化构造函数,则编译器自如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个拷贝构造函数。己生成一个拷贝构造函数。这个构造函数执行的功能是:用作为初始值的对象的每个数这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。据成员的值,初始化将要建立的对象的对应数据成员。Point(Point &p) Point A(1,2);X=p.x; Point B(A); Y=p.y; B.X=A.X 如果没有定义这个函数如果没有定义这个函数 B.Y=A.Y构造函数和析构函数C+语言程序设计析构函数析构函数l完成对象被删除前的一些清理工作。完成对象被删除前的一些清理工作。l在对象的生存期结束的时刻系统自动调用它,在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。然后再释放此对象所属的空间。l如果程序中未声明析构函数,编译器将自动如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数。产生一个默认的析构函数。构造函数和析构函数C+语言程序设计构造函数和析构函数举例构造函数和析构函数举例#includeusing namespace std;class Point public: Point(int xx,int yy); Point(); /.其它函数原形其它函数原形 private: int X,int Y;构造函数和析构函数Point:Point(int xx,int yy) X=xx; Y=yy;Point:Point()/.其它函数的实现略其它函数的实现略41C+语言程序设计#include class Q int x,y; public: Q(int a=0,int b=0) cout“调用了构造函数!调用了构造函数!”endl; x=a; y=b; void P(void) coutxtyn; Q() cout“调用了析构函数!调用了析构函数!”n;void main(void) Q q(50,100); q.P(); cout“退出主函数!退出主函数!n”;输出:调用了构造函数!输出:调用了构造函数! 50 100 退出主函数!退出主函数! 调用了析构函数!调用了析构函数! C+语言程序设计类的应用举例类的应用举例一圆型游泳池如图所示,现在需在其周围建一圆型游泳池如图所示,现在需在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为一圆型过道,并在其四周围上栅栏。栅栏价格为35元元/米,过道造价为米,过道造价为20元元/平方米。过道宽度为平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。输出过道和栅栏的造价。游泳池过道#include using namespace std;const float PI = 3.14159;const float FencePrice = 35;const float ConcretePrice = 20;/声明类声明类Circle 及其数据和方法及其数据和方法class Circle private: float radius; public: Circle(float r); /构造函数构造函数 float Circumference(); /圆周长圆周长 float Area(); /圆面积圆面积;43/ 类的实现类的实现/ 构造函数初始化数据成员构造函数初始化数据成员radiusCircle:Circle(float r)radius=r/ 计算圆的周长计算圆的周长float Circle:Circumference() return 2 * PI * radius; / 计算圆的面积计算圆的面积 float Circle:Area() return PI * radius * radius;44void main () float radius; float FenceCost, ConcreteCost; / 提示用户输入半径提示用户输入半径 coutradius; / 声明声明 Circle 对象对象 Circle Pool(radius);/自动调用构造函数自动调用构造函数 Circle PoolRim(radius + 3); 45/ 计算栅栏造价并输出计算栅栏造价并输出 FenceCost = PoolRim.Circumference() * FencePrice; cout Fencing Cost is ¥ FenceCost endl; / 计算过道造价并输出计算过道造价并输出 ConcreteCost = (PoolRim.Area() - Pool.Area()*ConcretePrice; cout Concrete Cost is ¥ ConcreteCost endl;运行结果运行结果Enter the radius of the pool: 10Fencing Cost is ¥2858.85Concrete Cost is ¥4335.3946C+语言程序设计例:复数例:复数(类与对象、内联成员函数)(类与对象、内联成员函数)#include #include Class complexPrivate: double real; double imag;Public: void init(double r,double i) real=r;imag=I; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;void main() complex a; a.init(1.1,2.2); cout“real of complex a=”a.realcomplex()endl; cout“image of complex a=”a.imagecomplex()endl; cout“abs of complex a=”a.abscomplex()endl;C+语言程序设计例:复数例:复数 (构造函数)(构造函数)#include #include Class complexPrivate: double real; double imag;Public: complex(double r,double i) real=r;imag=I; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;void main() complex a(1.1,2.2); cout“real of complex a=”a.realcomplex()endl; cout“image of complex a=”a.imagecomplex()endl; cout“abs of complex a=”a.abscomplex()endl;C+语言程序设计例:复数例:复数 (带缺省参数构造函数)(带缺省参数构造函数)#include #include Class complexPrivate: double real; double imag;Public: complex(double r=0.0,double i=0.0) real=r;imag=I; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;void main() complex a(1.1,2.2); complex b; complex c(1.0); cout“real of complex a=”a.realcomplex()endl; cout“image of complex a=”a.imagecomplex()endl; cout“abs of complex a=”a.abscomplex()endl;C+语言程序设计例:复数例:复数 (构造函数重载)(构造函数重载)#include #include Class complexPrivate: double real; double imag;Public: complex(double r,double i) real=r;imag=I; complex() real=0.0; imag=0.0; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;void main() complex a(1.1,2.2); cout“real of complex a=”a.realcomplex()endl; cout“image of complex a=”a.imagecomplex()endl; cout“abs of complex a=”a.abscomplex()endl; complex b; cout“real of complex a=”b.realcomplex()endl; cout“image of complex a=”b.imagecomplex()endl; cout“abs of complex a=”b.abscomplex()endl;l参数能不能给一个值参数能不能给一个值 如:如:complex c(1.1);行吗?行吗?C+语言程序设计例:复数例:复数 (析构函数)(析构函数)#include #include Class complexPrivate: double real; double imag;Public: complex(double r=0.0,double i=0.0) real=r;imag=I; complex() cout“destructing.n”; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;void main() complex a(1.1,2.2); cout“real of complex a=”a.realcomplex()endl; cout“image of complex a=”a.imagecomplex()endl; cout“abs of complex a=”a.abscomplex()endl; complex b; cout“real of complex a=”b.realcomplex()endl; cout“image of complex a=”b.imagecomplex()endl; cout“abs of complex a=”b.abscomplex()endl;C+语言程序设计例:复数例:复数 (拷贝构造函数(拷贝构造函数应用应用A)#include #include Class complexPrivate: double real; double imag;Public: complex(double r=0.0,double i=0.0) real=r;imag=I; complex(complex &c) real=c.real; imag=c.imag; complex() cout“destructing.n”; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;void main() complex a(1.1,2.2); cout“real of complex a=”a.realcomplex()endl; cout“image of complex a=”a.imagecomplex()endl; cout“abs of complex a=”a.abscomplex()endl; complex b(a); cout“real of complex a=”b.realcomplex()endl; cout“image of complex a=”b.imagecomplex()endl; cout“abs of complex a=”b.abscomplex()endl;C+语言程序设计例:复数例:复数 (拷贝构造函数(拷贝构造函数应用应用B)#include #include Class complexPrivate: double real; double imag;Public: complex(double r=0.0,double i=0.0) real=r;imag=I; complex(complex &c) real=c.real; imag=c.imag; complex() cout“destructing.n”; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;void display(complex p) cout“real of complex p=”p.realcomplex()endl; cout“image of complex p=”p.imagecomplex()endl; cout“abs of complex p=”p.abscomplex()endl;void main() complex a(1.1,2.2); display(a); C+语言程序设计例:复数例:复数 (拷贝构造函数(拷贝构造函数应用应用C)#include #include Class complexPrivate: double real; double imag;Public: complex(double r=0.0,double i=0.0) real=r;imag=I; complex(complex &c) real=c.real; imag=c.imag; complex() cout“destructing.n”; double realcomplex() return real; double imagcomplex() return imag; double abscomplex() double t; t=real*real+imag*imag; return sqrt(t); ;complex fun() complex a(1.1,2.2); return a;void main() complex a(1.1,2.2); p=fun(); cout“real of complex p=”p.realcomplex()endl; cout“image of complex p=”p.imagecomplex()endl; cout“abs of complex p=”p.abscomplex()endl; C+语言程序设计静态成员静态成员l全局对象不好,但复杂程序都是由许多程序员共同全局对象不好,但复杂程序都是由许多程序员共同设计的,因此需要这种性的对象。设计的,因此需要这种性的对象。l使用类中的静态数据成员使用类中的静态数据成员-解决访问权限控制问题。解决访问权限控制问题。class employeeprvate: int EmpNo; int ID; char *name; 需要一个成员来统计雇员的总数,这个成员放在哪?需要一个成员来统计雇员的总数,这个成员放在哪?静态成员C+语言程序设计l一个类的所有对象具有相同的属性。一个类的所有对象具有相同的属性。l属性值不同。属性值不同。l类属性:描述类的所有对象的共同特类属性:描述类的所有对象的共同特征的一个数据项,对于任何对象实例。征的一个数据项,对于任何对象实例。它的属性值是相同的。它的属性值是相同的。静态成员静态成员静态成员C+语言程序设计静态成员静态成员l静态数据成员静态数据成员用关键字static声明该类的所有对象维护该成员的同一个拷贝必须在类外定义和初始化,用(:)来指明所属的类。静态成员C+语言程序设计例例 具有静态数据成员的具有静态数据成员的 Point类类#include using namespace std;class Pointpublic:Point(int xx=0, int yy=0) X=xx; Y=yy; countP+; Point(Point &p);int GetX() return X;int GetY() return Y;void GetC() cout Object id=countPendl;private:int X,Y;static int countP; /统计对象的个数统计对象的个数;静态成员C+语言程序设计Point:Point(Point &p) X=p.X;Y=p.Y;countP+;int Point:countP=0; void main() Point A(4,5);coutPoint A,A.GetX(),A.GetY();A.GetC();Point B(A);coutPoint B,B.GetX(),B.GetY();B.GetC();28在类的声明中仅仅对静态数据成员在类的声明中仅仅对静态数据成员进行引用说明,必须在文件作用域进行引用说明,必须在文件作用域的某个地方使用类名限定进行定义的某个地方使用类名限定进行定义性说明,这时也可初始化。性说明,这时也可初始化。注意(1)用类名初始化 (2)访问控制属性C+语言程序设计静态函数成员静态函数成员l使用静态成员函数使用静态成员函数-解决操作合法解决操作合法性控制问题。性控制问题。l例:例:coutP的初始值如何输出?的初始值如何输出?l静态成员函数静态成员函数类外代码可以使用类名和作用域操作符来调用静态成员函数。静态成员函数只能引用属于该类的静态数据成员或静态成员函数。静态成员C+语言程序设计静态成员函数举例静态成员函数举例#includeusing namespace std;class Application public: static void f(); static void g(); private: static int global;int Application:global =0;void Application:f() global=5;void Application:g() coutglobalendl;int main() Application:f(); Application:g(); return 0;静态成员C+语言程序设计总结:总结:l静态数据成员静态数据成员Application:global该类的所有对象维护该成员的同一个拷该类的所有对象维护该成员的同一个拷贝贝必须在类外定义和初始化,用必须在类外定义和初始化,用(:)来指明来指明所属的类。所属的类。l静态成员函数f() g()类外代码可以使用类名和作用域操作符类外代码可以使用类名和作用域操作符或者对象名来调用静态成员函数。或者对象名来调用静态成员函数。C+语言程序设计静态成员函数举例静态成员函数举例class A public: static void f(A a); private: int x;void A:f(A a) coutx; /对对x的引用是的引用是错误错误的的 couta.x; /正确正确静态成员C+语言程序设计说明:说明:l静态成员属于类,非静态成员属于对象。静态成员属于类,非静态成员属于对象。l静态成员函数只能引用属于该类的静态静态成员函数只能引用属于该类的静态数据成员或静态成员函数。数据成员或静态成员函数。l由于静态成员不是对象成员,所以在静由于静态成员不是对象成员,所以在静态成员函数态成员函数f()的实现中不能直接引用类的实现中不能直接引用类中声明的非静态成员。中声明的非静态成员。l在在f()的实现中,可以通过对象的实现中,可以通过对象a来访问来访问x-a.x静态成员C+语言程序设计具有静态数据、函数成员的具有静态数据、函数成员的 Point类类#include using namespace std;class Point /Point类声明类声明public:/外部接口外部接口Point(int xx=0, int yy=0) X=xx;Y=yy;countP+;Point(Point &p); /拷贝构造函数拷贝构造函数int GetX() return X;int GetY() return Y;static void GetC() cout Object id=countPendl;private:/私有数据成员私有数据成员int X,Y;static int countP;静态成员C+语言程序设计Point:Point(Point &p) X=p.X;Y=p.Y;countP+;int Point:countP=0;void main() /主函数实现主函数实现 Point A(4,5);/声明对象声明对象AcoutPoint A,A.GetX(),A.GetY();A.GetC(); /输出对象号,对象名引用输出对象号,对象名引用Point B(A);/声明对象声明对象BcoutPoint B,B.GetX(),B.GetY();Point:GetC();/输出对象号,类名引用输出对象号,类名引用32C+语言程序设计例:例:#includeclass Studentpublic: Student(int StudentNo=0) cout“creat one student”; NoOfStudents+;cout“NoOfStudents” NoOfStudents;StudentNo =9022100+NoOfStudents;Cout“StudentNo”StudentNoendl;Student()cout“destruct ont student n”; NoOfStudents-;C+语言程序设计staic int number()return NoOfStudents;protected: static int NoOfStudents; int StuedntNo;int Student:NoOfStudents=0;void fn() Student s1; Student s2; coutStudent:number()endl;void main() fn(); coutStudent:number()endl;C+语言程序设计组合的概念组合的概念l类中的成员数据是另一个类的对象。类中的成员数据是另一个类的对象。l可以在已有的抽象的基础上实现更复杂的抽象。可以在已有的抽象的基础上实现更复杂的抽象。 通过对复杂对象进行分解、抽象,使我们能够将一个复杂对象通过对复杂对象进行分解、抽象,使我们能够将一个复杂对象理解为简单对象的组合。理解为简单对象的组合。 分解得到复杂对象的部件对象,这些部件对象比它高层的复杂分解得到复杂对象的部件对象,这些部件对象比它高层的复杂对象更容易理解和实现。然后由这些部件对象来对象更容易理解和实现。然后由这些部件对象来“装配装配”复杂对象。复杂对象。类 的 组 合C+语言程序设计举例举例class Point private: float x,y; /点的坐标 public: Point(float h,float v); /构造函数 float GetX(void); /取X坐标 float GetY(void); /取Y坐标 void Draw(void); /在(x,y)处画点;/.函数的实现略类 的 组 合C+语言程序设计class Line private: Point p1,p2; /线段的两个端点线段的两个端点 public: Line(Point a,Point b); /构造函数构造函数 Void Draw(void); /画出线段画出线段;/.函数的实现略函数的实现略49C+语言程序设计类组合的构造函数设计类组合的构造函数设计l原则:不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员原则:不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员初始化。初始化。l声明形式:声明形式:类名:类名(对象成员所需的形参,本类成员形参) :内嵌对象1(参数),内嵌对象2(参数),. 本类初始化 Point(int xx=0,int yy=0)x=xx;y=yy;Line:Line( Point a, Point b):p1(a),p2(b) double x=double(p1.GetX()-p2.GetX(); double y=double(p1.GetY()-p2.Gety(); dist=sqrt(x*x+y*y);类 的 组 合C+语言程序设计类组合的构造函数调用类组合的构造函数调用l构造函数调用顺序:先调用内嵌对象构造函数调用顺序:先调用内嵌对象的构造函数(按内嵌时的声明顺序,的构造函数(按内嵌时的声明顺序,先声明者先构造)。然后调用本类的先声明者先构造)。然后调用本类的构造函数。(析构函数的调用顺序相构造函数。(析构函数的调用顺序相反)反)l若调用默认构造函数(即无形参的),若调用默认构造函数(即无形参的),则内嵌对象的初始化也将调用相应的则内嵌对象的初始化也将调用相应的默认构造函数。默认构造函数。类 的 组 合C+语言程序设计类的组合举例(二)类的组合举例(二)class Part /部件类部件类 public: Part(); Part(int i); Part(); void Print(); private: int val;类 的 组 合C+语言程序设计class Whole public: Whole(); Whole(int i,int j,int k); Whole(); void Print(); private: Part one; Part two; int date;53C+语言程序设计Whole:Whole() date=0;Whole:Whole(int i,int j,int k): two(i),one(j),date(k)/.其它函数的实现略其它函数的实现略54C+语言程序设计前向引用声明前向引用声明l类应该先声明,后使用类应该先声明,后使用l如果需要在某个类的声明之前,引用如果需要在某个类的声明之前,引用该类,则应进行前向引用声明。该类,则应进行前向引用声明。l前向引用声明只为程序引入一个标识前向引用声明只为程序引入一个标识符,但具体声明在其它地方。符,但具体声明在其它地方。类 的 组 合C+语言程序设计前向引用声明举例前向引用声明举例class B; /前向引用声明前向引用声明class A public: void f(B b);class B public: void g(A a);类 的 组 合C+语言程序设计前向引用声明注意事项前向引用声明注意事项l使用前向引用声明虽然可以解决一些问题,但它并使用前向引用声明虽然可以解决一些问题,但它并不是万能的。需要注意的是,尽管使用了前向引用不是万能的。需要注意的是,尽管使用了前向引用声明,但是在提供一个完整的类声明之前,不能声声明,但是在提供一个完整的类声明之前,不能声明该类的对象,也不能在内联成员函数中使用该类明该类的对象,也不能在内联成员函数中使用该类的对象。请看下面的程序段:的对象。请看下面的程序段:class Fred;/前向引用声明class Barney Fred x; /错误:类Fred的声明尚不完善 ;class Fred Barney y; ;类 的 组 合Chapter Nine: Pointers and Dynamic Memory指针和动态内存分配指针和动态内存分配C+语言程序设计关于内存地址关于内存地址l内存空间的访问方式内存空间的访问方式通过变量名访问通过地址访问l地址运算符:地址运算符:&例:int var;则&var 表示变量var在内存中的起始地址例:P9AC+语言程序设计声明例: int i;int *i_pointer=&i;如例:P9B 指向整型变量的指针概念指针:指针:内存地址,用于 间接访问内存单元指针变量:指针变量: 用于存放地址的变量20003i_pointer*i_pointeri2000内存用户数据区变量 i变量 j变量 i_pointer362000200020043010引用例1: i=3;例2: *i_pointer=3; 指 针C+语言程序设计解引用运算符解引用运算符*l解引用运算符解引用运算符“*”用于访问指针变量所用于访问指针变量所指向的存储单元的内容。指向的存储单元的内容。l如,如,*ptr为指针变量为指针变量ptr所指向的存储所指向的存储单元中的内容。单元中的内容。l如例:如例:P9CC+语言程序设计l语法形式 存储类型 数据类型 *指针名初始地址;例: int *pa=&a;l注意事项用变量地址作为初值时,该变量必须在指针初始化之前已说明过,且变量类型应与指针类型一致。可以用一个已赋初值的指针去初始化另一 个指针变量。 指 针C+语言程序设计指针变量的赋值运算指针变量的赋值运算指针名指针名=地址地址l“地址地址”中存放的数据类型与指针类型必须中存放的数据类型与指针类型必须相符。相符。l向指针变量赋的值必须是地址常量或变量,向指针变量赋的值必须是地址常量或变量,不能是普通整数。但可以赋值为整数不能是普通整数。但可以赋值为整数0,表,表示空指针。示空指针。l指针的类型是它所指向变量的类型,而不指针的类型是它所指向变量的类型,而不是指针本身数据值的类型,任何一个指针是指针本身数据值的类型,任何一个指针本身的数据值都是本身的数据值都是unsigned long int型。型。l允许声明指向允许声明指向 void 类型的指针。该指针可类型的指针。该指针可以被赋予任何类型对象的地址。以被赋予任何类型对象的地址。例:例: void *general; 指 针C+语言程序设计指针的声明、赋值与使用指针的声明、赋值与使用#includeusing namespace std;void main() int *i_pointer;/声明声明int型指针型指针i_pointerint i;/声明声明int型数型数ii_pointer=&i;/取取i的地址赋给的地址赋给i_pointeri=10;/int型数赋初值型数赋初值coutOutput int i=iendl; /输出输出int型数的值型数的值coutOutput int pointer i=*i_pointerendl; /输出输出int型指针所指地址的内容型指针所指地址的内容 指 针程序运行的结果是:程序运行的结果是:Output int i=10Output int pointer i=10127C+语言程序设计例例 void类型指针的使用类型指针的使用void vobject; /错,不能声明错,不能声明void类型的变量类型的变量void *pv; /对,可以声明对,可以声明void类型的指针类型的指针int *pint; int i;void main() /void类型的函数没有返回值类型的函数没有返回值pv = &i; /void类型指针指向整型变量类型指针指向整型变量 / void指针赋值给指针赋值给int指针需要类型强制转换指针需要类型强制转换: pint = (int *)pv; 指 针C+语言程序设计指向常量的指针指向常量的指针l不能通过指针来改变所指对象的值,但指针本不能通过指针来改变所指对象的值,但指针本身可以改变,可以指向另外的对象身可以改变,可以指向另外的对象。l例例1char *name1 = John; /name1是一般指针*name1=A; /编译正确,运行出错l例例2const char *name1 = John; /指向常量的指针char s=abc;name1=s; /正确,name1本身的值可以改变*name1=1; /编译时指出错误 指 针C+语言程序设计指针类型的常量指针类型的常量l若声明指针常量,则指针本身的值不能若声明指针常量,则指针本身的值不能被改变。例:被改变。例:char *const name2 = John; name2=abc; /错误,指针常量值不能改变C+语言程序设计指针变量的算术运算指针变量的算术运算l指针与整数的加减运算指针与整数的加减运算指针 p 加上或减去 n ,其意义是指针当前指向位置的前方或后方第 n 个数据的地址。这种运算的结果值取决于指针指向的数据类型。l指针加一,减一运算指针加一,减一运算指向下一个或前一个数据。 指 针papa-2pa-1pa+1pa+2pa+3*(pa-2)*pa*(pa+1)*(pa+2)*(pa+3)*(pa-1)short *pa132pb-1pbpb+1pb+2*(pb-1)*pb*(pb+1)*(pb+2)long *pb133C+语言程序设计l关系运算关系运算指向相同类型数据的指针之间可以进行各种关系运算。指向不同数据类型的指针,以及指针与一般整数变量之间的关系运算是无意义的。指针可以和零之间进行等于或不等于的关系运算。例如:p=0或p!=0l赋值运算赋值运算向指针变量赋的值必须是地址常量或变量,不能是普通整数。但可以赋值为整数0,表示空指针。 指 针C+语言程序设计指向数组元素的指针指向数组元素的指针l声明与赋值声明与赋值例: int a10, *pa; pa=&a0; 或 pa=a;l通过指针引用数组元素通过指针引用数组元素经过上述声明及赋值后:*pa就是a0,*(pa+1)就是a1,. ,*(pa+i)就是ai.ai, *(pa+i), *(a+i), pai都是等效的。不能写 a+,因为a是数组首地址是常量。 指 针C+语言程序设计例例设有一个设有一个int型数组型数组a,有有10个元素。用个元素。用三种方法输出各元素:三种方法输出各元素:使用数组名和下标使用数组名和指针运算使用指针变量 指 针main() int a10; int i; for(i=0; iai; coutendl; for(i=0; i10; i+) coutai;使用数组名和下标使用数组名和下标137main() int a10; int i; for(i=0; iai; coutendl; for(i=0; i10; i+) cout*(a+i);使用数组名指针运算使用数组名指针运算使用指针变量使用指针变量main() int a10; int *p,i; for(i=0; iai; coutendl; for(p=a; p(a+10); p+) cout成员名ptr-getx() 相当于 (*ptr).getx(); 指 针C+语言程序设计对象指针应用举例对象指针应用举例int main() Point A(5,10); Point *ptr; ptr=&A; int x; x=ptr-GetX(); coutxX=xx;this-Y=yy; 指 针C+语言程序设计指针变量作为函数实参指针变量作为函数实参l以地址方式传递数据,可以用来返回以地址方式传递数据,可以用来返回函数处理结果。函数处理结果。l例如:例如:P9G。 指针与函数C+语言程序设计例例P9Gvoid swap_vals(float *ptr1,float *ptr2) float temp=*ptr1; *ptr1=*ptr2; *ptr2=temp;main() float num1,num2; cinnum1mum2; swap_vals(&num1,&num2);C+语言程序设计动态申请内存操作符动态申请内存操作符 newnew 类型名类型名T(初值列表)初值列表)功能:功能:在程序执行期间,申请用于存放在程序执行期间,申请用于存放T类型对象的内存空间,并依初值列表类型对象的内存空间,并依初值列表赋以初值。赋以初值。结果值:结果值:成功:成功:T类型的指针,指向新类型的指针,指向新分配的内存。失败:分配的内存。失败:0(NULL) 动态存储分配C+语言程序设计释放内存操作符释放内存操作符deletedelete 指针指针P功能:功能:释放指针释放指针P所指向的内存。所指向的内存。P必须必须是是new操作的返回值。操作的返回值。 动态存储分配C+语言程序设计main() int* int_array ; int no_els, i ; cout no_els ; / Allocate the memory while the program is running. int_array = new intno_els ; / Enter the elements into the array. for ( i = 0; i no_els; i+ ) cout Enter element i int_arrayi ; / Display the array values just entered. for ( i=0; ino_els; i+ ) cout Element i is *( int_array + i ) endl ; delete int_array ; / Free the allocated memory. C+语言程序设计例例 动态创建对象举例动态创建对象举例#includeusing namespace std;class Point public: Point() X=Y=0; coutDefault Constructor called.n; Point(int xx,int yy) X=xx; Y=yy; cout Constructor called.n; Point() coutDestructor called.n; int GetX() return X; int GetY() return Y;void Move(int x,int y) X=x; Y=y; private: int X,Y; 动态存储分配int main() coutStep One:endl; Point *Ptr1=new Point; delete Ptr1; coutStep Two:endl; Ptr1=new Point(1,2); delete Ptr1; return 0;运行结果:运行结果:Step One:Default Constructor called.Destructor called.Step Two:Constructor called.Destructor called.157C+语言程序设计例例 动态创建对象数组举例动态创建对象数组举例#includeusing namespace std;class Point /类的声明同上例,略类的声明同上例,略 ;int main() Point *Ptr=new Point2; /创建对象数组创建对象数组 Ptr0.Move(5,10); /通过指针访问数组元素的成员通过指针访问数组元素的成员 Ptr1.Move(15,20); /通过指针访问数组元素的成员通过指针访问数组元素的成员 coutDeleting.endl; delete Ptr; /删除整个对象数组删除整个对象数组 return 0; 动态存储分配运行结果:运行结果:Default Constructor called.Default Constructor called.Deleting.Destructor called.Destructor called.159C+语言程序设计分配多维数组内存分配多维数组内存main() int no_of_rows, no_of_cols ; int i, j ; float *data ; cout no_of_rows ; cout no_of_cols ; / Allocate requested storage: / (a) allocate storage for the rows. data = new float* no_of_rows ; / (b) allocate storage for each column. for ( j = 0; j no_of_rows; j+ ) dataj = new floatno_of_cols;/ Place some values in the array. for ( i = 0; i no_of_rows; i+ ) for ( j = 0; j no_of_cols; j+ ) dataij = i * 10 + j ; / Display elements of the array. for ( i = 0; i no_of_rows; i+) for ( j = 0; j no_of_cols; j+ ) cout dataij ; cout endl ; / Free the allocated storage: / (a) delete the columns. for ( i = 0; i b; if (b0) int c; . 具有块作用域的变量也称为局部变量。具有块作用域的变量也称为局部变量。c的作用域b的作用域作用域与可见性C+语言程序设计类作用域类作用域l可以将类看成是一组有名成员的集合,除个别可以将类看成是一组有名成员的集合,除个别例外情况外,类作用域作用于特定的成员名,例外情况外,类作用域作用于特定的成员名,类及其对象有特殊的访问和作用域规则。类及其对象有特殊的访问和作用域规则。l例:类例:类X的成员的成员M,在在X的任何一个没有重新声的任何一个没有重新声明变量明变量M的函数成员中都可以访问到的函数成员中都可以访问到M, (1)即)即M在这样的函数起作用,在这样的函数起作用, (2)这样的)这样的M就具有类作用域。就具有类作用域。类的封装作用就在于限制数据的作用域。类的封装作用就在于限制数据的作用域。作用域与可见性C+语言程序设计类作用域类作用域通过表达式x.M或者X:M访问。这是程序中访这是程序中访问对象成员的最基本的方法。问对象成员的最基本的方法。X:M的方式用于访问的方式用于访问类的静态成员。类的静态成员。通过表达式prt-M,其中其中ptr为指向类的一个对为指向类的一个对象的指针。象的指针。作用域与可见性C+语言程序设计命名空间作用域命名空间作用域l一个大型的程序通常由不同模块构成,一个大型的程序通常由不同模块构成,不同的模块甚至有可能是由不同人员不同的模块甚至有可能是由不同人员开发的。不同模块中的类和函数之间开发的。不同模块中的类和函数之间有可能发生重名,这样就会引发错误。有可能发生重名,这样就会引发错误。如南京路(上海的、武汉的)如南京路(上海的、武汉的)作用域与可见性C+语言程序设计命名空间的语法形式:命名空间的语法形式:namespace 命名空间名命名空间名 命名空间内的各种声明(函数声明、类命名空间内的各种声明(函数声明、类声明、声明、) 一个命名空间确定了一个命名空间作一个命名空间确定了一个命名空间作用域,凡是在该命名空间之内声明的、不用域,凡是在该命名空间之内声明的、不属于前面所述各个作用域的标识符都属于属于前面所述各个作用域的标识符都属于该命名空间作用域。在命名空间内部可以该命名空间作用域。在命名空间内部可以直接引用当命名空间中声明的标识符,如直接引用当命名空间中声明的标识符,如果需要引用其他命名空间的标识符,需要果需要引用其他命名空间的标识符,需要使用下面的语法:使用下面的语法: 命名空间名命名空间名:标识符名标识符名 作用域与可见性C+语言程序设计例:例:namespace SomeNs class SomeClass(); ; SomeNs:SomeClass obj1; /声明一个声明一个SomeNs:SomeClass型型的对象的对象obj1 作用域与可见性命名空间作用域命名空间作用域C+语言程序设计 有时,在标识符前总使用这样的命名空间有时,在标识符前总使用这样的命名空间限定会显得过于冗长,为了解决这一问题,限定会显得过于冗长,为了解决这一问题,C+又提供了又提供了using语句,语句,using语句有两种形语句有两种形式:式: using 命名空间名命名空间名:标识符名;标识符名; using namespace 命名空间名;命名空间名; C+标准程序库的所有标识符都被声明在标准程序库的所有标识符都被声明在std命名空间内,如命名空间内,如cin、cout、endl等标识符等标识符皆如此,因此都使用了皆如此,因此都使用了 using namespace std; 如果去掉这条语句,则引用相应的标识符如果去掉这条语句,则引用相应的标识符需要使用需要使用std:cin、std:cout、std:endl这样这样的语句的语句作用域与可见性命名空间作用域命名空间作用域C+语言程序设计命名空间也允许嵌套,如:命名空间也允许嵌套,如:Namespace OuterNs namespace InnerNs class SomeClass; 引用其中的引用其中的SomeClass类,需要使用类,需要使用OuterNs:InnerNs:SomeClass的语法。的语法。作用域与可见性命名空间作用域命名空间作用域C+语言程序设计两类特殊的命名空间两类特殊的命名空间l全局命名空间全局命名空间l匿名命名空间匿名命名空间 全局命名空间是默认的命名空间,在显全局命名空间是默认的命名空间,在显式声明的命名空间之外声明的标识符都在一式声明的命名空间之外声明的标识符都在一个全局命名空间中(文件作用域)。个全局命名空间中(文件作用域)。 匿名命名空间是一个需要显式声明的没匿名命名空间是一个需要显式声明的没有名字的命名空间,声明方式如下:有名字的命名空间,声明方式如下:namespace 匿名命名空间内的各种声明(函数声明、匿名命名空间内的各种声明(函数声明、类声明、类声明、) 作用域与可见性C+语言程序设计 在包含多个源文件的工程中,匿在包含多个源文件的工程中,匿名命名空间常常被用来屏蔽不希望暴名命名空间常常被用来屏蔽不希望暴露给其他源文件的标识符,这是因为露给其他源文件的标识符,这是因为每个源文件的匿名命名空间是彼此不每个源文件的匿名命名空间是彼此不同的,在一个源文件中没有办法访问同的,在一个源文件中没有办法访问其他源文件的匿名命名空间。例:其他源文件的匿名命名空间。例:作用域与可见性命名空间作用域命名空间作用域C+语言程序设计#include using namespace std;int i; /在全局命名空间的全局变量在全局命名空间的全局变量(又称文件作用域又称文件作用域)namespace Ns int j; /在在Ns命名空间中的全局变量命名空间中的全局变量int main() i=5; /为全局变量为全局变量i赋值赋值 Ns:j=6;/为全局变量为全局变量j赋值赋值 using namespace Ns; /使得当前块中可以直接引使得当前块中可以直接引用用Ns命命 名空间的标识符名空间的标识符 int i; /局部变量局部变量 i=7; cout“i=”iendl; /输出输出7 cout“j=”jendl; /输出输出6 cout“i=”iendl; return 0;作用域与可见性具有命名空间作用域的变量也称全局变量C+语言程序设计可见性可见性l程序运行到某一点。能够引用到的标识符,就程序运行到某一点。能够引用到的标识符,就是该处可见的标识符。是该处可见的标识符。l可见性是从对标识符的引用的角度来谈的概念可见性是从对标识符的引用的角度来谈的概念l可见性表示从内层作用域向外层作用域可见性表示从内层作用域向外层作用域“看看”时时能看到什么。能看到什么。l如果标识符在某处可见,则就可以在该处引用如果标识符在某处可见,则就可以在该处引用此标识符。此标识符。块作用域类作用域命名空间作用域作用域与可见性C+语言程序设计作用域、可见性的一般规则作用域、可见性的一般规则l标识符应先声明,后引用。标识符应先声明,后引用。l在同一作用域中,不能声明同名的标识符。在同一作用域中,不能声明同名的标识符。l如果某个标识符在外层中声明,且在内层中如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层没有同一标识符的声明,则该标识符在内层可见。可见。l对于两个嵌套的作用域,如果在内层作用域对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。外层作用域的标识符在内层不可见。作用域与可见性C+语言程序设计例例#includeusing namespace std;int i; /文件作用域 int j=3;int main() i=5; int i; /块作用域 i=7; couti=iendl; /输出7 cout“j=”jendl; /输出输出3 couti=i; /输出5 return 0;作用域与可见性C+语言程序设计对象的生存期对象的生存期对象(包含简单变量)都有诞生和消失对象(包含简单变量)都有诞生和消失的时刻。的时刻。对象从产生到结束的这段时间就是它的对象从产生到结束的这段时间就是它的生存期。生存期。在对象生存期内,对象将保持它状在对象生存期内,对象将保持它状态(即数据成员的值),变量也将保持它的态(即数据成员的值),变量也将保持它的值不变,直到被更新为止。值不变,直到被更新为止。对象的生存期C+语言程序设计静态生存期静态生存期l这种生存期与程序的运行期相同。这种生存期与程序的运行期相同。l在文件作用域中声明的对象具有这种在文件作用域中声明的对象具有这种生存期。生存期。l在函数内部声明静态生存期对象,要在函数内部声明静态生存期对象,要冠以关键字冠以关键字static 。l例:例:static int i;对象的生存期C+语言程序设计#includeusing namespace std;int i=5; /文件作用域int main() static int k=3; couti=iendl; cout“k=”kendl; return 0; i,k具有静态生存期,分配在全局数据区,如果不初始化,它的初始值为0。对象的生存期例例C+语言程序设计动态生存期动态生存期l块作用域中声明的,没有用块作用域中声明的,没有用static修修是的对象是动态生存期的对象(习惯是的对象是动态生存期的对象(习惯称局部生存期对象)。称局部生存期对象)。l开始于程序执行到声明点时,结束于开始于程序执行到声明点时,结束于命名该标识符的作用域结束处。命名该标识符的作用域结束处。对象的生存期C+语言程序设计#includeusing namespace std;void fun();void main() fun(); fun();void fun() static int a=1; int i=5; a+; i+; couti=i,a=aendl;运行结果:i=6, a=2i=6, a=3i是动态生存期a是静态生存期对象的生存期例例C+语言程序设计具有静态、动态生存期对象的时钟程序具有静态、动态生存期对象的时钟程序#includeusing namespace std;class Clock /时钟类声明时钟类声明public:/外部接口外部接口Clock();/构造函数构造函数void SetTime(int NewH, int NewM, int NewS); /三个形参均具有函数原型作用域三个形参均具有函数原型作用域void ShowTime();Clock()/析构函数析构函数private:/私有数据成员私有数据成员int Hour,Minute,Second;对象的生存期/时钟类成员函数实现时钟类成员函数实现Clock:Clock()/构造函数构造函数 Hour=0;Minute=0;Second=0;void Clock:SetTime(int NewH, int NewM, int NewS) Hour=NewH;Minute=NewM;Second=NewS;void Clock:ShowTime() coutHour:Minute:Secondendl;20Clock globClock;/声明对象声明对象globClock, /具有静态生存期,文件作用域具有静态生存期,文件作用域void main() /主函数主函数coutFirst time output:endl;/引用具有文件作用域的对象:引用具有文件作用域的对象:globClock.ShowTime(); /对象的成员函数具有类作用域对象的成员函数具有类作用域globClock.SetTime(8,30,30);Clock myClock(globClock); /声明具有块作用域的对象声明具有块作用域的对象myClock /调用拷贝构造函数,以调用拷贝构造函数,以globClock为初始值为初始值coutSecond time output:endl;myClock.ShowTime(); /引用具有块作用域的对象引用具有块作用域的对象21程序的运行结果为:程序的运行结果为:First time output:0:0:0Second time output:8:30:30 globClock具有静态生存期,与程序的具有静态生存期,与程序的运行期相同,其余都具有动态生存期。运行期相同,其余都具有动态生存期。22C+语言程序设计局部变量局部变量(1)在函数内部定义的变量)在函数内部定义的变量(2)auto可省,在栈中分配可省,在栈中分配(3)只在本函数中使用)只在本函数中使用(4)不初始化,则为任意值)不初始化,则为任意值 数据存储在局部对象中,数据存储在局部对象中,通过参数传递实现共享通过参数传递实现共享 函数间的参数传递函数间的参数传递程序在内存形式程序在内存形式C+语言程序设计全局变量全局变量(1)在所有函数的外部(例)在所有函数的外部(例如在如在main()主函数之前)定主函数之前)定义;义;(2)在程序的每个函数中是)在程序的每个函数中是可见的;可见的;(3)存放在全局数据区内;)存放在全局数据区内;(4)若不初始化则初始化为)若不初始化则初始化为0 程序在内存形式程序在内存形式C+语言程序设计例:例:int n=5;void main() int m=n; void func() int s; n=s; l全局变量可以是各个全局变量可以是各个函数都可以访问的变函数都可以访问的变量,但其可能造成结量,但其可能造成结构不清晰,应尽量少构不清晰,应尽量少用。用。C+语言程序设计l全局变量通常在程序顶部定义,一旦全局变量通常在程序顶部定义,一旦定义在程序的每个函数中是可见的。定义在程序的每个函数中是可见的。也可在程序中间的任何定义,但要在也可在程序中间的任何定义,但要在函数之外全局变量定义前的所有函数函数之外全局变量定义前的所有函数不会知道该变量。不会知道该变量。C+语言程序设计例:例:void main() int m=n; /error: n /无定义无定义 int n; void func() int s; n=s; C+语言程序设计数据存储在全局对象中,不同的函数中都可以访问。例:数据存储在全局对象中,不同的函数中都可以访问。例:#include int global;void f() global=5;void g() coutglobalendl;int main() f(); g(); /输出输出“5” return 0; 这样声明的全局变量没有控制它的可见性范围这样声明的全局变量没有控制它的可见性范围(可见性范围太大)、访问权限。这种共享数据的方(可见性范围太大)、访问权限。这种共享数据的方法不好。法不好。C+语言程序设计常类型常类型 常类型的对象必须进行初始化,而常类型的对象必须进行初始化,而且不能被更新。且不能被更新。l常引用:被引用的对象不能被更新。常引用:被引用的对象不能被更新。const 类型说明符 &引用名l常对象:常对象:必须进行初始化必须进行初始化, ,不能被更新。不能被更新。类名 const 对象名l常数组:数组元素不能被更新。常数组:数组元素不能被更新。类型说明符 const 数组名大小.l常指针:指向常量的指针常指针:指向常量的指针( (下一章介绍下一章介绍) )。C+语言程序设计例例 常引用做形参常引用做形参#includeusing namespace std;void display(const double& r);int main() double d(9.5); display(d); return 0;void display(const double& r)/常引用做形参,在函数中不能更新常引用做形参,在函数中不能更新 r所引用的对象。所引用的对象。 coutrendl; C+语言程序设计常对象举例常对象举例class A public: A(int i,int j) x=i; y=j; . private: int x,y;A const a(3,4); /a是常对象,不能被更新是常对象,不能被更新C+语言程序设计用用const修饰的对象成员修饰的对象成员l常成员函数常成员函数使用const关键字说明的函数。常成员函数不更新对象的数据成员。常成员函数说明格式:类型说明符 函数名(参数表)const;这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。const关键字可以被用于参与对重载函数的区分l通过常对象只能调用它的常成员函数。通过常对象只能调用它的常成员函数。l常数据成员常数据成员使用const说明的数据成员。C+语言程序设计例例 常成员函数举例常成员函数举例#includeusing namespace std;class R public: R(int r1, int r2)R1=r1;R2=r2; void print(); void print() const; private: int R1,R2;void R:print() coutR1:R2endl;void R:print() const coutR1;R2endl;void main() R a(5,4); a.print(); /调用调用void print() const R b(20,52); b.print(); /调用调用void print() const46C+语言程序设计例例 常数据成员举例常数据成员举例#includeusing namespace std;class Apublic:A(int i);void print();const int& r;private:const int a;static const int b; /静态常数据成员静态常数据成员;const int A:b=10; A:A(int i):a(i),r(a) void A:print() couta:b:rendl; void main()/*建立对象建立对象a和和b,并以并以100和和0作为初值,分别调用构造作为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员函数,通过构造函数的初始化列表给对象的常数据成员赋初值赋初值*/ A a1(100),a2(0); a1.print(); a2.print();48C+语言程序设计编译预处理命令编译预处理命令l#include 包含指令包含指令将一个源文件嵌入到当前源文件中该点处。#include l按标准方式搜索,文件位于C+系统目录的include子目录下#include文件名l首先在当前目录中搜索,若没有,再按标准方式搜索。l#define 宏定义指令宏定义指令定义符号常量,很多情况下已被const定义语句取代。定义带参数宏,已被内联函数取代。l#undef删除由#define定义的宏,使之不再起作用。C+语言程序设计条件编译指令条件编译指令 #if #if 和和 # #endifendif#if 常量表达式常量表达式 /当当“ 常量表达式常量表达式”非零时编译非零时编译 程序正文程序正文 #endif. 编译预处理命令C+语言程序设计条件编译指令条件编译指令#else#else #if 常量表达式常量表达式 /当当“ 常量表达式常量表达式”非零时编译非零时编译 程序正文1#else /当“ 常量表达式”为零时编译 程序正文2#endif 编译预处理命令C+语言程序设计条件编译指令条件编译指令 #elif#if 常量表达式常量表达式1 程序正文程序正文1 /当当“ 常量表达式常量表达式1”非零时编译非零时编译#elif 常量表达式常量表达式2 程序正文程序正文2 /当当“ 常量表达式常量表达式2”非零时编译非零时编译#else 程序正文程序正文3 /其它情况下编译其它情况下编译#endif 编译预处理命令C+语言程序设计条件编译指令条件编译指令#ifdef 标识符标识符 程序段程序段1#else 程序段程序段2#endif如果如果“标识符标识符”经经#defined定义过,且未定义过,且未经经undef删除,则编译程序段删除,则编译程序段1,否则编,否则编译程序段译程序段2。 编译预处理命令C+语言程序设计条件编译指令条件编译指令#ifndef 标识符标识符 程序段程序段1#else 程序段程序段2#endif如果如果“标识符标识符”未被定义过,则编译程序未被定义过,则编译程序段段1,否则编译程序段,否则编译程序段2。 编译预处理命令C+语言程序设计多文件结构多文件结构l一个源程序可以划分为多个源文件:一个源程序可以划分为多个源文件:类声明文件(.h文件)类实现文件(.cpp文件)类的使用文件(main()所在的.cpp文件)l利用工程来组合各个文件。利用工程来组合各个文件。C+语言程序设计不使用条件编译的头文件不使用条件编译的头文件/main.cpp#include file1.h#include file2.hvoid main() /file1.h#include head.h /file2.h#include head.h /head.h class Point 多文件结构C+语言程序设计使用条件编译的头文件使用条件编译的头文件/head.h#ifndef HEAD_H #define HEAD_H class Point #endif 多文件结构C+语言程序设计对象数组对象数组l声明:声明:类名 数组名元素个数;l访问方法:访问方法:通过下标访问 数组名下标.成员名C+语言程序设计对象数组初始化对象数组初始化l数组中每一个元素对象被创建时,系统数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。都会调用类构造函数初始化该对象。l通过初始化列表赋值。通过初始化列表赋值。例:Point A2=Point(1,2),Point(3,4);l如果没有为数组元素指定显式初始值,如果没有为数组元素指定显式初始值,数组元素便使用默认值初始化(调用默数组元素便使用默认值初始化(调用默认构造函数)。认构造函数)。C+语言程序设计数组元素所属类的构造函数数组元素所属类的构造函数l不声明构造函数,则采用默认构造函数。不声明构造函数,则采用默认构造函数。l各元素对象的初值要求为相同的值时,各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。可以声明具有默认形参值的构造函数。l各元素对象的初值要求为不同的值时,各元素对象的初值要求为不同的值时,需要声明带形参的构造函数。需要声明带形参的构造函数。l当数组中每一个对象被删除时,系统都当数组中每一个对象被删除时,系统都要调用一次析构函数。要调用一次析构函数。C+语言程序设计例例 对象数组应用举例对象数组应用举例/Point.h#if !defined(_POINT_H)#define _POINT_Hclass Point public: Point(); Point(int xx,int yy); Point(); void Move(int x,int y); int GetX() return X; int GetY() return Y; private: int X,Y;#endifC+语言程序设计/Point.cpp#includeusing namespace std;#include Point.hPoint:Point() X=Y=0; coutDefault Constructor called.endl;Point:Point(int xx,int yy) X=xx; Y=yy; cout Constructor called.endl;Point :Point() coutDestructor called.endl; void Point :Move(int x,int y) X=x; Y=y; 217C+语言程序设计#include#include Point.husing namespace std;int main() coutEntering main.endl; Point A2; for(int i=0;i2;i+) Ai.Move(i+10,i+20); coutExiting main.b; if (b0) int c; . 具有块作用域的变量也称为局部变量。具有块作用域的变量也称为局部变量。c的作用域b的作用域作用域与可见性C+语言程序设计类作用域类作用域l可以将类看成是一组有名成员的集合,除个别可以将类看成是一组有名成员的集合,除个别例外情况外,类作用域作用于特定的成员名,例外情况外,类作用域作用于特定的成员名,类及其对象有特殊的访问和作用域规则。类及其对象有特殊的访问和作用域规则。l例:类例:类X的成员的成员M,在在X的任何一个没有重新声的任何一个没有重新声明变量明变量M的函数成员中都可以访问到的函数成员中都可以访问到M, (1)即)即M在这样的函数起作用,在这样的函数起作用, (2)这样的)这样的M就具有类作用域。就具有类作用域。类的封装作用就在于限制数据的作用域。类的封装作用就在于限制数据的作用域。作用域与可见性C+语言程序设计类作用域类作用域通过表达式x.M或者X:M访问。这是程序中访这是程序中访问对象成员的最基本的方法。问对象成员的最基本的方法。X:M的方式用于访问的方式用于访问类的静态成员。类的静态成员。通过表达式prt-M,其中其中ptr为指向类的一个对为指向类的一个对象的指针。象的指针。作用域与可见性C+语言程序设计命名空间作用域命名空间作用域l一个大型的程序通常由不同模块构成,一个大型的程序通常由不同模块构成,不同的模块甚至有可能是由不同人员不同的模块甚至有可能是由不同人员开发的。不同模块中的类和函数之间开发的。不同模块中的类和函数之间有可能发生重名,这样就会引发错误。有可能发生重名,这样就会引发错误。如南京路(上海的、武汉的)如南京路(上海的、武汉的)作用域与可见性C+语言程序设计命名空间的语法形式:命名空间的语法形式:namespace 命名空间名命名空间名 命名空间内的各种声明(函数声明、类命名空间内的各种声明(函数声明、类声明、声明、) 一个命名空间确定了一个命名空间作一个命名空间确定了一个命名空间作用域,凡是在该命名空间之内声明的、不用域,凡是在该命名空间之内声明的、不属于前面所述各个作用域的标识符都属于属于前面所述各个作用域的标识符都属于该命名空间作用域。在命名空间内部可以该命名空间作用域。在命名空间内部可以直接引用当命名空间中声明的标识符,如直接引用当命名空间中声明的标识符,如果需要引用其他命名空间的标识符,需要果需要引用其他命名空间的标识符,需要使用下面的语法:使用下面的语法: 命名空间名命名空间名:标识符名标识符名 作用域与可见性C+语言程序设计例:例:namespace SomeNs class SomeClass(); ; SomeNs:SomeClass obj1; /声明一个声明一个SomeNs:SomeClass型型的对象的对象obj1 作用域与可见性命名空间作用域命名空间作用域C+语言程序设计 有时,在标识符前总使用这样的命名空间有时,在标识符前总使用这样的命名空间限定会显得过于冗长,为了解决这一问题,限定会显得过于冗长,为了解决这一问题,C+又提供了又提供了using语句,语句,using语句有两种形语句有两种形式:式: using 命名空间名命名空间名:标识符名;标识符名; using namespace 命名空间名;命名空间名; C+标准程序库的所有标识符都被声明在标准程序库的所有标识符都被声明在std命名空间内,如命名空间内,如cin、cout、endl等标识符等标识符皆如此,因此都使用了皆如此,因此都使用了 using namespace std; 如果去掉这条语句,则引用相应的标识符如果去掉这条语句,则引用相应的标识符需要使用需要使用std:cin、std:cout、std:endl这样这样的语句的语句作用域与可见性命名空间作用域命名空间作用域C+语言程序设计命名空间也允许嵌套,如:命名空间也允许嵌套,如:Namespace OuterNs namespace InnerNs class SomeClass; 引用其中的引用其中的SomeClass类,需要使用类,需要使用OuterNs:InnerNs:SomeClass的语法。的语法。作用域与可见性命名空间作用域命名空间作用域C+语言程序设计两类特殊的命名空间两类特殊的命名空间l全局命名空间全局命名空间l匿名命名空间匿名命名空间 全局命名空间是默认的命名空间,在显全局命名空间是默认的命名空间,在显式声明的命名空间之外声明的标识符都在一式声明的命名空间之外声明的标识符都在一个全局命名空间中(文件作用域)。个全局命名空间中(文件作用域)。 匿名命名空间是一个需要显式声明的没匿名命名空间是一个需要显式声明的没有名字的命名空间,声明方式如下:有名字的命名空间,声明方式如下:namespace 匿名命名空间内的各种声明(函数声明、匿名命名空间内的各种声明(函数声明、类声明、类声明、) 作用域与可见性C+语言程序设计 在包含多个源文件的工程中,匿在包含多个源文件的工程中,匿名命名空间常常被用来屏蔽不希望暴名命名空间常常被用来屏蔽不希望暴露给其他源文件的标识符,这是因为露给其他源文件的标识符,这是因为每个源文件的匿名命名空间是彼此不每个源文件的匿名命名空间是彼此不同的,在一个源文件中没有办法访问同的,在一个源文件中没有办法访问其他源文件的匿名命名空间。例:其他源文件的匿名命名空间。例:作用域与可见性命名空间作用域命名空间作用域C+语言程序设计#include using namespace std;int i; /在全局命名空间的全局变量在全局命名空间的全局变量(又称文件作用域又称文件作用域)namespace Ns int j; /在在Ns命名空间中的全局变量命名空间中的全局变量int main() i=5; /为全局变量为全局变量i赋值赋值 Ns:j=6;/为全局变量为全局变量j赋值赋值 using namespace Ns; /使得当前块中可以直接引使得当前块中可以直接引用用Ns命命 名空间的标识符名空间的标识符 int i; /局部变量局部变量 i=7; cout“i=”iendl; /输出输出7 cout“j=”jendl; /输出输出6 cout“i=”iendl; return 0;作用域与可见性具有命名空间作用域的变量也称全局变量C+语言程序设计可见性可见性l程序运行到某一点。能够引用到的标识符,就程序运行到某一点。能够引用到的标识符,就是该处可见的标识符。是该处可见的标识符。l可见性是从对标识符的引用的角度来谈的概念可见性是从对标识符的引用的角度来谈的概念l可见性表示从内层作用域向外层作用域可见性表示从内层作用域向外层作用域“看看”时时能看到什么。能看到什么。l如果标识符在某处可见,则就可以在该处引用如果标识符在某处可见,则就可以在该处引用此标识符。此标识符。块作用域类作用域命名空间作用域作用域与可见性C+语言程序设计作用域、可见性的一般规则作用域、可见性的一般规则l标识符应先声明,后引用。标识符应先声明,后引用。l在同一作用域中,不能声明同名的标识符。在同一作用域中,不能声明同名的标识符。l如果某个标识符在外层中声明,且在内层中如果某个标识符在外层中声明,且在内层中没有同一标识符的声明,则该标识符在内层没有同一标识符的声明,则该标识符在内层可见。可见。l对于两个嵌套的作用域,如果在内层作用域对于两个嵌套的作用域,如果在内层作用域内声明了与外层作用域中同名的标识符,则内声明了与外层作用域中同名的标识符,则外层作用域的标识符在内层不可见。外层作用域的标识符在内层不可见。作用域与可见性C+语言程序设计例例#includeusing namespace std;int i; /文件作用域 int j=3;int main() i=5; int i; /块作用域 i=7; couti=iendl; /输出7 cout“j=”jendl; /输出输出3 couti=i; /输出5 return 0;作用域与可见性C+语言程序设计对象的生存期对象的生存期对象(包含简单变量)都有诞生和消失对象(包含简单变量)都有诞生和消失的时刻。的时刻。对象从产生到结束的这段时间就是它的对象从产生到结束的这段时间就是它的生存期。生存期。在对象生存期内,对象将保持它状在对象生存期内,对象将保持它状态(即数据成员的值),变量也将保持它的态(即数据成员的值),变量也将保持它的值不变,直到被更新为止。值不变,直到被更新为止。对象的生存期C+语言程序设计静态生存期静态生存期l这种生存期与程序的运行期相同。这种生存期与程序的运行期相同。l在文件作用域中声明的对象具有这种在文件作用域中声明的对象具有这种生存期。生存期。l在函数内部声明静态生存期对象,要在函数内部声明静态生存期对象,要冠以关键字冠以关键字static 。l例:例:static int i;对象的生存期C+语言程序设计#includeusing namespace std;int i=5; /文件作用域int main() static int k=3; couti=iendl; cout“k=”kendl; return 0; i,k具有静态生存期,分配在全局数据区,如果不初始化,它的初始值为0。对象的生存期例例C+语言程序设计动态生存期动态生存期l块作用域中声明的,没有用块作用域中声明的,没有用static修修是的对象是动态生存期的对象(习惯是的对象是动态生存期的对象(习惯称局部生存期对象)。称局部生存期对象)。l开始于程序执行到声明点时,结束于开始于程序执行到声明点时,结束于命名该标识符的作用域结束处。命名该标识符的作用域结束处。对象的生存期C+语言程序设计#includeusing namespace std;void fun();void main() fun(); fun();void fun() static int a=1; int i=5; a+; i+; couti=i,a=aendl;运行结果:i=6, a=2i=6, a=3i是动态生存期a是静态生存期对象的生存期例例C+语言程序设计具有静态、动态生存期对象的时钟程序具有静态、动态生存期对象的时钟程序#includeusing namespace std;class Clock /时钟类声明时钟类声明public:/外部接口外部接口Clock();/构造函数构造函数void SetTime(int NewH, int NewM, int NewS); /三个形参均具有函数原型作用域三个形参均具有函数原型作用域void ShowTime();Clock()/析构函数析构函数private:/私有数据成员私有数据成员int Hour,Minute,Second;对象的生存期/时钟类成员函数实现时钟类成员函数实现Clock:Clock()/构造函数构造函数 Hour=0;Minute=0;Second=0;void Clock:SetTime(int NewH, int NewM, int NewS) Hour=NewH;Minute=NewM;Second=NewS;void Clock:ShowTime() coutHour:Minute:Secondendl;20Clock globClock;/声明对象声明对象globClock, /具有静态生存期,文件作用域具有静态生存期,文件作用域void main() /主函数主函数coutFirst time output:endl;/引用具有文件作用域的对象:引用具有文件作用域的对象:globClock.ShowTime(); /对象的成员函数具有类作用域对象的成员函数具有类作用域globClock.SetTime(8,30,30);Clock myClock(globClock); /声明具有块作用域的对象声明具有块作用域的对象myClock /调用拷贝构造函数,以调用拷贝构造函数,以globClock为初始值为初始值coutSecond time output:endl;myClock.ShowTime(); /引用具有块作用域的对象引用具有块作用域的对象21程序的运行结果为:程序的运行结果为:First time output:0:0:0Second time output:8:30:30 globClock具有静态生存期,与程序的具有静态生存期,与程序的运行期相同,其余都具有动态生存期。运行期相同,其余都具有动态生存期。22C+语言程序设计局部变量局部变量(1)在函数内部定义的变量)在函数内部定义的变量(2)auto可省,在栈中分配可省,在栈中分配(3)只在本函数中使用)只在本函数中使用(4)不初始化,则为任意值)不初始化,则为任意值 数据存储在局部对象中,数据存储在局部对象中,通过参数传递实现共享通过参数传递实现共享 函数间的参数传递函数间的参数传递程序在内存形式程序在内存形式C+语言程序设计全局变量全局变量(1)在所有函数的外部(例)在所有函数的外部(例如在如在main()主函数之前)定主函数之前)定义;义;(2)在程序的每个函数中是)在程序的每个函数中是可见的;可见的;(3)存放在全局数据区内;)存放在全局数据区内;(4)若不初始化则初始化为)若不初始化则初始化为0 程序在内存形式程序在内存形式C+语言程序设计例:例:int n=5;void main() int m=n; void func() int s; n=s; l全局变量可以是各个全局变量可以是各个函数都可以访问的变函数都可以访问的变量,但其可能造成结量,但其可能造成结构不清晰,应尽量少构不清晰,应尽量少用。用。C+语言程序设计l全局变量通常在程序顶部定义,一旦全局变量通常在程序顶部定义,一旦定义在程序的每个函数中是可见的。定义在程序的每个函数中是可见的。也可在程序中间的任何定义,但要在也可在程序中间的任何定义,但要在函数之外全局变量定义前的所有函数函数之外全局变量定义前的所有函数不会知道该变量。不会知道该变量。C+语言程序设计例:例:void main() int m=n; /error: n /无定义无定义 int n; void func() int s; n=s; C+语言程序设计数据存储在全局对象中,不同的函数中都可以访问。例:数据存储在全局对象中,不同的函数中都可以访问。例:#include int global;void f() global=5;void g() coutglobalendl;int main() f(); g(); /输出输出“5” return 0; 这样声明的全局变量没有控制它的可见性范围这样声明的全局变量没有控制它的可见性范围(可见性范围太大)、访问权限。这种共享数据的方(可见性范围太大)、访问权限。这种共享数据的方法不好。法不好。C+语言程序设计常类型常类型 常类型的对象必须进行初始化,而常类型的对象必须进行初始化,而且不能被更新。且不能被更新。l常引用:被引用的对象不能被更新。常引用:被引用的对象不能被更新。const 类型说明符 &引用名l常对象:常对象:必须进行初始化必须进行初始化, ,不能被更新。不能被更新。类名 const 对象名l常数组:数组元素不能被更新。常数组:数组元素不能被更新。类型说明符 const 数组名大小.l常指针:指向常量的指针常指针:指向常量的指针( (下一章介绍下一章介绍) )。C+语言程序设计例例 常引用做形参常引用做形参#includeusing namespace std;void display(const double& r);int main() double d(9.5); display(d); return 0;void display(const double& r)/常引用做形参,在函数中不能更新常引用做形参,在函数中不能更新 r所引用的对象。所引用的对象。 coutrendl; C+语言程序设计常对象举例常对象举例class A public: A(int i,int j) x=i; y=j; . private: int x,y;A const a(3,4); /a是常对象,不能被更新是常对象,不能被更新C+语言程序设计用用const修饰的对象成员修饰的对象成员l常成员函数常成员函数使用const关键字说明的函数。常成员函数不更新对象的数据成员。常成员函数说明格式:类型说明符 函数名(参数表)const;这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。const关键字可以被用于参与对重载函数的区分l通过常对象只能调用它的常成员函数。通过常对象只能调用它的常成员函数。l常数据成员常数据成员使用const说明的数据成员。C+语言程序设计例例 常成员函数举例常成员函数举例#includeusing namespace std;class R public: R(int r1, int r2)R1=r1;R2=r2; void print(); void print() const; private: int R1,R2;void R:print() coutR1:R2endl;void R:print() const coutR1;R2endl;void main() R a(5,4); a.print(); /调用调用void print() const R b(20,52); b.print(); /调用调用void print() const46C+语言程序设计例例 常数据成员举例常数据成员举例#includeusing namespace std;class Apublic:A(int i);void print();const int& r;private:const int a;static const int b; /静态常数据成员静态常数据成员;const int A:b=10; A:A(int i):a(i),r(a) void A:print() couta:b:rendl; void main()/*建立对象建立对象a和和b,并以并以100和和0作为初值,分别调用构造作为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员函数,通过构造函数的初始化列表给对象的常数据成员赋初值赋初值*/ A a1(100),a2(0); a1.print(); a2.print();48C+语言程序设计编译预处理命令编译预处理命令l#include 包含指令包含指令将一个源文件嵌入到当前源文件中该点处。#include l按标准方式搜索,文件位于C+系统目录的include子目录下#include文件名l首先在当前目录中搜索,若没有,再按标准方式搜索。l#define 宏定义指令宏定义指令定义符号常量,很多情况下已被const定义语句取代。定义带参数宏,已被内联函数取代。l#undef删除由#define定义的宏,使之不再起作用。C+语言程序设计条件编译指令条件编译指令 #if #if 和和 # #endifendif#if 常量表达式常量表达式 /当当“ 常量表达式常量表达式”非零时编译非零时编译 程序正文程序正文 #endif. 编译预处理命令C+语言程序设计条件编译指令条件编译指令#else#else #if 常量表达式常量表达式 /当当“ 常量表达式常量表达式”非零时编译非零时编译 程序正文1#else /当“ 常量表达式”为零时编译 程序正文2#endif 编译预处理命令C+语言程序设计条件编译指令条件编译指令 #elif#if 常量表达式常量表达式1 程序正文程序正文1 /当当“ 常量表达式常量表达式1”非零时编译非零时编译#elif 常量表达式常量表达式2 程序正文程序正文2 /当当“ 常量表达式常量表达式2”非零时编译非零时编译#else 程序正文程序正文3 /其它情况下编译其它情况下编译#endif 编译预处理命令C+语言程序设计条件编译指令条件编译指令#ifdef 标识符标识符 程序段程序段1#else 程序段程序段2#endif如果如果“标识符标识符”经经#defined定义过,且未定义过,且未经经undef删除,则编译程序段删除,则编译程序段1,否则编,否则编译程序段译程序段2。 编译预处理命令C+语言程序设计条件编译指令条件编译指令#ifndef 标识符标识符 程序段程序段1#else 程序段程序段2#endif如果如果“标识符标识符”未被定义过,则编译程序未被定义过,则编译程序段段1,否则编译程序段,否则编译程序段2。 编译预处理命令C+语言程序设计多文件结构多文件结构l一个源程序可以划分为多个源文件:一个源程序可以划分为多个源文件:类声明文件(.h文件)类实现文件(.cpp文件)类的使用文件(main()所在的.cpp文件)l利用工程来组合各个文件。利用工程来组合各个文件。C+语言程序设计不使用条件编译的头文件不使用条件编译的头文件/main.cpp#include file1.h#include file2.hvoid main() /file1.h#include head.h /file2.h#include head.h /head.h class Point 多文件结构C+语言程序设计使用条件编译的头文件使用条件编译的头文件/head.h#ifndef HEAD_H #define HEAD_H class Point #endif 多文件结构C+语言程序设计对象数组对象数组l声明:声明:类名 数组名元素个数;l访问方法:访问方法:通过下标访问 数组名下标.成员名C+语言程序设计对象数组初始化对象数组初始化l数组中每一个元素对象被创建时,系统数组中每一个元素对象被创建时,系统都会调用类构造函数初始化该对象。都会调用类构造函数初始化该对象。l通过初始化列表赋值。通过初始化列表赋值。例:Point A2=Point(1,2),Point(3,4);l如果没有为数组元素指定显式初始值,如果没有为数组元素指定显式初始值,数组元素便使用默认值初始化(调用默数组元素便使用默认值初始化(调用默认构造函数)。认构造函数)。C+语言程序设计数组元素所属类的构造函数数组元素所属类的构造函数l不声明构造函数,则采用默认构造函数。不声明构造函数,则采用默认构造函数。l各元素对象的初值要求为相同的值时,各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。可以声明具有默认形参值的构造函数。l各元素对象的初值要求为不同的值时,各元素对象的初值要求为不同的值时,需要声明带形参的构造函数。需要声明带形参的构造函数。l当数组中每一个对象被删除时,系统都当数组中每一个对象被删除时,系统都要调用一次析构函数。要调用一次析构函数。C+语言程序设计例例 对象数组应用举例对象数组应用举例/Point.h#if !defined(_POINT_H)#define _POINT_Hclass Point public: Point(); Point(int xx,int yy); Point(); void Move(int x,int y); int GetX() return X; int GetY() return Y; private: int X,Y;#endifC+语言程序设计/Point.cpp#includeusing namespace std;#include Point.hPoint:Point() X=Y=0; coutDefault Constructor called.endl;Point:Point(int xx,int yy) X=xx; Y=yy; cout Constructor called.endl;Point :Point() coutDestructor called.endl; void Point :Move(int x,int y) X=x; Y=y; 275C+语言程序设计#include#include Point.husing namespace std;int main() coutEntering main.endl; Point A2; for(int i=0;i2;i+) Ai.Move(i+10,i+20); coutExiting main.endl; return 0;276C+语言程序设计运行结果:运行结果:Entering main.Default Constructor called.Default Constructor called.Exiting main.Destructor called.Destructor called.277Chapter Eleven Inheritance第十一章第十一章 继承继承C+语言程序设计C+语言程序设计Chapter Eleven:Inheritance(继承)(继承)lWhat is inheritance? (什么是继承(什么是继承?) lInheritance syntax(继承语法)(继承语法)lPassing arguments to a base class constructor(向基类的构造函数传递实参)(向基类的构造函数传递实参)lProtected class members(受保护的类成员)(受保护的类成员)lTypes of inheritance: public, protected and private(继承的类型:(继承的类型:public、protected和和private) lComposition(组合)(组合) lMultiple inheritance(多重继承)(多重继承) lVirtual base classes(虚基类)(虚基类) C+语言程序设计类的继承与派生类的继承与派生l保持已有类的特性而构造新类的过程保持已有类的特性而构造新类的过程称为继承。称为继承。l这个新的类拥有或继承已有类的数据这个新的类拥有或继承已有类的数据成员和成员函数,也可添加新的数据成员和成员函数,也可添加新的数据成员和成员函数,对已有的类进行扩成员和成员函数,对已有的类进行扩充。充。l这个已有的类就称为基类,而这个新这个已有的类就称为基类,而这个新类则称为派生类。类则称为派生类。l如如P276中的中的Figure11.1类的继承与派生C+语言程序设计继承与派生问题举例继承与派生问题举例类的继承与派生C+语言程序设计继承与派生问题举例继承与派生问题举例类的继承与派生C+语言程序设计继承与派生问题举例继承与派生问题举例类的继承与派生C+语言程序设计继承与派生问题举例继承与派生问题举例类的继承与派生多继承单继承C+语言程序设计继承与派生的目的继承与派生的目的l继承的目的:实现代码重用。继承的目的:实现代码重用。l派生的目的:当新的问题出现,原有派生的目的:当新的问题出现,原有程序无法解决(或不能完全解决)时,程序无法解决(或不能完全解决)时,需要对原有程序进行改造。需要对原有程序进行改造。l如如p277中的中的Figure11.2类的继承与派生C+语言程序设计Inheritance syntax(继承语法继承语法)class base/ class data members/ claa member functions;class inherited:public base/ additional class data members for this class/ additional class member functions for this class;C+语言程序设计派生类的声明(单继承)派生类的声明(单继承)class 派生类名:派生类名:继承方式继承方式 基类名基类名 成员声明;成员声明;一个派生类只有一个直接基类。一个派生类只有一个直接基类。例例P278的的P11A.cpp类的继承与派生C+语言程序设计l基类的构造函数总是在派生类的构造基类的构造函数总是在派生类的构造函数之前被调用。函数之前被调用。l析构函数的调用次序正好相反;基类析构函数的调用次序正好相反;基类的析构函数总是在派生类的析构函数的析构函数总是在派生类的析构函数被调用之后调用。被调用之后调用。C+语言程序设计employeeemployee numberemployee surnaeemployee forenamecurrently employedpart-time employeehourly rate of payfull-time employeeannual salaryannual leavemanagerannual bonusEmployee hierarchy如P281的例P11B.cppC+语言程序设计Passing arguments to a base class constructor向基类的构造函数传递实参向基类的构造函数传递实参如例如例P11C.cppemployee : : employee ( unsigned int number ,string sname , string fname ) employee_number = number ; currently_employed = ture ; surname = sname ; forename = fname ;C+语言程序设计part_time : : part_time ( unsigned int number ,string sname , string fname ,double rate ) : employee ( number , sname , fname ) , hourly_rate (rate)Passing arguments to a base class constructor向基类的构造函数传递实参向基类的构造函数传递实参C+语言程序设计full_time : : full_time ( unsigned int number ,string sname , string fname ,double salary, int leave ) : employee ( number , sname , fname ) , annual_salary (salary) , annual_leave(leave)manager : : manager ( unsigned int number ,string sname , string fname ,double salary, int leave, double annual_bonus ): full_time( number ,sname ,fname ,salary ,leave ) , bonus(annual_bonus)Passing arguments to a base class constructor向基类的构造函数传递实参向基类的构造函数传递实参C+语言程序设计 基类总是在派生类初始化数据成员之基类总是在派生类初始化数据成员之前调用自己的构造函数来初始化基类数据前调用自己的构造函数来初始化基类数据成员。初始化列表中项的实际顺序并不重成员。初始化列表中项的实际顺序并不重要。要。part_time : : part_time ( unsigned int number ,string sname , string fname ,double rate ) : hourly_rate (rate),),employee ( number , sname , fname ) Passing arguments to a base class constructor向基类的构造函数传递实参向基类的构造函数传递实参C+语言程序设计11.4Protected class members(受保护的类成员受保护的类成员)l关键字关键字private和和public用来控制对类用来控制对类的数据成员和成员函数的访问。的数据成员和成员函数的访问。l派生类的成员函数不能访问基类的私派生类的成员函数不能访问基类的私有数据成员和成员函数,从而保证了有数据成员和成员函数,从而保证了信息隐藏的原则。信息隐藏的原则。C+语言程序设计l基类和派生类之间共享数据有两种方法:基类和派生类之间共享数据有两种方法:一种是把相关的一种是把相关的private成员的访问控制级成员的访问控制级别修改为别修改为public,但这样导致数据可以被,但这样导致数据可以被程序中的任何部分随意地访问和修改,这程序中的任何部分随意地访问和修改,这又违背了信息隐藏的原则。又违背了信息隐藏的原则。 另一种把另一种把private改为改为protected,它允许派它允许派生类(并且只允许派生类)访问被指定为生类(并且只允许派生类)访问被指定为protected的基类成员。如例的基类成员。如例P11D.cpp注意:注意:Figure 11.4 11.4Protected class members(受保护的类成员受保护的类成员)C+语言程序设计11.5 Types of inheritance: public, protected and privatel不同继承类型的影响主要体现在:不同继承类型的影响主要体现在:派生类中的新增成员新增成员对基类成员的访问权限通过派生类对象对象对基类成员的访问权限l三种继承类型三种继承类型公有继承(public)私有继承(private)保护继承(protected)类成员的访问控制C+语言程序设计公有继承公有继承(public)l基类的基类的public和和protected成员的访问属成员的访问属性在派生类中性在派生类中保持不变保持不变,但,但基类的基类的private成员成员不可不可直接直接访问访问。l派生类中的派生类中的成员函数成员函数可以直接访问基类可以直接访问基类中的中的public和和protected成员,但不能成员,但不能直接访问基类的直接访问基类的private成员。成员。l通过派生类的通过派生类的对象对象只能访问基类的只能访问基类的public成员。成员。类成员的访问控制C+语言程序设计例例 公有继承举例公有继承举例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;类成员的访问控制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;299#include#includeusing namecpace std;int main() Rectangle rect;rect.InitR(2,3,20,10); /通过派生类对象访问基类公有成员通过派生类对象访问基类公有成员rect.Move(3,2); coutrect.GetX(), rect.GetY(),rect.GetW(),rect.GetH()endl;return 0;300C+语言程序设计私有继承私有继承(private)l基类的基类的public和和protected成员都以成员都以private身份出现在派生类中,但基类身份出现在派生类中,但基类的的private成员成员不可直接访问不可直接访问。l派生类中的派生类中的成员函数成员函数可以直接访问基可以直接访问基类中的类中的public和和protected成员,但不成员,但不能直接访问基类的能直接访问基类的private成员。成员。l通过派生类的通过派生类的对象对象不能直接访问基类不能直接访问基类中的任何成员。中的任何成员。类成员的访问控制C+语言程序设计例例 私有继承举例私有继承举例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;类成员的访问控制#include#includeusing namecpace std;int main() /通过派生类对象只能访问本类成员通过派生类对象只能访问本类成员 Rectangle rect;rect.InitR(2,3,20,10);rect.Move(3,2);coutrect.GetX(), rect.GetY(),rect.GetW(),rect.GetH()endl;return 0;303C+语言程序设计保护继承保护继承(protected)l基类的基类的public和和protected成员都以成员都以protected身份出现身份出现在派生类中,但在派生类中,但基类的基类的private成员成员不可直接访问不可直接访问。l派生类中的成员函数可以直接访问基派生类中的成员函数可以直接访问基类中的类中的public和和protected成员,但不成员,但不能直接访问基类的能直接访问基类的private成员。成员。l通过派生类的对象不能直接访问基类通过派生类的对象不能直接访问基类中的任何成员中的任何成员类成员的访问控制C+语言程序设计protected 成员的特点与作用成员的特点与作用l对建立其所在类对象的模块来说对建立其所在类对象的模块来说(水平水平访问时访问时),它与,它与 private 成员的性质相成员的性质相同。同。l对于其派生类来说对于其派生类来说(垂直访问时垂直访问时),它,它与与 public 成员的性质相同。成员的性质相同。l既实现了数据隐藏,又方便继承,实既实现了数据隐藏,又方便继承,实现代码重用。现代码重用。类成员的访问控制C+语言程序设计例例 protected 成员举例成员举例class A protected: int x;int main() A a; a.x=5; /错误错误类成员的访问控制class A protected: int x;class B: public A public: void Function();void B:Function() x=5; /正确正确307void main()B b; b.x=3;/错误错误C+语言程序设计11.6 Composition(组合组合)l类中的成员数据是另一个类的对象。类中的成员数据是另一个类的对象。l可以在已有的抽象的基础上实现更复杂的抽象。可以在已有的抽象的基础上实现更复杂的抽象。 通过对复杂对象进行分解、抽象,使我们能够将一个复杂对象通过对复杂对象进行分解、抽象,使我们能够将一个复杂对象理解为简单对象的组合。理解为简单对象的组合。 分解得到复杂对象的部件对象,这些部件对象比它高层的复杂分解得到复杂对象的部件对象,这些部件对象比它高层的复杂对象更容易理解和实现。然后由这些部件对象来对象更容易理解和实现。然后由这些部件对象来“装配装配”复杂对象复杂对象例:例:P297中的中的P11E.cppC+语言程序设计基类与派生类的对应关系基类与派生类的对应关系l单继承单继承派生类只从一个基类派生。l多继承多继承派生类从多个基类派生。l多重派生多重派生由一个基类派生出多个不同的派生类。l多层派生多层派生派生类又作为基类,继续派生新的类。单继承与多继承C+语言程序设计派生类的声明(多继承)派生类的声明(多继承)class 派生类名:派生类名:继承方式继承方式 基类名基类名1,继承方式,继承方式 基类名基类名2, 成员声明;成员声明;一个派生类可以同时有多个基类。一个派生类可以同时有多个基类。如:如:class Dr1:public Base1,private Base2 public: Dr1(); Dr1(); 类的继承与派生C+语言程序设计多继承时派生类的声明多继承时派生类的声明class 派生类名:继承方式派生类名:继承方式1 基类名基类名1,继承方式继承方式2 基类名基类名2,. 成员声明;成员声明;注意:每一个注意:每一个“继承方式继承方式”,只用于限制对,只用于限制对紧随其后之基类的继承。紧随其后之基类的继承。单继承与多继承C+语言程序设计多继承举例多继承举例class A public: void setA(int); void showA(); private: int a;class B public: void setB(int); void showB();private: int b;class C : public A, private B public: void setC(int, int, int); void showC(); private: int c;单继承与多继承void A:setA(int x) a=x; void B:setB(int x) b=x; void C:setC(int x, int y, int z) /派生类成员直接访问基类的派生类成员直接访问基类的 /公有成员公有成员 setA(x); setB(y); c=z;/其它函数实现略其它函数实现略int main() C obj; obj.setA(5); obj.showA(); obj.setC(6,7,9); obj.showC();/ obj.setB(6); 错误错误/ obj.showB(); 错误错误 return 0;313C+语言程序设计继承时的构造函数继承时的构造函数l基类的构造函数不被继承,派生类中需要基类的构造函数不被继承,派生类中需要声明自己的构造函数。声明自己的构造函数。l声明构造函数时,只需要对本类中新增成声明构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成。始化,自动调用基类构造函数完成。l派生类的构造函数需要给基类的构造函数派生类的构造函数需要给基类的构造函数传递参数传递参数派生类的构造、析构函数C+语言程序设计单一继承时的构造函数单一继承时的构造函数派生类名派生类名:派生类名派生类名(基类所需的形参,基类所需的形参,本类成员所需的形参本类成员所需的形参):基类名基类名(参数表参数表) 本类成员初始化赋值语句;本类成员初始化赋值语句;派生类的构造、析构函数C+语言程序设计单一继承时的构造函数举例单一继承时的构造函数举例#includeusing namecpace std;class B public: B(); B(int i); B(); void Print() const; private: int b;派生类的构造、析构函数B:B() b=0;coutBs default constructor called.endl;B:B(int i) b=i; coutBs constructor called. endl;B:B() coutBs destructor called.endl; void B:Print() const coutbendl; 317class C:public B public: C(); C(int i,int j); C(); void Print() const;private: int c;318C:C() c=0;coutCs default constructor called.endl;C:C(int i,int j):B(i) c=j;coutCs constructor called.endl;C:C() coutCs destructor called.endl; void C:Print() const B:Print();coutcendl; void main() C obj(5,6);obj.Print(); 319C+语言程序设计多继承时的构造函数多继承时的构造函数派生类名派生类名:派生类名派生类名(基类基类1形参,形参,基类基类2形形参,参,.基类基类n形参形参,本类形参,本类形参):基类名基类名1(参参数数), 基类名基类名2(参数参数), .基类名基类名n(参数参数) 本类成员初始化赋值语句;本类成员初始化赋值语句;派生类的构造、析构函数C+语言程序设计派生类与基类的构造函数派生类与基类的构造函数l当基类中声明有默认形式的构造函数或当基类中声明有默认形式的构造函数或未声明构造函数时,派生类构造函数可未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数。以不向基类构造函数传递参数。l若基类中未声明构造函数,派生类中也若基类中未声明构造函数,派生类中也可以不声明,全采用缺省形式构造函数。可以不声明,全采用缺省形式构造函数。l当基类声明有带形参的构造函数时,派当基类声明有带形参的构造函数时,派生类也应声明带形参的构造函数,并将生类也应声明带形参的构造函数,并将参数传递给基类构造函数。参数传递给基类构造函数。派生类的构造、析构函数C+语言程序设计多继承且有内嵌对象时多继承且有内嵌对象时的构造函数的构造函数派生类名派生类名:派生类名派生类名(基类基类1形参,基类形参,基类2形参,形参,.基类基类n形参,本类形参形参,本类形参):基类基类名名1(参数参数), 基类名基类名2(参数参数), .基类名基类名n(参数参数),对象数据成员的初始化对象数据成员的初始化 本类成员初始化赋值语句;本类成员初始化赋值语句;派生类的构造、析构函数C+语言程序设计构造函数的调用次序构造函数的调用次序1 调用基类构造函数,调用顺序按照它们调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。被继承时声明的顺序(从左向右)。2 调用成员对象的构造函数,调用顺序按调用成员对象的构造函数,调用顺序按照它们在类中声明的顺序。照它们在类中声明的顺序。3 派生类的构造函数体中的内容。派生类的构造函数体中的内容。派生类的构造、析构函数C+语言程序设计拷贝构造函数拷贝构造函数l若建立派生类对象时调用缺省拷贝构若建立派生类对象时调用缺省拷贝构造函数,则编译器将自动调用基类的造函数,则编译器将自动调用基类的缺省拷贝构造函数。缺省拷贝构造函数。l若编写派生类的拷贝构造函数,则需若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参要为基类相应的拷贝构造函数传递参数。例如数。例如:C:C(C &c1):B(c1)派生类的构造、析构函数C+语言程序设计例例 派生类构造函数举例派生类构造函数举例#include using namecpace std;class B1/基类基类B1,构造函数有参数构造函数有参数public:B1(int i) coutconstructing B1 iendl;class B2/基类基类B2,构造函数有参数构造函数有参数public:B2(int j) coutconstructing B2 jendl;class B3/基类基类B3,构造函数无参数构造函数无参数public:B3()coutconstructing B3 *endl;派生类的构造、析构函数class C: public B2, public B1, public B3 public:/派生类的公有成员派生类的公有成员C(int a, int b, int c, int d): B1(a),memberB2(d),memberB1(c),B2(b) private:/派生类的私有对象成员派生类的私有对象成员B1 memberB1;B2 memberB2;B3 memberB3;/不带参数不带参数;void main() C obj(1,2,3,4); 运行结果:constructing B2 2constructing B1 1constructing B3 *constructing B1 3constructing B2 4constructing B3 *326C+语言程序设计继承时的析构函数继承时的析构函数l析构函数也不被继承,派生类自行声明析构函数也不被继承,派生类自行声明l声明方法与一般(无继承关系时)类的声明方法与一般(无继承关系时)类的析构函数相同。析构函数相同。l不需要显式地调用基类的析构函数,系不需要显式地调用基类的析构函数,系统会自动隐式调用。统会自动隐式调用。l析构函数的调用次序与构造函数相反。析构函数的调用次序与构造函数相反。派生类的构造、析构函数C+语言程序设计例例 派生类析构函数举例派生类析构函数举例派生类的构造、析构函数#include using namecpace std;class B1/基类基类B1声明声明 public:B1(int i) coutconstructing B1 iendl;B1() coutdestructing B1 endl;class B2/基类基类B2声明声明public:B2(int j) coutconstructing B2 jendl;B2() coutdestructing B2 endl;class B3/基类基类B3声明声明public:B3()coutconstructing B3 *endl;B3() coutdestructing B3 endl;class C: public B2, public B1, public B3public:C(int a, int b, int c, int d): B1(a),memberB2(d),memberB1(c),B2(b)private:B1 memberB1;B2 memberB2;B3 memberB3;void main() C obj(1,2,3,4); 329C+语言程序设计例例 运行结果运行结果constructing B2 2constructing B1 1constructing B3 *constructing B1 3constructing B2 4constructing B3 *destructing B3destructing B2destructing B1destructing B3destructing B1destructing B2C+语言程序设计同名隐藏规则同名隐藏规则当派生类与基类中有相同成员时:当派生类与基类中有相同成员时:l若未强行指名,则通过派生类对象使若未强行指名,则通过派生类对象使用的是派生类中的同名成员。用的是派生类中的同名成员。l如要通过派生类对象访问基类中被覆如要通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定。盖的同名成员,应使用基类名限定。派生类成员的标识与访问C+语言程序设计例例 多继承同名隐藏举例多继承同名隐藏举例派生类成员的标识与访问#include using namecpace std;class B1/声明基类声明基类B1 public:/外部接口外部接口int nV;void fun() coutMember of B1endl;class B2/声明基类声明基类B2 public:/外部接口外部接口int nV;void fun()coutMember of B2endl;class D1: public B1, public B2 public:int nV;/同名数据成员同名数据成员void fun()coutMember of D1endl;/同名函数成员同名函数成员;void main() D1 d1;d1.nV=1; /对象名对象名.成员名标识成员名标识, 访问访问D1类成员类成员d1.fun(); d1.B1:nV=2; /作用域分辨符标识作用域分辨符标识, 访问基类访问基类B1成员成员d1.B1:fun(); d1.B2:nV=3; /作用域分辨符标识作用域分辨符标识, 访问基类访问基类B2成员成员d1.B2:fun();333C+语言程序设计二义性问题二义性问题l在多继承时,基类与派生类之间,或基在多继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时类之间出现同名成员时,将出现访问时的二义性(不确定性)的二义性(不确定性)采用虚函数采用虚函数或同名隐藏规则来解决。或同名隐藏规则来解决。l当派生类从多个基类派生,而这些基类当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性基类中的成员时,将产生二义性采采用虚基类来解决。用虚基类来解决。派生类成员的标识与访问C+语言程序设计二义性问题举例(一)二义性问题举例(一)class A public: void f();class B public: void f(); void g();class C: public A, public B public: void g(); void h();如果声明:如果声明:C c1;则则 c1.f(); 具有二义性具有二义性而而 c1.g(); 无二义性(同名覆盖)无二义性(同名覆盖)派生类成员的标识与访问C+语言程序设计二义性的解决方法二义性的解决方法l解决方法一:用类名来限定解决方法一:用类名来限定c1.A:f() 或或 c1.B:f()l解决方法二:同名覆盖解决方法二:同名覆盖在在C 中声明一个同名成员函数中声明一个同名成员函数f(),f()再根据需要调用再根据需要调用 A:f() 或或 B:f()派生类成员的标识与访问C+语言程序设计二义性问题举例(二)二义性问题举例(二)class B public: int b;class B1 : public B private: int b1;class B2 : public B private: int b2;class C : public B1,public B2 public: int f(); private: int d;派生类成员的标识与访问派生类派生类C的对象的存储结构示意图:的对象的存储结构示意图:bb1bb2dB类成员B类成员B1类成员B2类成员C类对象有二义性:有二义性:C c;c.bc.B:b无二义性:无二义性:c.B1:bc.B2:b338C+语言程序设计虚基类虚基类l虚基类的引入虚基类的引入用于有共同基类的场合l声明声明以virtual修饰说明基类例:class B1:virtual public Bl作用作用主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题.为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝l注意:注意:在第一级继承时就要将共同基类设计为虚基类。C+语言程序设计虚基类举例虚基类举例class B public: int b;class B1 : virtual public B public: int b1;class B2 : virtual public B public: int b2;class C : public B1, public B2 public: float d;下面的访问是正确的:下面的访问是正确的:C cobj;cobj.b; 虚 基 类虚基类的派生类对象存储结构示意图:虚基类的派生类对象存储结构示意图:BB1B2Cb1b2dB1类成员B2类成员C类对象bB类成员341C+语言程序设计例例 虚基类举例虚基类举例 虚 基 类D1nV :int nVd:intB1:nV1:intB2:nV2:intfund():voidfun():voidB1nV1 :intB2nV2 :intD1nVd :intfund():void B0nV :intfun():voidB0B1新增成员B0B2新增成员D1新增成员B0B0B1B2D1nV,fun()343#include using namecpace std;class B0/声明基类声明基类B0 public:/外部接口外部接口int nV;void fun()coutMember of B0endl;class B1: virtual public B0 /B0为虚基类,派生为虚基类,派生B1类类 public:/新增外部接口新增外部接口int nV1;class B2: virtual public B0 /B0为虚基类,派生为虚基类,派生B2类类 public:/新增外部接口新增外部接口int nV2;344class D1: public B1, public B2/派生类派生类D1声明声明 public: /新增外部接口新增外部接口int nVd;void fund()coutMember of D1endl;void main()/程序主函数程序主函数 D1 d1; /声明声明D1类对象类对象d1d1.nV=2;/使用最远基类成员使用最远基类成员d1.fun();345C+语言程序设计虚基类及其派生类构造函数虚基类及其派生类构造函数l建立对象时所指定的类称为建立对象时所指定的类称为最(远)派生类最(远)派生类。l虚基类的成员是由虚基类的成员是由最(远)最(远)派生类的构造函数派生类的构造函数通过调用虚基类的构造函数进行初始化的。通过调用虚基类的构造函数进行初始化的。l在整个继承结构中,直接或间接继承虚基类的在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的缺省构造函数。列出,则表示调用该虚基类的缺省构造函数。l在建立对象时,只有最(远)派生类的构造函在建立对象时,只有最(远)派生类的构造函数调用虚基类的构造函数,该派生类的其它基数调用虚基类的构造函数,该派生类的其它基类对虚基类构造函数的调用被忽略。类对虚基类构造函数的调用被忽略。 虚 基 类C+语言程序设计有虚基类时的构造函数举例有虚基类时的构造函数举例 虚 基 类#include using namecpace std;class B0/声明基类声明基类B0 public:/外部接口外部接口B0(int n) nV=n;cout“Member of B0”endl;int nV;void fun()cout“fun of B0endl;class B1: virtual public B0 public:B1(int a,int b1) : B0(a) nv1=b1; cout“Member of B1”endl;int nV1;class B2: virtual public B0 public:B2(int a) : B0(a) cout“Member of B2”endl;int nV2;class D1: public B1, public B2public:D1(int a) : B0(a), B1(a), B2(a) cout“Member of D1”endl;int nVd;void fund()cout“fun of D1endl;void main()D1 d1(1);d1.nV=2; d1.fund(); d1.fun();348Member of B0Member of B1Member of B2Member of D1fun of D1fun of B0Chapter Twelve Polymorphism(第第12章章 多态性多态性)C+语言程序设计C+语言程序设计Chapter 12 PolymorphismlPolymorphism(多态性多态性)lVirtual functions(虚函数虚函数)lPure virtual function(纯虚函数纯虚函数)lAbstract class(抽象类抽象类)C+语言程序设计多态性的概念多态性的概念l多态性是面向对象程序设计的重要特征之一。多态性是面向对象程序设计的重要特征之一。l多态性是指发出同样的消息被不同类型的对象多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为。接收时有可能导致完全不同的行为。l多态性是面向对象编程最重要的特征之一,是多态性是面向对象编程最重要的特征之一,是指不同的对象对同一命令做出不同响应的能力。指不同的对象对同一命令做出不同响应的能力。如简单的例子:如简单的例子:P12A.cpp、P12B.cppl多态的实现:多态的实现:函数重载运算符重载虚函数静态:编译时的多态动态:运行时的多态C+语言程序设计联编(联编(binding)l联编:是指计算机程序自身彼此关联联编:是指计算机程序自身彼此关联的过程的过程l静态联编:在编译连接阶段完成的静态联编:在编译连接阶段完成的l动态联编:在程序运行阶段完成的动态联编:在程序运行阶段完成的C+语言程序设计虚函数虚函数l虚函数是动态联编的基础。虚函数是动态联编的基础。l是非静态的成员函数。是非静态的成员函数。l在类的声明中,在函数原型之前写在类的声明中,在函数原型之前写virtual。lvirtual 只用来说明类声明中的原型,不能用在只用来说明类声明中的原型,不能用在函数实现时。函数实现时。l具有继承性,基类中声明了虚函数,派生类中具有继承性,基类中声明了虚函数,派生类中无论是否说明,同原型函数都自动为虚函数。无论是否说明,同原型函数都自动为虚函数。l本质:不是重载声明而是覆盖。本质:不是重载声明而是覆盖。l调用方式:通过基类指针或引用,执行时会调用方式:通过基类指针或引用,执行时会根据根据指针指向的对象的类指针指向的对象的类,决定调用哪个函数。,决定调用哪个函数。虚 函 数C+语言程序设计例例#include using namespace std;class B0/基类基类B0声明声明public:/外部接口外部接口virtual void display() /虚成员函数虚成员函数 coutB0:display()endl; ;class B1: public B0/公有派生公有派生 public: void display() coutB1:display()endl; ;class D1: public B1/公有派生公有派生 public: void display() coutD1:display()display(); void main() /主函数主函数 B0 b0, *p;/声明基类对象和指针声明基类对象和指针B1 b1;/声明派生类对象声明派生类对象D1 d1;/声明派生类对象声明派生类对象p=&b0;fun(p);/调用基类调用基类B0函数成员函数成员p=&b1;fun(p);/调用派生类调用派生类B1函数成员函数成员p=&d1;fun(p);/调用派生类调用派生类D1函数成员函数成员运行结果:运行结果:B0:display()B1:display()D1:display()359C+语言程序设计When to use virtual functionsl使用虚函数会涉及内存和执行时间开使用虚函数会涉及内存和执行时间开销的问题,所以在选择一个基类函数销的问题,所以在选择一个基类函数是否声明为虚函数时要慎重考虑。是否声明为虚函数时要慎重考虑。l一般而言,如果一个基类的成员函数一般而言,如果一个基类的成员函数可能在派生类中被覆盖,那么就应将可能在派生类中被覆盖,那么就应将其声明为虚函数。其声明为虚函数。C+语言程序设计Overriding and overloadingl重载是一项编译器技术,用来区分函重载是一项编译器技术,用来区分函数名字相同但是参数列表不同的函数。数名字相同但是参数列表不同的函数。l覆盖发生在继承的时候,当派生类的覆盖发生在继承的时候,当派生类的成员函数与基类的成员函数的函数名成员函数与基类的成员函数的函数名和参数列表都相同时便会发生覆盖。和参数列表都相同时便会发生覆盖。C+语言程序设计虚析构函数虚析构函数何时需要虚析构函数?何时需要虚析构函数?l当你可能通过基类指针删除派生类对当你可能通过基类指针删除派生类对象时象时l如果你打算允许其他人通过基类指针如果你打算允许其他人通过基类指针调用对象的析构函数(通过调用对象的析构函数(通过delete这这样做是正常的),并且被析构的对象样做是正常的),并且被析构的对象是有重要的析构函数的派生类的对象,是有重要的析构函数的派生类的对象,就需要让基类的析构函数成为虚拟的。就需要让基类的析构函数成为虚拟的。虚 函 数C+语言程序设计纯虚函数与抽象类纯虚函数与抽象类纯虚函数的声明格式为:纯虚函数的声明格式为: virtual 类型类型 函数名(参数表)函数名(参数表)=0;即没有函数体的函数。即没有函数体的函数。带有纯虚函数的类称为抽象类带有纯虚函数的类称为抽象类:class 类名类名 virtual 类型 函数名(参数表)=0; /纯虚函数纯虚函数 .纯虚函数与抽象类C+语言程序设计抽象类抽象类纯虚函数与抽象类l作用作用抽象类为抽象和设计的目的而建立,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。l注意注意抽象类只能作为基类来使用。不能声明抽象类的对象。构造函数不能是虚函数,析构函数可以是虚函数。C+语言程序设计例例纯虚函数与抽象类#include using namespace std;class B0 /抽象基类抽象基类B0声明声明 public: /外部接口外部接口virtual void display( )=0; /纯虚函数成员纯虚函数成员;class B1: public B0 /公有派生公有派生 public:void display()coutB1:display()endl; /虚成员函数虚成员函数;class D1: public B1 /公有派生公有派生 public:void display()coutD1:display()display(); void main() /主函数主函数 B0 *p;/声明抽象基类指针声明抽象基类指针B1 b1;/声明派生类对象声明派生类对象D1 d1;/声明派生类对象声明派生类对象p=&b1;fun(p);/调用派生类调用派生类B1函数成员函数成员p=&d1;fun(p);/调用派生类调用派生类D1函数成员函数成员运行结果:运行结果:B1:display()D1:display()366C+语言程序设计总结:总结:l一个函数一旦被说明为虚函数,则无一个函数一旦被说明为虚函数,则无论说明它的类被继承了多少层,在每论说明它的类被继承了多少层,在每一层派生类中该函数都保持一层派生类中该函数都保持virtual特特性。因此,在派生类中重新定义该函性。因此,在派生类中重新定义该函数时,不再需要关键字数时,不再需要关键字virtual。l但习惯上,为了提高程序的可读性,但习惯上,为了提高程序的可读性,常常在每层派生类中都重复地使用常常在每层派生类中都重复地使用virtual关键字。关键字。C+语言程序设计不恰当的虚函数不恰当的虚函数class Basepublic: virtual void fn(int x) cout“In Base class,int=”xendl;class Subclass:public Basepublic:virtual void fn(float x) cout“in subclass,float x=”xendl;C+语言程序设计void test(Base &b)int i=1; b.fn(i); float f=2.0; b.fn(f);void main()Base bc; Subclass sc; cout“calling test(bc)n”; test(bc); cout“calling test(sc)n”; test(sc);calling test(bc)In Base class,int=1In Base class,int=2classing test(sc)In Base class,int=1In Base class,int=2C+语言程序设计虚函数的限制虚函数的限制l(1)只有类的成员函数才能说明为虚函数,因为)只有类的成员函数才能说明为虚函数,因为虚函数仅适用于继承关系的类对象,所以普通不能虚函数仅适用于继承关系的类对象,所以普通不能说明为虚函数。说明为虚函数。l(2)内联函数不能是虚函数,因为内联函数是在)内联函数不能是虚函数,因为内联函数是在编译时决定其位置。编译时决定其位置。l(3)构造函数不能是虚函数,因为构造时对象还)构造函数不能是虚函数,因为构造时对象还是一片未定型的空间。是一片未定型的空间。l(4)析构函数可以是虚函数,而且通常声明为虚)析构函数可以是虚函数,而且通常声明为虚函数。函数。Void finishWithObject(Base *pHeapObject) delete pHeapObject;C+语言程序设计class Basepublic: Base()cout“construct in Basen”; Base() cout“destruting Base”endl;class Subclass:public Basepublic: Subclass() cout“construct in Subclassn”; Subclass() cout“destruting Subclass”endl;例例1:void main() cout“first:n”;Base bc; cout“second:n”;Subclass sc;cout“end!n”;输出:输出:first:construct in Basesecond:construct in Baseconstruct in Subclassend!destruting Subclassdestruting Basedestruting BaseC+语言程序设计#includeclass Basepublic: Base()cout“construct in Basen”; Base() cout“destruting Base”endl;class Subclass:public Basepublic: Subclass() cout“construct in Subclassn”; Subclass() cout“destruting Subclass”endl;例例2:void main() cout“first:n”;Base *bc=new Base; cout“secondn”;Subclass *sc=new Subclass;cout“end!n”;输出:输出:first:construct in Basesecondconstruct in Baseconstruct in Subclassend!C+语言程序设计#includeclass Basepublic: Base()cout“construct in Basen”; Base() cout“destruting Base”endl;class Subclass:public Basepublic: Subclass() cout“construct in Subclassn”; Subclass() cout“destruting Subclass”endl;void test(Base *x) delete x;void main() cout“first:n”;Base *bc=new Base; cout“secondn”;Subclass *sc=new Subclass;cout“calling test(bc)n”;test(bc);cout“calling test(sc)n”;test(sc);cout“end!n”;C+语言程序设计输出结果:输出结果:first:construct in Basesecondconstruct in Baseconstruct in subclasscalling test(bc)destructing basecalling test(sc)destructing baseend!C+语言程序设计#includeclass Basepublic: Base()cout“construct in Basen”; virctual Base() cout“destruting Base”endl;class Subclass:public Basepublic: Subclass() cout“construct in Subclassn”; virctual Subclass() cout“destruting Subclass”endl;void test(Base *x) delete x;void main() cout“first:n”;Base *bc=new Base; cout“secondn”;Subclass *sc=new Subclass;cout“calling test(bc)n”;test(bc);cout“calling test(sc)n”;test(sc);cout,写操作被称为(向流中添加),写操作被称为(向流中添加)插入插入来从流中读取内置来从流中读取内置数据类型(数据类型(char、int、float等)的数据,从而提等)的数据,从而提供基本的输入处理操作。流供基本的输入处理操作。流cin是派生类是派生类istream的的对象,通常和键盘输入相关联。对象,通常和键盘输入相关联。l派生类派生类ostream中包含的一些成员函数可用于向流中包含的一些成员函数可用于向流中输出数据。它通过重载运算符中输出数据。它通过重载运算符来向流中输出来向流中输出内置数据类型的数据,从而提供基本的输出处理操内置数据类型的数据,从而提供基本的输出处理操作。流作。流cout是派生类是派生类ostream的对象,通常和屏幕的对象,通常和屏幕输出相关联。输出相关联。The C+ input/output class hierarchyC+语言程序设计lifstream是从是从istream派生的类,用于创建派生的类,用于创建输入文件对象;输入文件对象;lofstream是从是从ostream派生的类,用于创派生的类,用于创建输出文件对象;建输出文件对象;lfstream既能用于输入也能用于输出。既能用于输入也能用于输出。l见见Talbe 14.1 Summary of iostream and fstream header filesThe C+ input/output class hierarchyC+语言程序设计Opening and Closing a filel为打开一个文件,首先要创建一个适当的类的实例:为打开一个文件,首先要创建一个适当的类的实例:ifstream in; / in is an input file objectofstream out; /out is an output file objecel在创建一个适当的类的实例之后,必须打开该文件对象,在创建一个适当的类的实例之后,必须打开该文件对象,将其和存储在硬盘或其他存储设备上的文件相关联。将其和存储在硬盘或其他存储设备上的文件相关联。 in.open(“in.dat”); out.open(“out.dat”);l创建文件对象的实例和打开文件可以合并为一条语句来创建文件对象的实例和打开文件可以合并为一条语句来完成,这可以通过使用类完成,这可以通过使用类ifstream和和ofstream的构造函的构造函数来实现。数来实现。 ifstream in(“in.dat”); ofstream out(“out.dat”);C+语言程序设计Opening and Closing a filel在文件处理完毕后立即关闭该文件。在文件处理完毕后立即关闭该文件。l如果不关闭文件将会如果不关闭文件将会丢失丢失数据。数据。l关闭方式:关闭方式: in.close(); /Close input stream in.dat out.close();/Close output stream out.dat如:如:P348中的中的P14A.cppC+语言程序设计Opening a filel文件流类不是标准设备,没有文件流类不是标准设备,没有cout那样的那样的预定义的全局对象,要定义文件流对象,预定义的全局对象,要定义文件流对象,需规定文件名和打开方式。需规定文件名和打开方式。l打开文件有两种方法:打开文件有两种方法:l(1)建立流对象时使用构造函数将一个文)建立流对象时使用构造函数将一个文件和这个流对象连接起来:件和这个流对象连接起来:文件名文件名 打开方式(缺省为文本文件)打开方式(缺省为文本文件) C+语言程序设计void fn() ofstream myf(“mydata”); if(myf.fail() /fail()文件打开否,见文件打开否,见P349的文件的文件出错检查出错检查 cerr“error.n”; return 1; myfch;(4)用完文件后作用成员函数关闭文件)用完文件后作用成员函数关闭文件 e.g.: infile.close(); C+语言程序设计File error checking(文件出错检查文件出错检查)l在打开文件时检查是否发生错误,是一个良好的在打开文件时检查是否发生错误,是一个良好的编程习惯。编程习惯。l文件打开时可能发生的错误包括输出设备上没有文件打开时可能发生的错误包括输出设备上没有可用的空间,或者是要读取的文件并不存在。可用的空间,或者是要读取的文件并不存在。lIos类中包含的一些成员函数可用来检查这类错类中包含的一些成员函数可用来检查这类错误。误。l如如Table 14.2 Some member functions of iosC+语言程序设计File error checking(文件出错检查文件出错检查) Table 14.2 Some member functions of ios-Member Function Return Value -fail() Returns the Boolean value true if the operation failed; otherwise the Boolean value false is returned.good() Returns the Boolean value true if the operation succeeded; otherwise the Boolean value false is Return. This is the opposite of fail().eof() Returns the Boolean value true if the end of the file has been reached; otherwise the Boolean value false is returned.-见见P14B.cppC+语言程序设计Single character I/O and detecting the end of a filel从文件中读取数据时,必须检测是否到达从文件中读取数据时,必须检测是否到达了文件的末尾,以便结束对文件的处理操了文件的末尾,以便结束对文件的处理操作。作。l通过使用通过使用ios类的成员函数类的成员函数eof()来检测文件来检测文件的末尾,的末尾,ifstream类也从类也从ios类继承了这个类继承了这个函数。函数。l如例:如例:P14C.cpp P14D.cpp P14E.cppC+语言程序设计Appending data to the end of a filel为了向一个文件的末尾添加数据,在打开文为了向一个文件的末尾添加数据,在打开文件的时候必须指定文件的打开模式。文件的件的时候必须指定文件的打开模式。文件的打开模式在打开模式在ios类中定义。如表类中定义。如表14.3所示。所示。l可能通过使用按位或运算符可能通过使用按位或运算符|来联合使用多种来联合使用多种打开模式。打开模式。l如:如:fstream in_out(“file.txt”,ios:in|out:out); 打开一个文件,使其既可用做输入,也可用打开一个文件,使其既可用做输入,也可用做输出。做输出。见例:见例:P14F.cpp appending the line”This line is added to the end of the file” to the file file.txt.C+语言程序设计Appending data to the end of a file Table 14.3 File modes- Mode Meaning-ios:app Opens a file for appending-additional data is written at the end of the file.ios:ate Start at the end of the file when a file is opened.ios:binary Opens a file in binary mode (default is text mode).ios:in Opens a file for input (default for ifstream objects).ios:out Opens a file for output (default for ifstream objects). ios:trunc Truncates (deletes) the file contents if the already exists.ios:nocreate Do not create a new file. Open fails if the file does not already exist. For output file only.ios:noreplace Do not replace an existing file. Open fails if the file already exists. For output files only. -C+语言程序设计Reading lines from a filel使用函数使用函数getline()可以从文件读入一可以从文件读入一整行数据到整行数据到C/C+字符串。字符串。l见见P14G.cpp 使用使用C+字符串存储从字符串存储从文件中读入的一行数据。文件中读入的一行数据。 P14H.cpp 使用使用C字符串存储。字符串存储。 以上两个程序都是逐行读取源文以上两个程序都是逐行读取源文件,并将其显示在屏幕上,显示时增件,并将其显示在屏幕上,显示时增加了前导的行号。加了前导的行号。C+语言程序设计Random access(随机存取随机存取)l顺序文件处理:对数据项的读顺序文件处理:对数据项的读/写是一写是一个接着一个进行的。个接着一个进行的。l随机存取(直接存取):允许在文件随机存取(直接存取):允许在文件中随意定位,在文件的任何位置读写中随意定位,在文件的任何位置读写数据。数据。C+语言程序设计Random access(随机存取随机存取) Table 14.4 Offset valueslios : beg ( = 0 ) :The offset is from the beginning of the streamlios : cur ( = 1 ) :The offset is from the current position in the streamlios : end ( = 2) :The offset is from the end of the stream见见P358 的的Examples中的位置变化。中的位置变化。C+语言程序设计Random access(随机存取随机存取)l文件位置标记(文件位置标记(FPM):文件的位置。):文件的位置。lseekg():用来为输入文件设定用来为输入文件设定FPM,指定下指定下一个要读取的数据的位置。一个要读取的数据的位置。lseekp():在输出文件中用于设置在输出文件中用于设置FPM。ltellg():用来返回输入流中用来返回输入流中FPM的当前值。的当前值。ltellp():用来返回输出流中用来返回输出流中FPM的当前值。的当前值。l见见P14I.cppC+语言程序设计Object I/O(对象对象I/O)l通过磁盘文件来代替键盘和屏幕进行通过磁盘文件来代替键盘和屏幕进行输入和输出。输入和输出。l见例见例P14J.cppC+语言程序设计Binary I/O(二进制二进制I/O)lC+文件分为两种:文件分为两种:l二进制文件:在此文件中,数值型数二进制文件:在此文件中,数值型数据是以二进制形式存储的,单位是一据是以二进制形式存储的,单位是一个字节。个字节。l文本文件:由字符序列组成,也称为文本文件:由字符序列组成,也称为ASCII码文件。单位是一个字符,是码文件。单位是一个字符,是以字符的以字符的ASCII码形式存储的。码形式存储的。C+语言程序设计Binary I/O(二进制二进制I/O)lshort int n=123;123495051001100010011001000110011 短整型变量n在内存中占两个字节,但把变量n的值存储在文本文件中则需要3个字节的内存。 在ASCII文件中,每一位数字都要占用一个字节的存储空间。 在二进制文件中,一个数据的每一位数字并不占用单独的存储单元,而是把整个数据作为一个二进制数来存储。 见P363C+语言程序设计Serial writing of objects to a binary filel见见P14K.cpplwrite()用于以二进制格式写一个对象。用于以二进制格式写一个对象。带两个实参,第一个实参是存储对象带两个实参,第一个实参是存储对象的内存区域的地址,第二个实参是这的内存区域的地址,第二个实参是这个内存区域的字节大小。个内存区域的字节大小。l但,对象必须存储在一块连续的内存但,对象必须存储在一块连续的内存区域中,这就意味着对象不能有指向区域中,这就意味着对象不能有指向动态分配的内存的指针数据成员。动态分配的内存的指针数据成员。C+语言程序设计Serial reading of objects from a binary filel见见P14L.cpplread()用于从二进制文件中将对象的用于从二进制文件中将对象的数据读到内存中。带两个实参,第一数据读到内存中。带两个实参,第一个实参是存储对象的内存区域的地址,个实参是存储对象的内存区域的地址,第二个实参是这个内存区域的字节大第二个实参是这个内存区域的字节大小。小。C+语言程序设计Binary file radom accesslP14K.cpp直接访问存储在二进制文件直接访问存储在二进制文件中的中的stock对象。对象。
收藏 下载该资源
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号