зачет 26 мая в 17-30
"Чистый" зачёт -
Контрольное занятие. Zoo++
Задание состоит из четырёх частей. Все задания выполняются строго последовательно. Работать надо быстро. Но качественно.
Часть 1. Животные - 15 баллов
В заголовочном файле
Animals.h
создайте классAnimal
с двумя чисто виртуальными функциями-членами:say
(константная) иmove
(неконстантная); в классах-наследниках они будут возвращать строки-описания того, какие звуки издают животные и как они передвигаются. Там же создайте три класса-открытых наследникаAnimal
:Mammal
,Bird
,Reptile
(млекопитающие, птицы, пресмыкающиеся), с пустым телом. Там же создайте открытых наследников классовMammal
,Bird
иReptile
— конкретных видов животных: двух млекопитающих, одну птицу, одно пресмыкающееся, в этих классах должны быть определены функции-члены say и move. Конкретные виды животных и особенности их жизнедеятельности можно смотреть в Википедии.-
В деструкторе
Animal
выводите на консоль: « — животное погибло». Сообщение перед дефисом должно выводиться в деструкторах производных классов («Коровы больше нет» и т. п.).Для симметрии реализуйте в конструкторе того же животного вывод сообщения о том, что условная корова появилась на свет. Как изменилось поведение основной программы?
Для заселения животных в будущий зоопарк надо научиться их клонировать: добавьте в класс
Animal
чисто виртуальную константную функциюclone
, возвращающую указатель на животного-клона. В конкретных классах определите её реализацию, создающую и возвращающую указатель на копию текущего животного (this
) в динамической памяти:return new Cow(*this); // клонированная корова
Часть 2. Зоопарк - 15 баллов
Создайте класс зоопарка
Zoo
, который хранит полиморфную коллекцию зверей по указателям на базовый класс (в качестве конкретного класса-коллекции можно выбратьstd::vector
). Создайте в классе функцию-членaddAnimal
, которая добавляет некоторого животного в зоопарк (подсказка: параметр функции имеет типAnimal *
). В её реализации должно выполняться клонирование и помещение адреса клона в хранящуюся в зоопарке коллекцию.Замечание. Подумайте, как можно было бы реализовать
Zoo::addAnimal
без функции-членаAnimal::clone
.При создании клонов происходит выделение динамической памяти. Эту память необходимо возвращать операционной системе вручную, используя операцию
delete
. Организуйте забой животных при закрытии зоопарка (деструктор классаZoo
).Как только вы написали нетривиальный деструктор для вашего класса, сразу нужно подумать о переопределении сгенерированных автоматически конструктора копий и операции копирующего присваивания. В данном случае разумным будет удалить их: для этого необходимо дописать
= delete
к их заголовкам.Создайте функцию-член
Zoo::walk
, в которой каждое животное в зоопарке издаёт звуки и передвигается. В её реализации рекомендуется использоватьfor
из C++11 (akaforeach
). Помните, что обращение к функциям-членам по указателям на объекты принято осуществлять с помощью операции->
. Продемонстрируйте его работу в основной программе.
Часть 3. Персональная информация о животных 15 баллов
Время добавить персональную информацию животным — имя и возраст. Вопрос, как лучше это сделать, не прост.
Вариант 1 [плохой]. Можно было бы добавить соответствующие поля и геттеры в класс Animal
.
Однако общая рекомендация при создании больших иерархий классов состоит
в том, чтобы разделять наследование интерфейса и наследование
реализации. Практическую пользу от этого мы увидим в части 4. А пока
просто добавьте в «интерфейс» Animal
чисто виртуальные константные функции-члены getName
и getAge
.
Вариант 2 [получше]. Вынести реализацию в отдельный класс, назовём его Creature
, и добавить этот класс в иерархию каким-нибудь хитрым способом: чтобы каждый конкретный тип животного наследовал интерфейс от Animal
, а реализацию части этого интерфейса (getName
/getAge
) от Creature
. Чтобы в C++ такое решение корректно работало, сам Creature
должен реализовывать интерфейс Animal
(то есть наследоваться от Animal
). То есть можно было бы составить цепочку наследования:
Animal
←Creature
← [конкретное животное].
Однако в нашей цепочке есть ещё одно звено: Mammal
/Bird
/Reptile
. Вставить это звено можно в одно из двух мест в данной цепочке:
Слева от
Creature
— тогда получится, чтоCreature
одновременно и млекопитающее и птица и рептилия;Справа от
Creature
— мы получим смешение наследования интерфейса и реализации, от которого стремились уйти: классы конкретных животных будут наследовать от одного класса сразу и интерфейс и реализацию.
Удовлетворительный выход из сложившейся ситуации (которая должна казаться безвыходной!) — превращение Creature
в шаблон следующего вида, чтобы получить цепочку вида Animal
← ???
← Creature<???>
← [конкретное животное], где вместо ???
означает Mammal
/Bird
/Reptile
.
template<typename T>
class Creature : public T {
// закрытые поля name, age
// открытые геттеры для полей;
// конструктор с инициализацией полей
// (тип T никак не используется)
};
Конкретное животное теперь должно выглядеть так:
class Cow : public Creature<Mammal> {
// конструктор с параметрами для вызова конструктора Creature<Mammal>
};
Выполните реализацию
Creature
и изменение классов конкретных животных (наследование и конструкторы).Добавьте в класс
Animal
закрытую виртуальную функцию-членprint
с одним параметром — выходным потоком; реализация этой функции должна использоватьgetName
иgetAge
. Определите операцию вывода в поток для классаAnimal
, в ней нужно вызвать функциюprint
. Попробуйте распечатать с помощью данной операции вывода в поток объект какого-либо животного.
Часть 4. Производство зоопарков - 15 баллов
Для массового производства зоопарков и заполнения их животными создайте два базовых класса,
ZooCreator
иAnimalCreator
с пустыми открытыми виртуальными деструкторами. (Это можно сделать в одном заголовочном файле, например,ZooCreation.h
.) В первом классе объявите чисто виртуальную функциюZoo * create()
во втором — чисто виртуальную функцию
Animal * create(string const & spec, string const & name, int age)
где
spec
— это строковое описание вида конкретного животного.Замечание. Функции с именами
create
иclone
часто используются для размещения объектов в динамической памяти. В этом случае к ним нужно добавлять комментарии, настойчиво требующие от клиента этих функций освобождения этой памяти с помощьюdelete
. Добавьте такие комментарии там, где это необходимо.Создайте открытого наследника
AnimalCreator
— классSimpleAnimalCreator
, который в основе алгоритма создания животного использует длинную цепочкуif
’ов по параметруspec
. В основной программе попробуйте создать и использовать полиморфно его и создаваемых им животных. Не забывайте освобождать динамическую память.Создайте открытого наследника
ZooCreator
— классStreamZooCreator
, заполняющий зоопарк на основе информации о животных, которая приходит из переданного потока, с помощью переданного создателя животных (AnimalCreator
). У класса должно быть два поля типовistream &
иAnimalCreator *
, которые инициализируются в конструкторе. Функцияcreate
в цикле считывает из потока информацию, необходимую для создания животного, и вызывает функциюAnimalCreator::create
. Результат последней должен передаваться вZoo::addAnimal
для создаваемого зоопарка и, далее, удаляться (delete
).Попробуйте создать зоопарк с помощью потока. Для простоты — строкового:
istringstream iss("cow mumka 1 cow zorka 2"); SimpleAnimalCreator sac; StreamZooCreator zc(iss, &sac); Zoo * z = zc.create(); zoo->walk(); delete zoo;
Дополнительные задачи**
Создайте открытого наследника
ZooCreator
— классRandomZooCreator
, заполняющий зоопарк случайными животными со случайными данными в количестве, указанном в соответствующем поле класса (инициализируется в конструкторе). Как и прежде, используетсяAnimalCreator
. Базу имён для генерации случайных имён загрузите при создании объекта класса из файла pet-names.txt.Реализуйте наследника AnimalCreator — класс
PrototypedAnimalCreator
, который имеет поле-отображениеstd::map<std::string, Animal *> protoanimals
. (Шаблон класса map находится в заголовочном файле map.) Для каждого строкового описания животного он хранит экземпляр этого животного в динамической памяти. При вызовеcreate
по параметруspec
изprotoanimals
достаётся соответствующее животное, ему задаются имя и возраст, и, наконец, в качестве результата возвращается его клон. На данный момент в программе нет возможности изменения имени и возраста животного. Добавьте в классAnimal
закрытые сеттеры этих полей, объявите классPrototypedAnimalCreator
другомAnimal
. Реализация сеттеров выполняется каждом конкретном классе животного отдельно. Для этого потребуется добавить защищённые сеттеры в класс Creature. (Оцените затраты на изменение интерфейса…)
Обычное занятие
Часть 1. Разминка
- Скачайте заготовку и посмотрите реализацию first_find_cond и find_sum_min_in_pair_cond.
- Придумайте ещё по одной функции для задания условия и по одной лямбда-функции.
Реализуйте шаблон функции
bool in_range(It beg, It end, It it)
которая проверяет, что
it
находится в диапазоне[beg,end)
с помощью обычного цикла.[функция
std::next
, функцияstd::distance
] Реализуйте шаблон функцииIt random_it(It beg, It end)
которая возвращает случайный итератор в диапазоне
[beg,end)
.Указания. Использовать
rand
+next
. Вassert
для проверки использоватьin_range
. Вызовsrand
делается в основной программе.[функция
std::rotate
] Реализуйте шаблон функцииvoid shift_right(It begin, It end, size_t k)
которая выполняет циклический сдвиг элементов диапазона
[beg,end)
наk
элементов вправо. Для примененияrotate
нужно вычислить (и получить с помощьюnext
) какой элемент окажется на месте первого. Понадобится, как в прошлой задаче, узнать длину диапазона.
Каждый
решение должно сопровождаться набором автоматических тестов в основной
программе на разных контейнерах (как минимум: массив и std::list
) с разными типами элементов.
Часть 2*. Анализ содержимого контейнеров
[0.5 балла, шаблон
accumulate
(2)] Создайте шаблон функции, которая находит произведение элементов данного диапазона (диапазон, англ. range, всегда задаётся итераторами начала и конца), начиная умножение со значенияinit
:T product(It beg, It end, T const & init)
[1 балл, шаблон
for_each
] Создайте шаблон функции, которая находит среднее арифметическое элементов данного диапазона:double mean(It beg, It end)
— на основе лямбда-функции с захватом по ссылке переменных для суммы и для количества элементов. Сумма имеет тип
double
, а счётчик (count
) — типint
.[1.5 балла, шаблон
min_element
(2)] Создайте шаблон функции, которая в данном диапазоне находит итератор на элемент наиболее близкий к данному числу R (т. е. такой элемент X, для которого величина |X - R| является минимальной). Компаратор реализуется в виде лямбда-функции.[2 балла, шаблон
adjacent_find
(2)] Создайте шаблон функции, которая проверяет, что в данном диапазоне чередуются чётные и нечётные элементы.[2.5 балла,
count
] Создайте шаблон функцииvector<T> count_n_populate(It beg, It end, T const & val)
которая подсчитывает количество элементов в данном диапазоне, равных
val
, с помощью алгоритмаcount
и возвращает вектор из этого числа копий элементаval
с помощью конструктора вектора (2).