ITEEDU

6.14 使用数据成员和成员函数

员函数调整客户的银行借贷(例如BanLAccount类的private数据成员)。

类通常提供public成员函数,让类的客户设置(写入)或读取(取得)private数据成员的值。这些函数通常称为get和set。更具体地说,设置数据成员interestRate的成员函数通常称为setInterestRate,读取数据成员IntersetRate的值通常称为getInterestRate。读取函数也称为“查询”函数。

提供get和set函数与指定数据成员为public同样重要,这是C++语言在软件工程中的另一优势。如果数据成员为public,则程序中的任何函数可以随意读取和写入这个数据成员。如果数据成员为private.则public get函数可以让其他函数读取数据,而且数据的显示和格式化也可以用get函数控制。public set函数通常用于检查数据成员的修改,保证新值是适当的数据项目。例如,如果想把一个月的日期号数设置为37会被禁止,将人的身高设置为负值也会被禁止,将数字量设置为字母值也会被拒绝,将一个人的成绩设置为185分(取百分制时)同样也会被拒绝等等。

软件工程视点6. 22

指定private数据成员并通过public成员函数控制这些数据成员的访问(特别是写入访问)可以保证数据的完整性。

测试与调试提示6.5

指定private数据成员并不能自动保证数据完整性,程序员还要提供验证检查。但C++提供了让程序员方便地设计更好的程序的框架。

编程技巧6.7

设置private数据值的成员函数应验证所要新值是否正确,如果不正确,则set数应将Privte数据成员设置为相应的一致状态。

试图要对数据成员指定无效值时,应当提醒类客户。类的set函数常写成返回一个值,表示试图对数据成员指定无效值。这样就使类的客户可以测试set函数的返回值,确定其操作的对象是否为有效对象,并在对象无效时采取相应操作。

图6.1O将Time类扩展成包括private数据成员hour、minute和second的get和set函数。set函数严格控制数据成员的设置。如果想把数据成员设置为无效值,则会把数据成员设置为0(从而使数据成员保持一致状态)。每个get函数只是返回相应数据成员的值。程序首先用set函数设置Time对象t的private数据成员为有效值,接着用get函数读取这个值以便输出。然后set函数要将hour和second成员设置为无效值并将minute成员设置为有效值,并用get函数读取这个值以便输出。输出表明,无效值使得数据成员设置为0。最后,程序将时间设置为11:58:00并用函数incrementMinutes增加3分钟。函数incrementMinutes是个非成员函数,它调用get和set成员函数增加minute成员的值。尽管这样的方法实现了所需的功能,但是多次函数调用降低了程序的性能。下一章将介绍用友元函数消除多次函数调用的性能负担。

常见编程错误6.11

构造函数可以调用类的其他成员函数,如set和get函数,但由于构造函数初始化对象,因此数据成员可能还处于不一致状态。数据成员在初始化之前使用可能造成逻辑错误。

 // Fig. 6.10: time3.h
 // Declaration of the Time class.
 // preprocessor directives that
 // prevent multiple inclusions of header file
 #ifndef TIME3_H
 #define TIME3_H
 class Time {
 public:
   Time( int = 0, int = 0, int= 0 );  // constructor
   // set functions
   void setTime( int, int, int ); // set hour, minute, se
   void setHour( iht );  // set hour
   void setMinute( int ); // set minute
   void setSecond( int ); // set second
   // get functions
   int getHourO;       // return hour
   int getMinute();      // return minute
   int getSecond();      // return second
   void printMilitary();  // output military time
   void printStandard();  // output standard time
 private:
   int hour;           // 0 - 23
   int minute;          // 0 - 59
   int second;          // 0 - 59
 }
 #endif
 // Fig. 6.10: time3.cpp
 // Member function defintions for Time class
 #include "time3.h"
 #include < iostream.h>
 // Constructor function to initialize private data.
 // Default values are 0 (see class definition).
 Time::Time( int hr, int min, int sec )
  { setTime( hr, min, sec ); }
 // Set the values of hour, minute, and second.
 void Time::setTime(int h,int m,int s)
 {
   setHour( h );
   setMinute( m );
   setSecond( s );
 }
 // Set the hour value
 void Time::setHour(int h)
 {hour = (h>0 && h <24 )? h: 0;}
 // Set the minute value
 void Time::setMinute( int m )
   { minute = ( m >= 0 && m  60 ) ? m : 0; }
 // Set the second value
 void Time::setSecond( int s
 { second = ( s >= 0 && s < 60 ) ? s : 0; }
 // Get the hour value
 int Time::getHour() { return hour;}
 // Get the minute value
 int Time::getMinute() { return minute; }
 // Get the second value
 int Time::getSecond() { return second; }
 // Print time is military format
 void Time::printMilitary()
 {
   cout << ( hour < 10 ? "0" :  "" ) << hour << ":"
       << ( minute < 10 ? "0" :  "" ) << minute;
  }
 // Print time in standard format
 void Time::printStandard{)
 {
     cout << ( { hour == 0 II hour == 12 ) ? 12 : hour % 12 )
          << ":" << ( minute < 10 ? "0" :  "" ) << minute
          << ":" << ( second < 10 ? "0" :  "" ) << second
          << ( hour < 12 ? "AM" : "PM" );
 }
 // Fig. 6.10: fig06_lO.cpp
 #include < iostream.h>
 // Demonstrating the Time class ser and get functions
 #include< iostream.h>
 #include "time3.h"
 void incrementMinutes( Time &, const iht );
 int main()
 {
     Time t;
    t.setHour{ 17 );
    t.setMinute( 34 );
    t.setSecond( 25 );
     cout << "Result of setting all valid values:\n;
          <<    our:  << t.getHour()
          << " Minute: " << t.getMinute()
          <<" Second: "<< t.getSecond();
     t.setHour( 234 );   // invalid hour set to 0
     t.setMinute( 43 );
   t.setSecond( 6373 );     // invalid second set to 0
   cout << "\n\nResult of attempting to set invalid hour and"
        <<  "second:\m Hour: "<< t.getHour()
        <<" Minute: "<< t.getMinute()
        <<" Second: "<< t.getSecond() << "\n\n";
   t.setTime( 11, 58, 0 );
   incrementMinutes( t, 3 );
  return 0;
 }
 void incrementMinutes(Time &tt, const int count)
 {
   cout << "Incrementing minute" << count
       << "times:\nStart time: ";
   tt.priatStandard();
   for (int i = 0; i < count; i++ ) {
     tt.setMinute( (tt.getMinute() + 1 ) % 60);
      if (tt.getMinute() == 0 )
        tt.setHour( ( tt.getHour() + 1 ) % 24);
     cout << "\nminute + 1: ";
     tt.printStandard();
   }
   cout << endl;
 }

输出结果:

Result of setting all valid values:

Hour: 17 Minute: 34 Second: 25

Result of attempting to set inv]id hour and second:

Hour: 0 Minute: 43 Second: 0

Incrementing minute 3 times:

Start time: 11:58:00 AM

minute + 1; 11:59:00 AM

mioute + 1:12:00:00 PM

minute + 1:12:01:00 PM

图 6.10 使用set和get函数

从软件工程角度看,使用set函数非常重要,因为它们可以进行有效性检查。set和get函数还有其他重要的软件工程优势。

软件工程视点6.23

通过set和get函数访问private数据不仅能防止数据成员接受无效值,而且还使类的客户J需要考虑数据成员的表达方式。这样,如果数据表达方式因故改变(通常是为了减少所需存储量或提高性能),只要成员函数提 供的接口不变,那么只需改变成员函数而不必改变客户,但客户可能需要重新编译。

6.15 微妙的陷阱:返回对private数据成员的引用

对象的引用是对象名的别名,因此可以放在赋值浯句左边,在这种情况中,引用可以成为可接收赋值的左值。要使用这种功能,就要让类的public成员函数返回对该类private数据成员的非const引用。

图6.11用简化的Time类演示如何返回private数据成员的引用。调用badSetHour函数返回的引用作为private数据成员hour的别名。函数调用可以按任何使用private数据成员的方式使用,包括作为赋值语句的左值。

编程技巧6.8

不要让类的public成员函数返回对该类private数据成员的非const引用(或指针),返回这种引用会破坏类的封装。

 // Fig. 6.11: time4.h
// Declaration of the Time class.
// Member functions defined in time4.cpp
// Fig. 6.11: time4.h
// Declaration of the Time class.
// Member functions defined in time4.cpp
// preprocessor directives that
// prevent multiple inclusions of header file
#ifndef TIME4_H
#define TIME4_H
class Time {
public
Time( int = 0, int = 0, int = 0 );
void setTime( int, int, int );
int getHour();
int &badSetHour( int ); // DANGEROUS reference return
private:
int hour;
int minute;
int second;
};
#endif
// Fig. 6.11: time4.cpp
// Member function definitions for Time class.
#include "time4.h"
#include <iostream.h>
// Constructor function to initialize private data.
// Calls member function setTime to set variables.
// Default values are 0 (see class definition).
Time::Time( int hr, int min, int sec )
{ setTime( hr, min, sec ); }
// Set the values of hour, minute, and second.
void Time::setTime( int h, int m, int s )
{
hour = ( h >= o && h < 24 ) ? h : 0;
minute ( m >= 0 && m < 60 ) ? m : 0;
second = ( s >= 0 && s < 60 ) ? s : 0;
}
// Get the hour value
int Time::getHour() { return hour; ]
// POOR PROGRAMMING PRACTICE:
// Returning a reference to a private data member.
int &Time::badSetHour( int hh )
{
hour = ( hh >= 0 && hh < 24 ) ? hh : 0;
return hour; // DANGEROUS reference return
}
// Fig. 6.11: fig06_11.cpp
// Demonstrating a public member function that
// returns a reference to a private data member.
// Time class has been triced for this example.
#include <iostream.h>
#include "time4.h"
int main()
{
Time t;
int &hourRef = t.badSetHour( 20 );
cout << "Hour before modification: "<< hourRef;
hourRef = 30; // modification with invalid value
cout << "\nHour after modification: "<< t.getHour();
// Dangerous: Function call that returns
// a reference can be used as an lvalue!
t.badSetHour(12) = 74;
cout << "\n\n*******************************************\n"
<< "POOR PROGRkMMING PRACTICE!!!!!!!!\n"
<< "badSetHour as an lvalue, Hour:"
<< t.getHour()
<< "\n*************************************** << endl;
return 0;
}
// preprocessor directives that
// prevent multiple inclusions of header file
#ifndef TIME4_H
#define TIME4_H
class Time {
public
Time( int = 0, int = 0, int = 0 );
void setTime( int, int, int );
int getHour();
int &badSetHour( int ); // DANGEROUS reference return
private:
int hour;
int minute;
int second;
};
#endif
// Fig. 6.11: time4.cpp
// Member function definitions for Time class.
#include "time4.h"
#include <iostream.h>
// Constructor function to initialize private data.
// Calls member function setTime to set variables.
// Default values are 0 (see class definition).
Time::Time( int hr, int min, int sec )
{ setTime( hr, min, sec ); }
// Set the values of hour, minute, and second.
void Time::setTime( int h, int m, int s )
{
hour = ( h >= o && h < 24 ) ? h : 0;
minute ( m >= 0 && m < 60 ) ? m : 0;
second = ( s >= 0 && s < 60 ) ? s : 0;
}
// Get the hour value
int Time::getHour() { return hour; ]
// POOR PROGRAMMING PRACTICE:
// Returning a reference to a private data member.
int &Time::badSetHour( int hh )
{
hour = ( hh >= 0 && hh < 24 ) ? hh : 0;
return hour; // DANGEROUS reference return
}
// Fig. 6.11: fig06_11.cpp
// Demonstrating a public member function that
// returns a reference to a private data member.
// Time class has been triced for this example.
#include <iostream.h>
#include "time4.h"
int main()
{
Time t;
int &hourRef = t.badSetHour( 20 );
cout << "Hour before modification: "<< hourRef;
hourRef = 30; // modification with invalid value
cout << "\nHour after modification: "<< t.getHour();
// Dangerous: Function call that returns
// a reference can be used as an lvalue!
t.badSetHour(12) = 74;
cout << "\n\n*******************************************\n"
<< "POOR PROGRkMMING PRACTICE!!!!!!!!\n"
<< "badSetHour as an lvalue, Hour:"
<< t.getHour()
<< "\n*************************************** << endl;
return 0;
}

输出结果:

Hour before modification: 20

Hour after modification: 30

*********************************

POOR PROGRAMMING PRACTICE!!!!!!!!

badSetHour as an lvalue, Hour: 74

*********************************

图 6.11 返回对private数据成员引用

程序首先声明Time对象t和引用hourRef(把调用t.badSetHour(20)返回的引用赋给hourRef)。程序显示别名hourRef的值。然后用这个别名设置hour的值为30(无效值)并再次显示该值。最后,用函数调用本身作为左值并赋值74(另一无效值),再次显示该值。