⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 07章 类与数据抽象(二).txt

📁 C++大学教程txt版中文版 C++大学教程txt版中文版
💻 TXT
📖 第 1 页 / 共 5 页
字号:
107   delete e1Ptr;  // recapture memory
108   e1Ptr = 0;
109   delete e2Ptr;  // recapture memory
110   e2Ptr = 0;
111
112   cout << "Number of employees after deletion is"
113        << Employee::getCount() << endl;
114
115   return 0;
116 }

输出结果:
Number of employees before instantiation is 0
Employee constructor for Susan Baker called.
Employee constructor for Robert Jones called.
Number of employees after instantiation is 2

Employee 1: Susan Baker
Employee 2: Robert Jones

~ Employee() called for Susan Baker
~ Employee() called for Robert Jones
Number of employees after deletion is 0

                          图7. 9  用static数据成员维护类的对象个数

    Employee类的对象不存在时,仍然可以引用成员count,但只能通过调用static成员函数getCount:
    Employee::getCount()
  本例中,函数getCount确定当前实例化的Employee对象个数。注意,程序中没有实例化的对象时,发出employee::getCount()函数调用。但如果有实例化的对象,则可以通过一个对象调用函数getCount,见第97行和第98行的语句:
    cout << "Number of employees after instantiation is "
        <<elPtr—>getCount();
   注意,调用e2Ptr->getCount()和Employee::getCount()也能使上述语句运行。

    软件工程视点7.13
    有些公司的软件工程标准要求所有static成员函数只能对类名句柄调用,而不能对对象句柄调用。

    如果成员函数不访问非static类数据成员和成员函数,则可以声明为static。与非static成员函数不同的是,static成员函数没有this指针,因为static类数据成员和成员函数是独立于类对象而存在的。

    常见编程错误7.11
    在static成员函数中引用this指针是个语法错误。

    常见编程错语7.12
    将static成员函数声明为const是个语法错误。

     软件工程视点7.14
    即使在类没有实例化任何对象时,类的static数据成员和成员函数就已经存在并可使用。

    第94行和第95行用运算符new动态分配两个Employee对象。分配每个Employee对象时调用其构造函数。第107行和第109行用delete释放两个Employee对象的内存空间时,调用其析构函数。

    编程技巧7.4
    删除动态分配内存后,设置指向该内存的指针指向0,辽样就切断了指针与前面所分配内存的连接。

    注意Employee构造函数中使用了assert。assert宏在assert.h头文件中定义,测试条件值。如果表达式值为false,则assert发出错误消息,并调用abort函数(在一般实用程序头文件stdlib.h中)终止程序执行。这是个有用的调试工具,可以测试变量是否有正确值。在这个程序中,assert确定new运算符能否满足动态分配内存的请求。例如,在Employee构造函数中,下列语句(也称为断言):
    assert(firstName!=O);
测试指针fi~tNme以确定其是否不等于0。如果上述语句中的条件为true,则程序继续执行,不被中断。如果上述语句中的条件为false,则程序打印一个错误消息,包括行号、测试条件和断言所在的文件名,然后程序终止。程序员可以从这个代码区域找出错误。第13章"异常处理"中将介绍处理执行时错误的更好方法。
    断言不一定要在调试完成后删除。程序不再用这个断言进行调试时,只要在程序文件开头插入下列语句即可:
    #define NDEBUG
  这时预处理程序忽略所有断言而不必由程序员手工删除每条断言。
    注意函数getFirstName和getLastName的实现方法向类的客户返回常量字符指针。在这个实现方法中,如果客户要保留姓和名的副本,则客户要在取得对象的常量字符指针之后负责复制Employee对象的动态分配内存。注章,还可以使用getFirstName和getlastName让客户向每个函数传递字符数组和数组长度。然后函数可以将姓或名复制到客户提供的字符数组中。

7.8  数据抽象与信息隐藏
    类通常对类的客户隐藏其实现细节,即所谓的信息隐藏。下列以堆栈数据结构作为信息隐藏的例子。
    可以把堆栈看成一堆盘子。将盘子放在堆中时,总是放在顶部(压入堆栈),从堆中取下盘子时,总是从顶上取(称为弹出堆栈)。堆栈是后进先出(last-in,first-out;LIFO)的数据结构,最后放进堆栈的项目最先从堆栈中取出。
    程序员可以生成堆栈类,对客户隐藏实现细节。堆栈可以方便地用数组实现(或用第15章“数据结构”中的链表)。堆栈类的客户不需要知道堆栈如何实现,只要求数据项目按后进先出的方式处理即可。描述类的功能而不管其实现细节称为数据抽象(data abstraction),C++的类定义了抽象数据类型(abstract data types,ADT)。尽管用户可能知道类的实现细节,但编码时不能依赖于这些实现细节。只要该类的public接口不变,类的实现细节(如堆栈的实现和“压入”“弹出”的操作)可能改变而不影响系统其余部分。
    高级语言的任务是生成便于程序员使用的视图。由于没有一个标准视图,因此编程语言种类很多。C++中的面向对象编程显示了另一种视图。
    大多数编程语言都强调操作。在这些语言中,数据的存在只是为了支持程序所需的操作。数据比操作更不重要,数据是原始的,只有几个内部数据类型,程序员很难生成自己的新数据类型。
    C++和面向对象编程中改变了这些观点。C++提高了数据的重要性。C++中的主要活动就是生成自己的新数据类型(即类)和表达这些数据类型之间的相互作用。
    要转向这个方向,编程语言组织要规范数据的一些概念。我们考虑的规范化就是抽象数据类型。抽象数据类型就像十多年前的结构化编程一样备受关注,抽象数据类型并不能代替结构化编程,只是提供了其他规范,可以进一步改善程序开发过程。
    什么是抽象数据类型呢?考虑内部类型int,我们看到它是数学中的整数,但int在计算机中并不完全是数学中的整数,计算机中的int长度是很有限的。例如,32位机器上的int只限于-20亿到20亿之间。如果计算结果超出这个范围,则会发生溢出错误,机器会以机器相关的方式响应,可能悄悄地产生错误结果,而数学中的整数则没有这个问题。因此,计算机中的int概念实际上只是实际整数的一个近似,float也是这样。
    char同样是个近似,char值通常是8位模式的0和1,这些模式与所表示的字符(如z、小写z、美元号$、数字5等等)完全不同。char类型的值在大多数计算机上都是很有限的。7位ASCII字符集只提供128个不同字符值。这显然不足以表达中文、日文等需要成千上万个字符的语言。
    由此可见.即使C++之类的编程语言提供的内部数据类型,实际上也只是实际生活中概念和
行为的近似或模型。前面我们一直理所当然地使用int,现在则有了全新的概念。int、float、char之类的类型都是抽象数据类型,实际上是在计算机系统中一定程度地近似表示实际中的概念。
    抽象数据类型实际上包含两个概念,即数据表达(data representation)和该数据允许的操作(operation)。例如,int的概念定义了C++中的加、减、乘、除、求模操作,但除数为0时则未定义,这种操作与机器参数有关,如计算机系统的定长字长度。另一个例子是负整数的概念,其运算和数据表达是可应用的,但负整数的平方根则没有定义。C++中程序员用类实现抽象数据类型。我们将在第]2章“模板”中生成自己的堆栈类,并在第20章“标准模板库(STL)”中介绍标准库stack类。

7.8.1  范例:数组抽象数据类型
    第4章曾介绍过数组。数组就是一个指针和一些内存空间。如果程序员小心谨慎,则利用这些原始功能就可以进行数组操作。数组还有许多精彩的操作,但C++中没有提供。利用C++类,程序员可以开发比“原始”数组更精彩的数组ADT。数组类可以提供许多有用的功能,例如:
    ●下标范围检查。
    ●任章范围下标而不一定从0开始。
    ●数组赋值。
    ●数组比较。
    ●数组输入/输出。
    ●已知长度数组。
    我们将在第8章“运算符重载”中生成自己的数组类,并在第20章“标准模板库(STL)”中介绍标准库vector类。
C++有少量内部类型,类可以扩展这个基础编程语言。

    软件工程视点7.15
    程序员可以通过类机制生成新类型。这些新类型可以方便地像内部类型一样使用。这样,C++是个可扩展的语言。尽管这个语言可以方便地用新类型扩展,但基础语言本身不能改变。

    C++环境中生成的新类可以专属一个人、一个小组或一个公司。类也可以放进标准类库中,以便推广使用。这不一定能上升为标准,但事实上标准正在出现。C++的全部价值只有在充实而标准化的类库得到广泛应用时才能完全体现。在美国,ANSI(美国国家标准协会)正在进行这种标准化。ANSI和ISO(国际标准化组织)正在开发C++的标准版本。学习C++和面向对象编程的读者可以利用丰富的库实现快速的面向组件的软件开发。

7.8.2  范例:字符串抽象数据类型
    C++-是一种定义简练的语言,只向程序虽提供了建立各种系统的原始功能。这个语言保证了最小编程负担。C++适合应用编程和系统编程,后者对程序的性能有更高要求。当然,C++内部数据类型中也可以包括字符串数据类型,但这个语言设计成包括通过类生成和实现字符串抽象数据类型的机制。
我们将在第8章开发自己的字符串ADT。ANSI/ISO草案标准中有一个由string类,将在第19章详细介绍。

7.8.3  范例:队列抽象数据类型
    我们经常遇到排队,等待的队称为队列(queue)。我们排队在超市结账,排队换煤气,排队上公共汽车,排队在高速公路上交费,学生注册时和到食堂买饭时都要排队。计算机系统内部也使用许多排队,因此,我们需要编写一个模拟排队的程序。
    队列是个抽象数据类型的范例。队列向客户提供了明确的行为。客户一次一件地将东西放进队列中,称为进队(enqueue),然后从队列中一次一件地取出东西,称为出队(dequeue)。理论上,队列可以无限长,但真正的队列是有限的。队列中的项目按先进先出(first-in.first-out;FIFO)顺序出列,第一个插入队列的项目第一个离开队列。
    队列隐藏了内部数据表示,跟踪队列中的当前项目,为客户提供一组操作,如入队和出队。客户并不关心队列的实现,只要求队列正常操作。客户让一个新项目入队时,队列应接受这个项目,并放在某种先进先出的数据结构中。客户要从队列中取一个项目时,应从内部表示中删除这个项目,并将该项目按先进先出的顺序传递给外界(即队列的客户),下一次出队的项目应是队列中等待时间最长的项目。
    队列ADT保证内部数据结构的完整性。客户不能直接操作这个数据结构,只有队列ADT能访问其内部数据。客户只能对数据表达进行允许的操作,ADT的public接口中不提供的操作将由ADT以适当方式拒绝,例如发一个错误消息、终止执行或忽略操作请求。
    第15章“数据结构”中将建立自己的队列类,第20章将介绍标准库queue类。

7.9  容器类与迭代
    最常见的类型包括容器类(container class),也称集合类(collection class),是保存一组对象集合的类。容器类通常提供插入、删除、查找、排序和测试类成员项目等操作。数组、堆栈、队列、树和链表都是容器类,第4章介绍了数组,第11章和20章将介绍其他数据结构。
    容器类经常与迭代对象(iterator object;或简称迭代器,aerator)相关联。迭代对象返回集合中的下一个项目(或对集合中的下一个项目进行某种操作)。编写类的迭代器之后,要取得类中的下一个元素很简单,迭代器通常指定为类的友元,以提高性能,使迭代器能通过迭代直接访问private数据。就像几个人共读的书中可以插好几个标签一样,可以有同时操作几个迭代器的容器类,每个迭代器包含自己的位置信息。第20章“标准模板库(STL)”中将详细介绍容器和迭代器。

7.10  代理类
    通过隐藏类的实现细节可以防止访问类中的专属信息(包括private数据)和专属程序逻辑。向客户提供代理类(proxy class),代理类只能访问类的public接口,这样就可以让客户使用类的服务而不必让客户访问类的实现细节。
    实现代理类需要几个步骤(如图7.10)。首先,我们生成要隐藏private数据的类的类定义和实现文件。我们的例子使用Implementation类、代理类Interface和测试程序,并输出了结果。
    Implementation类提供一个private数据成员value(这是要对客户隐藏的数据)、一个初始化value的构造函数以及函数setValue和getValue。
1 // Fig. 7.10: implementation.h
2 // Header file for class Implementation
3
4 class Implementation {
5   public:
6     Implementation( int v ) { value = v; }
7     void setValue( int v ) { value = v; }
8     int getValue() const { return value; }
9
10   private:
11     int value;
12 };
13 // Fig. 7.10: interface.h
14 // Header file for interface.cpp
15 class Implementation;  // forward class declaration
16
17 class Interface {
18   public:
19     Interface( int );
20     void setValue( int );  // same public interface as
21      int getValue() const;  // class Implementation
22   private:
23     Implementation *ptr;  // requires previous
24                        // forward declaration
25 };
26 // Fig. 7.10: interface.cpp
27 // Definition of class Interface
28 #include "interface.h"
29 #include "implementation.h"
30
31 Interface::Interface( int v )
32   : ptr ( new Implementation( v ) ) { }
33
34 // call Implementation's setValue function
35 void Interface::setValue( int v ) { ptr->setValue( v ); }
36
37 // call Implementation's getValue function
38 int Interface::getValue() const { return ptr->getValue(); }
39 // Fig. 7.10: fig0710.cpp
40 // Hiding a class's private data with a proxy class.
41 #include <iostream.h>
42 #include "interface.h"
43
44 int main()
45 {
46   Interface i( 5 );
47
48   cout << "Interface contains: "<< i.getValue()
49        << "before setValue" << endl;      
50   i.setValue( 10 );
51   cout << "Interface contains: "<< i.getValue()
52       << "after setValue" << endl;
53   return 0;
54 }

输出结果:
Interface contains: 5 before setVal
Interface contains: 10 after setVal
                                               
                                 图 7.10  实现代理类

    我们用Implementation类的同一个public接口生成代理类的定义。代理类的惟一private成员是Implementation类对象的指针。利用指针可以隐藏Implementation类的实现细节。
    图7.10的Interface类是lmplementation类的代理类。注意Interface类中提到lmplementation类时只有第23行的指针声明。类定义(如Interface类)只使用另一个类(如Implementation类)的指针时,另一个类的头文件<通常显示该类的private数据)不需要用#include包含在内。只要在文件中使用该类型之前用提前类声明(forward class declaration)将另外的这个类声明为一种数据类型即可(见第15行)。
    实现文件包含代理类Interface的成员函数,这是惟一包含Implementation类所在头文件implementation.h的文件。文件interface.cpp以预编译对象文件形式和头文件interface.h一起提供给客户,该头文件包含代理类提供服务的函数原型。由于文件interface.cpp只以已编译对象文件形式提供给客户,因此客户无法看到代理类与专属类之间的交互。
    图7.10的程序测试Interface类。注意,main中只包含Interface类的头文件,而没有提到lmplemention类。因此,客户根本不知道Implementation类的private数据。

7.11  有关对象的思考:在电梯模拟程序中使用复合和动态对象管理
    第2章到第5章设计了电梯模拟程序,第6章开始了电梯模拟程序的编程。第7章中介绍了实现可投入使用的完整电梯模拟程序所需的其他技术,包括动态对象管理技术,用new和delete生成和删除模拟程序执行时所需的对象。我们还介绍了复合,从而可以在一个类中包含其他类对象成员。
    通过复合可以建立大楼类,包含电梯和层,并可建立电梯类,包含按钮、门和电铃。
电梯实验室任务5
    1.每当有另一个人进入时,用new生成新的Person对象,表示这个人。注意new调用所生成对
    象的构造函数,这个构造函数应初始化该对象。每次有人离开时,用delete删除Person对象
    并释放该对象占用的存储空间,delete调用所删除对象的析构函数。
    2.枚举电梯模拟程序中所实现的类之间的复合关系。修改第6章“有关对象的思考”一节生成
    的类定义,反映这种复合关系。
    3. 完成模拟程序的实现工作。后面各章会建议如何改进模拟程序。

小 结
    ●关键字const指定的对象不能修改。
    ●C++编译器不允许任何非const成员函数调用const对象。
    ●试图通过类的const成员函数修改该类对象的数据成员是个语法错误。
    ●函数在原型和定义中指定为const。
    ●const成员函数可以用非const版本重载。编译器根据对象是否声明为const自动选择所用的重载版本。
    ●const对象应初始化。要用成员初始化值向构造函数提供类对象数据成员的初始值。
    ●类可以将其他类对象作为类成员。
    ●成员对象按声明的顺序在构造所在类对象之前构造。
    ●如果不提供成员初始化值.则隐含调用成员对象的默认构造函数。
    ●类的友元函数在类范围之外定义,但有权访问类的所有成员。
    ●友元关系声明可以放在类定义中的任何地方。
    ●this指针隐式引用对象的数据成员和成员函数。
    ●每个对象都可以通过this指针访问自己的地址。
    ●this指针也可以显式使用。
    ●Rew运算符自动生成正确长度的对象,调用对象构造函数和返回正确类型的指针。要在C++中释放这个对象的空间.需要使用delete运算符。
    ●对象数组可以用new动态分配,如下所示:
            int *ptr = new int[100];
    分配100个整数的数组并将数组开始位置指定为ptr。上述整数数组可以用下列语句删除:
       delete[]  Ptr;
    ●stati类变量表示整个类范围的信息。static类成员的声明以static关键字开始。
    ●static数据成员的作用域是类范围。
    ●类的public static类成员可以通过类的任何对象访问,也可以用二元作用域运算符通过类名访问。
    ●如果成员函数不访问非static类数据成员和成员函数,则可以声明为static。与非static成员函数不同的是,static成员函数没有this指针,因为static类数据成员和成员函数是独立于类对象而存在的。
    ●类通常对类的客户隐藏实现细节,这称为信息隐藏。
    ●堆栈是后进先出(LIFO)的数据结构,最后放进堆栈的项目最先从堆栈中取出。
    ●描述类的功能而不管其实现细节称为数据抽象,C++类定义所谓的抽象数据类型(ADT)。
    ●C++提高了数据的重要性。C++中的主要活动就是生成自己的新数据类型(即类)和表达这些数据类型之间的相互作用。
    ●抽象数据类型实际上是在计算机系统中一定程度地近似表示现实世界的概念。
    ●抽象数据类型实际上包含两个概念,即数据表达和该数据允许的操作。
    ●C++是可扩展的语言。尽管这个语言可以方便地用新类型扩展,但基础语言本身不能改变。
    ●C++是一种定义简练的语言,只向程序员提供了建立各种系统的原始功能。这种语言保证了最小编程负担。
    ●队列中的项目按先进先出(FIFO)顺序出队,第一个插入队列的项目第一个离开队列。
    ●容器类是保存一组对象集合的类。容器类通常提供插入、删除、查找、排序和测试类成员项目等操作。
    ●容器类经常与迭代对象(或简称迭代器)相关联。迭代对象返回集合中的下一个项目(或对集合中的下一个项目进行某种操作)。
    ●向客户提供代理类,代理类只能访问类的public接口,这样就可以让客户使用类的服务而不必让客户访问类的实现细节。
    ●代理类惟一的private成员是隐藏该类对象的private数据的指针。
    ●类定义只使用另—个类的指针时,另一个类的头文件(通常显示该类的private数据)不需要用#include包含在内。只要在文件中使用该类型之前用提前类声明将另外的这个类声明为一种数据类型即可。
    ●实现文件包含代理类成员函数,该文件包含隐藏该类private数据的头文件。
    ●实现文件以预编译对象文件形式和头文件一起提供给客户,该头文件包含代理类提供服务的函数原型。

术 语

  abstract data type(ADT)   抽象数据类型             dequeue(queueoperation)  出队(队列操作)
  binary scope resolution operator(::)  二元作用域   destructor析构函数
             运算符                                  dynamic objects   动态对象
  cascading member function calls  连续使用成员      enqueue(queueoperation)  入队(队列操作)
      函数调用                                       extensiblelanguage可扩展语言
  class scope   类范围                               first-in-first-out(FIFO)   先进先出
  composition   复合                                 foward class declaration   提前类声明
  const member function    const成员函数             friend class   友元类
  const object     const对象                         frie

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -