首页 > 代码库 > C++基础学习教程(五)

C++基础学习教程(五)

这一讲我们集中讲解类和他的一些特性.首先我们从自定义一个有理数类来开始.

在C语言中有一个关键字: struct ,用来创建一个结构体类型.但是在C++中这个关键的含义就不仅仅如此了,下面我们可以看下示例:

/// Represent a rational number.
struct rational
{
  int numerator;     ///< numerator gets the sign of the rational value
  int denominator;   ///< denominator is always positive
};

首先觉得这个和C语言中看起来是一样的,分子是numerator,分母是denominator,其实不然,上面那段代码实际上是创建了一个类型,即是一个类型的定义,换言之,就是编译器会记住rational命名了一个类型,只是没有为对象numerator和denominator分配内存,用C++的说法,numerator和denominator是数据成员. 注意花括号后面的分号结束符!!!!

下面为了验证这是一个类型,我们可以创建一个rational的对象,然后使用点(.)操作符访问成员,如下:

/** Using a Class and Its Members */
#include <iostream>
#include <ostream>

/// Represent a rational number.
struct rational
{
  int numerator;     ///< numerator gets the sign of the rational value
  int denominator;   ///< denominator is always positive
};

int main()
{
  rational pi;
  pi.numerator = 355;
  pi.denominator = 113;  
  std::cout << "pi is about " << pi.numerator << "/" << pi.denominator << '\n';
}

运行结果如下:


现在可以看出这确实是一个类型,那么现在我们就进一步扩展这个类型,首先,增加一个分数自动约分的功能.rational类型已经有了数据成员,我们现在为其编写一个约分的函数成员,如下:

/** @file ReduceduceRational.cpp */
/** Adding the reduce Member Function */
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <ostream>

/// Compute the greatest common divisor of two integers, using Euclid’s algorithm.
int gcd(int n, int m)
{
  n = abs(n);
  while (m != 0) {
    int tmp(n % m);
    n = m;
    m = tmp;
  }
  return n;
}

/// Represent a rational number.
struct rational
{
  /// Reduce the numerator and denominator by their GCD.
  void reduce()
  {
    assert(denominator != 0);
    int div(gcd(numerator, denominator));
    numerator = numerator / div;
    denominator = denominator / div;
  }

  int numerator;     ///< numerator gets the sign of the rational value
  int denominator;   ///< denominator is always positive
};

int main()
{
  rational pi;
  pi.numerator = 1420;
  pi.denominator = 452;
  pi.reduce();
  std::cout << "pi is about " << pi.numerator << "/" << pi.denominator << '\n';
}

执行结果如下:


注意reduce看起来是一个普通的函数,但是出现在rational的类型定义内.并要注意在reduce内部调用rational数据成员的方式.而当调用reduce函数时候,点操作符的左边必须是一个对象,当reduce()函数指向一个数据成员时,该数据成员从左操作数获取.

上面代码中gcd函数是利用欧几里得算法就最大公约数,是一个自由函数,任何的两个整数都可以作为参数调用它,他作为自由函数和rational类型没有任何的关系,仅仅是用作计算最大公约数,完全没有修改rational()的数据成员.

而在上面的代码中我们定义了一个reduce()成员函数,现在我们继续扩展rational类型,增加一个assign()成员函数,并让assign()成员函数调用reduce()函数.即是在类型定义内部可以定义多个函数,并且函数之间可以调用.代码如下:

/** @file ReduceAssignRational.cpp */
/** Adding the assign Member Function */
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <ostream>
/// Compute the greatest common divisor of two integers, using Euclid’s algorithm.
int gcd(int n, int m)
{
  n = abs(n);
  while (m != 0) {
    int tmp(n % m);
    n = m;
    m = tmp;
  }
  return n;
}

/// Represent a rational number.
struct rational
{
  /// Assign a numerator and a denominator, then reduce to normal form.
  /// @param num numerator
  /// @param den denominator
  /// @pre denominator > 0
  void assign(int num, int den)
  {
    numerator = num;
    denominator = den;
    reduce();
  }

  /// Reduce the numerator and denominator by their GCD.
  void reduce()
  {
    assert(denominator != 0);
    int div(gcd(numerator, denominator));
    numerator = numerator / div;
    denominator = denominator / div;
  }

  int numerator;     ///< numerator gets the sign of the rational value
  int denominator;   ///< denominator is always positive
};

int main()
{
  rational pi;
  pi.assign(1420, 452);
  std::cout << "pi is about " << pi.numerator << "/" << pi.denominator << '\n';
}

在上面的代码中,assign()函数调用了reduce()函数,但是assign()函数的定义早于reduce()函数,这里我们有这样的规则----编译器在使用一个名字前必须看到该名字的至少一个声明,新类型的成员可以调用其他成员,而不必考虑在类型内的声明顺序.在其他情况下,声明必须早于调用.

同时我们也可以看出,在上面创建rational实例对象的时候,我们要么是直接给数据成员赋值,要么调用其中的一个assign()函数赋值,而学习过其他高级语言的应该都知道我们应该通过构造函数来初始化一个类型的实例,那么现在我们就把rational类型添加构造函数,(构造器),代码如下:

/** @file ConstructRational.cpp */
/** Adding the Ability to Initialize a rational Object */
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <ostream>

/// Compute the greatest common divisor of two integers, using Euclid’s algorithm.
int gcd(int n, int m)
{
  n = abs(n);
  while (m != 0) {
    int tmp(n % m);
    n = m;
    m = tmp;
  }
  return n;
}

/// Represent a rational number.
struct rational
{
  /// Construct a rational object, given a numerator and a denominator.
  /// Always reduce to normal form.
  /// @param num numerator
  /// @param den denominator
  /// @pre denominator > 0
  rational(int num, int den)
  : numerator(num), denominator(den)
  {
    reduce();
  }

  /// Assign a numerator and a denominator, then reduce to normal form.
  /// @param num numerator
  /// @param den denominator
  /// @pre denominator > 0
  void assign(int num, int den)
  {
    numerator = num;
    denominator = den;
    reduce();
  }

  /// Reduce the numerator and denominator by their GCD.
  void reduce()
  {
    assert(denominator != 0);
    int div(gcd(numerator, denominator));
    numerator = numerator / div;
    denominator = denominator / div;
  }

  int numerator;     ///< numerator gets the sign of the rational value
  int denominator;   ///< denominator is always positive
};

int main()
{
  rational pi(1420, 452);
  std::cout << "pi is about " << pi.numerator << "/" << pi.denominator << '\n';
}

其中的构造函数就是


执行结果如下:


对上面的构造函数分析,可以知道构造函数的形式和要求为:

  • l  函数名称必须为类型名称
  • l  函数可以有参数或者无参数(按需而定)
  • l  函数参数(非空)后有一个冒号(:)后面是对应的参数初始化对应的数据成员。

现在我们继续扩展这个rational类型,上面的代码都是默认为分母为正,分子可为负,现在我们要使可以初始化的时候传递负数的分母,这就要求我们对分子分母同时取反,这样能够保证分母依然为正,而数值不变.部分代码如下:

 /// Reduce the numerator and denominator by their GCD.
  void reduce()
  {
    assert(denominator != 0);
    if (denominator < 0)   // if denominator < 0
    {
      denominator = -denominator;
      numerator = -numerator;
    }
    int div(gcd(numerator, denominator));
    numerator = numerator / div;
    denominator = denominator / div;
  }

构造函数是我们接触的最新的函数类型,他同样的可以被重载,重载构造函数的代码如下:

/** Constructing a Rational Object from an Integer */
  rational(int num)
  : numerator(num), denominator(1)
  {}

这个是一个参数的构造函数,默认分母为1.而且显然不需要调用reduce函数.

我们既然创造了一个新的数据类型,那么就也需要创造这个数据类型的操作行为,就是重载操作符,使他也能够实现四则运算和大小比较等.下面就是运算符重载的代码:

/** @file OverrideOperator.cpp */
/** Overloading the Equality Operator */
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <ostream>

using namespace std;

/// Compute the greatest common divisor of two integers, using Euclid’s algorithm.
int gcd(int n, int m)
{
  n = abs(n);
  while (m != 0) {
    int tmp(n % m);
    n = m;
    m = tmp;
  }
  return n;
}

/// Represent a rational number.
struct rational
{
  /// Construct a rational object, given a numerator and a denominator.
  /// Always reduce to normal form.
  /// @param num numerator
  /// @param den denominator
  /// @pre denominator > 0
  rational(int num, int den)
  : numerator(num), denominator(den)
  {
    reduce();
  }
  rational(int num)
  : numerator(num), denominator(1)
  {}
  
  /// Assign a numerator and a denominator, then reduce to normal form.
  /// @param num numerator
  /// @param den denominator
  /// @pre denominator > 0
  void assign(int num, int den)
  {
    numerator = num;
    denominator = den;
    reduce();
  }

  /// Reduce the numerator and denominator by their GCD.
  void reduce()
  {
    assert(denominator != 0);
    if (denominator < 0)
    {
      denominator = -denominator;
      numerator = -numerator;
    }
    int div(gcd(numerator, denominator));
    numerator = numerator / div;
    denominator = denominator / div;
  }

  int numerator;     ///< numerator gets the sign of the rational value
  int denominator;   ///< denominator is always positive
};

/// Compare two rational numbers for equality.
/// @pre @p a and @p b are reduced to normal form
bool operator==(rational const& a, rational const& b)
{
  return a.numerator == b.numerator && a.denominator == b.denominator;  // ==
}

/// Compare two rational numbers for inequality.
/// @pre @p a and @p b are reduced to normal form
inline bool operator!=(rational const& a, rational const& b)
{
  return ! (a == b);                                                   // !=
}

/// Compare two rational numbers for less-than.
bool operator<(rational const& a, rational const& b)
{
  return a.numerator * b.denominator < b.numerator * a.denominator;      // <
}

/// Compare two rational numbers for less-than-or-equal.
inline bool operator<=(rational const& a, rational const& b)
{
  return ! (b < a);                                                    // <=
}

/// Compare two rational numbers for greater-than.
inline bool operator>(rational const& a, rational const& b)
{
  return b < a;                                                          // >
}

/// Compare two rational numbers for greater-than-or-equal.
inline bool operator>=(rational const& a, rational const& b)
{
  return ! (b > a);                                                    // >=
}

/** Addition Operator for the rational Type */
rational operator+(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.denominator + rhs.numerator * lhs.denominator,
                  lhs.denominator * rhs.denominator);                   // +
}

rational operator-(rational const& r)
{
  return rational(-r.numerator, r.denominator);                         // -
}

/** Arithmetic Operators for the rational Type */
rational operator-(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.denominator - rhs.numerator * lhs.denominator,
                  lhs.denominator * rhs.denominator);                  // -
}

rational operator*(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.numerator, 
                  lhs.denominator * rhs.denominator);                  // *
}

rational operator/(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.denominator, 
                  lhs.denominator * rhs.numerator);                   // /
}

void print(rational const & result)
{
    cout << result.numerator << '/' << result.denominator << '\n' ;
}

int main()
{
  rational rat_a (1,9);
  rational rat_b (2,7);
  rational rat_c (3,8);
  rational rat_d (4,7);
  rational result_a(rat_a + rat_b);
  rational result_b(rat_a / rat_b);
  rational result_c(rat_c - rat_a);  // the result < 0
  rational result_d(- rat_d);        // override -
  bool result_bool_a (rat_a > rat_d);
  bool result_bool_b (rat_a <= rat_c);

  print(rat_a);
  print(rat_b);
  print(rat_c);
  print(rat_d);
  print(result_a);
  print(result_b);
  print(result_c);
  print(result_d);
  cout << result_bool_a << '\n';
  cout << result_bool_b << '\n';
  
 rational result(3 * rational(1, 3));
 cout << result.numerator << '/' << result.denominator << '\n';
}

上面就是所有的运算符重载的函数,其中的构造函数有两个,一个是双参数一个是单参数,所以在上面的main函数中最后两行代码才可以通过编译并成功执行,因为里面的整数3被自动转换为rational类型的.

同样一些数学函数也是一样,比如求绝对值的函数重载代码段如下:

/** Computing the Absolute Value of a Rational Number */
rational absval(rational const& r)
{
  return rational(abs(r.numerator), r.denominator);
}

当然这个函数很容易重载,但是如果是涉及到浮点数的怎么办呢?比如sqrt开开方函数.而如果编译器知道如何将有理数rational转换为浮点数的话,就可以将rational实参传递给已有的浮点函数,无需再做进一步的工作.但是要用那种类型的浮点数,这个由于需求不同而变化,所以我们要放弃自动转换浮点类型,取而代之的是使用三个显式的计算有理数的浮点值的函数.代码段如下(需要使用static_cast将分子和分母转为需要的浮点类型)

/** Converting to Floating-point Types */
struct rational
{
  float as_float()
  {
    return static_cast<float>(numerator) / denominator;
  }

  double as_double()
  {
    return numerator / static_cast<double>(denominator);
  }

  long double as_long_double()
  {
    return static_cast<long double>(numerator) / 
           static_cast<long double>(denominator);
  }
};

目前为止的完整版的代码如下:

/** @file OverrideOperator.cpp */
/** Overloading the Equality Operator */
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <ostream>

using namespace std;

/// Compute the greatest common divisor of two integers, using Euclid’s algorithm.
int gcd(int n, int m)
{
  n = abs(n);
  while (m != 0) {
    int tmp(n % m);
    n = m;
    m = tmp;
  }
  return n;
}

/// Represent a rational number.
struct rational
{
  /// Construct a rational object, given a numerator and a denominator.
  /// Always reduce to normal form.
  /// @param num numerator
  /// @param den denominator
  /// @pre denominator > 0
  rational(int num, int den)
  : numerator(num), denominator(den)
  {
    reduce();
  }
  rational(int num)
  : numerator(num), denominator(1)
  {}
  
  /// Assign a numerator and a denominator, then reduce to normal form.
  /// @param num numerator
  /// @param den denominator
  /// @pre denominator > 0
  void assign(int num, int den)
  {
    numerator = num;
    denominator = den;
    reduce();
  }

  /// Reduce the numerator and denominator by their GCD.
  void reduce()
  {
    assert(denominator != 0);
    if (denominator < 0)
    {
      denominator = -denominator;
      numerator = -numerator;
    }
    int div(gcd(numerator, denominator));
    numerator = numerator / div;
    denominator = denominator / div;
  }
  
  /** Converting to Floating-point Types */
  float as_float()
  {
    return static_cast<float>(numerator) / denominator;
  }

  double as_double()
  {
    return numerator / static_cast<double>(denominator);
  }

  long double as_long_double()
  {
    return static_cast<long double>(numerator) / 
           static_cast<long double>(denominator);
  }

  int numerator;     ///< numerator gets the sign of the rational value
  int denominator;   ///< denominator is always positive
};

/// Compare two rational numbers for equality.
/// @pre @p a and @p b are reduced to normal form
bool operator==(rational const& a, rational const& b)
{
  return a.numerator == b.numerator && a.denominator == b.denominator;  // ==
}

/// Compare two rational numbers for inequality.
/// @pre @p a and @p b are reduced to normal form
inline bool operator!=(rational const& a, rational const& b)
{
  return ! (a == b);                                                   // !=
}

/// Compare two rational numbers for less-than.
bool operator<(rational const& a, rational const& b)
{
  return a.numerator * b.denominator < b.numerator * a.denominator;      // <
}

/// Compare two rational numbers for less-than-or-equal.
inline bool operator<=(rational const& a, rational const& b)
{
  return ! (b < a);                                                    // <=
}

/// Compare two rational numbers for greater-than.
inline bool operator>(rational const& a, rational const& b)
{
  return b < a;                                                          // >
}

/// Compare two rational numbers for greater-than-or-equal.
inline bool operator>=(rational const& a, rational const& b)
{
  return ! (b > a);                                                    // >=
}

/** Addition Operator for the rational Type */
rational operator+(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.denominator + rhs.numerator * lhs.denominator,
                  lhs.denominator * rhs.denominator);                   // +
}

rational operator-(rational const& r)
{
  return rational(-r.numerator, r.denominator);                         // -
}

/** Arithmetic Operators for the rational Type */
rational operator-(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.denominator - rhs.numerator * lhs.denominator,
                  lhs.denominator * rhs.denominator);                  // -
}

rational operator*(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.numerator, 
                  lhs.denominator * rhs.denominator);                  // *
}

rational operator/(rational const& lhs, rational const& rhs)
{
  return rational(lhs.numerator * rhs.denominator, 
                  lhs.denominator * rhs.numerator);                   // /
}

void print(rational const & result)
{
    cout << result.numerator << '/' << result.denominator << '\n' ;
}

int main()
{
  rational rat_a (1,9);
  rational rat_b (2,7);
  rational rat_c (3,8);
  rational rat_d (4,7);
  rational result_a(rat_a + rat_b);
  rational result_b(rat_a / rat_b);
  rational result_c(rat_c - rat_a);  // the result < 0
  rational result_d(- rat_d);        // override -
  bool result_bool_a (rat_a > rat_d);
  bool result_bool_b (rat_a <= rat_c);

  print(rat_a);
  print(rat_b);
  print(rat_c);
  print(rat_d);
  print(result_a);
  print(result_b);
  print(result_c);
  print(result_d);
  cout << result_bool_a << '\n';
  cout << result_bool_b << '\n';
  
  rational result(3 * rational(1, 3));
  cout << result.numerator << '/' << result.denominator << '\n';

  rational pi(355, 113);
  rational bmi(90*100*100, 180*180); // Body-mass index of 90 kg, 180 cm
  double circumference(0), radius(10);

  circumference = 2 * pi.as_double() * radius;
  cout << "circumference of circle with radius " << radius << " is about "
            << circumference << '\n';
  cout << "bmi = " << bmi.as_float() << '\n';
  
}

执行结果如下:

现在我们的rational类型几乎已经和int内置类型差不多了,但是还有一个功能有待实现,那就是输入输出,上面的输出输入是通过专门的函数实现的,而不能直接向内置数据类型的输入输出那样,所以我们需要重载I/O操作符.

在C++中,输入出符一样可以被重载.对于输入操作符,及时提取器(>>),他的第一个参数为std::istream& 。必须是非常了引用,以便在函数中修改流对象。而第二个参数因为要存储输入值,也必须是非常量引用。返回值通常就是他的第一个参数,类型std::istream& ,以便在同一个表达式中连接多个输入操作符。

函数体完成输入的提取解析和翻译工作。尽管完善的错误处理目前还难以实现,但是假如基本的错误处理还是很简单的。每个流用一个状态掩码跟踪错误,如下:

当输入非法时,输入函数在流的错误状态设置failbit位。调用者通过状态测试字检测流状态,若状态字设置了failbit则说明流出现了错误。

下面定义rational类型格式,一方面应该易于阅读和书写,同时也应该易于快速读取和解析,而且要让输入格式能够识别输出格式以及其他格式。

定义格式为整数、斜线(/),整数,且各个元素之间可插入任意的空格,除非设置流的状态位为禁用。若一个整数后没有斜线,则该数即为结果(分母为一),成员函数unget()使输入操作符退回多读入的字符。整数操作符与此类似:尽量多读入字符直到读入的字符不属于该整数为止,然后回退最后一个字符。输入操作符的重载代码如下:

/** @file OverrideIO.cpp */
/** Input Operator */
#include <ios>      // declares failbit, etc.
#include <istream>  // declares std::istream and the necessary >> operators

std::istream& operator>>(std::istream& in, rational& rat)
{
  int n(0), d(0);
  char sep('\0');
  if (not (in >> n >> sep))
    // Error reading the numerator or the separator character.
    in.setstate(std::cin.failbit);
  else if (sep != '/')
  {
    // Read numerator successfully, but it is not followed by /.
    // Push sep back into the input stream, so the next input operation
    // will read it.
    in.unget();
    rat.assign(n, 1);
  }
  else if (in >> d)
    // Successfully read numerator, separator, and denominator.
    rat.assign(n, d);
  else
    // Error reading denominator.
    in.setstate(std::cin.failbit);
    
  return in;
}

使用输出操作符时,面对许多的棘手的格式化问题,包括字符宽度设置和对齐方式选择,按需插入填充字符,以及仅重设字符宽度而不改变其他格式设置等。

编写复杂的输出操作符的关键在于利用临时输入流将文字存在string类型的字符串中。Std::osringstream类型在<sstream>中声明,使用方式与cout等输出流相同。操作完成后,使用成员函数str()返回生成的string类型字符串。

在rational的输入操作符中,创建一个ostringstream,并写入分子,分隔符,分母。然后将生成的字符串写入实际的输出流中,并让输出流处理宽度,对齐和填充。如果将分子分隔符,分母直接写入输出中,则宽度设置只能应用于分子,使对齐格式出错。和输入符一样,输出操作符返回也是第一个参数,类型std::ostream& ,第二个参数可以按值传递或者传入常量引用。代码如下:

/** @file Output.cpp */
/** Output Operator */
#include <ostream>  // declares the necessary << operators
#include <sstream>  // declares the std::ostringstream type

std::ostream& operator<<(std::ostream& out, rational const& rat)
{
  std::ostringstream tmp;
  tmp << rat.numerator;
  if (rat.denominator != 1)
    tmp << '/' << rat.denominator;
  out << tmp.str();

  return out;
}

下面就是检测I/O错误的讲解:

首先上文说过输入输出流有几个标志位,程序通过检查和设置标志位来测试错误与否。

输入循环在正常情况下会处理输入流的全部数据,直到到达输入流尾端时设置eofbit。此时fail()仍然返回false表明输入流状态正常,因此输入继续下一轮循环。但这一轮中再无数据可读,因此设置failbit并返回错误,循环条件因为false而退出。

当流中包含非法输入,。例如在要读取一个整数的时候却都到了非数字字符,或者出现了硬件错误时候,循环会退出。首先调用bad()成员函数检测硬件错误,返回true则退出,其次eof()成员函数用来检测是否到达文件尾,仅当eofbit被设置返回true。若bad() eof()同时为false且fail()为true,则表明流中含有非法输入。然后程序按需处理。可以调用clear函数清除错误状态,跳过后进行下一次读取。

代码如下,:

/// Tests for failbit only
bool iofailure(istream& in)
{
  return in.fail() && !(in.bad());
}
// test for istream
bool infoistream(istream & in)
{
  return !(in.bad()) && !(in.eof()) && in.fail()
}

完成rational类型的最后一步是完善操作符函数和构造函数,首先是赋值操作符的重载,示例如下:

/** First Version of the Assignment Operator */
struct rational
{
  rational& operator=(rational const& rhs)
  {
    numerator = rhs.numerator;
    denominator = rhs.denominator;
    return *this;
  }
};

另外一种实现如下:

/** Assignment Operator with Explicit Use of this-> */
struct rational
{
  rational& operator=(rational const& that)
  {
    this->numerator = that.numerator;
    this->denominator = that.denominator;
    return *this;
  }
};

上面只是赋值操作符的参数为rational类型的实现,参数也可以是整数,对赋值操作符再次重载如下:

/** Assignment of an Integer to a Rational */
struct rational
{
  rational& operator=(int num)
  {
    this->numerator = num;
    this->denominator = 1;
    return *this;
  }
};

编译器会自动生成构造函数.特别的是,复制构造函数会将一个rational对象的所有对象成员复制到另一个rational对象.只要声明一个按值传递的rational实参,编译器就调用复制构造函数将实参复制到形参,或者定义一个rational变量且用另一个rational值初始化时,编译器也会通过复制构造函数生成该变量.和赋值操作符一样,编译器默认实现版本和我们的需要的版本一样,因此没必要再次手动编写复制构造函数.

构造函数一样,必须要编写自己实现的不同的参数的重载版本的构造函数,才能避免编译器自动生成可能不符合的默认构造函数.完整版的构造函数如下:

/** Overloaded Constructors for rational */
struct rational
{
  rational()
  : numerator(0), denominator(1)
  {/*empty*/}

  rational(int num)
  : numerator(num), denominator(1)
  {/*empty*/}

  rational(int num, int den)
  : numerator(num), denominator(den)
  {
    reduce();
  }
};

现在为止,对rational类型的定义大概完成了,现在贴一下目前为止的完整版的代码,以及测试运行结果:

/** @file OverrideOperator.cpp */
/** Overloading the Equality Operator */
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <ostream>
#include <sstream>

using namespace std;

/// Compute the greatest common divisor of two integers, using Euclids algorithm.
int gcd(int n, int m)
{
    n = abs(n);
    while (m != 0) 
    {
        int tmp(n % m);
        n = m;
        m = tmp;
    }
    return n;
}

/// Represent a rational number.
struct rational
{
    /// Construct a rational object, given a numerator and a denominator.
    /// Always reduce to normal form.
    /// @param num numerator
    /// @param den denominator
    /// @pre denominator > 0
    // ??
    rational(int num, int den)
        : numerator(num), denominator(den)
    {
        reduce();
    }
    // ?
    rational(int num)
        : numerator(num), denominator(1)
    {}
    // Ξ?
    rational()
        : numerator(0), denominator(1)
    {/*empty*/}
    // ??
    rational(double r)
        : numerator(static_cast<int>(r * 10000)), denominator(10000)
    {
        reduce();
    }
    /// Assign a numerator and a denominator, then reduce to normal form.
    /// @param num numerator
    /// @param den denominator
    /// @pre denominator > 0
    void assign(int num, int den)
    {
        numerator = num;
        denominator = den;
        reduce();
    }
    
    rational& operator=(int num)
    {
        this->numerator = num;
        this->denominator = 1;
        return *this;
    }
    
    /// Reduce the numerator and denominator by their GCD.
    void reduce()
    {
        assert(denominator != 0);
        if (denominator < 0)
        {
            denominator = -denominator;
            numerator = -numerator;
        }
        int div(gcd(numerator, denominator));
        numerator = numerator / div;
        denominator = denominator / div;
    }
    
    /** Converting to Floating-point Types */
    float as_float()
    {
        return static_cast<float>(numerator) / denominator;
    }
    
    double as_double()
    {
        return numerator / static_cast<double>(denominator);
    }
    
    long double as_long_double()
    {
        return static_cast<long double>(numerator) / 
            static_cast<long double>(denominator);
    }
    
    int numerator;     ///< numerator gets the sign of the rational value
    int denominator;   ///< denominator is always positive
};

/// Compare two rational numbers for equality.
/// @pre @p a and @p b are reduced to normal form
bool operator==(rational const& a, rational const& b)
{
    return a.numerator == b.numerator && a.denominator == b.denominator;  // ==
}

/// Compare two rational numbers for inequality.
/// @pre @p a and @p b are reduced to normal form
inline bool operator!=(rational const& a, rational const& b)
{
    return ! (a == b);                                                   // !=
}

/// Compare two rational numbers for less-than.
bool operator<(rational const& a, rational const& b)
{
    return a.numerator * b.denominator < b.numerator * a.denominator;      // <
}

/// Compare two rational numbers for less-than-or-equal.
inline bool operator<=(rational const& a, rational const& b)
{
    return ! (b < a);                                                    // <=
}

/// Compare two rational numbers for greater-than.
inline bool operator>(rational const& a, rational const& b)
{
    return b < a;                                                          // >
}

/// Compare two rational numbers for greater-than-or-equal.
inline bool operator>=(rational const& a, rational const& b)
{
    return ! (b > a);                                                    // >=
}

/** Addition Operator for the rational Type */
rational operator+(rational const& lhs, rational const& rhs)
{
    return rational(lhs.numerator * rhs.denominator + rhs.numerator * lhs.denominator,
        lhs.denominator * rhs.denominator);                   // +
}

rational operator-(rational const& r)
{
    return rational(-r.numerator, r.denominator);                         // -
}

/** Arithmetic Operators for the rational Type */
rational operator-(rational const& lhs, rational const& rhs)
{
    return rational(lhs.numerator * rhs.denominator - rhs.numerator * lhs.denominator,
        lhs.denominator * rhs.denominator);                  // -
}

rational operator*(rational const& lhs, rational const& rhs)
{
    return rational(lhs.numerator * rhs.numerator, 
        lhs.denominator * rhs.denominator);                  // *
}

rational operator/(rational const& lhs, rational const& rhs)
{
    return rational(lhs.numerator * rhs.denominator, 
        lhs.denominator * rhs.numerator);                   // /
}
/** Input and output operators for the rational Type */
istream& operator>>(std::istream& in, rational& rat)
{
    int n(0), d(0);
    char sep('\0');
    if ( !(in >> n >> sep))                 // read integer and separator from istream
        // Error reading the numerator or the separator character.
        in.setstate(std::cin.failbit);          // if con`t read them , return failbit, and exit or print tips        
    else if (sep != '/')        
    {
        // Read numerator successfully, but it is not followed by /.
        // Push sep back into the input stream, so the next input operation
        // will read it.
        in.unget();                            // only one integer ,no separator
        rat.assign(n, 1);
    }
    else if (in >> d)                        // separator is /
        // Successfully read numerator, separator, and denominator.
        rat.assign(n, d);                      // assign the rantional
    else
        // Error reading denominator.
        in.setstate(std::cin.failbit);         // read fail
    
    return in;
}

ostream& operator<<(std::ostream& out, rational const& rat)
{
    std::ostringstream tmp;
    tmp << rat.numerator;
    if (rat.denominator != 1)
        tmp << '/' << rat.denominator;
    out << tmp.str();
    
    return out;
}

/// Tests for failbit only
bool iofailure(istream& in)
{
    return in.fail() && !(in.bad());
}

// test for istream
bool infoistream(istream & in)
{
    return !(in.bad()) && !(in.eof()) && in.fail();
}

void print(rational const & result)
{
    cout << result.numerator << '/' << result.denominator << '\n' ;
}

int main()
{
    rational rat_a (1,9);
    rational rat_b (2,7);
    rational rat_c (3,8);
    rational rat_d (4,7);
    rational result_a(rat_a + rat_b);
    rational result_b(rat_a / rat_b);
    rational result_c(rat_c - rat_a);  // the result < 0
    rational result_d(- rat_d);        // override -
    bool result_bool_a (rat_a > rat_d);
    bool result_bool_b (rat_a <= rat_c);
    
    print(rat_a);
    print(rat_b);
    print(rat_c);
    print(rat_d);
    print(result_a);
    print(result_b);
    print(result_c);
    print(result_d);
    cout << result_bool_a << '\n';
    cout << result_bool_b << '\n';
    
    rational result(3 * rational(1, 3));
    cout << result.numerator << '/' << result.denominator << '\n';
    
    rational pi(355, 113);
    rational bmi(90*100*100, 180*180); // Body-mass index of 90 kg, 180 cm
    double circumference(0), radius(10);
    
    circumference = 2 * pi.as_double() * radius;
    cout << "circumference of circle with radius " << radius << " is about "
        << circumference << '\n';
    cout << "bmi = " << bmi.as_float() << '\n';
    
    //  cout << "Test for I/O operator !" << endl;
    // ??
    //cout << "????,rational ?"
    
    rational inte(12);
    rational dou(123.2);
    rational z(12,213);
    //cout << rational r() << endl;
    cout <<  inte << endl;
    cout << dou << endl;
    cout << z << endl;
    /*
    while (cin)
    {
    if (cin >> r)
    // Read succeeded, so no error state is set in the stream.
    cout << r << '\n';
    else if (iofailure(cin))
    {
    // Only failbit is set, meaning invalid input. Clear the error state, 
    // and then skip the rest of the input line.
    cout << "δ??,????!" << endl;
    cin.clear();
    cin.ignore(numeric_limits<int>::max(), '\n');
    }
    else if (infoistream(cin))
    {
    cout << "Error input " << endl;
    } 
    }
    
      if (cin.bad()) 
      cerr << "Unrecoverable input error\n";
    */
}

结果如下:


现在我们就可以继续深入的学习与类相关的编写了.