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=new c(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; system("pause"); 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; /* Вариант 5. Класс "Человек" */ 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); //достиг ли возраст значения bool operator < (c &); //сравнение возрастов 2 людей }; int c::year=2017; 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; } bool c::operator < (c &b) { return this->getAge()<b.getAge(); } int main(void) { c a("Ivanov",1980); a.show(); string s("Petrov"); a+=s; a.show(); bool b=a<30; cout << endl << (b ? "1" : "0"); c a2("Popov",1990); a2.show(); b=a<a2; cout << endl << (b ? "a<a2" : "a>=a2"); 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); int operator !(void); //текущая емкость }; c::c(int size) { this->size=size; current=0; items = new int[size]; } 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); a.push(4); //уже не добавится cout << a.pop() << "," << a.pop() << "," << a.pop(); //!!! cout << endl << !a; cout << endl; system("pause"); 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 *
.
Кроме текста строки, конечно, введём и свойство "размер". "Очищать" строку будем, просто зануляя первый байт. Как и в предыдущим примерах, для краткости не пишем явный деструктор и конструктор копирования.
Переопределённый оператор == не будет использовать свой правый аргумент, ну и что? Переопределённый постфиксный аргумент тоже не использует, но работает же :)
#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(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 = new c(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; system("pause"); return 0; }
Я проверял это всё в QT, но и в Studio не должно быть разницы, кроме его склонности ругаться на "устаревшие" функции вроде strlen
. На самом деле, устарели не функции, а Microsoft, вынужденный латать стандарты в поисках совместимости с эпохой однобайтовых кодировок и восьмибитных игрушек :)
10.04.2017, 22:39; рейтинг: 2382