📄 06章 类与数据抽象(一).txt
字号:
C++大学教程(第6章 类与数据抽象(一)-01)
教学目标
●了解封装与数据隐藏的软件工程概念
●了解数据抽象和抽象数据类型(ADT)的符号
●生成C++的ADT(即类)
●了解如何生成、使用和删除类对象
●控制对象数据成员和成员函数的访问
●开始认识面向对象的价值
6.1 简介
下面开始介绍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++类的开发之前.我们先使用结构建立用户自定义类型,通过介绍这种方法的缺点从而说明类的优点。
6.2 结构定义
结构是用其他类型的元素建立的聚合数据类型。考虑下列结构定义:
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初始化)。
6.3 访问结构成员
访问结构成员或类成员时,使用成员访问运算符(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章介绍的作用域运算符,结合律为从左向右。
常见编程错误6.1
表达式(*timePtr).hour指timePtr所指struct的hour成员。省略括号的*timePtr.hour是个语法错误,因为“.”
的优先级高于“*”,表达式变成*(timePtrhour)。这是个语法错误,因为指针要用箭头运算符引用成员。
6.4 用struct实现用户自定义类型Time
图6.1生成用户自定义类型Time,有三个整数成员hour、minute和second。程序定义一个Time类型的结构dinnerTime,并用圆点运算符初始化结构成员hour、minute和second的值分别为18、30和0。然后程序按军用格式(或所谓“通用格式”)和标准格式打印时间。注意打印函数接收常量Time结构的引用,从而通过引用将Time类型的结构传递给打印函数,避免了按值传人打印函数所涉及的复制开销.并用const防止打印函数修改Time结构。第7章将介绍const对象与const成员函数。
1 // Fig. 6.1: fig0601.cpp
2 // Create a structure, set its members, and print it.
3 #include <iostream.h>
5 struct Time { // structure definition
6 int hour; // 0-23
7 int minute; // 0-59
8 int second; // 0-59
9 };
10
11 void printMilitary( const Time & ); // prototype
12 void printStandard( const Time & ); // prototype
13
14 int main()
15 (
16 Time dinnerTime; // variable of new type Time
17
18 // set members to valid values
19 dinnerTime.hour = 18;
20 dinnerTime.minute = 30;
21 dinnerTime.second = O;
22
23 cout << "Dinner will be held at ";
24 printMilitary( dinnerTime );
25 cout << " military time, \nwhich is ";
26 printStandard( dinnerTime );
27 cout << "standard time.\n";
28
29 // set members to invalid values
30 dinnerTime.hour = 29;
31 dinnerTime.minute = 73;
32
33 cout << "\nTime with invalid values: ";
34 printMilitary( dinnerTime );
35 cout << endl;
36 return 0;
37 }
38
39 // Print the time in military format
40 void printMilitary( const Time &t )
41 {
42 cout << ( t.hour < 10? "0" : "" ) << t.hour << ":"
43 << ( t.minute < 10? "0" : "" ) << t.minute;
44 }
45
46 // Print the time in standard format
47 void printStandard( const Time &t )
48 {
49 cout << ( ( t.hour == 0 || t.hour == 12 ) ?
50 12 : t.hour % 12 )
51 << ":" << ( t.minute < 10 ? "0" : "" ) << t.minute
52 << ":" << ( t.second < 10? "0" : "" ) << t.second
53 << ( t.hour < 12 ? "AM" : "PM" );
54 }
输出结果:
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 生成结构、设置结构成员和打印该结构
性能提示6.1
结构通常按值调用传递。要避免复制结构的开销,可以按引用调用传递结构。
软件工程视点6.2
要避免按值调用传递的开销而且保护调用者的原始数据不被修改.可以将长度很大的参数作为const引用
传递。
用这种方式通过结构生成新数据类型有一定的缺点。由于初始化并不是必须的,因此就可能出现未初始化的数据,从而造成不良后果。即使数据已经初始化,也可能没有正确地初始化。因为程序能够直接访问数据,所以无效数据可能赋给结构成员(如图6.1)。在第30行和第31行,程序很容易向Time对象dinnerTime的hour和minute成员传递错值。如果struct的实现方法改变(例如时间可以表示为从午夜算起的秒数),则所有使用这个struct的程序都要改变。这是因为程序员直接操作数据类型。没有一个”接口”保证程序员正确使用数据类型并保持数据的一致状态。
一定要编写易于理解和易于维护的程序。不断改变是规则而不走例外。程序员应预料到代码要经常改变。
可以看出,类能够提高程序的可修改性。
还有其他与C语言式结构相关的问题。在C语言中,结构不能作为一个单位打印,而要一次一个地打印和格式化结构成员。可以编写一个函数,以某种格式打印结构成员。第8章”运算符重载”中演示了如何重载<<运算符,使结构类型或类类型的对象能够方便地打印。在C语言中,结构不能整体进行比较,而只能一个成员一个成员地比较。第8章还会演示如何重载相等运算符与关系运算符,比较C++结构类型或类类型的对象。
下一节重新将Time结构实现为C++类,并演示用类生成抽象数据类型(abstract data type)的好处。从中将会看到,C++中类和结构的用法基本相同,差别在于各自的成员相关的默认访问能力不同。
6.5 用类实现Time抽象数据类
型
类使程序员可以构造对象的属性(attribute,表示为数据成员)和行为(behavior)或操作(operation,表示为成员函数)。C++中用关键字class定义包含数据成员和成员函数的类型。
成员函数在有些面向对象编程语言中也称为方法(method),响应对象接收的消息(message)。消息对应于一个对象发给另一个对象或由函数发给对象的成员函数调用。
一旦定义了一个类,可以用类名声明该类的对象。图6.2显示了Time类的简单定义。
Time类定义以关键字class开始。类定义体放在左右花括号(C1)之间,类定义用分号终止。Time类定义和Time类结构定义各包含三个整型成员hour、minute和second。
1 class Time {
2 public:
3 Time();
4 void setTime( int, int, int);
5 void printMilitary();
6 void printStandard();
7 private:
8 int hour; // 0-23
9 int minute; // 0-59
10 int second; // 0-59
11 };
图6. 2 Time类的简单定义
常见编程错误6.2
忘记类或结构定义结束时的分号是个语法错误。
类定义的其他部分是新内容。public:和private:标号称为成员访问说明符(member accessspecifier)。在程序能访问Time类对象的任何地方都可以访问任何在成员访问说明符public后面(和下一个成员访问说明符之前)声明的数据成员和成员函数。成员访问说明符private后面(和下一个成员访问说明符之前)声明的数据成员和成员函数只能由该类的成员函数访问。成员访问说明符总是加上冒号,可以在类定义中按任何顺序多次出现。本文余下部分使用不带冒号的成员访问说明符public和private。第9章还将介绍另一个成员访问说明符protected,并介绍继承及其在面向对象编
程中的作用。
编程技巧6.1
每个成员访问说明符只在类定义中使用一次,这样可以增加清晰性与可读性。将public成员放在前面,便
于寻找。
类定义中的访问说明符public后面是成员函数Time、setTime、printMihtary和printStandard的函数原型。这些函数是类的public成员函数(或public服务、public行为、类的接口)。类的客户(client,即程序中的用户部分)使用这些函数操作该类的数据。
注意与类名相同的成员函数,称为该类的构造函数(constructor)。构造函数是个特殊成员函数,该函数初始化类对象的数据成员。类的构造函数在生成这个类的对象时自动调用。一个类常常有几个构造函数,这是通过函数重载完成的。注意,构造函数不指定返回类型。
常见编程错误6.3
对构造函数指定返回类型或返回值是个语法错误。
成员访问说明符private后面有三个整型成员,表示类的这些数据成员只能让成员函数访问(下一章会介绍还可由类的友元访问)。这样,数据成员只能由类定义中出现函数原型的4个函数(和类的友元)访问。数据成员通常放在类的private部分,成员函数通常放在Public部分。稍后会介绍,也可以用private成员函数和public数据,但比较少见,这不是好的编程习惯。
定义类之后,可以在声明中将其当作类型,如下所示:
Time sunset, // object of type Time
arrayOfTimes[ 5 ], // array of Time objects
*pointerToTime, // pointer to a Time object
&dinnerTime = sunset; // reference to a Time object
类名成为新的类型说明符。一个类可以有多个对象,就像int类型的变量可以有多个。程序员可以在需要时生成新的类类型,因此C++是个可扩展语言(exlensible language)。
图6.3使用Time类。程序实例化Time类的一个对象t。当对象实例化时,Time构造函数自动调用,显式地将每个private数据成员初始化为0。然后按军用格式和标准格式打印时间,确保成员已经正确地初始化。然后用setTime成员函数设置时间,并再次按两种格式打印时间。接着用setTime成员函数设置时间为无效值.并再次按两种格式打印时间。
1 // Fig. 6.3: fig06_03.cpp
2 // Time class.
3 #include <iostream.h>
4
5 // Time abstract data type (ADT) definition
6 class Time {
7 public:
8 Time(); // Constructor
9 void setTime( int, int, int ); // set hour, minute, second
10 void printMilitary(); // print military time format
11 void printStandard(); // print standard time format
12 private:
13 int hour; // 0 - 23
14 int minute; // 0 - 59
15 int second; // 0 - 59
16 };
17
18 // Time tructor initiali ...... h data membertt~tzer~'-st t
19 // Ensures all Time objects start in a conchs en s a e.
21
22 // Set a new Time value using military time. Perform validity
25 {
26 hour=e ( h >= 0 && h < 24 ) ? h : 0;
minut = ( m >= 0 && m < 60 ) ? m : 0;
28 second = ( s >= 0 && s < 60 ) ? s : 0;
29}
31 // Print Time in military format
32 void Time::printMilitary()
35 << ( minute < 10 ? "0" : "" ) << minute;
37
38 // Print Time in standard format
39 void Time::printStandard()
4O {
41 cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 )
42 << ":" << ( minute < 10 ? "0" : .... ) << mlnu e
43 << ":" << ( second < 10 ? "0" : "" ) << second
44 << ( hour < 12 ? "AM" : "PM" );
45 }
46
47 // Driver)trna (in ...... imple class Time
48 int main()
49 {
50 Time t; // instantiate object t of class Time
51
52 cout << "The initial military time is ";
53 t.printMilitary();
54 cout << "\nThe initial standard time is ";
55 t.printStandard();
56
57 t.setTime( 13, 27, 6 );
58 cout << "\n\nMilitary time after setTime is ";
59 t.printMilitary();
60 cout << "\nStandard time after setTime is ";
61 t.printStandard();
62
63 t.setTime( 99, 99, 99 ); // attempt invalid settings
64 cout << "\n\nAfter attempting invalid settings:"
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -