Итоговый тест №8.
Объектно-ориентированное программирование в языке C++.
Теория
Классы позволяют создавать собственные типы данных, которые объединяют данные и функции, работающие с этими данными. Данные и функции внутри класса называются членами. Доступ к членам класса осуществляется через оператор выбора членов . (или через оператор ->, если вы получаете доступ к элементу через указатель).
Спецификаторы доступа позволяют указать, кто будет иметь доступ к членам класса. Доступ к открытым (public) членам класса имеют все. Доступ к закрытым (private) членам класса имеют только другие члены класса. О protected мы поговорим детально, когда будем рассматривать наследование в языке С++. По умолчанию все члены класса являются private, а все члены структуры — public.
Инкапсуляция — это когда все переменные-члены вашего класса являются закрытыми, и доступ к ним напрямую невозможен. Это защищает ваш класс от неправильного/некорректного использования.
Конструктор — это специальный тип метода класса, который позволяет инициализировать объекты класса. Конструктор, который не принимает никаких параметров (или имеет все параметры по умолчанию), называется конструктором по умолчанию. Конструктор по умолчанию выполняется, если пользователем не предоставлены значения для инициализации. Вы всегда должны иметь по крайней мере один конструктор для выполнения в каждом из своих классов.
Список инициализации членов класса позволяет инициализировать переменные-члены из конструктора (вместо присваивания значений переменным-членам).
В C++11 инициализация нестатических членов класса позволяет напрямую указывать значения по умолчанию для переменных-членов при их объявлении.
До C++11 конструкторы не должны вызывать другие конструкторы (хоть это и скомпилируется, но будет работать не так, как вы ожидаете). В C++11 конструкторам разрешено вызывать другие конструкторы. Этот процесс называется делегированием конструкторов (или «цепочкой конструкторов»).
Деструктор — это специальный тип метода класса, с помощью которого выполняется очистка класса. Именно в деструкторах следует выполнять освобождение динамически выделенной памяти.
Все методы имеют скрытый указатель *this, который указывает на текущий объект класса (который используется в данный момент). В большинстве случаев вам не нужно напрямую обращаться к этому указателю.
Хорошей практикой в программировании является использование заголовочных файлов при работе с классами, когда определения классов находятся в заголовочном файле с тем же именем, что у класса, а определения методов класса — в файле .cpp с тем же именем, что у класса.
Методы класса могут (и должны) быть const, если они не изменяют данные класса. Константные объекты класса могут вызывать только константные методы класса.
Статические переменные-члены класса являются общими для всех объектов класса. Доступ к ним можно получить как из любого объекта класса, так и непосредственно через оператор разрешения области видимости ::.
Аналогично, статические методы класса — это методы, которые не имеют указателя this. Они имеют доступ только к статическим переменным-членам класса.
Дружественные функции — это внешние функции, которые имеют доступ к закрытым членам класса.
Дружественные классы — это классы, в которых все методы являются дружественными функциями.
Анонимные объекты создаются для обработки выражений или для передачи/возврата значений.
В качестве вложенных типов в классах обычно используются перечисления, но также можно использовать и другие пользовательские типы данных (включая другие классы), если это необходимо.
Тайминг кода осуществляется через библиотеку chrono и позволяет засечь время выполнения определенного фрагмента кода.
Тест
Задание №1
a) Напишите класс с именем Point. В классе Point должны быть две переменные-члены типа double: m_a и m_b со значениями по умолчанию 0.0. Напишите конструктор для этого класса и функцию вывода print ().
Следующая программа:
#include <iostream>
int main () {
Point first;
Point second (2.0, 5.0);
first.print ();
second.print ();
return 0;
}
Должна выдавать следующий результат:
Point (0, 0)
Point (2, 5)
b) Теперь добавим метод distanceTo(), который будет принимать второй объект класса Point в качестве параметра и будет вычислять расстояние между двумя объектами. Учитывая две точки (a1, b1) и (a2, b2), расстояние между ними можно вычислить следующим образом: sqrt ((a1 - a2) * (a1 - a2) + (b1 - b2) * (b1 - b2)).
Следующая программа:
int main () {
Point first;
Point second (2.0, 5.0);
first.print ();
second.print ();
std::cout << "Distance between two points: " <<
first.distanceTo (second) << '\n';
return 0;
}
Должна выдавать следующий результат:
Point (0, 0)
Point (2, 5)
Distance between two points: 5.38516
c) Измените функцию distanceTo () из метода класса в дружественную функцию, которая будет принимать два объекта класса Point в качестве параметров. Переименуйте эту функцию на distanceFrom ().
Следующая программа:
int main () {
Point first;
Point second (2.0, 5.0);
first.print ();
second.print ();
std::cout << "Distance between two points: " << distanceFrom (first, second) << '\n';
return 0;
}
Должна выдавать следующий результат:
Point (0, 0)
Point (2, 5)
Distance between two points: 5.38516
Задание №2
Напишите деструктор для следующего класса:
#include <iostream>
class Welcome {
private:
char *m_data;
public:
Welcome () {
m_data = new char [14];
const char *init = "Hello, World!";
for (int i = 0; i < 14; ++i)
m_data [i] = init [i];
}
~Welcome () {
// Реализация деструктора
}
void print () const {
std::cout << m_data;
}
};
int main () {
Welcome hello;
hello.print ();
return 0;
}
Третье задание не надо делать. Очень длинное.
Задание №3
Давайте создадим генератор случайных монстров.
a) Сначала создайте перечисление MonsterType со следующими типами монстров: Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire и Zombie + добавьте MAX_MONSTER_TYPES, чтобы иметь возможность подсчитать общее количество всех перечислителей.
b) Теперь создайте класс Monster со следующими тремя атрибутами (переменными-членами): тип (MonsterType), имя (std::string) и количество здоровья (int).
c) Перечисление MonsterType является специфичным для Monster, поэтому переместите его внутрь класса под спецификатор доступа public.
d) Создайте конструктор, который позволит инициализировать все переменные-члены класса.
Следующий фрагмент кода должен скомпилироваться без ошибок:
int main () {
Monster jack (Monster::Orc, "Jack", 90);
return 0;
}
e) Теперь нам нужно вывести информацию про нашего монстра. Для этого нужно конвертировать MonsterType в std::string. Добавьте функцию getTypeString (), которая будет выполнять конвертацию, и функцию вывода print ().
Следующая программа:
int main () {
Monster jack (Monster::Orc, "Jack", 90);
jack.print ();
return 0;
}
Должна выдавать следующий результат:
Jack is the orc that has 90 health points.
f) Теперь мы уже можем создать сам генератор монстров. Для этого создайте статический класс MonsterGenerator и статический метод с именем generateMonster (), который будет возвращать случайного монстра. Пока что возвратом метода пускай будет анонимный объект: (Monster::Orc, "Jack", 90).
Следующая программа:
int main () {
Monster m = MonsterGenerator::generateMonster();
m.print ();
return 0;
}
Должна выдавать следующий результат:
Jack is the orc that has 90 health points.
g) Теперь MonsterGenerator должен генерировать некоторые случайные атрибуты. Для этого нам понадобится генератор случайного числа. Воспользуйтесь следующей функцией:
// Генерируем случайное число между min и max (включительно).
// Предполагается, что srand() уже был вызван
int getRandomNumber(int min, int max) {
static const double fraction = 1.0 / (static_cast<double>(RAND_MAX) + 1.0);
// используем static, так как это значение нужно вычислить единожды
// Равномерно распределяем вычисление значения из нашего //диапазона
return static_cast<int>(rand() * fraction * (max - min + 1) + min);
}
Поскольку MonsterGenerator будет полагаться непосредственно на эту функцию, то поместите её внутрь класса в качестве статического метода.
h) Теперь измените функцию generateMonster () для генерации случайного MonsterType (между 0 и Monster::MAX_MONSTER_TYPES-1) и случайного количества здоровья (от 1 до 100). Это должно быть просто. После того, как вы это сделаете, определите один статический фиксированный массив s_names размером 6 элементов внутри функции generateMonster () и инициализируйте его 6-ю любыми именами на ваш выбор. Добавьте возможность выбора случайного имени из этого массива.
Следующий фрагмент должен скомпилироваться без ошибок:
#include <ctime> // для time ()
#include <cstdlib> // для rand () и srand ()
int main () {
srand (static_cast<unsigned int>(time (0))); // используем системные //часы в качестве стартового значения
rand (); // пользователям Visual Studio: делаем сброс первого //случайного числа
Monster m = MonsterGenerator::generateMonster();
m.print();
return 0;
}
i) Почему мы объявили массив s_names статическим?
