Chuck Allison是盐湖城圣Latter Day教堂总部下耶稣教堂家族历史研究处的软件体系设计师。他拥有数学学士和数学硕士学位。他从1975年起开始编程,从1984年起他开始从事c语言的教学和开发。他目前的兴趣是面向对象的技术及其教育。他是X3J16,ANSI C ++标准化委员会的一员。发送e-mail到[email protected],或者拨打电话到(801)240-4510均可以与他取得联系。
在上个月的封装中我提出了一个简单的C++日期类的雏形。为了提供一个能够计算两个日期的间隔的函数,这个类举例说明了C++的下列特征:
· 内联函数
· 引用
· 构造函数
· 对私有数据成员的访问控制
在这个月的部分里我将增加相关的运算符、输入/输出操作和得到当前日期的能力。它们示范了下列特征:
· 运算符重载
· 流
· 友元函数
· 静态成员
当使用日期的时候你经常需要确定某一日期是否在另一日期之前。我将为日期类增加下面这个成员函数(参见 Listing 1):
int compare(const Date& d2) const;
Date::compare类似于strcmp—如果当前对象(*this)在d2之前,它返回一个负整数;如果这两个日期相同,则返回0;否则返回一个正整数(参见 Listing 2 中的函数实现和 Listing 3 中的示例程序)。就像你们都很熟悉的C标准库中的qsort一样,你也可以使用Date::compare来对日期进行排序,就好像你使用strcmp对字符串进行排序一样。下面是一个可传递给qsort的比较函数(下个月的代码封装将包括qsort):
#include "date.h"
int datecmp(const void *p1, const void *p2)
{
const Date
*d1p = (const Date *) p1,
*d2p = (const Date *) p2;
return d1p->compare(*d2p);
}
运算符重载
大多数时候,拥有相关的运算符是更方便的,例如:
if (d1 < d2)
// do something appropriate..
使用Date::compare来添加一个“小于”运算符是非常容易的——只要在类的定义里插入下面这个内联成员函数就可以了:
int operator<(const Date& d2) const
{return compare(d2) < 0};
每一个表达式:d1 < d2出现的地方,都会被编译器翻译成函数调用的形式:
d1.operator<(d2)
Listing 4 中类的定义中拥有六个相关的操作符,Listing 5中展示了更新之后的示范程序。
既然函数Date::interval 的功能类似减法(它给出两个日期的差),把它重命名为Date::operator-就是件很自然的事情了。在做这个事情之前,我们仔细研究一下下列语句的语音:
a = b - c;
无论变量是什么类型,下述语句总是成立的:
a 是一个由减法产生的明确的对象,并且 b - c == - (c - b)
我们使用下列约定俗成的习惯,即一个正的日期对象的所有数据成员都是正的,反之亦然(不允许符号的混合)。在Listing 7中我用Date::operator- (const Date&)代替了Date::interval,前者为每一个数据成员增加了正确的符号并且返回重新构造过的类的对象。Listing 6中重新定义的类中还包括了一个一元的“-”运算符函数,它的名字还是Date::operator-,但是没有任何参数。编译器将把下列的语句
d1 - d2;
-d1;
分别替换为:
d1.operator-(d2); // Calls Date::operator-(const Date&)
d1.operator-(); // Calls Date::operator-()
Listing 8中有一个使用了新的成员函数的简单示例程序。
#include <iostream.h>
main()
{
int i;
cout << "Enter an integer: ";
cin >> i;
cout << "You typed " << i << endl;
return 0;
}
的输出结果为:
Enter an integer: 5
You typed 5
cout是C++流库中提供的output流(类ostreom)而cin是C++流库中提供的input流(类istreom),它们分别与标准输出和标准输入相关。当编译器看到下面的表达式:
cout << "Enter an integer: "
它将用如下语句代替:
cout.operator<<("Enter an integer: ")
上述语句调用了成员函数ostream::operator<<(const char *)。同样的,表达式
cout << i调用了函数ostream::-operator<<(int)。endl是一个特殊的流指示,它输出一个换行符并清空输出缓冲区。Output行可以连在一起:
cout << "You typed " << i
因为ostream::operator<<返回一个到stream自身的引用。上述语句变成了
(cout.operator<<("You typed ")).operator<<(i)
为了适应Date对象的输出,你需要一个全局函数,该函数将要输出的内容发送给一个给定的输出流,并且返回到那个流的引用:
ostream& operator<<(ostream& os, const Date& d)
{
os << d.get_month() << '/'
<< d.get_day() << '/'
<< d.get_year();
return os;
}
这当然不能是一个成员函数,因为流(并非正在被输出的对象)总是出现在流插入符号的左边。
友元
为了提高效率,通常会赋予operator<<进入到一个对象的私有数据成员的权限(大多数的类的实现都提供了相关的I/O操作符,因此在这种情况下打破封装的边界似乎是比较安全的)。为了穿破对私有数据成员访问的限制,你需要在类的声明中加入如下语句,把operator<<声明为Date的友元:
friend ostream& operator<<(ostream&, const Date&);
Listing 9中展示了新的类的声明,并且包括了输入函数operator>>的声明。Listing 10中展示了这些函数的实现,Listing 11中有一个简单的示例程序。
静态成员
C++中的类定义了一个作用域。这就是为什么函数Date::compare不会和一个叫做compare的全局函数发生冲突的原因(即使它们参数和返回值的类型都相同)。现在考虑实现文件中的一个数组dtab[]。dtab的静态存储类型使它对文件来说是private的。但是它实际上属于整个类,而不是这个文件。如果我想要传递Date的成员函数到多个文件中,我不得不将需要访问dtab的函数传递到同一个文件中。
一个更好的办法是使dtab成为类的静态成员。静态成员属于整个类,而不是一个单独的对象。这意味着只有一个dtab的拷贝存在,它被所有类的对象所共享。使函数isleap成为static则允许你不需要和一个对象相关就能调用它,比如,你只需要这样写:
isleap(y);
而不需要这样写:
d.isleap(y);
要想使isleap对任何调用者都可用,使它为public,用如下方式调用:
Date::isleap(y);
最后,我将重新定义缺省构造函数,用当前日期初始化类的对象。最后的类的定义、实现和示例程序分别参见Listing 12 - Listing 14。
总结
在上面两个部分中,我试图说明C++是如何支持数据抽象——使用者的产物——自定义的数据类型。构造函数使得当你声明一个对象的时候能够自动对它进行初始化。你可以通过声明类的成员为private来保护它们不受到无意中的访问。重载公用的运算符可以使得你的对象看起来跟系统内建的数据类型很相似——这增加了可读性和可维护性。
// date4.h
class Date
{
int month;
int day;
int year;
public:
// Constructors
Date()
{month = day = year= 0;}
Date(int m, int d, int y)
{month = m; day = d; year = y;}
// Accessor Functions
int get_month() const
{return month;}
int get_day() const
{return day;}
int get_year() const
{return year;}
Date * interval(const Date&) const;
int compare(const Date&) const;
};
// End of File
// date4.cpp
#include "date4.h"
inline int isleap(int y)
{return y%4 == 0 && y%100 != 0 || y%400 == 0;}
static int dtab[2][13] =
{
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
Date * Date::interval(const Date& d2) const
{
static Date result;
int months, days, years, prev_month;
// Compute the interval - assume d1 precedes d2
years = d2.year - year;
months = d2.month - month;
days = d2.day - day;
// Do obvious corrections (days before months!)
//
// This is a loop in case the previous month is
// February, and days < -28.
prev_month = d2.month - 1;
while (days < 0)
{
// Borrow from the previous month
if (prev_month == 0)
prev_month = 12;
-months;
days += dtab[isleap(d2.year)][prev_month-];
}
if (months < 0)
{
// Borrow from the previous year
-years;
months += 12;
}
// Prepare output
result.month = months;
result.day = days;
result.year = years;
return &result;
}
int Date::compare(const Date& d2) const
{
int months, days, years, order;
years = year - d2.year;
months = month - d2.month;
days = day - d2.day;
// return <0, 0, or >0, like strcmp()
if (years == 0 && months == 0 && days == 0)
return 0;
else if (years == 0 && months == 0)
return days;
else if (years == 0)
return months;
else
return years;
}
// End of File
// tdate4.cpp
#include <stdio.h>
#include "date4.h"
void compare_dates(const Date& d1, const Date& d2)
{
int compval = d1.compare(d2);
char *compstr - (compval < 0) ? "precedes" :
((compval > 0) ? "follows" : "equals"};
printf("%d/%d/%d %s %d/%d/%d/n",
d1.get_month(),d1.get_day(0),d1.get_year(),
compstr,
d2.get_month(),d2.get_day(),d2.get_year());
}
main()
{
Date d1(1,1,1970);
compare dates(d1,Date(10,1,1951));
compare_dates{d1,Date(1,1,1970));
compare_dates(d1,Date(12,31,1992));
return 0;
}
/* OUTPUT
1/1/1970 follows 10/1/1951
1/1/1970 equals 1/1/1970
1/1/1970 precedes 12/31/1992
*/
// End of File
// date5.h
class Date
{
int month;
int day;
int year;
public:
// Constructors
Date()
{month = day = year = 0;}
Date(int m, int d, int y)
{month = m; day = d; year = y;}
// Accessor Functions
int get_month() const
{return month;}
int get_day() const
{return day;}
int get_year() const
{return year;}
Date * interval(const Date&) const;
int compare(const Date&) const;
// Relational operators
int operator<(const Date& d2) const
{return compare(d2) < 0;}
int operator<=(const Date& d2) const
{return compare(d2) <= 0;}
int operator>(const Date& d2) const
{return compare(d2) > 0;}
int operator>=(const Date& d2) const
{return compare(d2) >= 0;}
int operator!=(const Date& d2) const
{return compare(d2) != 0;}
int operator!=(const Date& d2) const
{return compare(d2) !=0;}
};
// End of File
// tdate5.cpp
#include <stdio.h>
#include <stdlib.h>
#include "date5.h"
void compare_dates(const Date& d1, const Date& d2)
{
char *compstr = (d1 < d2) ? "precedes" :
((d1 > d2) ? "follows" : "equals");
printf("%d/%d/%d %s %d/%d/%d/n",
d1.get_month(),d1.get_day(),d1.get_year(),
compstr,
d2.get_month(),d2.get_day(),d2.get_year());
}
main()
{
Date d1(1,1,1970);
compare_dates(d1,Date(10,1,1951));
compare_dates(d1,Date(1,1,1970));
compare_dates(d1,Date(12,31,1992));
return 0;
}
/* OUTPUT
1/1/1970 follows 10/1/1951
1/1/1970 equals 1/1/1970
1/1/1970 precedes 12/31/1992
*/
// End of File
// date6.h
class Date
{
int month;
int day;
int year;
public:
// Constructors
Date()
{month = day = year = 0;}
Date(int m, int d, int y)
{month = m; day = d; year = y;}
// Accessor Functions
int get_month() const
{return month;}
int get_day() const
{return day;}
int get_year() const
{return year;}
Date operator-(const Date& d2) const;
Date& operator-()
{month = -month; day = -day; year = -year;
return *this;}
int compare(const Date&) const;
// Relational operators
int operator<(const Date& d2) const
{return compare(d2) < 0;}
int operator<=(const Date& d2) const
{return compare(d2) <= 0;}
int operator>(const Date& d2) const
{return compare(d2) > 0;}
int operator>=(const Date& d2) const
{return compare(d2) >= 0;}
int operator==(const Date& d2) const
{return compare(d2) == 0;}
int operator!=(const Date& d2) const
{return compare(d2) != 0;}
};
// End of File
// date6.cpp
#include <assert.h>
#include "date6.h"
inline int isleap(int y)
{return y%4 == 0 && y%100 != 0 || y%400 == 0;}
static int dtab[2][13] =
{
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
Date Date::operator-(const Date& d2) const
{
int months, days, years, prev_month, order;
const Date * first, * last;
// Must know which date is first
if (compare(d2) <= 0)
{
// this <= d2
order = -1;
first = this;
last = &d2;
}
else
{
order = 1;
first = &d2;
last = this;
}
// Compute the interval; first <= last
years = last->year - first->year;
months = last->month - first->month;
days = last->day - first->day;
assert(years >= 0 && months >= 0 && days >= 0);
// Do obvious corrections (days before months!)
// This is a loop in case the previous month is
// February, and days < -28.
prev_month = last->month - 1;
while (days < 0)
{
// Borrow from the previous month
if (prev_month == 0)
prev_month = 12;
--months;
days += dtab[isleap(last->year)][prey_month--];
}
if {months < 0)
{
// Borrow from the previous year
--years;
months += 12;
}
// Return a date object with the interval
if (order == -1)
return Date(-months,-days,-years);
else
return Date(months, days, years);
}
int Date::compare(const Date& d2) const
{
// same as in Listing 2
}
// End of File
// tdate6.cpp:
#include <stdio.h>
#include "date6.h"
main()
{
Date d1(1,1,1970), d2(12,8,1992);
Date result = d1 - d2;
printf("years: %d, months: %d, days: %d/n",
result.get_year(),
result.get_month(),
result.get_day());
result = d2 - d1;
printf("years: %d, months: %d, days: %d/n",
result.get_year(),
result.get_month(),
result.get_day());
int test = d1 - d2 == -(d2 - d1);
printf("d1 - d2 == -(d2 - d1)? %s/n",
test ? "yes" : "no");
return 0;
}
/* OUTPUT
years: -22, months: -11, days: -7
years: 22, months: 11, days: 7
d1 - d2 == -(d2 - d1)? yes
*/
// End of File
// date7.h
class ostream;
class Date
{
int month;
int day;
int year;
public:
// Constructors
Date()
{month = day = year = 0;}
Date(int m, int d, int y)
{month = m; day = d; year = y;}
// Accessor Functions
int get_month() const
{return month;}
int get_day() const
{return day;}
int get_year() const
{return year;}
Date operator-(const Date& d2) const;
Date& operator-()
{month= -month; day = -day; year = -year;
return *this;}
int compare(const Date&) const;
// Relational operators
int operator<(const Date& d2) const
{return compare(d2) < 0;}
int operator<=(const Date& d2) const
{return compare(d2) <= 0;)
int operator>(const Date& d2) const
{return compare(d2) > 0;}
int operator>=(const Date& d2) const
{return compare(d2) >= 0;}
int operator==(const Date& d2) const
{return compare(d2) == 0;}
int operator!=(const Date& d2) const
{return compare(d2) != 0)
// I/O operators
friend ostream& operator<<(ostream&, const Date&);
friend istream& operator>>(istream&, Date&);
};
// End of File
#include <iostream.h>
#include "date7.h"
ostream& operator<<(ostream& os, const Date& d)
{
os << d.month << '/' << d.day << '/' << d.year;
return os;
}
istream& operator>>(istream& is, Date& d)
{
char slash;
is >> d.month >> slash >> d.day >> slash >> d.year;
return is;
}
// End of File
// tdate7.cpp:
#include <iostream.h>
#include "date7.h"
main()
{
Date d1, d2;
cout << "Enter a date: ";
cin >> d1;
cout << "Enter another date: ";
cin >> d2;
cout << "d1 - d2 = "<< d1 - d2 << endl;
cout << "d2 - d1 = "<< d2 - d1 << endl;
return 0;
}
/* OUTPUT
Enter a date: 10/1/1951
Enter another date: 5/1/1954
d1 - d2 = -7/0/-2
d2 - d1 = 7/0/2
*/
// End of File
// date8.h
// Forward declarations
class istream;
class ostream;
class Date
{
int month;
int day;
int year;
static int dtab[2][13];
public:
// Constructors
Date(); // Get today's date (see .cpp file)
Date(int m, int d, int y)
{month = m; day = d; year = y;}
// Accessor Functions
int get_month() const
{return month;}
int get_day() const
{return day;}
int get_year() const
{return year;}
Date operator-(const Date& d2) const;
Date& operator-()
{month = -month; day = -day; year = -year;
return *this;}
int compare(const Date&) const;
// Relational operators
int operator<(const Date& d2) const
{return compare{d2) < 0;}
int operator<=(const Date& d2) const
{return compare(d2) <= 0;}
int operator>(const Date& d2) const
{return compare(d2) > 0;}
int operator>=(const Date& d2) const
{return compare(d2) >= 0;}
int operator==(const Date& d2) const
{return compare(d2) == 0;}
int operator!=(const Date& d2) const
{return compare(d2) != 0;}
// Stream I/O operators
friend ostream& operator<<(ostream&, const Date&);
friend istream& operator>>(istream&, Date&);
static int isleap(int y)
{return y%4 == 0 && y%100 != 0 || y%400 == 0;}
};
// End of File
// date8.cpp
#include <iostream.h>
#include <time.h>
#include <assert.h>
#include "date8.h"
// Must initialize statics outside the class definition
int Date::dtab[2][13] =
{
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
Date Date::operator-(const Date& d2) const
{
int months, days, years, prev_month, order;
const Date * first, * last;
// Must know which date is first
if (compare(d2) <= 0)
{
// this <= d2
order= -1;
first = this;
last = &d2;
}
else
{
order = 1;
first = &d2;
last = this;
}
// Compute the interval; first <= last
years = last->year - first->year;
months = last->month - first->month;
days = last->day - first->day;
assert(years >= 0 && months >= 0 && days >= 0);
// Do obvious corrections (days before months!)
//
// This is a loop in case the previous month is
// February, and days < -28.
prev_month = last->month - 1;
while (days < 0)
{
// Borrow from the previous month
if (prev_month == 0)
prev_month = 12;
-months;
days += dtab[isleap(last->year)][prev_month-];
}
if (months < 0)
{
// Borrow from the previous year
-years;
months += 12;
}
// Return a date object with the interval
if (order == 1)
return Date(-months,-days,-years);
else
return Date(months,days,years);
}
int Date::compare(const Date& d2) const
{
int months, days, years, order;
years = year - d2.year;
months = month - d2.month;
days = day - d2.day;
// return <0, 0, or >0, like strcmp()
if (years == 0 && months == 0 && days == 0)
return 0;
else if (years == 0 && months == 0)
return days;
else if (years == 0)
return months;
else
return years;
}
ostream& operator<<(ostream& os, const Date& d)
{
os << d.month << '/' << d.day << '/' << d.year;
return os;
}
istream& operator>>(istream& is, Date& d)
{
char slash;
is >> d.month >> slash >> d.day >> slash >> d.year;
return is;
}
Date::Date()
(
// Get today's date
time_t tval = time(0);
struct tm *tmp= localtime(&tval);
month = tmp->tm_mon+1;
day = tmp->tm_mday;
year = tmp->tm_year + 1900;
}
// End of File
// tdate8.cpp:
#include <iostream.h>
#include "date8.h"
main()
{
Date today, d2;
cout << "Today's date is "<< today << endl;
cout << "Enter another date: ";
cin >> d2;
cout << "today - d2 = "<< today - d2 << endl;
cout << "d2 - today = "<< d2 - today << endl;
return 0;
}
/* OUTPUT
Today's date is 12/12/1992
Enter another date: 1/1/1970
today - d2 = 11/11/22
d2 - today = -11/-11/-22
*/
// End of File