c++ 第三章.ppt
面向对象程序设计面向对象程序设计第3章 类与对象 3.1 类类 类(Class)实际上是对某种类型的对象定义变量和方法的原型。它表示对现实生活中一类具有共同特征的事物的抽象,是面向对象编程的基础。类包含有关对象动作方式的信息,包括它的名称、方法、属性和事件。实际上它本身并不是对象,因为它不存在于内存中。当引用类的代码运行时,类的一个新的实例,即对象,就在内存中创建了。虽然只有一个类,但能从这个类在内存中创建多个相同类型的对象。可以把类看作“理论上”的对象,也就是说,它为对象提供蓝图,但在内存中并不存在。从这个蓝图可以创建任何数量的对象。类的出现解决了编程语言中数据类型的扩充问题,为实际问题的解决铺平了道路。3.1 类类 3.1.1 类的定义类的定义类定义的一般形式如下:class 类名private:私有的成员函数私有的数据成员定义protected:保护的成员函数保护的数据成员定义public:类的公有接口;3.1 类类 类的定义由类头和类体两部分组成。类头由关键字class开头,然后是类名,其命名规则与一般标识符的命名规则一致,是类的标识。类体包括所有的细节,并放在一对花括号中。类的定义也是一个语句,所以要有分号结尾,否则会产生编译错误。数据成员是描述类状态特征的变量也称为成员变量,成员函数用来描述类的自身功能。按照面向对象分析和设计得出的结论,类中不同的成员变量和成员函数应该有不同的信息隐藏程度。为叙述简洁起见,经常把类的成员变量和成员函数简称为类的成员。C+语言提供了三种不同的信息隐藏程度,分别用三种不同的关键字private、protected和public表示外界对它们的访问程度。在设计类时,一般情况下,应该将所有成员变量设计成私有的,将所有外部程序需要的成员函数(即向外部程序提供的接口)设计成公有的。3.1 类类【例3.1】设计具有加、减、乘、除运算功能的分数类。分析:如果有分数和,那么二者的相关运算规则可表示为下列形式。加运算:减运算:乘运算:除运算:3.1 类类 根据封装的特性,类的内部要封装数据及数据的操作,为保证外界不能够直接操作类内部的数据,必须将成员变量的访问权限设置为私有的或保护类型的,而外界只能通过类提供的公有访问权限的成员函数来操作。对于分数类来说,成员变量包括分数的分子和分母,成员函数包括分数的加、减、乘、除运算。外部程序只需要通过类的公共接口(即公有的成员函数)来使用复数,并不需要直接操作分数的分子和分母。因此,分数的成员变量应该定义成私有的,复数的成员函数(包括复数的加、减、乘、除运算)应该定义成公有的。通常情况下,按照算法的技术要求,经计算机处理的数据必须有一个或多个输出结果供用户查阅,因此分数类的对象计算完毕后会要求进行输出显示,因此,再增加一个公有的输出成员函数,用来输出一个完整的分数对象。为了后边使用方便,把复数类放在头文件中。3.1 类类 完成fraction类的设计后,就可以在应用程序中使用。使用时只需将保存有fraction类的库文件包含进可执行文件中,即可使用fraction类的功能。【例3.2】类的引入和使用程序运行的结果为:分数为:93/35分数为:33/-35分数为:54/35分数为:10/21本例中通过“fraction w(6,7),t(9,5),d;”完成了fraction类具体对象的定义,即实现了类的实例化,并对fraction类的功能进行了测试。3.1 类类 3.1.2 访问控制访问控制类成员有3种不同的访问权限:(1)私有(private)成员只能被该类的成员函数访问。在private后声明的成员变量和成员函数都是外部程序不可见的,称为私有成员变量和成员函数;(2)保护(protected)成员只能被该类的成员函数或派生类的成员函数访问。在protected后声明的成员变量和成员函数在类外部中是不可见的,称为保护成员变量和成员函数。(3)公有(public)成员可以在类外访问。在public后声明的成员变量和成员函数不仅内部成员可以调用,而且对外部程序也是可见的,称为公有成员变量和成员函数。数据成员通常是私有的,成员函数通常有一部分是公有的,一部分是私有的。公有的函数可以在类外被访问,也称之为类的接口。可以根据解决的问题,灵活地为各个数据成员和成员函数指定合适的访问权限。3.1 类类 3.1.3 成员变量成员变量成员变量又称为成员属性,它是描述对象状态的数据,是类中很重要的组成成分。成员变量用以维护对象的状态。不同的类对应的成员变量是不同的,同一个类的不同实例具有相同的成员变量,但它们具有不同的表示值。1.声明成员变量声明成员变量在面向对象程序设计中,类的成员变量抽象了现实世界中一类对象的属性(或状态)。因此成语变量的数据类型是丰富的,它们可以用任何基本数据类型、用户自定义的数据类型和任何类类型来定义。根据类的封装性,成员变量所表示的对象的属性值是不应允许外部程序擅自改变的,一般情况下,成员变量的访问权限应定义为私有的(private)或是保护的(protected)。基本的定义格式为:3.1 类类 private|protected|public:数据类型 成员变量名;在例3.1中,成员变量的定义如下:private:int numerator;int denominator;分数类中的分子和分母的访问权限被设置为私有的,这意味着numerator 和denominator只能被分数类内部的成员函数所操作,类外部的功能单元只能通过分数类提供的公共接口来完成对就numerator 和denominator的操作。根据问题要求的不同,可以将成员变量的数据类型定义为基本数据类型、用户自定义的数据类型或已经定义过的类的类型;也可以将访问权限定义为私有的(private)或是保护的(protected)。3.1 类类 例如,如下成员变量定义就是允许的:class A/类A;class B/类Bprotected:A x;/成员变量x的数据类型为类A类型;3.1 类类 2.成员变量的设计成员变量的设计在面向对象分析阶段确定下来的成员变量展现了类的特征,根据问题的需要可将它的数据类型和访问权限进行定义,但是在定义时还是有一定限制的。(1)成员变量不能在定义时初始化。类的对象的模板,成员变量是类的描述信息,程序编译的时候只是理解其语意,用来控制对象的内存布局和访问,并不占用内存空间,所以类的定义时不能够将成员变量初始化。例如,不能把例3.1的成员变量定义成如下形式:3.1 类类 private:int numerator=0;int denominator=1;(2)成员变量不能递归定义。成员变量不能用自己所属的类型来定义自身的成员变量。例如,下述结构的类定义是错误的:class AA x;编译器在编译时会提示“error C2460:w:uses A,which is being defined”来告知用户类A正在定义中。3.1 类类 3.1.4 成员函数成员函数成员函数也称类的方法,是实现类功能的手段,类对象通过成员函数访问类中的私有成员,同时外部功能单元通过公有权限的成员函数完成对类的操作。成员函数定义成员函数定义成员函数的定义体可以定义在类定义体内部,也可定义在类定义体的外部。定义在类定义体内部的成员函数的定义格式为:private|protected|public:函数返回值 函数名称(参数列表)函数体 3.1 类类 在例3.1中fraction类的所有成员函数都定义在了内定义体的内部。因此fraction类的成员函数都被编译器解释为内联成员函数。成员函数定义在定义体外部对类的功能没有影响,相对于内联函数,这种定义在类的外部的成员函数称之为外联成员函数。【例3.3】成员函数定义在类定义体外部。例3.3类定义体中,只是说明了成员函数的原型,而成员函数的定义体都放到了类定义体的外部。因为这些是类的成员函数定义而不是一般的C+函数定义,所以需要在这些成员函数的名字前面加上“fraction:”,告知编译器这些函数是fraction类的成员函数。对于采用外联成员函数形式定义的类,在使用时和采用内联成员函数实现的类并没有本质的区别。例3.1和例3.3中的类定义对例3.2的功能实现都能起到相同作用。3.1 类类 2.成员函数重载成员函数重载函数重载是指同在同一个命名空间内一个函数名可以对应着多个函数的实现。类是数据和对数据操作的封装体,成员函数在类的内部可以进行重载,来完成同一调用形式的不同功能实现,成员函数重载使得对象的使用得到了简化,为外部程序用各种参数形式调用成员函数提供了灵活性。【例3.4】设计加运算重载的分数类本例实现了分数加运算的重载,使分数类的计算更加灵活,更好地适应了复杂的应用环境。3.1 类类【例3.5】成员函数重载测试程序运行的结果为:分数为:62/7分数为:93/35通过对成员函数重载的测试,可以发现,如果不定义重载,就需要2个不同的函数来实现对分数加分数和分数加整数的操作。定义重载之后,只需一种形式就完成了,使得对象的使用得到了简化。3.1 类类 3.成员函数的行为限制成员函数的行为限制成员函数是类功能的具体体现,公有权限的成员函数对外提供公共接口,外部功能单元通过这些接口来操作成员变量,为了避免对类的数据成员产生误操作,可通过const关键字对成员函数进行行为限制。设计成员函数时,可以用const关键字修饰参数和成员函数。const关键字修饰参数和成员函数的作用与语法含义是:(1)当成员函数的某个参数修饰为const时,表示该参数在成员函数内不会也不能被修改。(2)当成员函数修饰为const时,表示限制该成员函数只能读取当前对象的成员变量,但不能修改当前对象的成员变量。用const修饰成员函数时,关键字const既可以放在成员函数定义的最前面,也可以放在成员函数定义的最后面,但通常把关键字const放在成员函数定义的最后面。3.1 类类【例3.6】重新设计例3.1的分数类。分析:根据例3.1的分析可以知道,分数类的加、减、乘、除运算等都只需要读取分子和分母的值,不需要也不应该修改它们值。因此,应该使用关键字const限定这些成员函数使其不能够修改成员变量值。本例中,分数类fraction的加、减、乘、除运算由于没有进行自身成员变量值的改变,故用const来限制它们。对于成员函数show和成员函数commonden是不能够用const修饰的,因为这两个成员函数是要对自身所在对象的成员变量进行修改的。3.2 构造函数与析构函数构造函数与析构函数数据类型包含了空间的分配和对该类数据的操作。“int a=5;”表示的意义在于:n定义了一个变量a;n内存分配4个字节的连续空间;n对a可进行数学运算操作。类为解决基本数据类型的不足提供了有效途径,自定义的类是一种数据类型,它包含了数据和对数据的操作,也涉及到内存空间的分配。类和对象的关系就相当于基本数据类型与该类型变量之间的关系。对象可以看作是一个复合的数据单元,其外在的区别在于对象的名称,内在区别就是对象的属性和属性值。构造函数和析构函数是在类体中说明的两种特殊的成员函数。构造函数的功能是在创建对象时,使用给定的值来将对象初始化。析构函数的功能是用来释放一个对象的。在对象删除时,用它来做一些清理工作,它与构造函数的功能正好相反。3.2 构造函数与析构函数构造函数与析构函数3.2.1 构造函数构造函数构造函数是在对象被创建时由系统自动调用的成员函数。当定义对象时,需要在内存为对象开辟空间,并对对象内部的成员变量赋初值。构造函数充当了这一角色。1.构造函数的特点构造函数的特点构造函数是一种完成对象初始化的特殊成员函数,它的定义格式和使用方法都有很强的特点。构造函数的格式为::()函数体3.2 构造函数与析构函数构造函数与析构函数构造函数的设计应注意以下几点:(1)构造函数的命名必须和类名完全相同;而一般方法则不能和类名相同。(2)构造函数不能有返回类型,即使是void也不行。这是由于构造函数返回类本身的对象,因此不需要给出构造函数返回值类型。(3)构造函数的参数用来传递定义对象时的初始值,通常情况下要注意参数的类型与成员变量的类型相匹配。(4)构造函数的参数允许使用默认值。当构造函数的参数有默认值时,表示如果定义对象时给出了初始值,则使用这样的初始值;如果定义对象时没有给出初始值,则使用默认值。如果可能,最好给出构造函数所有参数的默认值,这可以方便定义对象,特别是,这样可以定义数组对象。构造函数没有默认值时,不能定义数组对象。3.2 构造函数与析构函数构造函数与析构函数(5)构造函数的访问权限一定是公有的(public),这是因为构造函数是定义对象时由系统自动调用的,对象都是在类的外部定义的,所以只有公有的访问权限才能让外部的对象看到这个类的接口。(6)构造函数可以重载,即允许一个类中有多个参数个数或参数类型不同的构造函数。2.默认的构造函数默认的构造函数每个类必须有一个构造函数,否则没法创建对象。若程序没有提供任何构造函数,则C+提供一个默认的构造函数,该默认构造函数是无参构造函数,它仅负责创建对象,不做任何初始化的工作。只要程序定义了一个构造函数(不管是无参还是有参构造),C+就不再提供默认的默认构造函数。即如果为类定义了一个带参的构造函数,还想要无参构造函数,就必须自己定义。3.2 构造函数与析构函数构造函数与析构函数【例3.7】定义一个点类,示例构造函数的用法本例中定义了3个构造函数,实现了构造函数的重载,其中point()是默认的构造函数,由于定义了其它两个构造函数,point()就必须由用户自己定义出来。【例3.8】测试point类的构造函数重载程序运行的结果为:点的坐标是:(4,5)点的坐标是:(3,5)点的坐标是:(5,6)3.2 构造函数与析构函数构造函数与析构函数3.2.2 析构函数析构函数析构函数(destructor)与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做“清理善后”的工作。以C+语言为例,析构函数名也应与类名相同,只是在函数名前面加一个波浪符,例如stud(),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数,它也不进行任何操作,所以许多简单的类中没有用显式的析构函数。3.2 构造函数与析构函数构造函数与析构函数1.析构函数的特点析构函数的特点析构函数也是一种特殊的成员函数,它的功能和构造函数互逆。它的格式为::()函数体构造函数的设计应注意以下几点:(1)析构函数名是在类名前再加上字符“”。(2)析构函数不能带任何参数,不能有返回类型(即使是void类型也不行)。(3)一个类中只能有一个析构函数。3.2 构造函数与析构函数构造函数与析构函数(4)析构函数的访问权限一定是公有的(即public)。这和构造函数的访问权限一定是公有的原因相同。(5)函数体为空的析构函数可以省略不设计。此时,系统将自动为其定义一个缺省的、函数体为空的析构函数。2.默认的析构函数默认的析构函数类必须有析构函数,如果一个类没有定义析构函数,则系统会自动生成一个默认的构造函数,其格式为::()3.2 构造函数与析构函数构造函数与析构函数一般情况下,如果类中没有用new运算符动态申请内存空间,则该类的析构函数为空。并可以不设计。例如,例3.1的分数类中没有使用过new运算符,因此该类的析构函数的函数体为空。但是,如果类中用new运算符动态申请了内存空间,则该类的析构函数一定不能为空。此时,析构函数应该设计用delete运算符动态释放内存空间。如果构造函数中使用了new申请了空间,而析构函数没有使用delete运算符,会造成不再被程序使用的内存空间没有被系统回收,造成内存泄漏。3.2 构造函数与析构函数构造函数与析构函数【例3.9】设计一个一维动态数组,要求析构函数不为空。本例中,构造函数在判断数组长度后进行了动态内存申请,在析构函数中对动态申请的内存用delete进行了释放,防止产生内存泄漏,并且在析构函数的最后输出提示语句。【例3.10】测试一维动态数组程序运行的结果为:0 1 2 3 4已经释放内存空间!3.2 构造函数与析构函数构造函数与析构函数如果一维动态数类的析构函数中没有进行delete操作,在for循环结束后,构造函数中用new动态申请的由指针p所指向的空间就不会释放,必将会产生内存泄露。例3.9中一维动态数组对象a在内存中的空间结构如图3-1所示。图3-1 一维动态数组3.2 构造函数与析构函数构造函数与析构函数3.2.3 拷贝构造函数拷贝构造函数在某些情况下,需要用已存在的对象初始化同类的另一个对象,这时需要一种特殊的构造函数拷贝构造函数。拷贝构造函数也称复制构造函数,它的参数是本类对象的引用。拷贝构造函数的定义格式为:class 类名public:类名(参数表);类名(const 类名&对象名);3.2 构造函数与析构函数构造函数与析构函数类名:类名(const 类名&对象名)函数体;拷贝构造函数是一种特殊的构造函数,因此它具有构造函数的性质,在三种情况下拷贝构造函数会被系统自动地调用。(1)用类的一个对象初始化该类的另一个对象时;(2)对象作为一个函数实参传递给函数的形参时;(3)对象作为函数的返回值返回给调用者时。3.2 构造函数与析构函数构造函数与析构函数【例3.11】修改point类,示例拷贝构造函数的调用本例中设计了自动调用拷贝构造函数的三种情况,具体以拷贝构造函数、disp函数和reoj函数实现。【例3.12】测试拷贝构造函数程序运行的结果为:拷贝构造函数运行拷贝构造函数运行点(2,3)拷贝构造函数运行3.2 构造函数与析构函数构造函数与析构函数3.2.4 浅拷贝与深拷贝浅拷贝与深拷贝1.浅拷贝浅拷贝浅拷贝是指由缺省的拷贝构造函数所实现的数据成员逐一赋值。通常情况下,缺省的构造函数是能够完成这项工作的,如果类的内部含有指针类型的数据,缺省构造函数的做法将会使程序产生错误。【例3.13】浅拷贝示例程序运行的结果为:析构函数运行,学生李四占用的内存释放!析构函数运行,学生葺葺葺葺菡加玫哪诖媸头牛?3.2 构造函数与析构函数构造函数与析构函数在本例的程序执行时,首先创建s1对象,s1对象中的name指针指向由new运算符开辟的一块空间。“student s2(s1);”语句的作用是用学生对象s1为学生对象s2复制,系统会自动调用一个缺省的拷贝构造函数来完成任务。由于缺省的构造函数只是将s2中的name指针指向s1对象中name所指向的空间,而没有开辟自己的空间,所以当程序结束时,学生对象s2在内存中先行消亡,它所占的内存空间被析构函数释放掉,此时s1的name指针悬空,造成程序的运行结果出错。本例中对象具体的空间分配见图3-2。3.2 构造函数与析构函数构造函数与析构函数图3-2 浅拷贝内存解析图3.2 构造函数与析构函数构造函数与析构函数2.深拷贝深拷贝为了解决浅拷贝出现的问题,必须定义一个符合需要的拷贝构造函数,使之不但拷贝数据成员,而且为对象s1和s2分配各自的内存空间,这种情况称之为深拷贝。【例3.14】深拷贝示例程序运行的结果为:析构函数运行,学生李四占用的内存释放!析构函数运行,学生李四占用的内存释放!本例中没有采用缺省的拷贝构造函数,而根据需要自行设计了一个拷贝构造函数来完成指针类型数据的复制。本例中对象具体的空间分配见图3-2。3.2 构造函数与析构函数构造函数与析构函数图3-2 深拷贝内存解析图3.2 构造函数与析构函数构造函数与析构函数3.2.5 构造函数和析构函数的调用过程构造函数和析构函数的调用过程程序是由对象构成的,在程序完成功能期间存在着对象的创建和消亡,因此不可避免地构造函数和析构函数被频繁地调用。深刻理解它们的调用过程是研究面向对象机制的一种方法,因为这其中涉及到了对象的作用域。将类的构造函数、重载的构造函数和析构函数中加入输出语句,通过运行时的输出提示可以分析构造函数和析构函数的调用过程。【例3.15】构造函数和析构函数的调用过程示例3.2 构造函数与析构函数构造函数与析构函数程序运行的结果为:构造函数被调用!学生姓名:李四总评成绩:23拷贝构造函数被调用!学生姓名:李四总评成绩:23-程序结束,析构函数开始被调用-析构函数运行,学生李四占用的内存释放!析构函数运行,学生李四占用的内存释放!3.2 构造函数与析构函数构造函数与析构函数“student s1(李四,23);”执行时,标准的构造函数被调用,为s1对象初始化;“student s2(s1);”执行时student类的拷贝构造函数被调用,为s2对象初始化。“s2.show();”执行完毕后,main函数中不再有发向s1对象和s2对象的消息。当main函数的最后一条语句执行完毕后,析构函数开始运行。析构函数的调用顺序恰好是构造函数调用顺序的逆序。3.3 对象对象对象是一种结构化的数据,类的实例化过程中,通过系统自动调用类的构造函数,使得在创建对象的同时,给对象赋了初值。3.3 对象对象3.3.1 对象的定义对象的定义对象是由类产生的,类似于用基本数据类型产生普通变量。根据创建对象的位置和时间的不同,对象有着不同的作用域。【例3-16】分数类的不同对象程序运行结果为:全局分数为:3/4局部分数为:1/2显示分数数组的内容:分数为:0分数为:0分数为:0动态分数为:03.3 对象对象若某个类成员(包括成员变量和成员函数)定义为public访问权限,则允许外部程序访问该成员,这也称为类的公共接口;若某个类成员定义为private访问权限,则只允许该类本身的成员函数访问这些成员,不允许外部程序访问这些成员,这也称为类的封装或信息隐藏。类的封装性提高了数据的安全性,规范了外界对类成员的访问权限和访问形式。1.类成员的访问权限类成员的访问权限【例3.17】重新设计分数类,使外部程序能够设置和读取分数对象的分子和分母,并设计测试程序。exam3-17.h 测试程序设计如下:exam3-17.cpp3.3 对象对象程序运行的结果为:分数为:5/4分数为:4/7读取的分子为:4读取的分母为:72.类成员的访问形式类成员的访问形式在访问权限合法的前提下,外界对类成员的访问有圆点访问形式和指针访问两种形式。3.3 对象对象圆点访问形式采用的是成员运算符“.”,格式为:对象名.公有成员指针访问形式使用成员访问运算符“-”,例3.15中就用到了这种形式,其格式为:对象指针变量名-公有成员 或(*对象指针变量名).公有成员【例3-18】访问形式示例程序运行的结果为:分数为:5/4分数为:8/9分数为:8/93.4 子对象子对象当一个类的成员是某一个类的对象时,该对象就为子对象。子对象实际就是对象成员。如:class Apublic:private:;3.4 子对象子对象class Bpublic:private:A a;其中,B类中成员a就是子对象,它是A类的对象作为B类的成员。3.4 子对象子对象3.4.1 组合模式组合模式面向对象程序设计语言中,表示这种组合模式的方法是首先定义表示部分概念的类,然后再定义表示整体概念的类,并在定义整体概念的类中,定义一个部分概念类的成员变量,通常把这种成员变量也叫做子对象。整体概念类就是通过子对象来调用部分概念类的成员的。【例3.19】设计学生类程序运行的结果为:学生名字为:lisi学生学号为:090310计算机成绩为:89.5英语成绩为:78逻辑成绩为:953.4 子对象子对象-重新修订成绩后-学生名字为:lisi学生学号为:090310计算机成绩为:98英语成绩为:78逻辑成绩为:833.4 子对象子对象3.4.2 子对象和构造函数设计子对象和构造函数设计当一个类中存在子对象时,系统无法自动调用相应的构造函数为子对象赋初值。因此,需要在构造函数设计中,考虑为子对象赋初值的问题。通常的做法是显式调用相应的构造函数为子对象赋初值。系统在调用构造函数对student类对象进行初始化的同时,也要对子对象sc进行初始化,所以在【例3.18】中student类的构造函数设计为:student:student(char*na,char*nu,float s1,float s2,float s3):sc(s1,s2,s3)name=new charstrlen(na)+1;strcpy(name,na);3.4 子对象子对象number=new charstrlen(nu)+1;strcpy(number,nu);这时构造函数的调用顺序是:先调用子对象的构造函数,随后再执行student构造函数的函数体。【3.20】整体部分模式中构造函数和析构函数的调用过程程序运行的结果为:point 类构造函数调用point 类构造函数调用line 类构造函数 1 调用point 类构造函数调用3.4 子对象子对象point 类构造函数调用point 类拷贝构造函数调用point 类拷贝构造函数调用line 类构造函数 2 调用-line 类析构函数调用point 类析构构造函数调用point 类析构构造函数调用point 类析构构造函数调用point 类析构构造函数调用line 类析构函数调用point 类析构构造函数调用point 类析构构造函数调用3.4 子对象子对象3.4.3 内部类内部类当类A完全是为类B服务的,外部程序不需要也没有必要创建类A的对象时,类A可以定义在类B的内部。此时,把定义在另一个类中的类(如类A)称为内部类,把封装有内部类的类(如类B)称为外部类。例如,对例3.19的score类和student类来说,如果外部程序不需要也没有必要创建score类的对象,则可以把score类定义在student类中。此时,score类就称为student类的内部类。【例3.21】重新定义学生类,将score类定义为内部类程序运行结果为:3.4 子对象子对象学生名字为:lisi学生学号为:090310计算机成绩为:89.5英语成绩为:78逻辑成绩为:95-重新修订成绩后-学生名字为:lisi学生学号为:090310计算机成绩为:98英语成绩为:78逻辑成绩为:83本例中,score类定义在student类内部,它为student类提供技术支撑,而自身在main函数中却不能够再定义对象。3.5 静态成员静态成员为了实现一个类的不同对象之间的数据和函数共享,C+提出了静态成员的概念。静态成员包括静态数据成员和静态成员函数。3.5.1 定义与引用定义与引用无论是静态成员变量还是静态成员函数,其声明方式和非静态成员的声明类似,都是在原先的声明基础上,以关键字static加以限定,其声明格式为:static 类成员的声明;例如:static int count;/静态成员变量static void show();/静态成员函数对静态成员变量和静态成员函数的引用方式,均可以通过类名加以标识,语句格式为:类名:静态成员名称3.5 静态成员静态成员3.5.2 静态数据成员静态数据成员静态成员变量不是定义每个具体对象时都单独具有的成员变量,而是整个类只有一个的成员变量,所以静态变量不能够用构造函数初始化,因此静态成员变量必须单独进行初始化。程序运行时静态成员变量就必须存在,直到程序结束时才消除静态成员变量。静态成员变量可以定义为公有的,但一般情况下应定义为私有的。静态成员变量不能再限定为const,因为const成员变量只能一次赋值,而静态成员变量一般需要多次修改。静态成员变量在主函数外(包括类外)初始化,语句格式为::=;3.5 静态成员静态成员【例3.22】静态成员变量的使用程序运行结果为:教师类的总工资为:100教师类的总工资为:300总工资为:0本例中,在程序没有结束而所有对象都已释放的情况下,查询teacher类的总工资只能通过外部程序访问teacher类静态成员变量的手段得到,前提是teacher类静态成员变量allsalary的权限是public,实际上这样做在某种程度上破坏了类的封装性。3.5 静态成员静态成员3.5.3 静态成员函数静态成员函数静态成员函数可以定义为私有的,但一般情况下应定义为公有的。静态成员函数不能再限定为const,否则编译时将出错。应用程序调用静态成员函数的语句格式是::();【例3.23】静态成员函数的使用程序运行结果为:教师类的总工资为:100教师类的总工资为:300总工资为:0对本例和例3.21进行对比,二者的运行结果完全一致,但是采用的手段却不尽相同。本例采用的是静态成员函数来实现在程序没有结束而所有对象都已释放的情况下,查询teacher类的总工资,而且没有打破类的封装性。3.6 友元友元封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。封装使类的使用者不必了解具体的实现细节,而只是要通过类提供的外部接口,以特定的访问权限来使用类的成员可以看到通过封装使一部分成员充当类与外部的接口,而将其他的成员隐蔽起来,这样就达到了对成员访问权限的合理控制,使不同类之间的相互影响减少到最低限度,进而增强数据的安全性和简化程序的编写工作。3.6 友元友元3.6.1 友元的作用友元的作用在有些时候需要在类的外部访问类的私有成员。为此,要寻求一种途径,在不放弃私有成员安全性的前提下,使得一个普通的函数或类的成员函数可以访问到封装到某一个类中的信息,在C+中采用友元作为实现这个需求的手段。友元分友元类、友元成员函数和友元函数三种。3.6 友元友元3.6.2 友元函数友元函数若类B定义某个函数是自己的友元,则称该函数是类B的友元函数。如果某函数是类B的友元函数,则该函数可以访问类B的所有成员。【例3.24】友元函数操作类的私有成员程序运行结果为:lisi是一个病人!lisi是一个健康的人!本例中,对于patient类对象来说,treatment函数在其外部,按照封装的原则,treatment函数无权操作patient类对象内部的私有成员。但是,patient类定义treatment函数是自己的友元函数,因此treatment函数就具有了操作patient类对象内部的私有成员的权限。3.6 友元友元3.6.3 友元类友元类若类B定义类A是自己的友元,则称类A是类B的友元类。如果类A是类B的友元类,则类A中的所有成员函数都可以访问类B的所有(公有、私有、保护)成员。【例3.25】友元类的设计程序运行结果为:lisi是一个病人!lisi是一个健康的人!本例中,doctor类被定义为patient类的友元类,因此doctor类中的所有成员函数都可以像操作本类中的成员一样来操作patient类中的成员。3.6 友元友元3.6.4 友元成员函数友元成员函数若类B定义类A的某个成员函数是自己的友元,则称该成员函数是类B的友元成员函数。如果某成员函数是类B的友元成员函数,则该成员函数可以访问类B的所有成员。相对于友元类,友元成员函数只是将某一个类中的成员函数定义为另一个类的友元,而友元类是将某一个类中的所有成员函数定义为另一个类的友元。友元成员函数的定义方法如下:class B/定义类Bfriend void A:f(void);/定义函数f()是类B的友元成员函数;由于这种方法可以用定义友元类的方法取代,因而不常采用。3.7 设计举例设计举例类是面向对程序设计的基础,当类的定义完成后,在应用程序中就可根据问题域的需要来生成对象,通过对象间消息的传递来实现应用程序的功能。对于一个简单允许客户透支的银行信用卡系统的设计要求如下:(1)客户余额的输出采用类似n元n角n分这样的形式。(2)允许客户存款、取款和转账,并允许客户取款时透支,即允许客户欠款。(3)设计一个主程序进行基本情况的演示。银行信用卡客户的操作允许存款、取款、转账和输出,并允许取款时透支。因此,银行信用卡类应该有客户账号、姓名和余额成员变量,应该有存款、取款、转账和输出成员函数。3.7 设计举例设计举例系统要求客户余额的输出采用类似n元n角n分这样的形式,这需要特殊处理,因此,应该把信用卡类里的成员变量余额设计成一个余额类。余额类应该有余额成员变量,应该有加款、减款和输出成员函数。信用卡类和余额类之间是一种组合关系,即余额类是组成信用卡类的一部分,对象模型如图3-3所示。图3-3 信用卡对象模型3.7 设计举例设计举例【例3.26】可透支的信用卡设计程序运行的结果为:姓名:张三 账户:100000 余额:3元3角1分-姓名:李斯 账户:100001 余额:40元4角2分-姓名:王五 账户:100002 欠款:500元5角0分-用户:张三 转账给 李斯 金额:100元3.7 设计举例设计举例姓名:张三 账户:100000 欠款:96元6角10分-姓名:李斯 账户:100001 余额:190元4角2分-姓名:王五 账户:100002 欠款:600元5角0分-3.7 设计举例设计举例C+程序的多文件结构结合到类的应用可以将源程序清晰划分。在实际设计中,一个源程序按照结构分可以划分为3个文件:类声明文件(*.h)、类实现文件(*.cpp)和类的使用文件(*.cpp)。将类的声明部分放在类的声明文件中,就形成了类的公共接口,负责向用户提供调用类成员函数所需的函数原型。将类的成员函数的定义放在类实现文件中,就形成了类的实现方法。将类的使用部分放在类的使用文件中,可以清晰地表示出本程序所要完成的任务。按照这样的划分,例3.1和例3.3就可以写成fraction.h(类声明文件)、fraction.cpp(类实现文件)和fs.cpp(类使用文件)三个文件,共同完成工作。【3.36】实现分数的四则运算3.7 设计举例设计举例把类的声明和实现放在不同的文件之中,主要有以下考虑:(1)类的实现文件通常较大,将两者混在一起不便于阅读、管理和维护。软件工程的一个基本原则是将接口与实现方法分离,这样可以更容易地修改程序。对 类的用户而言,类的实现方法的改变并不影响用户,只要接口不变即可。例如对例3.35中fration.cpp文件所包含的成员函数的函数体内部进行代码优化,优化后的成员函数在某个方面得到性能提升,但是这些改动不会反映到frction.h中,frction.h对外界提供的界面是稳定的,从而不会影响到fs.cpp,达到了程序升级而调用关系和使用方法没有改变的目的。3.7 设计举例设计举例(2)将类中的成员函数的实现放在其声明文件中与放在实现文件中,在编译时的含义是不一样的,若将成员函数的实现直接放在类声明文件,则类的成员函数将作为内联函数处理。显然将所有的成员函数都作为内联函数处理是不合适的。(3)对于软件开发商来说,他们可以向用户提供一些程序模块,这些程序模块往往只向用户公开类的声明,即接口,而不公开程序的源代码。而类的用户使用类时不需要访问类的源代码,但需要连接类的目标码。类的声明和实现分开管理可以往好地解决这个问题。(4)便于团体式的大型软件开发。3.7 设计举例设计举例采用这样的组织结构,可以对各个文件进行单独编辑、编译,最后再连接和运行。同时可以充分利用类的封装特性,在程序的调试、修改时只对其中某一个文件进行操作,而其余部分根本就不用改动。例如,我们只修改了类的成员函数的实现部分,只需重新编译类实现文件并连接即可,其余的文件几乎可以不用看。如果是一个语句很多、规模特大的程序,效率就会得到显著的提高。思考与练习思考与练习思考与练习思考与练习1、什么是对象的属性和方法?它们在C+中是如何体现的?2、构造函数和析构函数的设计方法是什么?3、类成员的三种控制权限是什么?它们有何作用?4、成员函数重载有何价值?