类的数据成员(类定义中声明的变量)和成员函数(类定义中声明的函数)属于该类的类范围(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顸处理指令中用头文件名,井将圆点换成下划线,