ITEEDU

6.6 类范围与访问类成员

类的数据成员(类定义中声明的变量)和成员函数(类定义中声明的函数)属于该类的类范围(class's scope)。非成员函数在文件范围

(file scope)中定义。

在类范围中,类成员可由该类的所有成员函数直接访问,也可以用名称引用。在类范围外,类成员是通过一个对象的句柄引用,可以是对象名、对象引用或对象指针(第7章将介绍,每次引用对象中的数据成员和成员函数时,编译器插入一个隐式句柄)。

类的成员函数可以重载,但只能由这个类的其他成员函数重载。要重载成员函数,只要在类定义中提供该重载函数每个版本的原型,并对该重载函数每个版本提供不同的函数定义。

成员函数在类中有函数范围(function scope),成员函数内定义的变量只能在该函数内访问。如果成员函数定义与类范围内的变量同名的变量,则在函数范围内,函数范围内的变量掩盖类范围内的变量。这种隐藏变量可以通过在前面加上类名和作用域运算符(::)而访问。隐藏的全局变量可以用一元作用域运算符访问(见第3章)。

访问类成员的运算符与访问结构成员的运算符是相同的。圆点成员选择运算符(.)与对象名或对象引用组合,用于访问对象成员。箭头成员选择运算符(->)与对象指针组合,用于访问对象成员。

图6.4的程序用简单的Count,类和public数据成员x(int类型)以及public成员函数print演示如何用成员选择运算符访问类成员。程序实例化三个Count类型的变量--counter、counterRef(Count对象的引用)和counterPtr(Count对象的指针)。变量counterRef定义为引用Counter,变量countcrPtr定义为指向counter。注意,这里将数据成员x设置为public,只是为了演示public成员利用句柄(如名称、引用或指针)即可访问。前面曾介绍过,数据通常指定为private,第9章“继承”中将介绍有时可以将数据指定为protected。

// Fig. 6.4: fig06_04.cpp
 // Demonstrating the class member access operators . and ->
 //
 // CAUTION: IN FUTURE EXAMPLES WE AVOID PUBLIC DATA!
 #include < iostream.h>
 // Simple class Count
  class Count {
  public:
   int x;
   void print() { cout << x << endl; }
 };
 int main()
 {
   Count counter,             // create counter object
        *counterPtr = &counter, // pointer to counter
        &counterRef = counter;  // reference to counter
   cout << "Assign 7 to x and print using the object's name: ";
   counter.x = 7;      // assign 7 to data member x
   counter.print();    // call member function print
   cout << "Assign 8 to x and print using a reference: ";
   counterRef.x = 8;   // assign 8 to data member x
   counterRef.print();  // call member ~unction print
   cout << "Assign 10 to x and print using a pointer: ";
   counterPtr->x = 10;  // assign 10 to data member ~
   counterPtr->print(); // call member function print
   return 0;
 }

输出结果:

Assign 7 to x and print using the object's name: 7

Assign 8 to x and print using a reference: 8

Assign 10 to x and pring using a pointer: 10

图 6.4 通过各种句柄访问对象的数据成员和成员函数

6.7 接口与实现方法的分离

良好软件工程的一个基本原则是将接口与实现方法分离,这样可以更容易修改程序。就类的客户而言,类实现方法的改变并不影响客户,只要类的接口保持不变即可(类的功能可能扩展到原接口以外)。

软件工程视点6.10

将类声明放在使用该类的任何客户的头文件中,这就形成类的Public接口(并向客户提供调用类成员函数所需的函数原型)。将类成员函数的定义放在源文件中,这就形成类的实现方法。

软件工程视点6.11

类的客户使用类时不需要访问类的源代码,但客户需要连挂类的目标码。这样就可以由独立软件供应商(ISV)提供类库进行销售和发放许可证。ISV只在产品中提供头文件和目标模块,不提供专属信息(例如源代码)。C++用户可以享用更多的ISV生产的类库。

实际上,任何事情都不是十全十美的。头文件中包含一些实现部分,并隐藏了实现方法的其他函数定义。private成员列在头文件的类定义中.因此客户虽然无法访问private成员,但能看到这些成员。第7章将介绍如何用代理类从类的客户中隐藏类的private数据。

软件工程视点6.12

对类接口很重要的信息应放在头文件中。只在类内部使用而类的客户不需要的信息应放在不发表的源文件中。这是最低权限原则的又一个例子。

图6.5将图6.3的程序分解为多个文件。建立C++程序时,每个类定义通常放在头文件中,类的成员函数定义放在相同基本名字的源代码文件(source-code file)中。在使用类的每个文件中包含头文件(通过#include),而源代码文件编译并连接包含主程序的文件。编译器文档中介绍了如何编译和连接由多个源文件组成的程序。

图6.5包含声明Time类的time1.h头文件、定义Time类成员函数的Time1.cpp文件和定义main函数的fig06_05.cpp文件。这个程序的输出与图6.3的输出相同。

 // Fig. 6.5: timel.h
 // Declaration of the Time class.
 // Member functions are defined in timel.cpp
 // prevent multiple inclusions of header file
 #ifndef TIME1_H
 #define TIME1_H
 // Time abstract data type definition
 class Time {
 public:
   Time();                   // constructor
   void setTime( int, int, int ); // set hour, minute, second
   void printMilitary();        // print military time format
   void printStandard();        // print standard time format
 private:
   int hour;    // 0 - 23
   int minute;  // 0  59
   int second;  // 0 - 59
 };
 #endif
 // Fig. 6.5: timel.cpp
 // Member function definitions for Time class.
 #include 
 #include "time1.h"
 // Time constructor initializes each data member to zero.
 // Ensures all Time objects start in a consistent state.
 Time::Time() { hour = minute = second = 0; }
 // Set a new Time value using military time. Perform validity
 // checks on the data values. Set invalid values to zero.
 void Time::setTimm( int b, int m, int s )
 {
   hour  = ( h >= 0 && h < 24 ) ? h : 0;
   minute  ( m >= 0 && m < 60 ) ? m : 0;
   second = ( s >= 0 && s < 60 ) ? s : 0;
 }
 // Print Time in military format
 void Time::printMilitary()
 {
   cout << (hourt< 10 ? "0" : "" ) << hour << ":"
          ( minute < l0 ? "0" : "" ) << minute;
 }
 // Print time in standard format
 void Time::printStandard()
 {
   cout << ( ( hour( == 0 || hour == 12 ) ? 12 : hourt% 12 )
        <<  ":" <<  minute < l0 ? "0" :  "" ) << mlnute
       << ":" << ( second < l0 ? "0" :  "" ) << second
       << ( hour < 12 ? "AM" : "PM" );
 }
 // Fig. 6.5: fig06_05.cpp
 // Driver for Timel class
 // NOTE: Compile with timel.cpp
 #include <iostream.h>
 #include "time1.h"
 //  Driver to in main( test simple class Time
 int main()
 {
   Time t;  // instantiate object t of class time
   cout << "The initial military time is";
   t.printMilitary();
   cout << "\nThe initial standard time is";
   t.printStandardO;
   t.setTime( 13, 27, 6 );
   cout << "\n\nMilitary time after setTime is";
   t.printMilitary();
   cout << "%nStandard time after setTime is";
   t.printStandard();
   t.setTime( 99, 99, 99 );  // attempt invalid settings
   count << "\n\nAfter attempting invalid settings:\n"
        << "Military time:";
   t.printMilitary();
   cout << "\.Standard time:";
   t.printStamdard();
   cout << endl;
   return 0;
 }

输出结果:

The initial military time is 00:00

The initial standard time is 12:00:00 AM

Military time after setTime is 13:27

Standard time after setTime is 1:27:06 PM

After attempting invalid settings:

Military time: 00:00

Standard time: 12:00:00 AM

图 6.5 将Time类的接口与实现方法分离

注意类声明放在下列预处理代码中:

// prevent multiple inclusions of header file
#ifndef TIME1_H
#define TIME1_H
  ...
#defint

建立大程序时,其他定义和声明也放在头文件中。上述预处理指令使得在定义了TIME1_H名字时不再包含#ifndef和#endif之间的代码。如果文件中原先没有包含头文件,则TIME1_H名字由#define指令定义,并使该文件包含头文件语句。如果文件中已经包含头文件,则TIME1_H名字已经定义,不再包含头文件语句。多次包含头文件语句通常发生在大程序中,许多头文件本身已经包含其他头文件。注意:预处理指令中符号化常量名使用的规则是把头文件名中圆点(.)换成下划线。

测试与调试提示 6. 2

用#ifdef、#define和#endif预处理指令防止一个程序中多次包合相同的头文件。

编程技巧6.2

头文件的#ifdef和#define顸处理指令中用头文件名,井将圆点换成下划线,