- Основы ООП: абстракция
- Основы ООП: инкапусляция
- Основы ООП: наследование
- Основы ООП: полиморфизм
- Основы ООП: композиция (дизайн)
- Определение класса
- Права доступа
- Конструкторы: по умолчанию, параметрический, копирования
- Деструкторы
- Ссылка на себя: this
- Наследование
- Динамическое создание объектов: new и delete
- Указатели
- Виртуальные методы (virtual и override)
- Статические члены
- Вложенные типы, пространства имен и оператор контекста '::'
- Разнесение объявления и определения класса
- Абстрактные классы и интерфейсы
- Перегрузка операторов
- Приведение объектных типов: dynamic_cast и указатель void *
- Указатели, ссылки и const
- Управление наследованием: final и delete
Определение класса
Инструкция для определения нового класса имеет много необязательных компонентов, которые влияют на его характеристики. В обобщенном виде её можно представить так:
class имя_класса [: модификатор_доступа имя_родительского_класса ...]
|
Для простоты изложения мы начнем с минимального достаточного синтаксиса и будем дополнять его по мере продвижения по материалу.
В качестве полигона используем задачу с условной программой для рисования, поддерживающей несколько типов фигур.
Для определения нового класса используется ключевое слово class, после которого идет идентификатор класса и блок кода в фигурных скобках. Как и все инструкции, такое определение требуется завершить точкой с запятой.
Блок кода может быть пустым. Например, компилируемая заготовка класса Shape для программы рисования выглядит так:
class Shape
|
По предыдущей части книги мы знаем, что фигурные скобки обозначают контекст или область видимости переменных. Когда такие блоки встречаются в определении функции, они задают её локальный контекст. Кроме него существует глобальный контекст, в котором определяются сами функции, а также глобальные переменные.
На этот раз скобки в определении класса задают новый вид контекста — контекст класса. Он является контейнером как для переменных, так и функций, описанных внутри класса.
Описание переменных для хранения свойств класса делается привычными нам инструкциями внутри блока (Shapes1.mq5).
class Shape
|
Здесь мы задекларировали некоторые поля, о которых рассуждали в теоретических разделах: координаты центра фигуры и цвет заливки.
После такого описания пользовательский тип Shape становится доступным в программе наравне со встроенными типами. В частности, мы можем создать переменную этого типа, и она будет содержать внутри указанные поля. Однако сделать что-либо с ними и даже убедиться, что они там есть, мы пока не можем.
void OnStart()
|
Члены класса по умолчанию являются закрытыми (приватными), и потому из других частей кода, внешних по отношению к классу, обращаться к ним нельзя. Это — принцип инкапсуляции в действии.
Если мы попытаемся вывести фигуру в журнал, результат нас разочарует по нескольким причинам.
Наиболее прямолинейный подход вызовет ошибку "объекты передаются только по ссылке" (мы это видели и со структурами):
Print(s); // 's' - objects are passed by reference only |
Объекты могут состоять из множества полей и из-за большого размера их неэффективно передавать по значению. Поэтому компилятор требует передавать параметры объектных типов по ссылке, а Print принимает значения.
Из раздела про параметры функций (см. раздел Параметры-значения и параметры-ссылки) мы знаем, что для описания ссылок используется символ '&'. Логично было бы предположить, что для получения ссылки на переменную (в данном случае, объект s типа Shape) необходимо поставить тот же знак перед её именем.
Print(&s); |
Эта инструкция благополучно компилируется и работает, но делает не совсем то, что ожидалось.
Программа в процессе выполнения выводит некое целое число, например 1 или 2097152 (оно будет, скорее всего, отличаться). Знак амперсанда перед именем переменной означает получение указателя на эту переменную, а не ссылки (в отличие от описания параметра функции).
Указатели будут подробно рассмотрены в отдельном разделе. Пока же отметим, что MQL5 не предоставляет прямого доступа к памяти, и указатель на объект является дескриптором, а по-простому — уникальным номером объекта (он назначается самим терминалом). Но даже если бы указатель вел на адрес в памяти (как это происходит в C++), это не обеспечило бы легального способа прочитать содержимое объекта.
Чтобы выводить содержимое объектов Shape в журнал или еще куда-либо, требуется функция-член класса. Назовем её toString: она должна вернуть строку с неким описанием объекта. Что в него выводить, мы можем решить позднее. Также зарезервируем метод для отрисовки фигуры — draw: пока он выступит декларацией будущего программного интерфейса объекта.
class Shape
|
Определение функций-методов делается привычным способом, с тем лишь отличием, что они находятся внутри блока кода, формирующего класс.
В будущем мы узнаем, как можно разнести объявление функции внутри блока класса и её определение за пределами блока. Этот подход часто используется для вынесения объявлений в заголовочный файл, и "скрытия" определений в mq5-файле. Это делает код более понятным (за счет того, что программный интерфейс представлен отдельно, в компактном виде, без реализации). Кроме того, это позволяет, при необходимости, распространять библиотеки программ в виде ex5-файлов (без основных исходных кодов, но с предоставлением заголовочного файла, который достаточен для вызова методов внешнего интерфейса).
Поскольку метод toString является частью класса, он имеет доступ к переменным и может преобразовать их в строку. Например,
string toString()
|
Однако сейчас функции toString и draw такие же закрытые, как и остальные поля. Нам необходимо сделать их доступными извне класса.