类的数据成员(类定义中声明的变量)和成员函数(类定义中声明的函数)属于该类的类范围(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 通过各种句柄访问对象的数据成员和成员函数
良好软件工程的一个基本原则是将接口与实现方法分离,这样可以更容易修改程序。就类的客户而言,类实现方法的改变并不影响客户,只要类的接口保持不变即可(类的功能可能扩展到原接口以外)。
将类声明放在使用该类的任何客户的头文件中,这就形成类的Public接口(并向客户提供调用类成员函数所需的函数原型)。将类成员函数的定义放在源文件中,这就形成类的实现方法。
类的客户使用类时不需要访问类的源代码,但客户需要连挂类的目标码。这样就可以由独立软件供应商(ISV)提供类库进行销售和发放许可证。ISV只在产品中提供头文件和目标模块,不提供专属信息(例如源代码)。C++用户可以享用更多的ISV生产的类库。
实际上,任何事情都不是十全十美的。头文件中包含一些实现部分,并隐藏了实现方法的其他函数定义。private成员列在头文件的类定义中.因此客户虽然无法访问private成员,但能看到这些成员。第7章将介绍如何用代理类从类的客户中隐藏类的private数据。
对类接口很重要的信息应放在头文件中。只在类内部使用而类的客户不需要的信息应放在不发表的源文件中。这是最低权限原则的又一个例子。
图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名字已经定义,不再包含头文件语句。多次包含头文件语句通常发生在大程序中,许多头文件本身已经包含其他头文件。注意:预处理指令中符号化常量名使用的规则是把头文件名中圆点(.)换成下划线。
用#ifdef、#define和#endif预处理指令防止一个程序中多次包合相同的头文件。
头文件的#ifdef和#define顸处理指令中用头文件名,井将圆点换成下划线,