пятница, 2 августа 2013 г.

ПМО ИУС — Структуры данных и STL

В то время, когда я учился, нам в рамках ни одной из дисциплин структур данных (и соответственно STL) никто не давал. Во время становления курса Андрей Логвиненко вписал в программу одну лабораторную по ассоциативным контейнерам STL, но до неё практически никто не доживал.

У меня в то время уже набрался отдельный приличный опыт кодирования различных структур данных (от простого стека до сложных B-tree деревьев различных разновидностей, как динамические так и статически реализации), поэтому структуры данных были оформлены как отдельная тема. Она оказалась хорошей подготовкой к теме STL, так как структуры данных сами по себе абстрактны и не зависят от языка реализации, а STL в значительной степени реализует большое число структур данных.

Сначала мы пробовали делать работы по реализации отдельных структур данных, однако только единицы с этим справлялись. Поэтому общий материал и произвольные структуры данных были вынесены полностью в теорию, а изучение STL делалось сразу же после разбора общей информации по структурам данных. В работах соответственно уже студенты использовали готовые контейнеры STL.

Удержание фокуса

Каждая структура данных — это объект какого-то общего класса, который описывает данный тип структур данных. Интерфейс полностью инкапсулирует внутреннее состояние структуры.

Конкретная структура данных используется и проектируется под конкретные типы действий над данными. То есть, исходя из того набора операций и требований, которые мы предъявляем множеству однотипных элементов, выбирается тип структуры данных.

Сам тип структуры данных может быть спроектирован самостоятельно в случае необходимости. Готовые протестированные структуры данных хорошо, свои велосипеды — в случае необходимости.

Подробности

Структуры данных и STL слайды.

Общие понятия любой структуры данных — множество однотипных элементов, интерфейс доступа, пользователь структуры данных. Пока что не понятно, но будем возвращаться и надеемся что прояснится.

Одну структуру данных мы уже знаем — массив. У нас в массиве всегда хранятся элементы одного типа, доступ в С++ через оператор [] по индексу, внешний пользователь использует массив не думая о том, как и где он хранится. Хотя пользователь может рассчитывать, что элементы в массиве хранятся последовательно и доступ к ним по индексу очень быстр (например в Perl массивы хранятся не последовательно, а организованы через хэш). Элементы могут быть любого, но одного типа. Взаимодействие с ними жестко определено интерфейсом. Внешний пользователь может создавать столько массивов, сколько ему захочется, и практически для любых типов (тип должен иметь фиксированный размер или гарантию того, что по размеру не выйдет за установленный предел).

Теперь переходим к другим, незнакомым структурам данных. У каждой из них мы будем рассматривать интерфейс взаимодействия и способ хранения, а также что из всего этого следует.

Стек. Имеет операции вставки и извлечения, параметры глубины и максимального размера. Обычно в памяти структура данных сама хранит указатель HEAD, чаще всего указывающий на элемент, который должен быть вставлен в последствии. Описание операций вставки и извлечения. LIFO. Две разных реализации стека — статическая и динамическая. В первом случае через массив, который мы создаем заблаговременно и работаем по нему посредством указателей. Второй случай — каждый элемент структуры данных хранит рядом с собой указатели на соседние элементы. Певый случай быстр, не требует дополнительной памяти на указатели, но съедает заданное число памяти согласно первой инициализации и имеет ограничение на размер, в то время как динамическая реализация ограничена только всей оперативной памятью.

Примеры стеков — ханойская башня (3 стека). Стек может быть использован в ряде других, не так очевидных задач, — организация сохранения данных в памяти при работе подпрограмм; пример рекурсивного разбора выражений.

Очередь. Аналогично — есть HEAD и TAIL, можно вставлять в один хвост, а извлекать только из другого. FIFO. Аналогично реализуется статически и динамически.

Списки. Как двусторонние стеки или двусторонние очереди. С каждым из хвостов можно делать операции извлечения и вставки.

Двусторонне-связанные списки. Картинка динамической реализации. Аналогично односторонне-связанные. Преимущества и недостатки.

Упорядоченные списки — делают определенные действия, чтобы сохранять упорядоченность на каждом выполнении операции.

Кольцевые списки.

Как организуется структура данных — основные правила.

Мы рассматривали линейные структуры данных. Для каждой из них характерно то, что все элементы хранятся линейно, в жестко заданной последовательности. Такое деление частично условно, так как в большинстве структур данных задается условие, что можно перебрать все элементы структуры данных в прямом или обратном порядке.

Дерево как структура данных. Каждый элемент имеет родителя и потомков. В общем случае дерево начинается с корня, каждый элемент можно отнести к какому-то уровню, связи внутри дерева не пересекаются.

Бинарное дерево — количество потомков не более 2. Это имеет свои преимущества. Например в данном случае дерево отсортировано и можно простыми операциями сравнения найти нужный элемент, ища из корня.

Сбалансированное дерево — такое дерево, в котором все кроме одного последнего уровня обязательно заполнены. Такое дерево позволяет осуществить гарантированный поиск за log2(N) операций.

Реализация деревьев также может быть статической и динамической. В первом случае отношения между элементами описываются с помощью математических формул, а во втором случае посредством указателей.

Виды проходов деревьев (pre-order, in-order, post-order, level-order).

Множества. Могут быть реализованы по-разному, как статически, так и динамически, так и через другие структуры данных. В множестве элемент встречается только один раз или не встречается вообще, а операции для него определяются из математического определения множества.

Мультимножество.

Карта (отображение, map). Мультикарта (multimap).

Хэш. Сначала разбор понятия хэш функции (функция, возвращающая элемент, который имеет конечное число состояний). Данное число состояний в хэше ассоциируется с хэш-таблицей.

Изначально хэш-таблица пуста. Во время вставки элемента вычисляется значение хэш-функции, элемент сохраняется и помечается его присутствие в хэш таблице. При последующей операции поиска вместо того, чтобы искать по всей структуре данных идет вычисление хэша. Если элемент таблицы отсутствует, то значения в хэше нет. Если присутствует, то нам нужно проверить все элементы у которых такой хэш уже напрямую.

Понятий коллизий. Коллизии это плохо. как избежать коллизий (хорошая хэш-функция и изменение размеров хэш-таблицы).

Куча (heap). Выглядит как бинарное дерево, но в нем всегда в каждой тройке нижние элементы меньше верхнего, и справа всегда меньше чем слева. Такая организация позволяет нам создать приоритетную очередь, в которой скорость вставки и извлечения высока (log N), в обычных приоритетных очередях каждый раз нужно либо искать место вставки или пересортировать. Разбор операций вставки и извлечения и почему так происходит.

Пример экзотической структуры данных — sparse array и варианта его реализации.

STL. Сначала — пример задачи, где нужно выполнять однотипные действия над разными типами данных. Если нет специального механизма, то это приводит к тому, что имеем большое число повторяющегося кода. Его сложнее читать, сложнее сопровождать, сложнее использовать. В случае с использования шаблонов можно сделать по-другому, из-за чего сокращается объем и сложность, и кроме того, появляется гибкость, так как мы можем использовать этот код для ряда других типов, о которых в данный момент ещё даже не подозреваем.

STL. Базовые (но это не все) элементы — контейнеры, итераторы, алгоритмы.

Контейнеры — это фактически те структуры данных, которые мы рассматривали ранее, но на языке С++, и они уже готовы к применению. Итераторы — специальные объекты для доступа к элементам структуры данных, близкая аналогия — указатели. Алгоритмы — готовый набор функциональных действий, позволяющий получить что-то полезное.

Каждый контейнер представляет собой упорядоченную последовательность элементов. Т.е. все элементы можно выстроить в одну цепочку друг за другом. Если мы хотим перебрать все элементы контейнера (распечатать их, найти какой-то и др.), что является типичной операцией, то это делается через итерационный перебор. Пример перебора. Понятие end-of-sequence (элемент за последним, но не последний).

Vector. Это — массив, но только в отличие от него мы можем изменять его размер во время выполнения программы. Сам вектор имеет всегда какой-то запас по размеру в памяти, и поэтому мы можем его расширять быстро.

Если этот запас заканчивается, то вектор перемещает себя в другой участок памяти, где резервирует больший объем памяти, копирует себя туда, и работа продолжается.

Пример использования с разбором подключения, инициализации, типовых операций, передачи как параметра.

List. В STL — динамический двусвязный список со всеми вытекающими. Удаление и добавление элементов в список. Пример.

Образование контейнеров из list'a в STL: стек, очередь и приоритетная очередь сделаны на основании list'a.

Deque.

Множества и мультимножества как деревья в STL.

Карты и мультикарты в STL. Пример.

Разбор характеристик контейнеров — какие особенности у каждого из них, когда какой следует применять.

Типичные методы контейнеров, характерные для всех.

Разбор алгоритмов STL. Алгоритмов много. Они безопасны, проверены и быстры. Работают как с контейнерами, так и с массивами. Нужно знать.


четверг, 1 августа 2013 г.

ПМО ИУС — Исключения

В то время, когда я учился, исключения уже как отдельное понятие существовали, но в нашем курсе они отсутствовали, а на практике в той среде, которую мы использовали (Borland C++ 3.11) они были, но реализация была ужасна — как с точки зрения производительности, так и с точки зрения количества подводных камней особенностей компилятора и как следствие, безопасности и удобства использования.

Постепенно пришли к схеме, когда понятие обработки исключительных ситуаций вообще не давалось на этапе изучения инкапсуляции, необходимо было обычными любыми средствами контролировать корректность состояния объектов классов (например любое адекватное поведение при передаче неверных параметров в методы классов). В дальнейшем, для осознания разницы, во время лабораторной по исключениям все эти действия должны были быть изменены на работу через исключения. Здесь мы сразу работаем на ощущение разницы, а также это дает положительный эффект в качестве переписывания библиотеки, изменения тестов и подключения новой версии в основной проект.

Кроме общего изучения механизмов исключений были сформулированы два дополнительных акцента: создание собственных классов исключений (опционно с задействованием std::exception), задействование исключений других библиотек (VCL C++Builder, другие библиотеки также могут использовать стратегию обработки исключительных ситуаций через исключения, и это мы понимаем и используем).

Удержание фокуса

Полный цикл как работает исключение — бросок; формирование объекта описывающего исключительную ситуацию; раскручивание стека; условие перехвата или не перехвата; обработка исключительной ситуации (передача информации в контекст catch, функциональная обработка; перехват только одного; выход из режима обработки исключительной ситуации).

Смысловое и функциональное значение своих классов исключений.

Подробности

Слайды.

Изначальное преподнесение проблемы — без описания чего либо с точки зрения исключений. В настоящее время понятие исключительной ситуации укоренилось и так и называется, раньше, до изобретения и распространения исключений проблема также существовала, но встречал её под названием «ошибочная ситуация», и для вынесения понятия исключения как слова из контекста использовал его.

Итак, ошибочная ситуация. Суть стартует с простейшего примера — необходимо написать функцию деления двух чисел A на B. Пример функции:

int
func( int a, int b )
{
    return a / b;
}

В данной функции, как и в любой другой, имеется две разных роли людей, участвующих в разработке. Один человек является пользователем функции, второй — её разработчиком. Между ними имеется соглашение с точки зрения интерфейса — пользователь обязан передать два параметра, разработчик должен посчитать и вернуть результат вычисления в виде числа.

С точки зрения обязательств все понятно. Однако здесь может получится так, что параметр B равен 0. В этом случае разработчик находится в затруднительном положении — он может понять и проанализировать, что B=0 (через if например), но этот случай не может быть обработан, так как возвращаемый функцией результат как число будет неверным. Если такое происходит (мы понимаем, что сами разрулить ситуацию не можем из-за особенностей функции или состояния системы, но делать что-то надо), то это называется ошибочной ситуацией.

Таких ситуаций может быть много: нам передали имя файла на обработку, а доступа к файлу нет; пытаемся подключится к БД, а в ответ непонятная ошибка; выполняем некоторое системное действие, а оно отрабатывает слишком долго. Основной задачей в разруливании ситуации является передача внешнему пользователю информации о том, что что-то произошло, по возможности с каким-либо описанием.

Для решения проблемы традиционно используется 4 метода, каждый из которых имеет свои преимущества и недостатки (сначала просто список как это работает, потом думаем что хорошего а что плохого в каждом из методов, а потом только раскрываются ответы):

Останов программы.
Останов в надежде, что придет человек и все решит. Человек действительно может все решить, но на это может потребоваться много времени. Резкий останов позволяет быстро диагностировать факт проблемы (останов не заметить крайне сложно) и заняться её устранением (fail-fast), но например в системах реального времени, от которых многое зависит (ПО на борту самолета), прийти и разобраться будет поздно. Кроме того, может получится так, что проблема на самом деле проста, и решить её можно программной логикой.
Вернуть что-то, как будто ничего не произошло.
Самый страшный способ. Внешний пользователь не сможет узнать, что система работает некорректно, соответственно ошибку сложнее будет обнаружить, из-за чего мы будем думать, что все в порядке, а на самом деле все плохо. Однако в крайне редких случаях, например когда нет другого способа, а система должна работать, это может применяться, но при этом надо отдавать себе отчет и пытаться сделать все возможное для диагностики (страшная запись в лог) и попытки решения проблемы.
Вернуть код ошибки (некоторое.
Традиционный способ обработки ошибочных ситуаций, встречающийся во многих системах нижнего уровня. Может быть обработан широкий спектр ошибок (с добавлением кодов в документацию). Однако у него есть ряд недостатков. Во-первых, не всегда можно вернуть код ошибки (как например в нашей функции делания A на B, нет ни одного зарезервированного значения для возврата кода ошибки). Во-вторых, в случае большого числа кодов ошибок и большой вложенности функций становится сложно все это сопровождать. Фактически каждая функция должна быть проверена на возвращаемое значение (вы часто проверяете возвращаемое значение printf?), и зачастую то не делается. А если делается, то код резко разрастается, и при этом логика обработки ошибок смешивается с бизнес-логикой приложения.
Вызвать некоторую общую функцию, которая решит все проблемы.
Общая функция — это логическая обработка, которая потенциально может все. Однако здесь есть ряд проблем: не каждую ошибочную ситуацию можно обработать; в больших системах такая функция начинает на себя брать все, а это ведет её разрастанию и глобализации (зависимость в случае сохранения внутреннего состояния функции; многопоточность и пр., очень похожее на синглтон и глобальные переменные), что череповато; не всегда есть возможность вызова такого рода функции из всех контекстов.

Как видим варианты решения есть, их много, и у каждого из них свои особенности.

И только теперь на сцену выходят исключения, которые представляют собой удобный механизм, позволяющий реализовывать любую из перечисленных стратегий или их комбинацию эффективно.

Исключения не всегда поддерживаются (например в низкоуровневом программировании или в случае использования экзотических компиляторов), но по умолчанию рекомендуется использовать именно исключения для обработки ошибочных ситуаций.

Рассмотрение аналогичного примера, только уже с использованием механизма исключений.

Разбор синтаксиса исключений.

Общая логика работы приложения при бросках исключений (создание объекта, описывающего исключительную ситуацию; бросок и перевод системы в состояние обработки исключительной ситуации; поиск блока, который может решить проблему; раскручивание стека; перехват или не перехват (останов); обработка исключительной ситуации и переход в обычный режим работы).

Обработка множественных catch (только один из, выбор сверху вниз по типу исключения).

Собственные классы исключений и их иерархии. Выбор блока обработки исключительной ситуации в случае иерархии исключений.

Повторная генерация (отсутствие копии и сохранение оригинала).

Исключения std::exception, их рекомендация к использованию, почему они не обязательные как в других языках (исторически), уже готовые стандартные, наследование от них.

Исключения C++ Builder как пример исключений со сторонних библиотек. Другие библиотеки также могут использовать исключения и соответственно их функции бросать их.

Пример создания собственного класса исключений и как это можно использовать.


среда, 31 июля 2013 г.

ПМО ИУС — Rapid Application Development

Включение изучения среды RAD было обусловлено рядом причин: другой (GUI) интерфейс, второй тип проекта в курсе (не только консоль, т.е. два проекта используют одну библиотеку для понимания подключения разрабатываемых библиотек), мультяшность (эмоциональность другой среды, т.к. изначально консоль это тривиально и скучно), другая парадигма в построении приложения (консоль это одна стартовая нить, здесь же одна нить, которая выполняется на разных методах в случае наступления событий). Кроме того, среда RAD ранее в Delphi и сейчас в Builder'e имеет низкий порог вхождения, что позволяет её внедрять в массы.

В рамках данной темы две работы — одна на базовый интерфейс и компоненты VCL вообще, а вторая на понимание событий в RAD (чтобы можно было делать динамичное, например захватывать мышой и тягать объект по экрану).

RAD также хотелось запускать как можно раньше, сразу же после завершения тем ООП. Это обсуловлено было тем, что во-первых, эта тема эмоциональная и контрастная, во-вторых, существенно разбавляет консоль и в-третьих, делает переключение на второй проект (не консольный, а GUI-шный, который в дальнейшем развивается). Кроме того, после прохождения RAD тема по исключениям делается немногим более насыщенной, так как подхватываются не только пользовательские и стандартные исключения, но и исключения VCL.

Результат выполнения работы получался зачастую веселым и занимательным. В зависимости от задания требовалось изобразить графически в помощью методов Canvas (рисование простейших элементов — линии, эллипсы, прямоугольники, …) состояния разрабатываемого объекта класса,и далее этот объект становится в следующей работе динамическим. Это были и обезьяны, ползающие по пальмам за орехами, и змей горыныч в пару десятков голов с глазами следящими за мышкой, и спутник взлетающий на орбиту по параболе и др..

Удержание фокуса

Использование классов библиотек как есть, без изменений.

Продвинутые студенты использовали полиморфизм, работая через указатели. Т.е. объект в зависимости от типа подстановки вел себя различным образом.

Выделение отдельных функций для рисования-стирания объекта.

Подробности

Слайды.

RAD представляет собой комплекс инструментов, обеспечивающих быструю разработку приложений. Каждый из инструментов требует отдельного описания и комментирования что это дает.

Свойства, методы и события показываются на сборке приложения на пальцах. Свойство — практически то же самое, что и поле, но в RAD к нему добавлено ряд особенностей, например реакция на изменения состояния. Метод — то же самое что и метод функции. Событие — ассоциация метода и некоторого условия, при наступлении которого вызывается метод (нажатие кнопки, периодическое событие от таймера, внешний вызов, изменения состояния свойства и т.д..).

Разбор по классификации компонентов VCL (стандартные, оригинальные, графические и невидимые). Пострение иерархии классов VCL — на каждом уровне иерархии появляются новые свойства. Сначала это просто объекты, которые имеют имя как идентификатор и зарегистрированы в системе, а далее на них навешиваются комплексы различных методов и свойств зависимости от подгруппы, в которой классы реализуются). Простым спискам не обязательно быть отдельными компонентами, а менюшки можно объединить под одной крышей, только вызывать и отображать по-разному. Активные компоненты требуют дополнтельных ресурсов, поэтому эта группа функций лежит отдельно и подключается только для тех, кто может принимать фокус или реагировать на щелчки. …

Состав проекта важен для понимания — временные файлы и как они строятся и их не нужно ставить под контроль, файлы формы, которые нужно ставить под контроль.

Базовые классы — AnsiString он же String, свой внутренний велосипед стрингов внутри VCL и на нем построены все стринги, которые используются в VCL.

Различные типы свойств — простые типы, выбор из нескольких, множества, сложные со своими редакторами, массивы свойств, вложенные свойства. Их всех можно найти в Object Inspector'e.

Разбор типичных свойств и методов. Из-за иерархии свойства и методы таковы, что встречаются много где как одинаковые, и при этом ведут себя также приблизительно одинаково, что облегчает понимание библиотеки.

Примеры базовых методов Canvas для рисования элементов на нем. Есть несколько компонентов, которые имеют Canvas, и рисовать можно на каждом из них, делая это практически одинаковым образом.

Представление цвета в Canvas. RGB. Canvas как большой двумерный массив пикселов.

Пользовательские диалоги, призванные облегчить взаимодействие с пользователем. Типичные диалоги уже разработаны и ими можно пользоваться (получить от пользователя согласие на действие, передать пользователю сообещение, выбрать файл для записи на диск и др..). Диалоги есть в виде функций (вызвал, словил ответ и забыл), а есть и компоненты (взял компонент, настроил, использовал, перенастроил, ещё раз и т.д.).

По-хорошему лучше обойтись почти совсем без слайдов и объяснять на пальцах под проектором. В том числе как хватать и перетягивать мышой, а также события, менюшки, иерархии, свойства, интерфейс IDE по окнам и т.п..


вторник, 30 июля 2013 г.

ПМО ИУС — ООП — Наследование и полиморфизм

Наследование уже изучалось после прохождения 3-й работы и усваивания Google Test Framework. Таким образом, первые основные три темы, которые должны были введены быть в курс как можно быстрее для того, чтобы в каждой из работ потом повторятся — проведены.

Вместе с наследованием изучалась перегрузка операторов. Полиморфизм без наследования изучать сложно, а перегрузка операторов потом пригождается при изучении STL.

На этом этапе нет виртуальных функций и абстрактных классов. Просто идет добавление нового класса-наследника о существующего и к нему должна быть добавлена новая функциональность.

При перегрузке операторов обязательно использование унарного и бинароного операторов и демонстрация работы с ними.

Все уже происходит в сторонней библиотеке, в которой происходит добавление и тестирование согласно TDD. После прохождения всех тестов подключаемый класс из консольного приложения переключается на новой появившийся тег библиотеки посредством svn:externals. Все должно работать в консоли как прежде.

Пятой работой является полиморфизм и только полиморфизм. Как показывает практика, это одна из самых сложных тем в понимании, поэтому к этой теме в работе ничего больше не добавляется.

У нас уже есть два класса из предыдущей работы и теперь нужно построить что-то больше напоминающее иерархию — один базовый класс и два наследника. Только на этот раз базовый класс должен стать абстрактным, как минимум одна из ключевых функций стать чисто виртуальной, а реализация уйти в наследника. Во втором наследнике появляется новая реализация согласно заданию.

Все это проверяется в Google Test. Как и следовало ожидать, так как функция переносится в наследник, то все тесты ломаются, так как базовый класс уже создать нельзя в силу его абстрактности. В связи с этим чинятся тесты (что полезно — что-то изменилось, нужно привести в порядок тесты). И далее тестируется в том числе и новая функция.

После всех проверок консоль переключается на новый тег и тоже ломается. В ней необходимо уже задействовать как минимум два объекта разных конкретных классов, и при этом продемонстрировать полимозфизм — через ссылку или указатель, с передачей как параметр функции или без.

Фокус держится на контроле тестирования и на адекватности понимания полиморфизма. Т.е. важно понимать то, что можно работать с многими разными типами не вникая в более низкие детали реализации.

Подробности

Слайды, слайды.

Общее понятие самого простого наследования — есть базовый (супер-) класс, и наследник (под-) класс. Во время наследования по умолчанию все свойства базового класса передаются наследнику, т.е. все, что умеет и все что есть в базовом классе становится достоянием наследника.

Наследование может быть в несколько уровней. Кроме того, оно может быть множественное и очень разное.

При сборке иерархии наследования прежде всего ориентируются на тех типах с точки зрения абстрагирования, которые были нами выделены. Если мы можем объединить по какому-либо существенному признаку несколько объектов, то может быть целесообразным эту общее собрать в одном базовом классе. С точки зрения абстрагирования это могут быть как данные, так и код. (примеры для иерархий) Выполняя объединение получается так, что это дает нам возможность не переписывать общие части в каждом из наследников, а использовать одну и ту же функциональность несколько раз, при этом держа её под определенной абстракцией с конкретными границами. Кроме того, мы можем оперировать элементами более высокого уровня (например мы можем посчитать периметр для любой двумерной фигуры, или объем для любой трехмерной).

Как это делается в С++. Демонстрация повторного использования кода и данных.

Изменение доступа при наследовании в C++. По умолчанию делаем public.

Размещение данных в памяти и как может произойти срезка.

Синтаксис конструирования объектов иерархии. Порядок вызова конструкторов и деструкторов.

Доступ к одноименным функциям в рамках одной иеррархии наследования.

Множественное наследование (как так получается, контроль одного базового класса компилятором С++).

Перегрузка функций в С++. Одно имя, разный интерфейс. Выбор интерфейса исходя из контекста. Преимущества такого подхода по сравнению с зоопарком разноименных функций.

Перегрузка операторов. Оператор — та же функция, которая вызывается, только синтаксис немного другой. Бинарные и унарные операторы.


понедельник, 29 июля 2013 г.

ПМО ИУС — ООП — Инкапсуляция и модульность

Инкапсуляция хорошо по интуитивному смыслу дружит с модульностью, поэтому благоразумно давать их вместе — первое как контроль состояния объекта и защита от внешних воздействий, и модульность как уменьшение сложности методом распиливания на независимые части. В рамках первой — это перевод всех данных под private раз и навсегда до конца курса, в рамках второй — разделение класса на пару файлов hpp/cpp и вынесение их в отдельное место, отличное от главной программы, которая уже подключает их через include.

Несмотря на то, что инкапсуляция является стандартной и необходимой практикой в ООП, она вынесена в отдельный детерминированный шаг для того, чтобы: разделить прямой доступ к полям и доступ через методы на уровне написания кода; в первой работе сосредоточиться на абстрагировании, не пытаясь делать много вещей сразу; во второй уже работать с обкатанными понятиями первой л.р. (часть из них должна немного осесть в более долговременную память); сосредоточиться на понимании состояний объекта и его защите.

С точки зрения работы функционал практически не добавляется — только с точки зрения поведения классов необходимо добавить действия по сохранению объекта в корректном состоянии (чтобы Дюймовочка не плыла участки в минус 5 метров, иначе поведение объекта или его состояние может стать некорректным) — это делается как раз вместе с инкапсуляцией. Кроме того, идет разделение и вынос hpp/cpp, что тоже не меняет функционал.

В данной работе сопутствующей темой является оформление кода — по завершению работы код уже должен соответствовать правилам оформления и собрана документация с помощью Doxygen.

Разбиение hpp/cpp является подготовительным шагом к следующей работе, где классы выносятся как отдельная библиотека и там тестируются. Т.е. сначала сплошной cpp с классом и main(), далее main() + файлы hpp/cpp, и далее уже main подключает через svn:externals hpp/cpp. Малые шаги лучше чем все сразу.

Фокус на обязательности инкапсуляции и обязательности всех правил оформления кода.

Класс должен заниматься только тем, что от него требуется — никаких побочных печатей внутри логики. Все печати вне класса.

Сразу прописывыаем защиту от повторной компиляции — чтобы она стояла везде.

Подробности

Слайды.

Разделение реализации и пользователя класса. Это два разных человека, между которыми имеется некоторое соглашение о взаимодействии (интерфейс). Реализация может жить отдельно, выполняя условия интерфейса, пользователь класса может работать с классом, не думая о реализации. Профит — уменьшение сложности и возможность работы людьми независимо друг от друга. Но нужны ресурсы на содержание интерфейса.

Подобное соглашение есть для простых структур данных. По большому счету вы не знаете, как в памяти хранятся int'ы, float'ы и пр. базовые типы. Однако вы знаете, что есть набор операций, которые можно над ними выполнять (интерфейс) и работаете уже оперируя ими, а сами переменные — удобные абстракции соответствующего типа.

Как это делается в C++. Что это дает (защита, разделение).

Типичная сборка проекта в C++ с точки зрения файлов (пары hpp/cpp, которые независимы и подключаемы при необходимости).

Защита от повторного подключения в С++ (как выглядит и зачем).

Доступ к полям класса (public/private/protected). Что такое наследование пока что не знаем, просто помечаем на будущее. Как это выглядит в C++.

Getter'ы и Setter'ы.

Пример изменения реализации — изменяем private и логику функций, однако интерфейс не изменяется.