From a9fb21923d90d27fc419ddfeebdbb6ddc5a88705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9A=D0=BE?= =?UTF-8?q?=D0=B7=D0=BB=D1=8E=D0=BA?= Date: Mon, 3 Apr 2023 01:08:30 +0300 Subject: [PATCH] =?UTF-8?q?lab03:=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- labs/lab03/README.md | 1055 +++++++++++++++++++++++++++++++ labs/lab03/assets/histogram.svg | 9 + labs/lab03/assets/marks.txt | 3 + 4 files changed, 1068 insertions(+), 1 deletion(-) create mode 100644 labs/lab03/README.md create mode 100644 labs/lab03/assets/histogram.svg create mode 100644 labs/lab03/assets/marks.txt diff --git a/README.md b/README.md index 44ad09b..5535491 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ 1. [Основы языка C++](labs/lab01) 2. [Система контроля версий Git](labs/lab02) -3. Структурирование программ +3. [Структурирование программ](labs/lab03) 4. Использование библиотек ## Ресурсы diff --git a/labs/lab03/README.md b/labs/lab03/README.md new file mode 100644 index 0000000..3957b59 --- /dev/null +++ b/labs/lab03/README.md @@ -0,0 +1,1055 @@ +# Структурирование программ + +## Цель работы + +1. Уметь определять структуры. +1. Уметь структурировать программу при помощи функций. +1. Уметь писать программы из нескольких единиц трансляции. +1. Уметь писать модульные тесты. + +## Задание + +Работа ведется на основе кода лабораторной работы № 1 для своего варианта. + +1. Структурировать программу при помощи функций: + + * Определить структуру `Input` для хранения исходных данных. + * Вынести ввод данных в функцию `input_data()`. + * Вынести поиск минимума и максимума в функцию `find_minmax()`. + * Вынести расчет количества чисел в корзинах в функцию `make_histogram()`. + * Вынести отображение гистограммы в функцию `show_histogram_text()`. + +1. Разделить программу на единицы трансляции: + + * `main.cpp`: основная программа; + * `histogram.cpp`: функции для расчетов; + * `text.cpp`: отображение гистограммы в виде текста. + +1. Написать программу с модульными тестами функции `find_minmax()`. + +1. Перевести программу на отображение гистограммы в формате SVG. + +В результате должно быть две программы с частично общим кодом. +Основная программа при запуске без параметров работает так же, как ЛР № 1, +но выводит гистограмму в формате SVG. +Вторая программа выполняет модульные тесты функции `find_minmax()`. + +Код должен быть загружен в репозитарий `cs-lab34`. +Начальный коммит должен содержать код ЛР № 1 без изменений. +Должны быть коммиты, фиксирующие выполнение пунктов задания, +с номером и описанием пункта в первой строке сообщения к коммиту. +Можно делать больше промежуточных коммитов на свое усмотрение. + +Отчета не нужно. + +## Указания к выполнению + +### 1. Импорт кода ЛР № 1 в Git + +1. В каталоге с файлами ЛР № 1 инициализируйте репозитарий Git. +1. Настройте свое имя пользователя и почту МЭИ. +1. Закоммитьте файл исходного кода (`*.cpp`) и проекта (`*.cbp` для CodeBlocks). +1. Настройте игнорирование артефактов сборки (`bin/` и `obj/` для CodeBlocks). +1. Создайте пустой репозитарий `cs-lab34` на Git УИТ. +1. Загрузите код на сервер (при необходимости настройте доступ по SSH). + +### 2. Структурирование программы при помощи функций + +Программа для построения гистограммы из ЛР № 1 состоит из одной функции +`main()` на более чем 100 строк, из-за чего в ней неудобно ориентироваться. + +#### Структура для входных данных + +Входные данные включают вектор чисел и количество корзин: + +```cpp +struct Input { + vector numbers; + size_t bin_count{}; +}; +``` + +Если ваш вариант требует ввода дополнительных данных, добавьте поля для них. + +Поля примитивных типов (не `vector` и не `string`) рекомендуется +инициализировать нулевыми значениями, как это сделано для `bin_count`. +Тогда при объявлении переменной типа `Input` без инициализации +у полей будут удобные нулевые значения по умолчанию. + +#### Функция ввода данных + +Код для ввода данных в базовом варианте: + +``` cpp +size_t number_count; +cin >> number_count; + +vector numbers(number_count); +for (size_t i = 0; i < number_count; i++) { + cin >> numbers[i]; +} + +size_t bin_count; +cin >> bin_count; +``` + +Требуется вынести его в функцию `input_data()`, +чтобы в `main()` вместо строк выше осталась одна: + +```cpp +Input in = input_data(); +``` + +Функция не принимает параметров, а возвращает структуру `Input`: + +```cpp +Input +input_data() { +``` + +Предлагается в ЛР писать тип возвращаемого значения на отдельной строке. +В общем случае это регламентируется стилем кодирования в проекте. + +Количество чисел не входит напрямую в `Input` — входит вектор чисел, +который уже несет в себе количество элементов. +Тем не менее, ввод количества нужен для того, чтобы задать размер вектора. +Поэтому `number_count` объявляется как локальная переменная +и вводится так же, как вводилась в функции `main()`: + +```cpp + size_t number_count; + cin >> number_count; +``` + +Далее вводятся те данные, которые нужно будет возвращать из функции. +Это поля структуры `Input`, поэтому нужно объявить переменную-экземпляр: + +```cpp + Input in; +``` + +По умолчанию вектор чисел `in.numbers` инициализируется пустым, +а количество корзин `in.bin_count` инициализируется нулем. +Нужно изменить размер вектора на введенный: + +```cpp + in.numbers.resize(number_count); +``` + +Элементы `in.numbers` вводятся так же, +как элементы `numbers` вводились в `main()`: + +```cpp + for (size_t i = 0; i < number_count; i++) { + cin >> in.numbers[i]; + } +``` + +**Самостоятельно.** +Напишите ввод `in.bin_count` и дополнительных данных для своего варианта. + +В конце функции нужно вернуть результат: + +```cpp + return in; +} +``` + +На этом этапе структура кода должна получаться такой: + +``` cpp +#include ... + +struct Input { + ... +}; + +Input +input_data() { + ... +} + +int +main() { + Input in = input_data(); + ... +} +``` + +#### Функция поиска минимума и максимума + +При поиске минимума и максимума результатов два. +Оператор `return` не позволяет вернуть два значения, +если только они не упакованы в одно составное, например, в структуру. +Однако для тренировки используем выходные параметры: + +``` cpp +double min, max; +find_minmax(in.numbers, min, max); +``` + +Нужно сделать так, чтобы этот код работал следующим образом: в теле функции +`find_minmax()` изменялись бы аргументы `min` и `max`, и эти изменения +должны отразиться на переменных `min` и `max` в функции `main()`. + +Предположим, функция определена так: + +``` cpp +void +find_minmax(vector numbers, double min, double max) { + min = numbers[0]; + // (здесь код поиска минимума и максимума) +} +``` + +Желаемого поведения не будет: параметры `min` и `max` в функции — это отдельные +переменные, хотя они и называются так же, как переменные в `main()`. Когда +функция `find_minmax()` вызывается, параметры `min` и `max` получают *значения*, +которым были равны второй и третий аргумент (говорят: передаются по значению). +Любые изменения `min` и `max` в теле функции не отразятся на аргументах. + +Чтобы из `find_minmax()` менять внешние переменные, +можно использовать указатели или ссылки. +С точки зрения организации выходных параметров, указатели отличаются тем, +что у них есть особое значение, нулевой указатель (`nullptr`/`0`/`NULL`/`{}`). +Однако в данном случае мы не заинтересованы в том, чтобы такое значение было: +функция всегда ищет минимум и максимум и всегда должна мочь их записать. +Используем ссылки (см. амперсанды после `double`): + +``` cpp +void +find_minmax(vector numbers, double& min, double& max) { + min = numbers[0]; + // (здесь код поиска минимума и максимума) +} +``` + +Выделите `find_minmax()`, замените код в `main()` её вызовом. +Добейтесь компиляции программы и проверьте её работу. + +Оптимально ли выбран тип входного параметра `numbers`? +Во-первых, изменять его не требуется, он может быть константным. +Во-вторых, поскольку он не объявлен ссылкой или указателем, +это отдельная переменная, которая получает значением копию вектора, +который передается в функцию. +Однако копия не нужна. +Итого выгоднее использовать ссылку, чтобы не было копирования, +но константную, чтобы пользователь функции (программист) понимал, +что она не изменяет этот вектор: `const vector& numbers`. + +Измените тип параметра `numbers` функции `find_minmax()`. +Проверьте, что программа компилируется, а ее поведение не изменилось. + +#### Функция расчета гистограммы + +Функция `make_histogram()` принимает вектор чисел и количество корзин. +Она возвращает количества чисел в корзинах, то есть вектор `bins`. +В своей работе она вызывает `find_minmax()`. + +#### Прочие функции + +В итоге структура кода в `main()` должна получиться такой: + +```cpp +int +main() { + auto in = input_data(); + auto bins = make_histogram(in.numbers, in.bin_count); + show_histogram_text(bins, ...); +} +``` + +В `show_histogram_text()` может понадобиться передать дополнительные параметры +из входных данных, если это требуется согласно варианту. + +Добейтесь корректной работы программы и сделайте коммит. + +### 4. Разделение программы на файлы + +Хорошей практикой является отделять часть программы, которая выполняет расчеты, +от частей, которые занимаются вводом и выводом. +Разделим программу на файлы так: + +``` + +-----------------------------+ + | histogram.h | + +-----------------------------+ + | объявление make_histogram() | + +-----------------------------+ + ↑ ↑ + включает файл, включает файл, + вызывает функцию реализует функцию + ↑ ↑ ++------------------+ +------------------+ +| main.cpp | | histogram.cpp | ++------------------+ +------------------+ +| вызов | | определение | +| make_histogram() | | make_histogram() | ++------------------+ +------------------+ +``` + +#### Создание заголовочного файла + +При помощи меню *File → New → File...* +добавьте к проекту заголовочный файл *(C/C++ header).* +Он должен быть расположен в каталоге проекта: + +*Filename with full path:* `C:\cs-lab34\histogram.h` \ +*Add file to active project:* (отмечена) + +**Примечание.** +Здесь и далее каталог проекта обозначен как `C:\cs-lab34`. +Всюду нужно заменять его на тот, который реально используется. + +Заготовка файла включает «стража включения» (см. лекцию): + +```cpp +#ifndef HISTOGRAM_H_INCLUDED +#define HISTOGRAM_H_INCLUDED + +#endif // HISTOGRAM_H_INCLUDED +``` + +Можно заменить его на более простой вариант `#pragma once` +или писать код в этом файле между `#define` и `#endif`. + +Добавьте в этот файл объявление `make_histogram()`: + +```cpp +#include + +std::vector +make_histogram(const std::vector& numbers, size_t bin_count); +``` + +В заголовочных файлах не рекомендуется писать `using namespace std;`, +потому что в файле реализации, куда этот заголовочный файл подключается, +нет возможности отменить эту директиву, а она нужна на всегда. +Поэтому в примере `std::vector` используется с указанием пространства имен. + +#### Создание файла реализации + +1. При помощи меню *File → New → File...* + добавьте к проекту файл реализации *(C/C++ source).* + Необходимые настройки: + + *Filename with full path:* `C:\cs-lab34\histogram.cpp` \ + *Add file to active project in build target(s)*: + нажать *All*, либо проставить флажки в пунктах *Debug* и *Release* + +1. Перенесите определения `find_minmax()` и `make_histogram()` + в `histogram.cpp`. + +1. Сделайте функцию `find_minmax()` статической, + так как она не нужна за пределами `histogram.cpp`. + +1. Подключите `histogram.h` в `main.cpp` и `histogram.cpp`: + + ```cpp + #include "histogram.h" + ``` + + Нужно использовать двойные кавычки, так как файл находится + не в стандартном каталоге, а ищется относительно файла `*.cpp`. + +1. Убедитесь, что проект собирается и запускается. + +**Самостоятельно.** +Выделите `show_histogram_text()` в `text.h` и `text.cpp`. + +### 5. Модульные тесты + +Ранее для тестирования программы применялось перенаправление ввода и вывода. +Такое тестирование называется функциональным. +Однако функционального тестирования недостаточно для больших программ. +Предположим, большая программа перестала выдавать правильный результат — +как найти место ошибки? +Необязательно это последнее изменение: возможно, другая часть и ранее работала +неверно в некоторых случаях, и последняя правка лишь обнажила проблему. + +Модульное тестирование (unit testing) проверяет не работу всей программы, +а работу отдельных ее компонент, например, отдельных функций. +Модульные тесты пишутся программистами для собственного кода. +Функциональные тесты могут писаться или проводится вручную другими специалистами. + +Модульный тест — это отдельная программа, которая изолированно проверяет +части кода основной программы. +Если желательно протестировать части сложного алгоритма, +эти части должны быть оформлены в виде отдельных функций +(говорят: код должен быть *тестируемым).* + +#### Как сделать код тестируемым? + +Функция `find_minmax()`, которую нужно протестировать, скрыта в `histogram.cpp`. +Даже если разместить в другом файле её объявление, компоновка не прошла бы, +потому что эта функция статическая. +Это довольно типичная ситуация, когда желательно протестировать функции, +которые нет смысла описывать в интерфейсе модуля. +Она означает, что модуль достаточно сложный, +и имеет смысл выделить его части в подмодули. +С другой стороны, если перенести `find_minmax()` в отдельный файл, +читать код станет только сложнее — придется переходить по файлам. +Поэтому можно только объявление тестируемой функции описать в отдельном файле. +Подключив его, к ней можно будет обращаться из теста, +а сама функция будет в `histogram.cpp`, чтобы код оставался понятным. + +1. Сделайте функцию `find_minmax()` нестатической. + +1. Добавьте файл `histogram_internal.h` с объявлением `find_minmax()`. + +#### Создание проекта для модульных тестов + +1. При помощи меню *File → New → Project...* + создайте новый проект типа *Empty project.* + + **Важно.** Не выбирайте *Console application,* иначе содержимое `main.cpp` + будет заменено на пустую заготовку программы. + + Необходимые настройки: + + *Project title:* `unittest` \ + *Folder to create project in:* `C:\cs-lab34` \ + *Project filename:* `unittest.cbp` \ + *Resulting filename:* `C:\cs-lab34\unittest.cbp` + + В последнем параметре подкаталога `unittest` быть не должно. + +1. Дважды щелкните по проекту `unittest`, чтобы сделать его активным. + +1. Из контекстного меню проекта `unittest` выберите пункт *Add files...* + и добавьте к проекту `histogram_internal.h` и `histogram.cpp`. + В открывшемся диалоге *Select the targets this file should belong to:* + проставьте все флажки. + +1. При помощи меню *File → New → File...* добавьте к проекту `unittest` + файл реализации *(C/C++ source)* `unittest.cpp`. + Необходимые настройки: + + *Filename with full path:* `C:\cs-lab34\unittest.cpp` \ + *Add file to active project in build target(s)* — нажать *All*, + либо проставить флажки в пунктах *Debug* и *Release* + +#### Подключение библиотеки для модульного тестирования + +Модульные тесты представляют собой проверки утверждений о том, +как ведет себе код при различных условиях. +Технически это набор коротких функций, каждая их которых: + +1. Готовит условия, например, формирует исходные данные: + + ```cpp + vector numbers{1, 2}; + ``` + +2. Вызывает тестируемый код, например: + + ```cpp + double min, max; + find_minmax(numbers, min, max); + ``` + +3. Выполняет ряд проверок, например: + + ```cpp + if (min != 1) { puts("min != 1"); } + if (max != 2) { puts("max != 2"); } + ``` + +Важно, чтобы последний пункт в случае ошибок давал внятную диагностику: +в каком тесте какая проверка не прошла, какое значение было ожидаемым, +какое актуальное значение выдала функция. +Печатать все эти сведения вручную для каждого теста накладно и чревато ошибками. +Поэтому модульные тесты пишут с использованием библиотек. + +Мы предлагаем использовать библиотеку `doctest`, +потому что её очень просто подключить к проекту — это один заголовочный файл. +На практике самой популярной является Google Test, но её сложнее подключать. +В целом, все такие библиотеки похожи, но отличаются продвинутыми возможностями. + +Загрузите [doctest.h][doctest/doctest.h] и сохраните в каталоге проекта. +Закоммитьте этот файл. + +[doctest/doctest.h]: https://raw.githubusercontent.com/doctest/doctest/master/doctest/doctest.h + +#### Написание модульных тестов + +Напишем тест для простейшего случая в файле `unittest.cpp`: + +```cpp +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include "doctest.h" +#include "histogram_internal.h" + +TEST_CASE("distinct positive numbers") { + double min = 0; + double max = 0; + find_minmax({1, 2}, min, max); + CHECK(min == 1); + CHECK(max == 2); +} +``` + +Функцию `main()` писать не нужно — doctest автоматически определит её. + +Каждый тест должен определяться через макрос `TEST_CASE()`, как показано выше. +В скобках описывается смысл теста, doctest будет показывать его в сообщениях. +Тест всегда включает один или несколько вызовов макроса `CHECK()`. +При ошибках doctest покажет, какая из проверок не сработала, +и всю дополнительную информацию. +[Вводная документация doctest.][doctest/tutorial] + +[doctest/tutorial]: https://github.com/doctest/doctest/blob/master/doc/markdown/tutorial.md + +Соберите проект модульных тестов, запустите его +и убедитесь, что он работает без ошибок. + +Проверим, что будет, если тест выявляет ошибку. +Замените `CHECK(min == 1)` на `CHECK(min == 3)`, +соберите проект и запустите его. +Верните код к правильному варианту. + +**Самостоятельно.** +Придумайте больше тестовых случаев и напишите для них тесты. +В частности, можно проверить случаи: + +* пустого вектора +* вектора с одним элементом +* вектора отрицательных элементов +* вектора из одинаковых элементов + +### 6. Вывод гистограммы как изображения в формате SVG + +Требуется вместо текстовой гистограммы рисовать картинку, например: + +![Пример изображения-результата.](assets/histogram.svg) + +Это изображение описывается таким текстом, который должна выводить программа: + +```xml + + + 2 + + 5 + + 3 + + +``` + +Первые две строки и последняя строка — фиксированные. +Строки с отступом — это подписи к столбцам (элементы `text`) +и столбцы в виде цветных прямоугольников (**rect**angle, элементы `rect`). +На каждый столбец программа должна печатать строку ` Текст **с полужирными** словами и [ссылкой](#) в никуда. + +Бразуер отображает его из следующего фрагмента разметки +(язык разметки называется HTML, hyper-text markup language): + +```html +

Текст с полужирными словами и ссылкой в никуда.

+``` + +Всегда можно нажать *Ctrl+U* и увидеть исходный код страницы (разметку). + +SVG работает так же, только разметка описывает не текст с оформлением, +а изображение. HTML указывает, как отображать определенные блоки текста, +а SVG указывает, какие геометрические фигуры нарисовать. + +Если щелкнуть правой кнопкой мыши по изображению выше +и выбрать «Показать изображение», оно откроется отдельно. +Тем же *Ctrl+U* можно посмотреть код разметки гистограммы, +который в итоге будет выводить программа. + +Если если программа будет печатать размету SVG вместо звездочек, +можно перенаправить вывод программы в файл с расширением `*.svg` +и открыть его браузером — будет нарисовано изображение. +В случае ошибки оно нарисовано не будет. +В этом случае можно *Ctrl+U* посмотреть, что программа вывела не так. +Как правило, в окне кода, которое открывается по *Ctrl+U,* +красным цветом выделяются синтаксические ошибки, то есть некорректная разметка. + +## Формат SVG + +Разберем заголовок SVG: + +```xml + + +``` + +Так описывается изображение размером 300×100 точек. + +Система координат SVG отличается от математической: +ось Y направлена вниз, то есть точка *(0,0)* находится в верхнем левом углу. +Координаты в SVG действительные, то есть может быть точка *(0.5, 3.14).* + +Фрагменты вида ``, `` или `` называются *элементами.* +Параметры вида `height='300'` называются *атрибутами* элементов. +Элементы имеют открывающий тэг (``), закрывающий тэг (``) +с косой чертой и содержимое между ними (`содержимое`). +Если содержимого нет, то тэг может быть сразу закрыт: ``. + +Подробнее об SVG можно прочитать +[в спецификации][https://developer.mozilla.org/en-US/docs/Web/SVG/Element], +но все нужные для ЛР сведения даны в этих указаниях. + +SVG поддерживает множество элементов, среди них: + +* `anything you want`: + текст «anything you want», левый нижний угол которого в точке *(20,35);* +* ``: + прямоугольник 100×200 с верхним левым углом в точке *(0,0)*. + +#### Цикл отладки функций вывода SVG + +Вывод отдельных элементов SVG (заголовка, окончания, текста, прямоугольника) +стоит оформить в виде функций, который будут принимать параметры геометрии +и печатать соответствующий текст. +Функция `show_histogram_svg()` будет выводить гистограмму в формате SVG. +Функции для работы с SVG рекомендуется выделить в модуль `svg.h`/`svg.cpp`. + +Отработаем цикл модификации и проверки программы. +Функции вывода заголовка и окончания SVG: + +``` cpp +void +svg_begin(double width, double height) { + cout << "\n"; + cout << "\n"; +} + +void +svg_end() { + cout << "\n"; +} +``` + +Обратите внимание на пробелы в строках, например, перед закрывающей кавычкой +во фрагменте `"viewBox='0 0 "`. +Они необходимы между атрибутами. +Также обратите внимание на использование одинарных кавычек. +Двойные кавычки ограничивают в C++ строковые литералы; +одинарные кавычки внутри них выводятся «как есть» в результирующую строку. +Путаница кавычек или отсутствие некоторых пробелов сделает SVG некорректным. + +Пусть начальная реализация графического вывода гистограммы всегда выводит +пустое изображение фиксированного размера: + +``` cpp +void +show_histogram_svg(const vector& bins) { + svg_begin(400, 300); + svg_end(); +} +``` + +Заменим вызов `show_histogram_text(bins)` вызовом `show_histogram_svg(bins)`. + +Создать файл изображения можно так +(`marks.txt` можно [скачать](assets/marks.txt) или создать): + +``` sh +C:\cs-lab34\bin\Debug> cs-lab34.exe marks.svg +``` + +Эту команду нужно вводить в консоли, открытой в папке с exe-файлом проекта. +Если проект называется не `cs-lab34`, название файла нужно написать свое. +Просмотреть, какие файлы есть в текущем каталоге, можно командой `dir`. + +Чтобы быстро просмотреть из консоли содержимое `marks.svg` (разметку), +можно использовать команду: + +```sh +C:\cs-lab34\bin\Debug> type marks.svg +``` + +В браузере `marks.svg` открывается из меню *Файл → Открыть...* +Отобразится пустая страница (пустой рисунок). +Можно нажать *Ctrl+U* или пункт *«Исходный код страницы»* +в контекстном меню любого места страницы, чтобы увидеть результирующий код SVG +(контекстное меню — это то, что появляется по щелчку +правой кнопкой мыши в любом пустом месте страницы). + +#### Функции вывода элементов SVG + +Для вывода подписей к столбцам напишем функцию вывода текста в SVG, которая +принимает координату по горизонтали (`left`), координату нижнего края текста +по вертикали (`baseline`) и сам текст: + +``` cpp +void svg_text(double left, double baseline, string text); +``` + +Из введения в SVG выше известно, +что она должна выводить строку такого формата: + +``` xml +anything you want +``` + +На C++ точно такую строку можно вывести следующим образом (`svg_text()`) +должна размещаться выше `show_histogram_svg()`: + +``` cpp +void +svg_text(double left, double baseline, string text) { + cout << "anything you want"; +} +``` + +Чтобы вместо координаты `x` (числа 20) выводить значение `left`: + +``` cpp + cout << "anything you want"; +``` + +Обратите внимание на то, что сохранены одинарные кавычки внутри строк C++ +(в двойных кавычках) — они необходимы по правилам SVG. + +**Самостоятельно.** +Закончите реализацию `svg_text()`, +чтобы подставлять значение координаты `baseline` и текст надписи `text`. + +Для проверки выведем высоту первого столбца гистограммы: + +``` cpp +void +show_histogram_svg(const vector& bins) { + svg_begin(400, 300); + svg_text(20, 20, to_string(bins[0])); + svg_end(); +} +``` + +Функция `to_string()` преобразует значения разных типов в строки. + +Чтобы проверить модифицированную программу, её нужно запустить повторно: + +``` sh +C:\cs-lab34\bin\Debug> cs-lab34.exe marks.svg +``` + +После повторного запуска программы +открытый в браузере файл можно обновить клавишей *F5.* +На странице должен быть виден текст «2». +Если в файле ошибка, он не отобразится. +В этом случае нужно просмотреть код страницы на предмет ошибок в SVG, +исправить код программы, перезапустить ее и проверить результат. + +**Самостоятельно.** +Напишите функцию для вывода прямоугольника в SVG: + +``` cpp +void svg_rect(double x, double y, double width, double height); +``` + +Для проверки выведем первый столбец гистограммы справа от подписи к нему: + +``` cpp + svg_rect(50, 0, bins[0] * 10, 30); +``` + +Убедитесь, что вывод первого столбца гистограммы работает. + +#### Исключение «магических констант» + +Текст выводится в координатах *(20, 20)*, смещение левого края столбца *50* +(оно же — ширина подписей), а высота столбца *30.* +Эти числа подобраны так, чтобы элементы гистограммы не накладывались друг на друга, +но они разбросаны по коду и их смысл неясен. + +Прямо внутри функции `show_histogram_svg()` стоит завести константы, +чтобы пользоваться ими при выводе: + +``` cpp +const auto IMAGE_WIDTH = 400; +const auto IMAGE_HEIGHT = 300; +const auto TEXT_LEFT = 20; +const auto TEXT_BASELINE = 20; +const auto TEXT_WIDTH = 50; +const auto BIN_HEIGHT = 30; +const auto BLOCK_WIDTH = 10; +``` + +#### Вывод гистограммы + +Логика вывода гистограммы следующая: каждая корзина выводится так же, +как первая, но к вертикальной координате добавляется смещение — высота столбца: + +``` cpp +double top = 0; +for (size_t bin : bins) { + const double bin_width = BLOCK_WIDTH * bin; + svg_text(TEXT_LEFT, top + TEXT_BASELINE, to_string(bin)); + svg_rect(TEXT_WIDTH, top, bin_width, BIN_HEIGHT); + top += BIN_HEIGHT; +} +``` + +### Оформление гистограммы. Значения параметров по умолчанию + +Черная гистограмма не слишком эстетична или экономична при печати. +За цвет линий в SVG отвечает атрибут `stroke`, а за цвет заливки — `fill`. +Можно задать один из [стандартных цветов][svg/colors] или выбрать цвет +в формате `#RRGGBB` из [палитры][svg/rgb]. Пример прямоугольника с красными +границами и бледно-розовой заливкой: + +``` xml + +``` + +[svg/colors]: https://www.december.com/html/spec/colorsvghex.html +[svg/rgb]: http://getcolor.ru + +Функция `svg_rect()` может быть доработана для указания цвета линий и заливки: + + ``` cpp + void svg_rect(double x, double y, double width, double height, + string stroke, string fill); + ``` + +**Самостоятельно.** +Измените цвета вывода и проверьте работу программы. + +#### Значения параметров по умолчанию + +Цвета элементов нужно задавать не всегда. +В текущей реализации у функции `svg_rect()` шесть параметров, +два из которых отвечают за цвет. +Можно было бы перегрузить функцию: + +``` cpp +void svg_rect(double x, double y, double width, double height); + +void svg_rect(double x, double y, double width, double height, + string stroke, string fill); +``` + +Однако в этом случае пришлось бы в каждой функции писать похожий код. +В данном случае выгоднее применить значения параметров по умолчанию: + +``` cpp +void svg_rect(double x, double y, double width, double height, + string stroke = "black", string fill = "black"); +``` + +Значения по умолчанию указываются только один раз в объявлении функции +(если объявление и определение отделены). +Новая версия `svg_rect()` работает так: + +``` cpp +svg_rect(0, 0, 100, 200); // svg_rect(0, 0, 100, 200, "black", "black"); +svg_rect(0, 0, 100, 200, "red"); // svg_rect(0, 0, 100, 200, "red", "black"); +svg_rect(0, 0, 100, 200, "blue", "#aaffaa"); +``` + +**Самостоятельно.** +Реализуйте для гистограммы в SVG масштабирование, +как было сделано для текста в ЛР № 1. + +## Индивидуальные задания + +Результат выполнения нужно оформить отдельным коммитом на GitHub. +Доработку следует делать с использованием функции, для который нужно добавить +unit-тесты из не менее двух существенно отличающихся случаев. + +**Пример 1.** +По заданию нужно вместо запросить у пользователя цвет заливки столбцов, +а если введен красный, отказаться его использовать и запросить повторно. +Можно выделить функцию `bool check_color(string color)`, которая принимает +введенную пользователем строку цвета и возвращает `true`, если цвет +подходящий (не красный). Для нее можно придумать положительный тест, +что `check_color("blue") == true` и отрицательный, что `check_color("red") == +false`. + +**Пример 2.** +По заданию требуется добавить гистограмме заголовок *Гистограмма* по центру. +Можно выделить функцию, которая принимает ширину и высоту изображения +и вычисляет координаты надписи. Тестовые случаи — горизонтальное +и вертикальное изображение (ширина больше или меньше высоты соответственно). + +В вариантах примеры приведены для текстовых гистограмм, +но решение должно выдавать SVG. + +#### Вариант 1 + +Дайте пользователю возможность задавать произвольную ширину столбца +гистограммы вместо 400. Считайте некорректной ширину менее 70, более 800 +или менее трети количества чисел, умноженных на ширину блока (`BLOCK_WIDTH`) — +предлагайте пользователю ввести ширину заново с указанием причины. + +#### Вариант 2 + +Задавать автоматически яркость заливки каждого столбца гистограммы в градациях +серого в зависимости от высоты столбца. Чем больше столбец, тем темнее +заливка. + +Сделать это можно, передавая цвет в параметр fill в формате `"#RGB"` ([red, +green, blue](http://htmlbook.ru/html/value/color)). `"#111"` — самый темный, +`"#222"` — чуть менее темный, ..., `"#EEE"` — практически белый, `"#FFF"` — +белый. В лабораторной работе использовать диапазон цветов от `"#111"` +для самого большого столбца до `"#999"` для самого маленького столбца. +Поскольку используются градации серого, расчет сводится к вычислению только +одного значения и дублированию этого значения в качестве цвета каждого +из каналов (Red, Green, Blue). Для расчета цвета *i*-го столбца `bins[i]` +использовать формулу `(10 - (bins[i] * 9) / max_count)`. По ней мы получаем +значение цвета одного канала (от 1 до 9), который затем записываем три раза. + +Пример с текстом вместо SVG: + +`1|``▮` — цвет `#999` \ +`5|``▮▮▮▮▮` — цвет `#111` \ +`3|``▮▮▮` — цвет `#555` + +#### Вариант 3 + +Измените высоту изображения `IMAGE_HEIGHT = 700`. Дайте пользователю +возможность задавать высоту столбца гистограммы. Если итоговая высота +гистограммы больше `IMAGE_HEIGHT`, рассчитывать высоту столбца как +`(IMAGE_HEIGHT / bins_count)`. + +#### Вариант 4 + +Подсчитайте процент элементов, попавших в столбец, как целое двузначное число +с `%` в конце и отображайте этот процент после столбца гистограммы +с выравниванием: + +``` + 3|*** 25% + 5|***** 42% + 4|**** 33% +``` + +#### Вариант 5 + +Отображайте гистограмму зеркально по аналогии с заданием этого варианта +в лабораторной работе № 1. + +#### Вариант 6 + +После запроса количества столбцов запросите цвет для каждого столбца. + +#### Вариант 7 + +Вычислите среднюю высоту столбца. Если столбец выше, цвет столбца должен быть +красным, если ниже или равен средней высоте, цвет зеленый. + +#### Вариант 8 + +Запрашивать у пользователя размер шрифта. За размер шрифта отвечает атрибут +`font-size`. Считать 12 значением по умолчанию. Не позволять вводить +значения менее 8 и более 32. В этом случае предлагайте пользователю ввести +значение заново с указанием причины. + +#### Вариант 9 + +Запросите у пользователя ширину одного «блока» гистограммы `BLOCK_WIDTH`. +Не позволяйте вводить ширину блока менее 3px и более 30px. В этом случае +предлагайте пользователю ввести ее заново с указанием причины. + +#### Вариант 10 + +Отображайте гистограмму вертикально, с подписями сверху, по аналогии +с заданием этого варианта в лабораторной работе 1. Предусмотреть расчет +`IMAGE_HEIGHT` таким образом, чтобы вся гистограмма вмещалась в область +рисунка. + +#### Вариант 11 + +Добавьте рамку вокруг гистограммы, используя пунктирные линии. Для отрисовки +пунктирной линии можно использовать стандартный элемент ``, установив +в нем атрибут `stroke-dasharray = '10 10'` + +#### Вариант 12 + +Добавьте на ось подписей границы столбцов по аналогии с заданием этого +варианта в лабораторной работе 1. + +#### Вариант 13 + +Отображайте гистограмму вертикально с подписями снизу. + +``` + * + * +* * +* * +* * * +_ _ _ +3 5 1 +``` + +Предусмотреть расчет `IMAGE_HEIGHT` таким образом, чтобы вся гистограмма +вмещалась в область рисунка. + +#### Вариант 14 + +Разделяйте каждый столбец пунктирными линиями длиной `IMAGE_WIDTH`. +Запрашивайте у пользователя шаблон пунктира. Шаблон пунктира задается +в атрибуте `stroke-dasharray` блока `` в виде `stroke-dasharray = '20 +10'`, где 20 означает длину черточки, 10 - длину промежутка. У пользователя +нужно запросить как длину черточки, так и длину промежутка. + +#### Вариант 15 + +Добавьте горизонтальную шкалу под гистограммой по аналогии с заданием +лабораторной работы 1. Шкалу нужно разбить на интервалы, размер которых вводит +пользователь. Допустимы размеры от 2 до 9 `BLOCK_WIDTH`, при некорректном +вводе печатайте сообщение со словом «ERROR» и завершайте работу программы. +Под нулевой, первой и последней отметкой шкалы требуется напечатать +соответствующие числа. Шкала должна быть во всю ширину гистограммы. + +#### Вариант 16 + +После запроса количества столбцов запросить цвет линий для каждого столбца. +Проверять ввод: цвет должен либо начинаться с `#`, либо не иметь внутри +пробелов. + +#### Вариант 17 + +Задавать автоматически прозрачность заливки каждого столбца гистограммы +в зависимости от высоты столбца. Чем больше столбец, тем темнее заливка. +Сделать это можно, передавая процент прозрачности в параметр `fill-opacity` +в формате `"0.7"`. 1 соответствует отсутствию прозрачности, 0 соответствует +полной прозрачности (отсутствию цвета) + +Для расчета прозрачности каждого i-го столбца `bins[i]` использовать формулу +`(bins[i]) / max_count)`. + +Пример: + +`1|``▮` — прозрачность 0.2 \ +`5|``▮▮▮▮▮` — прозрачность 1.0 \ +`3|``▮▮▮` — прозрачность 0.6 + +#### Вариант 18 + +Позволять пользователю делать оформление текста - +подчеркивание, +надчеркивание, ~~зачеркивание~~ +текста. За оформление шрифта отвечает атрибут `text-decoration`. Сделать +`'none'` значением по умолчанию. Допустимые значения: `none`, `underline`, +`overline`, `line-through`. Проверять введенной пользователем значение, и если +оно не соответствует допустимым, запрашивать значение заново, выдавая +предупреждение. diff --git a/labs/lab03/assets/histogram.svg b/labs/lab03/assets/histogram.svg new file mode 100644 index 0000000..a9d95f4 --- /dev/null +++ b/labs/lab03/assets/histogram.svg @@ -0,0 +1,9 @@ + + + 2 + + 5 + + 3 + + diff --git a/labs/lab03/assets/marks.txt b/labs/lab03/assets/marks.txt new file mode 100644 index 0000000..2cdb9aa --- /dev/null +++ b/labs/lab03/assets/marks.txt @@ -0,0 +1,3 @@ +10 +3 3 4 4 4 4 4 5 5 5 +3