- Основы ООП: абстракция
- Основы ООП: инкапусляция
- Основы ООП: наследование
- Основы ООП: полиморфизм
- Основы ООП: композиция (дизайн)
- Определение класса
- Права доступа
- Конструкторы: по умолчанию, параметрический, копирования
- Деструкторы
- Ссылка на себя: this
- Наследование
- Динамическое создание объектов: new и delete
- Указатели
- Виртуальные методы (virtual и override)
- Статические члены
- Вложенные типы, пространства имен и оператор контекста '::'
- Разнесение объявления и определения класса
- Абстрактные классы и интерфейсы
- Перегрузка операторов
- Приведение объектных типов: dynamic_cast и указатель void *
- Указатели, ссылки и const
- Управление наследованием: final и delete
Вложенные типы, пространства имен и оператор контекста '::'
Классы, структуры и объединения могут быть описаны не только в глобальном контексте, но и внутри другого класса или структуры. И даже более того: определение можно сделать внутри функции. Это позволяет описывать все сущности, необходимые для работы какого-либо класса или структуры, внутри соответствующего контекста и тем самым избежать потенциального конфликта имен.
В частности, в программе рисования структура для хранения координат Pair была до сих пор определена глобально. Когда программа разрастется, вполне возможна ситуация, что появится необходимость в другой сущности с именем Pair (особенно учитывая довольно общее название). Поэтому описание структуры желательно перенести внутрь класса Shape (Shapes6.mq5).
class Shape
|
Вложенные описания получают права доступа согласно указанным модификаторам секций. В данном случае мы сделали имя Pair публично доступным. Внутри класса Shape обращением с типом структуры Pair никак не меняется из-за переноса. Однако во внешнем коде необходимо указывать полностью квалифицированное имя, включающее имя внешнего класса (контекста), оператор выбора контекста '::' и непосредственно идентификатор внутренней сущности. Например, чтобы описать переменную с парой координат потребуется написать:
Shape::Pair coordinates(0, 0); |
Уровень вложенности при описании сущностей не ограничен, поэтому полностью квалифицированное имя может содержать идентификаторы множества уровней (контекстов), разделенные '::'. Например, мы могли бы все классы рисования заключить внутри внешнего класса Drawing, в секции public.
class Drawing
|
Тогда полные имена типов (например, для использования в OnStart или других внешних функциях) удлинились бы:
Drawing::Shape::Rect coordinates(0, 0);
|
С одной стороны это неудобно, но с другой является порой необходимостью в больших проектах с большим числом классов. В нашем маленьком проекте данный подход используется только для демонстрации технической возможности.
Для объединения логически связанных классов и структур в именованные группы MQL5 предоставляет более простой способ, чем включение их в "пустой" класс-обертку.
Пространство имен объявляется с помощью ключевого слова namespace, после которого идет имя и блок фигурных скобок, включающий все необходимые определения. Вот как выглядит та же программа рисования с использованием namespace:
namespace Drawing
|
Основных отличий два: внутреннее содержимое пространства всегда доступно публично (модификаторы доступа в нем не применимы) и после закрывающей фигурной скобки нет точки с запятой.
Добавим в класс Shape метод move, принимающий структуру Pair в качестве параметра:
class Shape
|
Тогда в функции OnStart можно организовать сдвиг всех фигур на заданную величину с помощью вызова данной функции:
void OnStart()
|
Обратите внимание, что типы Shape и Pair приходится описывать полными именами: Drawing::Shape и Drawing::Shape::Pair соответственно.
Блоков с одним и тем же названием пространства может встретиться несколько: всё их содержимое попадет в один логически единый контекст с указанным именем.
Идентификаторы, определенные в глобальном контексте, в частности все встроенные функции MQL5 API, также доступны через оператор выбора контекста, перед которым ничего не ставится. Например, вот как может выглядеть вызов функции Print:
::Print("Done!"); |
Когда вызов делается из какой-либо функции, определенной в глобальном контексте, необходимости в такой записи нет.
Необходимость может проявиться внутри какого-либо класса или структуры, если в них определен одноименный элемент (функция, переменная или константа). Например, добавим метод Print в класс Shape:
static void Print(string x)
|
Поскольку тестовые реализации метода draw в производных классах вызывают Print, то теперь они перенаправлены на данный метод Print: из нескольких одинаковых идентификаторов компилятор выбирает тот, что определен в более приближенном контексте. В данном случае, определение в базовом классе находится ближе к фигурам, чем глобальный контекст. В результате вывод в журнал из классов фигур будет подавлен.
При этом вызов Print из функции OnStart по-прежнему работает (потому что он вне контекста класса Shape).
void OnStart()
|
Чтобы "починить" отладочную печать в классах, нужно все вызовы Print предварить оператором выбора глобального контекста:
class Rectangle : public Shape
|