我们都知道,有些类实际上是实现了某种特殊的类型,是对语言变量类型的扩展。如分数类,等级类等。这些类最终的实现目标是让使用者像使用语言原本就提供的标准类型一样的操作,甚至不必理睬其是类的事实。这种类的使用范围非常具有典型性,我称之为标准类型类nice类。C++Builder中的AnsiString 就是符合这种标准的SST类。
我们来看一个nice类的定义,看看作为一个nice类至少需要实现什么。
一个SST类的例
在C++中没有提供分数数据类型,我们以类的形式来实现它,因为分数是一种数据类型,自然要实现成nice形式。
class Faction
{
private:
//提供实际值的数据成员
int _Numerator ; //分子
int _Denominator ; //分母,注意不能为零
int _MaxCommonDivisor(int a,int b); //最大公约数
int _MinCommonMultiple(int a,int b); //最小公倍数
void _Simple() ; //化简
void CommonDenominator(const Fraction &OthFrac);//通分
public:
Faction();
Faction(int Numerator,int Denomonator);
Faction(int Value);
Faction(float Value);
Fraction & operator= (const Fraction &FracValue);
friend Fraction operator+(const Fraction &Frac1,const Fraction &Frac2);
friend Fraction operator-(const Fraction &Frac1,const Fraction &Frac2);
friend Fraction operator*(const Fraction &Frac1,const Fraction &Frac2);
friend Fraction operator/(const Fraction &Frac1,const Fraction &Frac2);
friend int operator >(const Fraction &Frac1,const Fraction &Frac2);
friend int operator <(const Fraction &Frac1,const Fraction &Frac2);
friend int operator >=(const Fraction &Frac1,const Fraction &Frac2);
friend int operator <=(const Fraction &Frac1,const Fraction &Frac2);
friend int operator ==(const Fraction &Frac1,const Fraction &Frac2);
friend int operator !=(const Fraction &Frac1,const Fraction &Frac2);
};
一般nice类都有一个或若干个用来存放实际值的数据成员,但显然不应该暴露它们,你有没有见过int.Value类似的形式呢。这就要求必须有不通过成员函数(操作符除外)或数据成员而能和其它类型数据直接交互,这可以通过操作符重载以及构造函数来实现类型转化。另外还有一些常用的操作符要也能想往常一样可以使用。
从以上例子可以看出,nice类的存放实际值的数据总是放在private部分声明,以避免直接访问,所有的访问都是通过操作符函数进行。作为一个类型,显然必须能够实例化变量,赋初值,加减乘除及逻辑比较运算。
Faction类为什么会被设计成这样?
我们先拿最常用的类型int来看看,一个类型一般如何使用,只要我们的nice类可以像使用int标识符一样使用,那就是我们的目标。
一. 首先是声明:
声明一个变量 int x;
带初值声明变量 int x=1;
要我们的nice类能同样的使用,显然须实现缺省构造函数(可以看实际情况,也可能不需要,但我们建议还是给实际值成员赋个初值)和赋值操作符。这里涉及到如何给新类型规定其常量形式,整形的常量形式为无小数点的数字字符,其又分为十进制形式,八进制形式,十六进制形式,这些皆是它的常量形式。作为分数的常量形式最容易想到是x/y的形式,然而因为其是一个合法的表达式,在传递值之前会进行运算,最后传递给分数类变量的却是已经除运算过的可能会因舍入而形成误差的整形结果。这不是我们想要的。在这里,暂且用加引号的方法避免形成表达式运算。那么如上整形的两种声明应用于分数:
Faction x;
Faction x="2/3"; 非有理数。或 Faction x=2 可除尽为整数; 或 Faction x=1.5; 可除尽为小数。
这需要分数Faction类实现:
- 缺省构造函数Faction::Faction();
- 转换字符串型数据的构造函数Faction::Faction(const char * const);
- 转换整形数据的构造函数Faction::Faction(int);
- 转换浮点型数据的构造函数Faction::Faction(float),
- 赋值运算符Faction::operator=(const Faction&)。
二. 基本算数运算:
假设x及y已经被声明为int型。
x加y:x+y;
x减y:x-y;
x和y相乘:x*y;
x除以y:x/y;
分数类要能完全实现这些运算符,需要注意的是和其常量形式及可转换类型的运算。比如和整形的运算:
Faction x;
x+2; 或 2+x;
这里对于参加运算的整形数的前后位置应该都可以使用,采用友元函数的方法来实现操作符重载:
friend Fraction operator+(const Fraction&,const Fraction&);
friend Fraction operator-(const Fraction&,const Fraction&);
friend Fraction operator*(const Fraction&,const Fraction&);
friend Fraction operator/(const Fraction&,const Fraction&);
三.逻辑比较运算符:
大于>,小于<,等于==,不等于!=,大于等于>=,小于等于<=。
这里也存在着算术运算符的常量形式使用位置的问题,自然也需要用友元的方法来实现,原理上节已经解释过,这里不再赘述。实现如下:
friend int operator >(const Fraction &,const Fraction &);
friend int operator <(const Fraction &,const Fraction &);
friend int operator >=(const Fraction &,const Fraction &);
friend int operator <=(const Fraction &,const Fraction &);
friend int operator ==(const Fraction &,const Fraction &);
friend int operator !=(const Fraction &,const Fraction &);
四、输出流:
不管什么类型的数据,最终都必须输出。在C++中最常见的输出机制便是cout,分数类也要实现能在cout中输出:cout< 为了实现在流中输出,需要重载运算符<<。
friend ostream& operator<<(const ostream& ,const Fraction&);
五、不需要实现的一些运算符:
一些运算符对于整形数是有意义的,而对于分数则意义不大或根本没有意义,这类运算符可以不必实现。如++,--,+=,-=,*=,/=等。
此外当然可以添加一些特有的方法,前提是只要满足上面的几点要求。这样,新的类型(SST类)的使用可以说和标准简单类型的使用完全一样。
nice类设计归纳
经过上面的分数类的实现及分析,读者对nice类应该有了一个大致的了解。如果你还没有完全搞清楚,也不必着急,你可以回过头来仔细看几遍,最好去真正调试一番。相信很快就可以搞明白。
总而言之,一个nice类一般应该具有如下的几个成员函数及友元函数:
- 缺省构造函数。
- 常量形式参数构造函数。
- 其它类型参数构造函数,可以和新类型混合运算的类型。
- 基本算术运算符函数,如:+,-,*,/,++,--等。
- 基本逻辑运算符函数,如:==,!=,<,>,<=,>=。
- 输入输出流运算符函数:>>,<<。
当然,以上的需要也并不绝对,实现者可以按照具体的情况进行选择。