Объектно-ориентированное
программирование
на языке C++
Лабораторная работа № 1
Архитектура консольного приложения
Новосибирск, 2013
Введение
Что такое консольное приложение?
Консольное приложение отличается от оконного тем, что для взаимодействия с пользователем используется текстовый режим ввода и вывода информации. Подобные приложения чаще всего можно встретить в ОС Linux, а также в тех случаях, когда работа с программой строится на основе ввода команд. Как правило, это требует от пользователя высокой квалификации, поэтому в виде консольных приложений обычно реализуются различные утилиты, используемые разработчиками и системными администраторами. В рамках лабораторной работы консольный интерфейс – это прекрасная возможность изучения ООП и принципов проектирования архитектуры ПО без траты времени на создание оконного интерфейса.
В чем суть лабораторной работы №1?
В первой контрольной работе мы научились проектировать простые классы. На следующем шаге мы сталкиваемся с ситуацией, когда необходимо создать не один, а несколько объектов разработанного класса (что нередко бывает на практике). Например, не только описать класс «Фигура», но и динамически создать нужное количество объектов этого класса, данные в которых будут различаться. Над набором объектов мы можем делать различные операции: удалять, создавать новые, проходить по всем ним в цикле (например, для вывода на экран). Все эти операции, очевидно, не могут являться методами класса, объекты которого мы создаем. Фактически мы говорим о втором классе, отвечающем за хранение объектов первого, и предоставляющего ряд методов по работе с этими объектами. Условно назовем этот класс коллекцией, или каталогом. Отсюда, в первой лабораторной нам потребуется разработать как минимум два класса: один согласно варианту задания, второй – для хранения объектов первого.
Разумеется, при разработке архитектуры консольного приложения мы должны исходить из тех же принципов, как если бы мы разрабатывали оконное приложение. В первую очередь речь идет о четком разделении всех классов на два слоя:
1. Слой модели (model) – классы, описывающие предметную область, суть программы. В нашем случае это уже упомянутые два класса: объект и коллекция.
2. Слой представления (view) – классы, отвечающие за отображение информации пользователю, в графическом (GUI) или текстовом виде (консоль).
Слой модели не должен ничего «знать» о слое представления, тогда как представление, конечно же, оперирует объектами слоя модели. Иными словами, если мы разработали класс «Дата», то в его методах не должен осуществляться ввод с клавиатуры или вывод на экран – методы этого класса должны обеспечивать работу с данными объекта, реализовывать определенную логику. Используя эти методы мы можем получить из объекта данные и вывести их на экран – но это происходит в другом классе, отвечающим за отображение меню и пр.
Кроме задачи разделения модели и представления нам необходимо выдержать всю программу в ООП-стиле, т. е. следовать идее «все есть объект». Было бы не вполне правильно разместить внутри void main некий код, создающий объекты и вызывающий их методы. Это вторая причина, почему данный код должен быть реализован в виде третьего класса. Поэтому, внутри main мы напишем лишь две строчки: создание объекта и вызов его метода. Это будет объект класса, отвечающего за взаимодействие с пользователем, т. е. консольный интерфейс.
И наконец, для демонстрации работы программы в объектах должны лежать какие-либо данные. Каждый раз вводить их с клавиатуры – долго, а задавать значения прямо в коде настолько неправильно, что не стоит этого делать, даже не смотря на то, что это лабораторная работа. Поэтому, нам предстоит считать эти данные из файла.
Требования к работе (глазами пользователя)
Разрабатываемая программа представляет собой справочник (чего именно – определяется вариантом задания), т. е. коллекцию объектов, отображаемых в виде таблицы. В итоге, это может выглядеть примерно так (каждая запись в таблице – это объект класса):

Основные требования к работе программы следующие:
1. Все данные должны загружаться из текстового файла.
2. Операции добавления, удаления, редактирования записей реализовывать не нужно.
3. Необходимо просто отобразить записи в виде таблицы.
4. Необходимо каким-либо образом выделить те из них, которые удовлетворяют заданному критерию (см. вариант задания). Например, выделить те жесткие диски, на которых заканчивается место, или те автомобили, которые превышают скорость выше заданной. Способ выделения не принципиален: цвет текста, звездочка рядом с записью и т. п.
Требования к работе (глазами разработчика)
Рекомендуемая среда выполнения: MS Visual Studio 2012 или Qt Creator.
Тип проекта: C++ Console Application, поставить галочку «Empty project» (пустой проект), и добавить все необходимые файлы самостоятельно.
Структура проекта следующая (пример для класса Star):

Star. h – объявление класса Star.
Star. cpp – реализация методов класса Star.
StarCatalog. h – объявление класса StarCatalog, содержащего динамический массив объектов типа Star, а также загрузку данных из файла.
StarCatalog. cpp – реализация методов класса StarCatalog.
ConsoleWindow. h – объявление класса ConsoleWindow, содержащего объект класса StarCatalog, и отвечающего за консольный интерфейс (вывод на экран).
ConsoleWindow. cpp – реализация методов класса ConsoleWindow.
LabOne. cpp – файл содержит точку входа в программу (main).
data. txt – файл данных, где хранятся объекты.
Необходимо помнить следующее:
1. Каждый h-файл начинается с директивы #pragma once, защищающей от множественного включения данного файла.
2. В h-файле подключается как можно меньше библиотек.
3. В файле методов указывается include реализуемого h-файла.
4. Если одним из полей класса B является указатель на класс A, то существует два варианта разрешения ситуации (второй более предпочтителен):
a. #include “A. h”
b. class A* m_a вместо A* m_a;
5. Разумеется, весь код должен быть корректно форматирован, названия файлов, классов, переменных и методов быть адекватными, не содержать транслит. И главное: кода должно быть как можно меньше.
Оформление отчета
Отчет по работе оформляется для каждого участника бригады и содержит:
1. Титульный лист
2. Формулировка задания
3. Описание архитектуры программы, а также ее нетривиальных алгоритмов.
4. Скриншоты программы (инвертировать цвета: белый фон!)
5. Исходный код
Ход работы
Возможен (но не обязателен) приблизительно следующий набор шагов:
1. Начните с создания класса, отражающего предметную область (согласно варианту задания). В нем необходимо объявить поля данных (в ТЗ раздел «Данные») и методы работы с ними (см. «Методы»). Убедитесь, что помимо правильно подобранных названий, в классе выдержана инкапсуляция, т. е. все данные закрыты от прямого доступа, они инициализируются конструктором. Попробуйте в main создать один объект такого класса, затем массив объектов (используя new) – убедитесь, что методы работают. В классе должны быть методы, позволяющие получить значения полей, чтобы вывести эти данные на экран. Как вариант, это может быть один метод ToString, возвращающий строковое представление объекта.
2. Создайте класс-каталог, он должен содержать динамический массив указателей на объекты класса из п.1. В задании есть раздел «Отображение в таблице». Подумайте, в каком из двух классов должен быть метод, реализующий эту логику (в разных вариантах ответ различен).
3. Каталог должен уметь загружать данные из файла, см. в приложении код работы с файловыми потоками. Файл с данными добавляется в проект, также как файлы кода (Add new item), только тип файла другой: текстовый (txt). Его можно открыть тут же в Visual Studio, и вписать данные (пример формата см. в приложении). При открытии файлового потока путь до файла можно не указывать полностью, только его имя.
4. Какие еще методы должны быть у каталога? Доступ к объекту по номеру (индексу), ведь чуть позже нам понадобится пройтись по всем объектам каталога, чтобы вывести их на экран (это будет делать третий класс). Объект нужно возвращать по ссылке, не по значению!
5. Вернитесь в main и проведите еще один тест: создайте объект каталога. Вызовите у него метод загрузки из файла. В случае ошибок используйте пошаговую отладку. Попробуйте в цикле получить из каталога все объекты и вывести их на экран. Внешне программа работает почти как надо, осталось только перенести код из main в отдельный класс.
6. Создайте третий класс: ConsoleWindow (название условное). У него должен быть только один public-метод: Show. Его вызов запускает все остальное. Разумеется, одним из полей данных данного класса является объект класса-каталога. Что делает класс ConsoleWindow? Он хранит объект класса-каталога, который пока пуст. По вызову Show он для этого объекта вызывает метод загрузки данных из файла, а затем в цикле получает из него все объекты, и выводит их на экран. Слово std::cout встречается нам только в методах класса ConsoleWindow и нигде больше!
7. Добавьте в ConsoleWindow вызов метода, определяющего необходимость выделить данный объект (в задании «выделение в таблице»). Это либо метод самого объекта, тогда, выводя их в цикле, ConsoleWindow добавляет выделение, либо это метод каталога, и тогда перед циклом ConsoleWindow получает номер элемента, который надо выделить в таблице.
8. Теперь посмотрите код всех классов еще раз. Подумайте, где должны быть деструкторы, конструкторы копирования, убедитесь в правильности всех именований, аккуратном форматировании кода.
Содержимое main-файла должно быть примерно таким:
#include "ConsoleWindow. h"
int main()
{
ConsoleWindow wnd;
wnd. Show();
return 0;
}
Как прочитать данные из файла
У нас будет текстовый, а не двоичный формат файла, что позволяет легко редактировать файл формат хранения. В начале идет число – это количество объектов, которое записано в файле. Дальше поочередно идут данные по каждому объекту, одно поле – одна строка. Например, нужно сохранить 2 объекта типа «Звезда», у которых по 2 поля: название (строка) и расстояние от Земли (double). Файл data. txt будет выглядеть так:
2
Проксима Центавра
4.2
Минтака
900
В начале количество объектов, потом название первого, потом расстояние до него, затем название второго, и расстояние до него. Важно, чтобы в конце последней строчки не было перевода строки, это усложнит обработку файла!
А вот код чтения из этого файла:
bool StarCatalog::LoadFromFile(std::string fileName)
{
// Открываем поток на чтение
std::ifstream file(fileName);
// Ошибка? Выход
if (!file. is_open())
{
return false;
}
// Считываем число объектов
int count = 0;
file >> count;
// ...тут можно создать массив объектов
// для последующего их заполнения данными в цикле
//
std::string name;
double dist = 0.0;
// Пока не конец файла
while (!file. eof())
{
// Пропускаем символ завершения строки
file. ignore(1, '\n');
// Считываем полностью строку, включая пробелы
std::getline(file, name);
// Считываем вещественное число
file >> dist;
//... здесь мы можем записать все прочитанные данные в очередной объект
}
file. close();
return true;
}
Варианты заданий
1 | Устройство хранения информации |
Легенда: вы работаете над промышленной системой инвентаризации, для которой необходимо разработать класс, каждый объект которого будет содержать данные об устройстве хранения информации. | |
Данные: - емкость (в байтах) - объем занятой памяти (в байтах) - количество байт в мегабайте (константа, для операций перевода б./Мб.) | |
Методы: - изменить значение объема занятой памяти (это можно сделать свойством) - получить емкость (мегабайты) - получить занятый объем (мегабайты) - получить процент заполнения памяти | |
Отображение в таблице: - выделить цветом все устройства, заполненные более чем на 90%. | |
2 | Карточка фильма |
Легенда: одним из ключевых компонентов электронного каталога фильмов, разрабатываемой вашей командой, является класс информации о фильме. | |
Данные: - Название фильма - Режиссер - Год - Длительность - Жанр - Количество проголосовавших - Средний балл (рейтинг) | |
Методы: - добавить оценку (пересчитывается рейтинг) | |
Отображение в таблице: - выделить цветом все фильмы, рейтинг которых выше 8 (по 10 балльной шкале) | |
3 | Марсианский жилой комплекс |
Легенда: вы работаете в проекте по автоматизации разрабатываемых жилых комплексов, использование которых планируется при освоении ближайших планет. Для каждого жилого комплекса необходимо хранить и обрабатывать ряд данных. | |
Данные: - номер комплекса - количество жителей - количество запасов воздуха - расход воздуха на 1 человека в сутки (константа) | |
Методы: - получить оценку времени, на сколько суток этому комплексу хватит запасов воздуха | |
Отображение в таблице: - выделить цветом те жилые комплексы, у которых запаса воздуха менее чем на месяц. | |
4 | Авиарейс |
Легенда: вы – разработчики информационной системы для диспетчерской службы аэропорта. Необходимо хранить данные о каждом самолете, находящемся в воздухе. | |
Данные: - номер рейса - скорость движения (горизонтальная) - скорость снижения (вертикальная) - текущая высота - расстояние до аэропорта назначения | |
Методы: - вычислить, сколько времени потребуется на снижение (с текущей скоростью снижения, с текущей высоты) - вычислить, за какое время будет пройдена оставшаяся часть маршрута - определить, достаточна ли скорость снижения (для проверки метод использует предыдущие два, если же до завершения полета еще более 30 минут – возвращает true). | |
Отображение в таблице: - выделить цветом те рейсы, которым необходимо ускорить снижение (осталось менее 30 минут полета, а с текущей скоростью снижения они не успеют снизиться с текущей высоты). | |
5 | Орбитальный модуль |
Легенда: работая над автоматизацией в рамках космической программы, вы столкнулись с задачей учета орбитальных спутников. Необходимо хранить сведения о каждом из них в отдельном объекте. | |
Данные: - название спутника - масса спутника - высота орбиты - скорость (относительно Земли) - длина окружности Земли (константа, 40.000км) | |
Методы: - вычислить время 1 оборота спутника вокруг Земли | |
Отображение в таблице: - выделить цветом все неподвижные относительно Земли спутники | |
6 | Платежный терминал |
Легенда: вы – разработчики сети платежных терминалов. Необходимо разработать класс, каждый объект которого будет хранить данные об одном терминале. | |
Данные: - номер терминала - адрес установки - емкость купюроприемника (константа, например 10.000 банкнот) - наполненность купюроприемника (банкнот) | |
Методы: - узнать, на сколько процентов заполнен купюроприемник терминала | |
Отображение в таблице: - выделить цветом те терминалы, купюроприемники которых заполнены более чем на 90%. | |
7 | Мегаполис |
Легенда: получив заказ от центра статистических исследований на разработку единой базы данных, вам потребовалось разработать класс, каждый объект которого хранит необходимый минимум данных о населенном пункте. | |
Данные: - название города - год основания - количество жителей в данный момент | |
Методы: - вычислить средний прирост населения в год[1] | |
Отображение в таблице: - выделить цветом город с самым высоким темпом роста. | |
8 | Конфигурация компьютера |
Легенда: в рамках создания корпоративной системы инвентаризации, вам необходимо разработать класс, каждый объект которого будет хранить основные данные о компьютере. | |
Данные: - частота процессора - объем оперативной памяти - объем жесткого диска | |
Методы: - определить возможность установки ОС Win7 и Ubuntu (метод получает параметром, для какой из ОС сделать проверку, возвращает значение типа bool). | |
Отображение в таблице: - выделить разными цветами компьютеры, где возможна установка только одной, только второй, а также обеих ОС. | |
9 | Небесное тело |
Легенда: вы разрабатываете программу астрономических вычислений, и для хранения сведений об отдельных небесных телах необходимо разработать класс. | |
Данные: - название - расстояние от Земли (в световых годах) - яркость (в условных единицах) | |
Методы: - получить время полета для заданной скорости (параметр метода, пусть будет 0.9 скорости света) | |
Отображение в таблице: - выделить цветом все объекты, полет до которых укладывается в 10 лет | |
10 | Летающий объект |
Легенда: разрабатываемый вами класс позволяет хранить данные о различных объектах, передвигающихся по воздуху, техногенных (самолет, ракета) и природных (насекомые, птицы). Задача сравнить скорости в относительных единицах (длин в секунду). | |
Данные: - название - скорость (в м/с) - длина (в метрах) | |
Методы: - вычислить скорость в относительных единицах[2] | |
Отображение в таблице: - выделить цветом самого быстрого (в относительных единицах) «летуна» |
Контрольные вопросы
1. Исправьте ошибки в следующих фрагментах:
а) X << cout;
b) int *p = new int*[10];
c) class Date
{
Date x;
}
2. Исправьте ошибки в следующих фрагментах:
a) class Man
{
public:
void Man();
void ~Man();
};
b) class Fraction
{
int N;
int D;
public:
Fraction(Fraction copy);
~Fraction(void *p);
};
c) class Point
{
int x;
int y;
public:
…..
static void Print() { cout << x << «:» << y; }
};
3. Что такое строгая и статическая типизация?
4. Для чего используется typedef?
5. Что такое перечисления?
6. Какие классы памяти переменных вы знаете?
7. Что такое ссылки?
8. Как осуществляется работа с динамической памятью?
9. Что такое приоритет и ассоциативность операций?
10. Для чего нужны inline-функции?
11. Как для формальных аргументов задаются значения по умолчанию?
12. Что такое перегрузка функций?
13. Какие существуют правила перегрузки функций?
14. Можно ли перегружать методы класса и его конструктор?
15. Каков синтаксис объявления конструктора по умолчанию, с параметрами, копирования?
16. Сколько формальных параметров может быть у конструктора и деструктора? Какой тип возвращаемого ими значения?
17. Сколько конструкторов и деструкторов может быть в классе?
18. Что такое указатель this?
19. Что произойдет, если объявить поле данных класса как static?
20. Что такое static-методы?
21. Как происходит отделение интерфейса и реализации класса?
22. Может ли класс содержать указатель на объект собственного типа?
23. Что такое список инициализации?
24. Что такое константные методы?
25. Зачем нужны константные объекты?
26. Как инициализируются константные данные-члены класса?
27. Что такое дружественная функция и дружественный класс?
[1] Упрощенно: прирост в год = число жителей / (текущий год – год основания).
[2] Например, длина стрижа 0,18м, а скорость 170 км/ч (47 м/с),
относительная скорость: 47 / 0,18 ~ 260 длин тела в секунду.


