第9章は、クラスに関する。この部分は、C言語とC++言語との大きな違いでる。練習問題は、いかにもクラスの問題らしい練習問題13を選んだ。問題文は、以下の通り。
有理数を表すクラスRationalを設計し、実装する。代入演算子、加算演算子、減算演算子、乗算演算子、除算演算子、等価演算子を提供する。また、doubule型への変換をサポートする。
私の回答は、まずヘッダファイルRational.hは、下記の通り。問題文にはなかった、分母取り出し関数と分子取り出し関数、有理数を出力するための関数を追加した。
#include "..\..\std_lib_facilities.h"
class Rational {
public:
class Invalid {};Rational(int denominator, int numerator);
Rational();double real() const { return double(b) / double(a); }
int den() const { return a; }
int num() const { return b; }private:
int a, b;
};
Rational operator+(const Rational& x, Rational& y);
Rational operator-(const Rational& x, Rational& y);
Rational operator*(const Rational& x, Rational& y);
Rational operator/(const Rational& x, Rational& y);
bool operator==(const Rational& x, Rational& y);
ostream& operator<<(ostream& os, const Rational& x);
これの実装の関数Rational.cppは下記の通り。正規化するための最大公約数関数gcm、正規化関数normalizeは、この実装で使うための関数である。
#include "Rational.h"
int gcm(int x, int y) {
int r;x = abs(x);
y = abs(y);if (x < y) swap(x, y);
/* ユークリッドの互除法 */
r = x % y;
while (r != 0) {
x = y;
y = r;
r = x % y;
}return y;
}void normalization(int& x, int& y) {
int g{ 1 };
if ((x!=0) && (y!=0))g = gcm(x, y);
x = x / g;
y = y / g;if (y == 0) x = 1;
return;
}
Rational::Rational(int denominator, int numerator)
{
if (denominator == 0) throw Invalid();normalization(denominator, numerator);
a = denominator;
b = numerator;}
Rational::Rational()
: a{ 1 }, b{ 1 }
{}
Rational operator+(const Rational& x, Rational& y)
{
int a, b;
a = x.den() * y.den();
b = x.num() * y.den() + y.num()*x.den();
return { a,b };
}Rational operator-(const Rational& x, Rational& y)
{
int a, b;
a = x.den() * y.den();
b = x.num()*y.den() - y.num()*x.den();
return{ a,b };
}Rational operator*(const Rational& x, Rational& y)
{
int a, b;
a = x.den() * y.den();
b = x.num() * y.num();
return{ a,b };
}
Rational operator/(const Rational& x, Rational& y)
{
int a, b;
if (y.num() == 0) error("divided by zero ");
a = x.den() * y.num();
b = x.num() * y.den();
return{ a,b };
}bool operator==(const Rational& x, Rational& y)
{
if (x.den() != y.den()) return false;
if (x.num() != y.num()) return false;
return true;
}ostream& operator<<(ostream& os, const Rational& x)
{
return os << x.num() << "/" << x.den() << endl;
}
最後にテストのメイン関数
#include "Rational.h"
int main() {
try {
Rational x, y;cout << x; //デフォルトは1/1
x = Rational{ 5,4 }; // 4/5
cout << x;
x = Rational{ 10,8 }; //これも4/5 正規化ができているか?
cout << x;y = Rational{ 5,4 }; // 4/5
cout << x + y;
cout << x - y;
cout << x * y;
cout << x / y;
cout << (x == y) << endl;
cout << x.real() << endl;
return 0;
}catch (Rational::Invalid) {
cerr << "Denominator = 0" << endl;
return 1;
}
catch (exception& e) {
cerr << "exception: " << e.what() << endl;
char c;
while (cin >> c&& c != ';');
return 2;
}
}
この問題は、結構勉強になった。模範解答http://www.stroustrup.com/Programming/Solutions/Ch9/e9-13.cppでは、有理数がマイナスの時に、分母ではなく分子をマイナスにするようにしている。確かにこちらの方が良さそうだ。
私の回答では、符号を反転させるーとか、等価演算の!=とかを忘れているのも問題だ。一方、除算の時には、ゼロの除算でエラーを出すようにしているのは、私の回答例の方がいいかもしれない。
分母と分子を、私の回答では隠蔽するようになっている。これは、第9章のDateクラスに倣ったからだ。でも、解答例のように、あっさりPublicに置く方がいいかもしれない。これは、どうなんだろう?有理数の使い方にもよるのだろうけど、1組で数値を表すということを考えると、やっぱり隠蔽して、分母、分子を直接さわれない方がいいように思う。
コメント