ITEEDU

8.11 重载++与--

所有四种自增和自减运算符(即前置和后置的自增及自减运算符)都可以被重载。本节介绍编译器如何识别前置和后置的自增及自减运算符。

要重载既能允许前置又能允许后置的自增运算符,每个重载的运算符函数必须有一个明确的特征以使编译器能确定要使用的++版本。重载前置++的方法与重载其他前置一元运算符一样。

例如,假设要给Date对象d1增加一天,当编译器遇到前置自增表达式:

++d1

编译器就会生成成员函数调用:

d1.operator++()

该函数的函数原型为:

Date &operator++();

如果前置自增运算符函数是一个非成员函数,则当编译器遇到表达式:

++d1

编译器就会生成函数调用:

operator++(d1)

该函数的函数原型在类Date中的声明形式为:

friend Date &0peratOr++(Date &);

由于编译器必须能区分重载的前置和后置自增运算符函数,所以重载后置自增运算符遇到了一点儿困难。C++中所采用的方法是,当编译器遇到后置自增表达式:

d1++

编译器就会生成成员函数调用:

d1.operator++(0)

该函数的函数原型为:

Date operator++(int)

严格说来,0是一个伪值,它使运算符函数operator++在用于后置自增操作和前置自增操作时的参数表有所区别。

如果后置自增运算符函数是一个非成员函数,则当编译器遇到表达式:

d1++

编译器就生成函数调用:

operator++(d1,0)

该函数的函数原型为:

friend Date operator++(Date &,int);

再重复一遍,编辑器使用参数。区别后置自增操作和前置自增操作所用到的operator++函数的参数表。

本节所讲述的重载前置和后置自增运算符的内容同样可以用来重载前置和后置自减运算符,下一节探讨了使用重载的前置和后置自增运算符的Date类。

8.12 实例研究:Date类

图8.6声明了类Date。类Date用重载的前置和后置自增运算符将一个Date对象增加1天,必要时使年、月递增。

类Date的Public接口提供了以下成员函数:一个重载的流插入运算符,一个默认的构造函数、一个setDate函数、一个重载的前置自增运算符函数、一个重载的后置自增运算符函数、一个重载的加法赋值运算符(+=)、一个检测闰年的函数和一个判断是否为每月最后一天的函数。

// Fig. 8.6: datel.h
 // Definition of class Date
 #ifndef DATE1_H
 #define DATE1_H
 #include < iostream.h>
 class Date {
   friend ostream &operator<<( ostream &, const Date & );
 public:
   Date( int m = 1, int d = 1, int y = 1900 ); // Constructor
   void setDate( int, int, int );    // set the date
   Date &operator++();               // preincrement operator
   Date operator++( int );           // postincrement operator
   const Date &operator+=( int );   // add days,modify object
   bool leapYear( int );            // is this a leap year?
   bool endOfMonth( int );          // is this end of month?
 private:
   int month;
   int day;
   int year;
   static const int days[];      // array of days per month
   void helpIncrement();         // utility function
 };
 #endif
 // Member function definitions for Date class
 #include < iostream.h>
 #include "date1.h"
 // Initialize static member at file scope;
 // one class-wide copy.
 const int Date::days[] = { 0,  31, 28, 31, 30, 31,30,
, 31, 30, 31, 30, 31 } ;
 
 // Date constructor
 Date::Date( int m, int d, int y ) { setDate( m, d, y ); }
 
 // Set the date
 void Date::setDate{ int mm, int dd, int yy )
 {
   month = ( mm >= 1 && mm <= 12 ) ? mm : 1;
   year = ( yy >= 1900 && yy <= 2100 ) ? yy : 1900;
   // test for a leap year
   if ( month == 2 && leapYear( year ) )
     day = ( dd >= 1 && dd <= 29 ) ? dd : 1;
   else
     day = ( dd >= 1 && dd <= days[ month ] ) ? dd : 1;
 }
 // Preincrement operator overloaded as a member function.
 Date &Date::operator++()
 {
   helpIncrement();
   return *this;  // reference return to create an lvalue
}
 // Postincrement operator overloaded as a member function.
 // Note that the dummy integer parameter does net have a
 Date Date::operator++( int)
 {
   Date temp = *this;
   helpIncrement();
   // return non-incremented, saved, temporary object
 return temp;    // value return; not a reference return
 }
 // Add a specific number of days to a date
 const Date &Date::operator+=( int additionalDays )
 {
   for ( int i = 0; i < additionalDays; i++ )
      helpIncrement();
   return *this;   // enables cascading
 }
 // If the year is a leap year, return true;
 // otherwise,return false
 bool Date::leapYear( int y )
 {
   if ( y % 400 == 0 || ( y % 100 != 0 && y % 4 == 0 ) )
     return true;  // a leap year
   else
     return false;  // not a leap year
 }
 // Determine if the day is the end of the month
 bool Date::endOfMonth( int d )
 {
   if ( month == 2 && leapYear( year ) )
     return d == 29; // last day of Feb. in leap year
   else
     return d == days[ month ];
 }
 // Function to help increment the date
 void Date::helpIncrement()
 {
    if ( endOfMonth( day ) && month == 12 ) {  // end year
      day = 1;
      month = 1;
      ++year;
    }
   else if ( endOfMonth( day ) ) {          // end
      day = 1;
     ++month;
   }
   else      // not end of month or year; increment day
     ++day;
 }
 // Overloaded output operator
 ostream &operator<<( ostream &output, const Date &d )
 {
   static char *monthName[ 13 ] = { "", "January",
      "February", "March", "April", "May", "June",
      "July", "August", "September", "October",
      "November", "December"} ;
   output << monthName[ d.month ] << ' '
          << d.day << ", "<< d.year;
     return output;        // enables cascading
 }
 // Fig. 8.6: fig08_06.cpp
 // Driver for class Date
 #include < iostream.h>
 #include "date1.h"
 int main()
 {
   Date d1, d2( 12, 27, 1992 ), d3( 0, 99, 8045 );
   cout << "d1 is "<< d1
        << "\nd2 is" << d2
        << "\nd3 is  << d3 << "\n\n";
   cout << "d2 + 7 is "<< ( d2 += 7 ) << "\n\n";
   d3.setDate( 2, 28, 1992 );
   cout <<" d3 is "<< d3;
   cout << "\n++d3 is" << ++d3 << "\n\n";
   Date d4( 3, 18, 1969 );
   cout << "Testing the preincrement operator:\n"
        <<" d4 is" << d4 << '\n';
   cout << "++d4 is "<< ++d4 << '\n';
   cout <<" d4 is  << d4 << "\n\n";
   cout << "Testing the postincrement operator:\n"
        <<" d4 is "<< d4 << '\n';
   cout << "d4++ is " << d4++ << '\n';
   cout << "d4 is " << << d4 << endl;
   return O;
 } 

输出结果:

dl is January 1, 1900

d2 is December 27, 1992

d3 is January 1,1900

d2 += 7 is January 3,1993

d3 is February 28, 1992

++d3 is February 29, 1992

Testing the preincrement operator:

d4 is March 18,1969

++d4isMarch 19, 1969

d4 is March 19, 1969

Testing the preincrement operator:

d4 is March 19, 1969

d4++ is March 19, 1969

d4 is March 20, 1969

图 8.6 重载自增运算符的Date类

main函数中的驱动程序建立了几个日期对象,包括:初始化为1990年1月1日的d1,初始化为1992年12月27日的d2以及初始化为一个非法日期的d3。Date的构造函数调用函数setDate检测月、日和年的合法性。如果月是非法的则置为1,年是非法的则置为1900,日是非法的则置为1。

驱动程序用重载的流插入运算符输出所建立的每一个Date对象。程序用重载的运算符+=将对象d2增加7天,用函数setDate将对象d3设置为1992年2月28日,接着将一个新对象d4设置为1969年3月18日并用重载的前置自增运算符将d4增加1天。为证实执行过程的正确性,在执行前置自增操作的前后分别输出了日期。最后,用重载的后置自增运算符将对象d4增加一天。为了证实执行的过程的正确性,在执行后置自增操作的前后也分别输出了日期。

重载前置自增运算符是简明直接的,前置自增运算符调用private工具函数helplncrement来执行实际的自增运算。函数helpIncrement必须要处理日期的边界情况,因为对某月的日期加1时,它可能已经达到了最大值,此时需要将月份加1并把日期置为1,如果月份已经是12,则必须将年份加1而月份置为1。函数helpIncrement使用函数leapYear和endofMonth正确地递增日期。

重载的前置自增运算符返回对当前对象Date(已自增)的引用。这是因为当前对象的*this作为Date&而返回。

重载后置自增运算符需要一点儿技巧。为模拟后置操作,函数必须返回该Date对象未自增的副本。在进入operator++时,函数先把当前对象(*this)保存在temp中,然后调用helpIncrement递增当前的Date对象,最后返回未递增的对象在temp中的副本。注意这个函数不能返回对局部Date对象temp的引用,因为声明该对象的函数退出时删除了局部变量。这样,声明这个函数的返回类型为Date&将返回不复存在的对象的引用。返回局部变量的引用是个常见的错误,一些编译器会发出警告。