1、第10章 类与对象10.1 面向对象程序设计的方法及特征10.2 类与对象10.3 构造函数和析构函数10.4 类的静态成员10.5 友元第10章 类与对象10.1 面向对象程序设计的方法及特征第10章 类与对象面向对象程序设计的主要特点是抽象、封装、继承和多态。抽象是对具体问题(对象)进行概括,抽取出一类对象的共有性质并加以描述的过程。抽象是一种思维的方法,其实就是在观察事物时,忽略其中不重要的方面,只留下关注的共性部分。面向对象方法中的抽象包括两个方面:数据抽象和行为抽象。前者描述某类对象的属性或状态,后者描述的是该类对象的共同行为或功能特征。即使面对同一个对象,在不同的应用场景下,抽象的
2、方式也经常不同。封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机整体的过程;也就是将表示属性的数据和操作数据的函数代码结合成“类”的过程。继承的概念来源于现实生活中特殊与一般的关系。使用类对对象进行建模的时候,不同的类可能会有一些共同的特征和行为,将这些定义在一个通用类中,然后由这个通用类派生出新类的过程就叫做继承。多态是指一段程序能够处理多种类型对象的能力。在C+语言中,多态性可以通过强制多态、重载多态、类型参数化多态、包含多态等形式来实现。强制多态是通过数据类型转换(隐式或显式)来实现的。重载是指给同一个名字赋予不同的含义,如函数重载、运算符重载等。类型参数多态化由模板来实现,
3、模板分函数模板和类模板两种。第10章 类与对象10.2 类与对象第10章 类与对象理解面向对象的程序设计方法,需要先理解什么是“类”。为了对具有同一类属性(又称为状态)和行为(又称为操作、方法)的对象进行分类描述,面向对象的程序设计方法引入了类(class)的概念。把对象进行分类依据的原则是抽象,即只考虑与当前求解问题有关的本质特征,而忽略对象的非本质特征。通过对同类实体进行抽象,找出其共有属性和行为,便可以构造出一种特定的类型,再使用该类型来定义同一类型的对象,这个类型就是“类”。从这个角度理解,类也可以看作自定义的数据类型。类是面向对象程序设计方法的核心,利用类可以实现对数据的封装和隐藏。
4、类是一个模板,可以定义同一类对象的属性和行为。其中属性由数据成员构成,行为由函数成员构成。一个对象是所属类的一个实例。对象与类的关系相当于变量与数据类型的关系。可以对一个类创建多个实例,也即可以创建一个类的多个对象。虽然属于某个类的所有对象具有相同的属性和行为,但各个对象在具体的属性上有所不同。一个对象的状态可以由具有当前值的数据成员来表示,一个对象的行为由函数成员定义,调用对象的某个函数成员就是要求对象完成一个特定动作。面向对象程序设计把数据和处理数据的函数当成一个整体,即对象,从现实世界出发,采用对象来描述问题空间的实体,用程序代码模拟现实世界中有形或无形的对象,使程序设计过程更加自然、直
5、观。结构化程序设计是以功能为中心来划分系统的,而面向对象程序设计是以数据为中心来划分系统的;相对于功能而言,数据具有更强的稳定性。类可以用来定义自己的数据类型;通过定义新的类型来反映待解决问题中的各种概念,可以使我们更容易编写、调试和修改复杂的程序。第10章 类与对象10.2.1 类从形式上看,类的定义分为数据成员和函数成员两部分。数据成员为表示属性的各种类型的变量,函数成员则是用来完成类的行为或功能的若干函数。例如,定义一个用来表示矩形的Rectangle类。假设我们关注的矩形的属性是其宽度和高度,用数据成员width和height来描述;分别为属性width和height提供输入和输出的函
6、数成员setwidth()、setheight()、getwidth()和getheight(),并定义getArea()和getPerimeter()函数成员,分别返回矩形的面积和周长。则按照C+语言的语法,Rectangle类的定义如下:Rectangle类中封装了矩形的数据和行为,分别称为其数据成员和函数成员。Rectangle类中定义的数据成员和函数成员第10章 类与对象描述了抽象的结果。所谓封装,就是将抽象出的各种数据成员和函数成员“打包”到一起形成类。封装在形式上的体现,就是用“”和“”限定了类的边界。“”后面以“;”作为声明语句的结束。关键字public、private和prot
7、ected用来指定成员的不同访问权限。声明为public的成员是公共的,在类外可以访问。通常来讲,一个类必须有公有成员,否则该类将无法被使用。通常在类中声明为public的是函数成员;声明为private的成员是本类的私有成员,外部无法直接访问,只有本类的函数成员可以访问;声明为protected的成员是保护类型成员,被它声明的成员变量的内容只能被该类自己的成员函数以及由该类派生的类的函数所使用,外部函数无权访问。未用访问权限修饰的成员默认为private。不同访问权限的成员可以按照任意次序排列,为了便于阅读,通常将相同权限的成员编排在一起,或者将数据成员编排在一起,函数成员编排在一起。实际上
8、,在了解了类成员的不同访问权限之后,对“封装”的概念就有了更深层次的了解。可以把类看作一个模块,在设计一个类的时候,把私有数据隐藏在内部,把公共的函数接口暴露在外面,这就是封装。定义类的语法形式如下:第10章 类与对象需要注意的是,在类中可以只声明函数的原型,以说明函数的参数列表和返回值类型,函数的实现(即函数体)可以在类外定义。这种处理方式的优点是类的定义中只包括数据成员和函数成员的声明,结构看起来比较清晰,可读性会比较好。若将函数的具体实现写在类定义之外,则需要指明类的名称。其具体形式为符号“:”叫做作用域运算符,用于表明当前对象的作用域,可以理解为查找范围。写在“:”前面的那个标识符就是
9、它的查找范围,编译器会去这个范围内找它的函数声明。例如本例中,编写getArea和getPerimeter两个函数的实现方法时,前面加上“Rectangle:”就是告诉编译器这两个函数是Rectangle类的成员函数,可以去Rectangle类中寻找它们的函数声明。如果某些成员函数需要被频繁调用,并且代码比较简单,也可以将函数体的代码直接写在类的定义中,这种情况称为内联函数。内联函数的声明可以使用显式声明或隐式声明。显式声明的函数体仍放在类体外,在函数返回值类型前加上inline关键字。对内联函数采用显式声明或者隐式声明,在效果是一样的。内联函数与非内联函数的区别体现在编译器对其的处理上。在编
10、译阶段,内联函数的代码会被插入到每一个调用它的地方,这样可以减少运行时函数调用过程中内存资源的消耗,但是会增加编译后代码的长度。所以只有相对简单的成员函数才可以声明为内联函数。类的成员函数可以有默认的形参值。成员函数的默认形参值,一定要写在类定义中,而不能写在类定义之外的函数实现中。第10章 类与对象说明:(1)类的定义代码分为两个部分:数据成员声明和函数成员声明,并指定各成员的访问权限。声明数据成员的语法形式类似于定义变量的语句,但声明时不能初始化。(2)函数成员可以访问本类中任意位置的数据成员,或调用本类中任意位置的函数成员。类成员之间互相访问不需要“先声明,后访问”,也不受访问权限的限制
11、。(3)在类定义代码(包括声明和实现两部分)范围内,任何类成员都可以被本类的其他成员访问,类成员具有类作用域。数据成员相当于类中的全局变量,函数成员相当于类中的外部函数。(4)每个类都是一个独立的类作用域,不同类作用域的标识符可以相同,即不同类的成员可以重名。10.2.2 对象类是一个抽象的概念,描述了一类事物的共同属性和行为。类的对象是指该类的某一特定实体,也称之为实例。例如,将矩形看作一个类,而某一个特定的宽度为2、高度为1的矩形就可以看作矩形类的一个实例,也就是一个对象。与声明一般变量相同,声明对象的一般形式为该语句声明了一个Rectangle类的对象rect。定义了类及其对象后,可以使
12、用成员运算符“.”访问类的成员。访问数据成员的一般形式为第10章 类与对象调用函数成员的一般形式为在类的外部只能访问到类的公有成员;在类的成员函数中,可以访问到类的全部成员。程序中可以定义指向变量的指针、指向数组的指针,同样也可以定义指向对象的指针,并通过指针访问对象中的成员。这是因为,在定义对象的时候,编译器会自动为这个新对象分配一定大小的空间,而这个对象的空间的首地址可以赋值给该类型对象的指针变量,获得的结果是一个指向对象的指针。使用对象指针需注意以下三点:(1)对象指针与被访问的对象应具有相同的类类型。(2)对象指针需先赋值,也即先指向被访问的对象,然后才能间接访问该对象及其成员。(3)
13、通过对象指针也只能访问对象的公有成员,不能访问非公有成员(即私有成员、保护成员)。第10章 类与对象10.3 构造函数和析构函数第10章 类与对象构造函数和析构函数是类中的两个特殊成员函数,分别用来实现对象创建时的初始化设置和对象消亡时的清理工作。10.3.1 构造函数构造函数也是类的成员函数,但是与普通成员函数有些不同:构造函数的函数名与类名相同,且没有返回值。构造函数不需要用户来调用它,而是在创建对象时由系统自动调用,通常被声明为公有函数。构造函数的作用是为新创建的对象的数据成员赋值,因为类的数据成员是不能在声明类的时候初始化的,所以构造函数的作用是在对象被创建时利用特定的值构造对象,将对
14、象初始化为一个特定的状态。如果类中没有写构造函数,编译器会自动生成一个隐含的不带参数的构造函数,也即默认构造函数。该构造函数的参数列表和函数体皆为空。在这种情况下创建的新对象的成员变量将不进行初始化。如果声明了构造函数(无论是否有参数),编译器将不再生成默认构造函数。作为类的成员函数,构造函数可以直接访问类的所有数据成员,构造函数可以是内联函数,可以被重载。构造函数可以带有参数列表,形参可以有默认值。因此,可以根据不同的需要,合理地设计符合实际需要的特定的构造函数。10.3.2 复制构造函数面向对象程序设计中,有时候会需要进行对象的复制。创建一个新的对象,然后将已有对象的数据成员的值一一赋值给
15、新的对象是可以的,但未免繁琐。在C+中,复制构造函数可以实现对象复制的功能。第10章 类与对象复制构造函数具备构造函数的一般特征,其形参是本类对象的引用,作用是以形参代指的已经存在的对象,去初始化同类的新对象。声明和实现复制构造函数的一般方法如下:复制构造函数在以下几种情况下会被调用:(1)当用类的一个对象去初始化该类的另一个对象时;(2)如果函数的形参是类的对象,函数调用过程中,实参对形参进行数据传递时;(3)如果函数的返回值是类的对象,函数执行完毕,向主调对象返回调用结果时。如果程序中没有显式定义类的复制构造函数,系统会在必要的时候自动生成一个隐含的复制构造函数。这个隐含的复制构造函数的功
16、能是把被引用对象的每个数据成员都复制到新建立的对象中。因此,也可以完成同类对象的复制。但系统默认生成的复制构造函数进行的是浅复制,也就是把当前对象的所有数据成员的值赋给新对象。然后在处理字符串或其他对象类型的数第10章 类与对象据成员时,只是将新对象的该成员指向初始值对象的该成员的地址,而不是为其开辟新空间,然后复制其数据成员的值。这样,一旦被复制的对象销毁了,那么复制对象中的该数据成员值就无法访问了。为避免该情况,可以自己编写复制构造函数,并对这些类型的数据成员使用new关键字,向系统申请空间,完成深复制。在定义复制构造函数时请注意,复制构造函数的形参只有一个,且必须是本类的引用。对象指针定
17、义的格式为通过对象指针访问对象成员时使用“-”运算符。其实在创建每一个对象的时候,都有一个对象指针被隐式创建了,它指向这个对象所在空间的首地址,这就是this指针。this指针很多情况下是隐式使用的,作为参数传递给成员函数。10.3.3 参数初始化列表除了在构造函数的函数体内通过赋值语句对数据成员进行初始化以外,C+还提供另外一种初始化数据成员的途径参数初始化列表。这种方法在函数首部的末尾加一个冒号,然后列出参数的初始化列表。通过这种方法直接在类体中定义构造函数,更为方便和简练。第10章 类与对象10.3.4 析构函数析构函数也是一个特殊的成员函数,它的作用与构造函数相反,在对象的生存期即将结
18、束时被自动调用,用于完成对象删除前的清理工作,释放对象占用的内存空间。析构函数的名称由类名前加“”构成,没有返回值,也不接收任何参数,因而参数列表为空,不能被重载。一个类只能有一个析构函数。一般情况下,类的设计者应该在声明类的同时定义析构函数,以指定如何完成“清理”的工作。如果用户没有显式定义析构函数,编译器会自动生成一个析构函数,但该析构函数并不进行任何操作。析构函数调用的顺序与构造函数相反,也即有多个对象被构造时,先构造的后析构,后构造的先析构。第10章 类与对象10.4 类的静态成员第10章 类与对象作用域表示一个标识符(变量)的有效范围,即起作用的范围;可见性表示一个标识符是否可以被引
19、用,即在编写程序的位置是否可以看到该标识符。两者之间既相互联系,又有很大的区别。对于类的成员,同样也存在作用域的问题。从作用域的角度,可以把类理解为一组数据成员与函数成员的集合。类中的成员都具有类作用域,表示这些成员是属于该类的,因此在访问类的成员时需要通过类名限定。类的静态成员是以关键字static修饰的成员,可以是静态数据成员,也可以是静态函数成员。静态成员主要用于解决一个类的不同对象之间数据和函数的共享问题。10.4.1 静态数据成员对于类的数据成员来讲,若将其定义为静态的,则该数据成员在每个类中只有一个副本,由所有该类的对象共同维护和使用,从而实现同一类的不同对象之间的数据共享。例如,
20、设计一个Circle类用来表示圆,该类可以创建半径不同的圆的对象,若要统计圆对象的总数Count,则可将其定义为Circle类的静态数据成员,以达到所有Circle类的对象共享同一个Count备份的目的。静态数据成员具有静态生存期,它可以被看作“类属性”,不属于任何一个对象,可以通过类名进行访问,一般用法是“类名:标识符”,也可以通过对象名引用。在类的定义中对静态数据成员声明后,可以对其进行初始化,但只能在类体外执行,其一般形式为第10章 类与对象初始化语句中不需使用static。一般来讲,用于描述该类所有对象公有特征的数据成员才定义为静态数据成员。这样的属性是用来描述整个圆对象的类别的,而不
21、是用于描述具体某个类对象的。使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储在一处,为所有对象共用。静态数据成员可以更新,更新以后,所有对象读取到的都将是更新后的值。静态数据成员的使用方法和注意事项如下:(1)静态数据成员在定义时前面加关键字static。(2)静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化需要在全局作用域范围内使用如下格式进行:(3)静态数据成员必须进行初始化。(4)引用静态数据成员时,采用如下格式:10.4.2 静态成员函数对于静态数据成员count的访问是通过函数showCount实现的。需要通过Circle
22、类的某个对象,如a或b来调用showCount才能输出count的值。实际上,在没有创建任何Circle类的对象时,count也是有值的,其初值为0。如果我们希望showCount函数也能与类关联,可以通过类名来调用,而不是必须通过对象来调用,可以将其声明为静态成员函数。声明方法是在原有的函数声明前加上关键字static。第10章 类与对象静态成员函数可以通过类名和对象名两种方式调用,但一般习惯于通过类名调用。本例中是通过类名调用的。静态成员函数一般用于访问该类的静态数据成员,也可以访问非静态成员,但必须通过对象名。采用静态成员函数的好处是可以不依赖于对象,直接访问静态数据。关于类的静态数据成
23、员与静态成员函数的使用,需注意以下几点:(1)不能通过类名来调用类的非静态成员函数。(2)通过类的对象可以调用静态成员函数和非静态成员函数。(3)静态成员函数中不能引用非静态成员。因为静态成员函数属于整个类,在类实例化之前就已经分配空间了,而类的非静态成员必须在类实例化之后才能有内存空间。(4)类的非静态成员函数可以调用静态成员函数,但反之不能。(5)类的静态成员变量必须先初始化,再使用。第10章 类与对象10.5 友元第10章 类与对象在C+的类中可以有公共的(public)成员和私有的(private)成员,在类外可以访问公共成员,只有本类中的成员函数才可以访问本类私有的成员。友元则是C+
24、提供的一种破坏数据封装和数据隐藏的机制,通过将模块A声明为另一个模块B的友元,模块A能够引用模块B中被隐藏的信息。友元提供了一种数据共享的方式。友元关系以关键字friend声明。如果友元是一般函数或类的成员函数,称为友元函数;如果友元是一个类,称为友元类,友元类的所有成员函数都自动称为友元函数。10.5.1 友元函数友元函数是在类声明中由关键字friend修饰说明的非成员函数,它可以是一个普通函数,也可以是其他类的成员函数。虽然它不是本类的成员函数,但是在它的函数体中能够通过对象名访问本类的 private 和 protected成员。10.5.2 友元类若在A类的定义中,将B类声明为其友元类,则B类中所有的成员函数都成为A类的友元函数,可以访问A类中的所有成员。声明友元类的语法形式为第10章 类与对象关于友元关系,需要注意以下几点:(1)友元关系是单向的。声明B类是A类的友元类,并不等于A类是B类的友元类。B类中的成员函数可以访问A类中的所有成员,但A类中的函数不能访问B类中的私有成员和保护成员。(2)友元关系不能传递。如果B类是A类的友元类,C类是B类的友元类,不等于C类是A类的友元类。除非在A类中另外声明C类是其友元类。(3)友元关系不能被继承。如果B类是A类的友元类,B的派生类并不会自动成为A类的友元类。