教学目标
●了解封装与数据隐藏的软件工程概念
●了解数据抽象和抽象数据类型(ADT)的符号
●生成C++的ADT(即类)
●了解如何生成、使用和删除类对象
●控制对象数据成员和成员函数的访问
●开始认识面向对象的价值
下面开始介绍C++中的面向对象。为什么把C++中的面向对象推迟到第6章才开始介绍呢原因是我们要建立的对象是由各个结构化程序组件构成,因此先要建立结构化程序的基础知识。
在第1章到第5章的“有关对象的思考”小节中,我们介绍了C++中的面向对象编程的基本概念(即“对象思想”)和术语(即”对象语言”)。在这些”有关对象的思考”小节中,介绍了面向对象设计(object-orienteddesign,OOD)的方法:我们分析了典型问题的陈述,要求建立一个系统(电梯模拟程序),确定实现该系统所需的类,确定这些类对象的属性,确定这些类对象的行为,指定对象之间如何通过交互以完成系统的总体目标。
下面简要介绍面向对象的一些关键概念和术语。OOP将数据(属性)和函数(行为)封装(encapsulate)到称为类(class)的软件包中,类的数据和成员是密切联系的。就像蓝图,建筑人员通过蓝图建造房子,而程序虽则通过类生成对象。一个蓝图可以多次复用.建造多幢房子;一个类也可以多次夏用,建立多个对象。类具有信息隐藏(information hiding)属性,即类对象只知道如何通过定义良好的接口(interface)与其他类对象通信,但通常不知道其他类的实现方法,实现细节隐藏在类中。我们可以熟练地开车,而不需要知道发动机、传递系统和燃油系统的工作原理。我们可以看到信息隐藏对良好的软件工程是多么重要。
在C语言和其他过程化编程语言(proceduralprogramminglanguage)中,编程是面向操作的(action-oriented),而在C++中,编程是面向对象的(object-oriented)。在C语言中,编程的单位是函数(function),而在C++中.编程的单位是类(class),对象最终要通过类实例化。
C语言程序员的主要工作是编写函数,完成某个任务的一组操作构成函数,函数的组合则构成程序。数据在C语言中当然很重要,但这些数据只用于支持函数所要进行的操作。系统指定中的动词帮助C语言程序员确定一组用于实现系统的函数。
C++程序员把重点放在生成称为类的用户自定义类型(user-definedtype),类也称为程序员定义类型(programmer-defined type)。每个类包含数据和操作数据的一组函数。类的数据部分称为数据成员(data member)。类的函数部分称为成员函数(member function,有些面向对象语言中也称为方法)。int等内部类型的实例称为变量(variable),而用户自定义类型(即类)的实例则称为对象(object)。在C++中,变量与对象常常互换使用,C++的重点是类而不是函数。系统指定中的名词帮助C++程序员确定实现系统所需的用来生成对象的一组类。
C++中的类是由C语言中的struct演变而来的。介绍C++类的开发之前.我们先使用结构建立用户自定义类型,通过介绍这种方法的缺点从而说明类的
优点。
结构是用其他类型的元素建立的聚合数据类型。考虑下列结构定义:
struct Time { int hour; // 0-23 int minute; // 0-59 int second; // 0-59 };
结构定义用关键字struct引入。标识符Time是个结构标志(structure tag),命名结构定义并声明该结构类型(structure type)的变量。本例中,新类型名为Time。结构定义花括号中声明的名称是结构的成员(member)。同一结构的成员应有惟一名称.但两个不同结构可以包含同名成员而不会发生冲突。每个结构定义应以分号结尾。上述解释对后面要介绍的类也适用,C++中的结构和类是非常相似的。
Time的定义包含三个int类型的成员hour、minute和second。结构成员可以是任何类型,一个结构可以包含不同类型的成员。但是,结构不能包含自身的实例。例如,Time类型的成员不能在Time的结构定义中声明,但该结构定义中可以包含另一Time结构的指针。当结构包含同一类型结构的指针时,称为自引用结构(self-referential structure)。自引用结构用于形成链接数据结构,如链表、队列、堆栈和树等(见第15章介绍)。
上述结构定义并没有在内存中保留任何空间,而是生成新的数据类型,用于声明变量。结构变量和其他类型的变量一样声明。下列声明:
Time timeObject,timeArray[10] ,*timePtr. &timeRef=timeobject;
声明timeObject为Time类型变量,timeArray为10个Time类型元素的数组,timePtr为Time对象的指针,timeRef为Time对象的引用(用timeObject初始化)。
访问结构成员或类成员时,使用成员访问运算符(member access operator),包括圆点运算符(.)和箭头运算符(—>)。圆点运算符通过对象的变量名或对象的引用访问结构和类成员。例如,要打印timeObject结构的hour成员,用下列语句:
cout<<timeobject.hour;
要打印timeRef引用的结构的hour成员,用下列语句:
cout << timeRef.hour;
箭头运算符由负号(—>和大于号(>)组成,中间不能插空格,通过对象指针访问结构和类成员。假没指针timePtr声明为指向Time对象,结构timeObject的地址赋给timePtr。要打印指针为timePtr的timeObjeet结构的hour成员,用下列语句:
tzmePtr=&timeObject; cout<< timePtr->hour;
表达式timePtr->hour等价于(*timePtr).hour,后者复引用指针并用圆点运算符访问hour成员。
这里的括号是必需的,因为圆点运算符的优先级高于复引用指针运算符(*)箭头运算符和圆点运算符以及括号与方括号([])的优先级较高,仅次于第3章介绍的作用域运算符,结合律为从左向右。
表达式(*timePtr).hour指timePtr所指struct的hour成员。省略括号的*timePtr.hour是个语法错误,因为“.”
的优先级高于“*”,表达式变成*(timePtrhour)。这是个语法错误,因为指针要用箭头运算符引用成员。
图6.1生成用户自定义类型Time,有三个整数成员hour、minute和second。程序定义一个Time类型的结构dinnerTime,并用圆点运算符初始化结构成员hour、minute和second的值分别为18、30和0。然后程序按军用格式(或所谓“通用格式”)和标准格式打印时间。注意打印函数接收常量Time结构的引用,从而通过引用将Time类型的结构传递给打印函数,避免了按值传人打印函数所涉及的复制开销.并用const防止打印函数修改Time结构。第7章将介绍const对象与const成员函数。
// Fig. 6.1: fig0601.cpp // Create a structure, set its members, and print it. #include <iostream.h> struct Time { // structure definition int hour; // 0-23 int minute; // 0-59 int second; // 0-59 }; void printMilitary( const Time & ); // prototype void printStandard( const Time & ); // prototype int main() ( Time dinnerTime; // variable of new type Time // set members to valid values dinnerTime.hour = 18; dinnerTime.minute = 30; dinnerTime.second = O; cout << "Dinner will be held at "; printMilitary( dinnerTime ); cout << " military time, \nwhich is "; printStandard( dinnerTime ); cout << "standard time.\n"; // set members to invalid values dinnerTime.hour = 29; dinnerTime.minute = 73; cout << "\nTime with invalid values: "; printMilitary( dinnerTime ); cout << endl; return 0; } // Print the time in military format void printMilitary( const Time &t ) { cout << ( t.hour < 10? "0" : "" ) << t.hour << ":" << ( t.minute < 10? "0" : "" ) << t.minute; } // Print the time in standard format void printStandard( const Time &t ) { cout << ( ( t.hour == 0 || t.hour == 12 ) ? : t.hour % 12 ) << ":" << ( t.minute < 10 ? "0" : "" ) << t.minute << ":" << ( t.second < 10? "0" : "" ) << t.second << ( t.hour < 12 ? "AM" : "PM" ); }
输出结果:
Dinner will be held at 18:30 military time,
which is 6:30:00 PM standard time.
Time with invalid values: 29:73
图6.1 生成结构、设置结构成员和打印该结构
结构通常按值调用传递。要避免复制结构的开销,可以按引用调用传递结构。
要避免按值调用传递的开销而且保护调用者的原始数据不被修改.可以将长度很大的参数作为const引用传递。
用这种方式通过结构生成新数据类型有一定的缺点。由于初始化并不是必须的,因此就可能出现未初始化的数据,从而造成不良后果。即使数据已经初始化,也可能没有正确地初始化。因为程序能够直接访问数据,所以无效数据可能赋给结构成员(如图6.1)。在第30行和第31行,程序很容易向Time对象dinnerTime的hour和minute成员传递错值。如果struct的实现方法改变(例如时间可以表示为从午夜算起的秒数),则所有使用这个struct的程序都要改变。这是因为程序员直接操作数据类型。没有一个”接口”保证程序员正确使用数据类型并保持数据的一致状态。
一定要编写易于理解和易于维护的程序。不断改变是规则而不走例外。程序员应预料到代码要经常改变。
可以看出,类能够提高程序的可修改性。
还有其他与C语言式结构相关的问题。在C语言中,结构不能作为一个单位打印,而要一次一个地打印和格式化结构成员。可以编写一个函数,以某种格式打印结构成员。第8章”运算符重载”中演示了如何重载<<运算符,使结构类型或类类型的对象能够方便地打印。在C语言中,结构不能整体进行比较,而只能一个成员一个成员地比较。第8章还会演示如何重载相等运算符与关系运算符,比较C++结构类型或类类型的对象。
下一节重新将Time结构实现为C++类,并演示用类生成抽象数据类型(abstract data type)的好处。从中将会看到,C++中类和结构的用法基本相同,差别在于各自的成员相关的默认访问能力不同。