C++: пишем маленькие классы
Просто несколько учебных примеров на классы под общим лозунгом "КРТКСТ - СТ".
1. Переопределение операторов - удобная и красивая вещь, на неё в блоге есть и теория, и примеры. Тем не менее, вот ещё один маленький пример - научим программу складывать объекты класса c
.
В простых случаях достаточно, чтобы переопределённый оператор возвращал новый объект класса, тогда
будет работать и сложение "по цепочке" вида a+b+...
Аргумент слева от знака "+" доступен через указатель this
, справа - через ссылочный аргумент оператора b
:
#include <iostream> #include <cstring> using namespace std; class c { public: double val; c(double val = 0); c operator + (c&); }; c::c(double val) { this->val = val; } c c::operator + (c& b) { c d(this->val + b.val); return d; } int main(void) { c a(1), b(2), d(3); c* e = new c(); *e = a + b + d; cout << e->val; c f; f = a + b + d; cout << endl << f.val; cin.get(); return 0; }
Правильные теоретики также часто рекомендуют делать для бинарных операторов дружественные функции:
friend c operator + (const c & a, const c & b); //прототип в классе c operator + (const c& a, const c& b) { //реализация return c (a.x + b.x); }
2. Класс "Человек" с условием задачи:
- Свойства: Имя; Год рождения
- Метод(ы): Показать информацию; Показать текущий возраст в полных годах
- Конструктор(ы): С заданными свойствами
- Оператор(ы): < (достиг ли возраст указанного значения); += (изменить имя на новую строку)
Чтобы не заморачиваться с получением текущего года из <ctime>, сделаем его статическим членом класса, который, как положено, инициализируем вне всех функций и до main
.
Оператор "<" определим дважды, для возможности сравнения как с целым числом, так и с возрастом, взятым из другого экземпляра класса.
#include <iostream> #include <string> using namespace std; class c { string name; int age; //имя и год рождения public: static int year; //статическое св-во "текущий год" void show(); //показать информацию int getAge(); //показать возраст в годах c(string name = 0, int age = 0); c& operator += (string); //переопределить имя bool operator < (int); //достиг ли возраст значения }; int c::year = 2021; void c::show() { cout << endl << name << ", " << age; } int c::getAge() { return year - age + 1; } c::c(string name, int age) { this->name = name; this->age = age; } c& c::operator += (string b) { this->name = b; return *this; } bool c::operator < (int b) { return getAge() <= b; } int main(void) { c a("Ivanov", 2000); a.show(); string s("Petrov"); a += s; a.show(); bool b = a < 30; cout << endl << (b ? "1" : "0"); cout << endl; system("pause"); return 0; }
3. Класс "Стек из целых чисел" с условем задачи:
- Свойства: Размерность; Элементы
- Метод(ы): Добавить элемент в начало стека; Извлечь элемент из начала стека
- Конструктор(ы): С заданным количеством элементов
- Оператор(ы): ! (Текущее количество элементов в стеке)
Для удобства добавим в стек ещё и свойство "текущее количество элементов" current
.
Если элемент нельзя положить в стек из-за заполненности, не будем делать ничего (хотя можно было что-то возвращать из метода push
).
Если элемент нельзя извлечь потому, что стек пуст, будем возвращать INT_MAX
.
Тогда реализация окажется простой.
#include <iostream> #include <string> #include <climits> using namespace std; class c { int size; //размер int* items; //элементы int current; //текущая позиция public: void push(int); //положить элемент int pop(); //взять элемент c(int size = 2); ~c(); int operator !(void); //текущая емкость }; c::c(int size) { this->size = size; current = 0; items = new int[size]; } c::~c() { if (size > 0) delete [] items; } int c::operator !(void) { return current; } void c::push(int item) { if (current < size) items[current++] = item; } int c::pop(void) { return current > 0 ? items[--current] : INT_MAX; } int main(void) { c a(3); a.push(1); a.push(2); a.push(3); cout << a.pop() << "," << a.pop() << "," << a.pop(); //!!! a.push(4); cout << endl << !a; cout << endl; cin.get(); return 0; }
Страшное случится, когда мы это запустим. Вместо ожидаемого порядка извлечения "3, 2, 1" мы увидим на экране консоли:
1,2,3 0 Для продолжения нажмите любую клавишу . . .
Если заменить оператор, помеченный комментарием //!!!
на
cout << a.pop() << ","; cout << a.pop() << ","; cout << a.pop();
то всё будет нормально:
3,2,1 0 Для продолжения нажмите любую клавишу . . .
Такие вещи способны свести с ума, пока не осознаешь некоторых концепций:
- эта проблема в моём списке ошибок;
- секретная инфа о "точках следования" при выполнении программ;
- классическая тема "Лурки" об ++i + ++i (18+).
Коротко:
Нельзя рассчитывать на то, аргументы функции вычисляются слева направо.
Вызов функции, изменяющей некие величины, не нужно ставить в одном операторе с изменяемыми величинами!
4. Класс "Строка" с условием задачи:
- Свойства: Текст
- Метод(ы): Показать информацию; Очистить текст
- Конструктор(ы): По умолчанию; С заданным значением
- Оператор(ы): + (сцепление текстов любого количества строк); == (узнать текущую длину строки)
Так как здесь мы именно моделируем строку, готовый класс string
не уместен, но используется классический char *
.
Кроме текста строки, конечно, введём и свойство "размер". "Очищать" строку будем, просто зануляя первый байт. Как и в предыдущим примерах, для краткости не пишем явный деструктор и конструктор копирования.
Переопределённый оператор == не будет использовать свой правый аргумент, ну и что? Переопределённый постфиксный аргумент тоже не использует, но работает же :)
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> using namespace std; class c { int size; char *text; //длина и сам текст public: void show() { cout << endl << text; } //показать void clear() { size = 0; text[0] = '\0'; } //очистить c(int size = 0) { //конструктор по умолчанию this->size = size; if (size > 0) text = new char[size + 1]; else text = NULL; } c(const char* text) { //конструктор с заданным знач. int size = strlen(text); if (size) { this->text = new char[size + 1]; strcpy(this->text, text); } else this->text = NULL; } c operator + (c& b) { //сцепление любого кол-ва строк int len = strlen(this->text) + strlen(b.text); c d (len); strcpy (d.text, this->text); strcat (d.text, b.text); return d; //почему возвращаем объект стека, а не "кучи"? } int operator == (int b) { return strlen(text); }; //почему не стоит создавать здесь явный деструктор? }; int main(void) { c a("Stroka"), b("Esche"), d("i esche"); c e = a + b + d; e.show(); int f = (e == 0); cout << endl << f; cout << endl; cin.get(); return 0; }
Вариант, при котором код работает и при явном деструкторе с возвращением объекта из бинарного оператора (только изменённые и новые методы). Отличия в поведении компилятора объясняются тем, что в данном случае он вернёт новый объект через стек и не удалит его автоматически по выходе из области видимости переменной.
c(const char* text, const char* text2) { int len = strlen(text) + strlen(text2); if (len) { size = len; this->text = new char[size + 1]; strcpy(this->text, text); strcat(this->text, text2); } } c operator + (c& b) { //сцепление любого кол-ва строк return c(this->text,b.text); } ~c() { if (size && text) delete[] text; }
Я проверял это всё в QT, но и в Studio не должно быть разницы, кроме его склонности ругаться на "устаревшие" функции вроде strlen
. На самом деле, устарели не функции, а Microsoft, вынужденный латать стандарты в поисках совместимости с эпохой однобайтовых кодировок и восьмибитных игрушек :)
10.04.2017, 22:39 [3202 просмотра]