Класс Rational. Сначала чуть не запутался. Буду показывать уже готовое решение без промежуточных файлов.
Вот еще задача:
Общее описание
В этой задаче вам надо разработать класс для представления рациональных чисел и внедрить его в систему типов языка С++ так, чтобы им можно было пользоваться естественным образом. Задание состоит из нескольких частей. К каждой части приложен файл с заготовкой программы, который имеет следующую структуру
| C++ | 1
2
3
4
5
6
7
8
9
10
| #include <iostream>
using namespace std;
// Комментарии, которые говорят, что именно нужно реализовать в этой программе
int main() {
// Набор юнит-тестов для вашей реализации
cout << "OK" << endl;
return 0;
} |
|
Вам нужно, не меняя тело функции main, реализовать то, что предложено в комментариях, так, чтобы ваша программа выводила на экран сообщение OK. Функция main содержит ряд юнит-тестов, которые проверяют правильность вашей реализации. Если эти тесты находят ошибку в вашей реализации, то вместо OK программа выведет подсказку о том, что именно работает неправильно.
Когда ваша программа начнёт выводить OK, можете отправлять свой файл на проверку. Ваша реализация будет тестироваться на другом, более полном наборе тестов. То есть, если ваша программа выводит OK, то это не даёт гарантии, что ваша реализация будет принята тестирующей системой. В случае неудачной посылки тестирующая система также выдаст вам подсказку о том, какая именно часть реализации работает неправильно.
Внимательно изучите набор юнит-тестов для каждой заготовки решения. Тесты описывают требования, предъявляемые к вашей реализации, которые могут быть не указаны в условии задач
Часть 1
В первой части вам надо реализовать класс Rational, который представляет собой рациональное число вида p/q, где p целое, а q натуральное. Он должен иметь следующий интерфейс:
| C++ | 1
2
3
4
5
6
7
8
| class Rational {
public:
Rational();
Rational(int numerator, int denominator);
int Numerator() const;
int Denominator() const;
}; |
|
Класс Rational должен гарантировать, что p/q — это несократимая дробь. Например, код
| C++ | 1
2
| Rational r(4, 6);
cout << r.Numerator() << '/' << r.Denominator(); |
|
должен выводить «2/3» — именно поэтому мы делаем Rational классом, а не структурой. Структура позволила бы нарушить этот инвариант:
| C++ | 1
2
3
4
5
6
| struct Rational {
int numerator, denominator;
};
Rational r{2, 3};
r.numerator = 6; // Нарушен инвариант несократимости |
|
Список требований, предъявляемых к реализации интерфейса класса Rational:
- Конструктор по умолчанию должен создавать дробь с числителем 0 и знаменателем 1.
- При конструировании объека класса Rational с параметрами p и q должно выполняться сокращение дроби p/q (здесь вам может пригодиться решение задачи «Наибольший общий делитель»).
- Если дробь p/q отрицательная, то объект Rational(p, q) должен иметь отрицательный числитель и положительный знаменатель.
- Если дробь p/q положительная, то объект Rational(p, q) должен иметь положительные числитель и знаменатель (обратите внимание на случай Rational(-2, -3)).
- Если числитель дроби равен нулю, то знаменатель должен быть равен 1.
Гарантируется, что ни в одном из тестов, на которых будет тестироваться ваша реализация, не будет знаменателя, равного нулю.
Часть 2
Реализуйте для класса Rational операторы ==, + и - так, чтобы операции с дробями можно было записывать естественным образом. Например, следующий код должен быть валидным:
| C++ | 1
2
3
4
| const Rational r = Rational(1, 2) + Rational(1, 3) - Rational(1, 4);
if (r == Rational(7, 12)) {
cout << "equal";
} |
|
Часть 3
Аналогично предыдущей части, реализуйте операторы * и /. Например, следующий код должен быть валидным:
| C++ | 1
2
3
4
| const Rational r = Rational(1, 2) * Rational(1, 3) / Rational(1, 4);
if (r == Rational(2, 3)) {
cout << "equal";
} |
|
Гарантируется, что ни в одном из тестов, на которых будет тестироваться ваша реализация, не будет выполняться деление на ноль.
Часть 4
В этой части вам нужно реализовать операторы ввода и вывода для класса Rational. В результате у вас должен работать, например, такой код
| C++ | 1
2
3
| Rational r;
cin >> r;
cout << r; |
|
Наконец, вам нужно реализовать возможность использовать объекты класса Rational в качестве элементов контейнера set и ключей в контейнере map. Пример:
| C++ | 1
2
3
4
5
6
| set<Rational> rationals;
rationals.insert(Rational(1, 2));
rationals.insert(Rational(1, 3));
map<Rational, string> name;
name[Rational(1, 2)] = "одна вторая"; |
|
Моё решение:
| C++ | 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
| #include <iostream>
#include <sstream>
#include <cmath>
#include <string>
#include <set>
#include <map>
#include <vector>
using namespace std;
class Rational {
public:
Rational() { //конструктор по умолчанию
num = 0;
den = 1;
}
Rational(int numerator, int denominator) {
if (denominator < 0) { //постепенно все проверяем, что пришло на вход
if (numerator < 0) { //и следим за отрицательностью
denominator = abs(denominator);
numerator = abs(numerator);
}
else {
denominator = abs(denominator);
numerator = numerator - 2 * numerator;
}
}
if (numerator == 0) {
denominator = 1;
}
//обязательно сократим дробь, если можно
int nod = Nod(abs(numerator), abs(denominator));
num = numerator / nod;
den = denominator / nod;
}
int Numerator() const { //тут просто возвращаем значения
return num;
}
int Denominator() const { //и тут
return den;
}
private:
int num; //поле числитель
int den; //и знаменатель
int Nod(int a, int b) { //маленькая функция нахождения делителя
while (a > 0 && b > 0) {
if (a > b) {
a %= b;
}
else {
b %= a;
}
} //while
return a + b;
}
};
bool operator == (Rational a, Rational b) { //перезгузим оператор ==
if ((a.Numerator() == b.Numerator()) && (a.Denominator() == b.Denominator())) {
return true;
}
else {
return false;
}
}
Rational operator + (Rational a, Rational b) { //и тут перегрузим, и дальше тоже
int p = (a.Numerator() * b.Denominator()) + (b.Numerator() * a.Denominator());
int q = a.Denominator() * b.Denominator();
return Rational(p, q);
}
Rational operator - (Rational a, Rational b) {
int p = (a.Numerator() * b.Denominator()) - (b.Numerator() * a.Denominator());
int q = a.Denominator() * b.Denominator();
return Rational(p, q);
}
Rational operator * (Rational a, Rational b) {
return Rational((a.Numerator()*b.Numerator()), (a.Denominator()*b.Denominator()));
}
Rational operator / (Rational a, Rational b) {
return Rational((a.Numerator() * b.Denominator()), (a.Denominator() * b.Numerator()));
}
istream& operator >> (istream& stream, Rational& r) { //возвращать будем ссылку на поток
//if (stream) return stream;
int p, q;
if (stream >> p && stream.ignore(1) && stream >> q) {
r = { p, q };
}
return stream;
}
ostream& operator << (ostream& stream, const Rational& r) {
stream << r.Numerator() << "/" << r.Denominator();
return stream;
}
bool operator > (Rational a, Rational b) {
return (a.Numerator() / (double)a.Denominator()) > double(b.Numerator() / (double)b.Denominator());
}
bool operator < (Rational a, Rational b) { //обязательно нужно это дело перегрузить, для использования в map и set
return (a.Numerator() / (double)a.Denominator()) < (b.Numerator() / (double)b.Denominator());
}
int main() {
{
const set<Rational> rs = { { 1, 2 },{ 1, 25 },{ 3, 4 },{ 3, 4 },{ 1, 2 } };
if (rs.size() != 3) {
cout << "Wrong amount of items in the set" << endl;
return 1;
}
vector<Rational> v;
for (auto x : rs) {
v.push_back(x);
}
if (v != vector<Rational>{ {1, 25}, { 1, 2 }, { 3, 4 }}) {
cout << "Rationals comparison works incorrectly" << endl;
return 2;
}
}
{
map<Rational, int> count;
++count[{1, 2}];
++count[{1, 2}];
++count[{2, 3}];
if (count.size() != 2) {
cout << "Wrong amount of items in the map" << endl;
return 3;
}
}
cout << "OK" << endl;
return 0;
} |
|
Писать комментарии долгое дело однако. Иногда неохота, иногда мало времени.
|