ITEEDU

6.8 控制对成员的访问

成员访问说明符public和private(和第9章“继承”中介绍的protected)可以控制类数据成员和成员函数的访问。类的默认访问模式是private,因此类的首部和第一个标号之间的所有成员的类型都是private。每个标号之后,采用该标号表示的方式,直到遇到下一个标号或遇到类定义的右花括号(})。标号public、private和protedted可以重复,但这种情况不常用,容易造成混乱。

类的private成员只能由类的成员函数(和第7章介绍的友元)访问,public成虽则可以由程序中的任何函数访问。

public成员的主要用途是向类的客户提供类的服务(行为),这组服务形成类的public接口。类的客户不必关心类如何完成任务。类的private成员和public成员函数的定义是类的客户无法访问的。

这些组件形成类的实现方法(implementation)。

软件工程视点6.13

C++提倡程序独立于实现方法。对于独立于实现方法的代码,改变类的实现方法时,代码不需要改变,但可能需要重新编译。

常见编程错误6.6

除了由类的成员函数(和第7章介绍的友元)访问外,其他函数想访问类的private成员是个语法错误。

图6.6演示了private类成员只能用public成员函数通过public类接口访问。编译这个程序时,编译器产生两个错误,表示每个语句中指定的private成员无法访问。图6.6包含time1.h并和图6.5的time1.cpp一起编译。

编程技巧6.3

如果在类定义中先列出private成员,尽管程序默认的访问模式为private,但最好还是显式使用private标号,这样可以使程序更清晰。我们喜欢先列出public成员以强调类的接口。

 // Fig. 6.6:fig06 06.cpp
 // Demonstrate errors resulting from attempts
 // to access private class members.
 #include < iostream.h>
 #include "time1.h"
 int main()
 {
   Time t;
   // Error: 'Time::hour' is not accessible
   t.hour = 7;
   // Error: 'Time::minute' is not accessible
   cout << "minute =" << t.minute;
   return 0;
 }

输出结果:

Compilin9 FIG06 06.CPP:

Error FIG06 06.CPP 12: 'Time::hour' is not accessible

Error FIG06_06.CPp 15: 'Time::minute' is not accessible

图6.6 访问类的private成员的错误

编程技巧6.4

尽管public和private标号可以重复和混合,但最好先将所有public成员列成一组,然后将所有private成

员列成一组,这样可以使客户集中注意类的public接口,而不是注意类的实现方法。

软件工程视点6.14

让类的所有数据成员保持private。让Public成员函数设置private数据成员的值并取得private数据成员的

值。这种结构能隐藏类的实现方法,减少错误和提高程序的可修改性。

类的客户可能是另一类的成员函数,也可能是全局函数(即文件中类c语言的“松散”函数,不是任何类的成员函数)。

类成员的默认访问方式为private。类成员的访问方式可以显式设置为public、protected(见第9章)和private。struct成员的默认访问方式为public。struct的成员的访问方式也可以设置为Public、protected或private。

较件工程视点6.15

类设计人员用public、protected或private成员实现信息隐藏和最低权限原则。

类数据为private并不表示客户不能改变这个数据。客户可以通过这个类的成员函数或友元改变这个数据,但这些函数的设计应保证数据完整性。

访问类的private数据应当用称为访问函数access funotion)或访问方法(access method)的成员函数严格控制。例如,要让客户读取private数据的值,类可以提供一个get函数。要让客户修改private数据的值,类可以提供一个set函数。这种修改似乎会破坏private数据的专用性,但set成员函数可以提供数据验证功能(如范围检查),保证数值设置正确,set函数也可以在接口使用的数据形式与实现方法使用的数据形式之间进行换算。get函数不必以原始形式显示数据,该函数可以编辑数据,限制客户可以看到的数据。

软件工程视点6.16

类设计人员不必提供每个private数据成员的get和set函数,只在需要时才提供数据成员的get和set函数。

测试与调试提示6.3

将类的数据成员指定为private、类的成员函数指定为public有助于调试,因为数据操作问题局部化在类成员函数或类的友元中。

6.9 访问函数与工具函数

并非所有成员函数都要用public指定为类接口的一部分。有些成员函数保持private,作为类中 其他函数的工具函数(utility function)。

软件工程视点6.17

成员函数分为几大类:读取和返回私有数据成员值的函数、设置私有数据成员值的函数、实现类特性的 函数和进行各种类操作的函数(如初始化类对象、指定类对象、将类与内部类型或其他类进行相互转换以及处理奥对象内存)。

访问函数可以读取和显示数据。访问函数的另一常见用法是测试条件的真假,这种函数称为判定函数(predicate funchon)。任何容器类都有的isEmpty函数(如链表、堆栈和队列)就是判定函 数。程序先测试isEmpty,再从容器对象中读取下一个项目。判定函数isFull于测试容器类对象还有没有多余的存储空间。Time类的判定函数包括isAM和isPM。

图6.7演示了工具函数(或称为帮助函数)的使用。工具函数不是类接口的一部分,而是private成员函数,支持类中其他函数的操作。类的客户不能使用工具函数。

// Fig. 6.7: salesp.h
 // SalesPerson Class definition
 // Member functions defined in salesp.cpp
 #ifndef SALESPH
 #define SALESPH
class SalesPerson {
 public:
   SalesPerson();             // constructor
  void getSalesFromUser(); // get sales figures from keyboard
  void setSales( int, double ); // User supplies one month's
                           // sales figures.
  void printAnnualSales();
 private:
  double totalAnnualSales();  // utility function
  double sales[ 12 ];         // 12 monthly sales figures
 };
 #endif
 // Fig. 6.7: salesp.cpp
 // Member functions for class SalesPerson
 #include < iostream.h>
 #include < iomanip.h>
 // Constructor function initializes array
 {
   for (int i = 0; i < 12; i++ )
      sales[ i ] = 0.0;
 }
 // Function to get 12 sales figures from tha user
 // at the keyboard
 {
   double salesFigure;
   for (int i = 0; i < 12; i++ ) {
     cout << "Enter sales amountfor month"
                        << i + 1 << ": ";
     cin >> salesFigure;
      setSales( i, salesFigure );
   }
 }
 // Function to set one of the 12 monthly sales figures.
 Note that the month value must be from 0 to 11
 void SalesPerson::setSales( int month, double amount )
 {
    if ( month >= 0 && month < 12 && amount > 0 )
     sales[ month ] = amount;
   else
  cout << "Inalid month or sales figure" << endl;
 }
 // Print the total annual sales
 void SalesPerson::printAnnualSales()
{
   cout << setprecision( 2 )
       << setiosflags( ios::fixed I ios::showpoint )
       << "\nThe total annual sales are: $"
       << totalAnnualSales() << endl;
 }
 // Private utility function to total annual sales
 double SalesPerson::totalAnnualSales()
 {
   double total = 0.0;
   for (int i = 0; i < 12; i++ )
      total += sales[ i ];
   return total;
 }
 // Fig.67:fig06_07.cpp
 // Demonstrating a utility function
 // Compile with salesp.cpp
 #include "salesp.h"
 int main()
 {
   SalesPerson s;       // create SalesPerson object s
   s.getSalesFromUser();  // note simple sequential code
   s.printAnnualSales();  // no control structures in main
   return 0;
 }

输出结果:

Enter sales amount for month 1:5314.76

Enter sales amount for month 2:4292.38

Enter sales amount for month 3:4589.83

Enter sales amount for month 4:5534.03

Enter sales amount for month 5:4376.34

Enter sales amount for month 6:5698.45

Enter sales amount for month 7:4439.22

Enter sales amount for month 8:5893.57

Enter sales amount for month 9:4909.67

Enter sales amount fez month 10:5123.45

Enter sales amount for month 11:2024.97

Enter sales amount for month 12:5923.92

The total annual sales are: $60120.58

图 6.7使用工具函数

SalesPerson类中的表示12个月销售数据的数组用构造函数初始化为0,并用setSales函数设置为用户提供的值。public成员函数printAnnualSales打印最近11个月的总销售额。工具函数,TotalAnnualSales为PrintAnnualSales计算12个月的总销售额。成员函数printAnnudSales将销售数据转换为美元金额格式。

注意main中只有一个简单的成员函数调用,没有任何控制结构。

软件工程视点6.18

面向对象编程的一个现象是定义类之后,生成和操作这个类的对象通常只要一个简单的成员函数调用,

没有任何或只有少量控制结构。相反,类成员函数的实现则通常需要控制结构。