-
Notifications
You must be signed in to change notification settings - Fork 0
CPP 17. Приведение типа в С : static_cast, dynamic_cast, const_cast, reinterpret_cast. Контейнерные классы и итераторы. Требования к контейнерам и итераторам. Категории итераторов. Операции над итераторами. Цикл for для работы с контейнерными объектами.
В С использовалась конструкция (новыйТип) ДанноеСтарогоТипа. Такое приведение небезопасно. Компилятор делает не задумываясь, не предупреждая нас возможно ли или не возможно.
В С++ указатель(или ссылка) на производный класс по умолчанию приводится к указателю на базовый. Если нужно обратное преобразование - проблема. В связи с этим появляется два оператора преобразования.
Один выполняется на этапе компиляции (static_cast), второй на этапе выполнения (dynamic_cast).
Для использование всех этих указателей надо
#include <memory>
static_cast используется для приведения родственных классов, находящихся на одной ветви. Так же используется для стандартных типов, для которых определен механизм явного приведения (но смысла нет).
-
тык
Class Base; Class FirstBranch: Base; Class FirstBranchContinue: FirstBranch; class SecondBranch: Base;
В таком раскладе static_cast позволит привести друг к другу Base, FirstBranch, FirstBranchContinue и Base, SecondBranch
FirstBranch и SecondBranch хоть и родственные, но статик кастом не приводятся.
-
Синтаксис:
Type* a = static_cast<Type*>(otherPtr)
-
Пример
Base* ptr = new FirstBranch; FirstBranch* ptr2 = static_cast<FirstBranch*>(ptr) FirstBranchContinue* ptr3 = static_cast<FirstBranchContinue*>(ptr2) // ну тут бан, UB. Но привидение произойдет
Проверяется на этапе компиляции.
dynamic_cast приводит к типу, только если указатель реально указывает на объект этого типа. Иначе возвращает nullptr
, в случае работы с ссылкой, возникает исключение bad_cast
. Требование: классы, к которым он приводит, должны быть полиморфны(virtual на методе или virtual деструктор).
-
Синтаксис такой же:
Type* a = dynamic_cast<Type*>(otherPtr).
-
Пример
Base* ptr = new FirstBranch; FirstBranch* ptr2 = dynamic_cast<FirstBranch*>(ptr) FirstBranchContinue* ptr3 = dynamic_cast<FirstBranchContinue*>(ptr2) // не приведется, вернет nullptr. Если работаем со ссылкой - возникает исключение bad_cast.
const_cast - убирает модификатор const. Некоторые компиляторы запрещают убирать const, если изначально объект был константным.
-
Пример
class B { f() // неконстантный метод } const B obj; const B* p = &obj; const_cast<B*>(p)->f(); // снимаем константность и вызываем неконстантный метод
reinterpret_cast - эквивалентен приведению в С
-
тык
class A {}; A* pA = new A; char* pByte = reinterpret_cast<char*>(pA); // Бандитизм, но reinterpret_cast позволяет
Контейнерные классы и итераторы. Работа с итераторами. Цикл for для работы с контейнерными объектами.
Общий механизм работы итераторов.
Итератор нужен для того, чтобы унифицировать работу с контейнерным классом.
Есть стандартный шаблон iterator, у которого есть специализации под разные виды работы с итераторами, у каждой есть свой тег:
- Итератор ввода - <input_iterator_tag> (можем менять то, на что он указывает)
- Итератор вывода - <output_iterator_tag> (не можем менять то, на что он указывает)
- Однонаправленный итератор на чтение и запись - <forward_iterator_tag> (
*
,->
,bool
,++
(префиксный и постфиксный инкремент ),!=
,==
) - Двунаправленный итератор на чтение и запись - <bidirectional_iterator_tag> (
*
,->
,bool
,++
(префиксный и постфиксный инкремент ),--
,!=
,==
) - Итератор произвольного доступа - <random_access_iterator_tag> (
*
,->
,bool
,++
(префиксный и постфиксный инкремент ),--
,!=
,==
,>
,<
,>=
,<=
,[]
,+=
,-=
,+
,-
(сложение и вычитание с целым числом))
Теги итераторов отличаются набором операций и оптимизациями под свою задачу.
Итератор может рассматривать контейнер как направленную последовательность, двунаправленную и последовательность произвольного доступа.
С помощью итератора просматриваем содержание контейнера.
Мы должны иметь возможность сравнивать итераторы и получать доступ к данным, на которые он указывает (операторы *
, ->
). В каждом итераторе надо минимум реализовать сравнение итераторов (!=
), доступ к данным (*
, ->
, bool
) и увеличение итератора (++
).
У контейнерного класса должны быть методы begin()
и end()
, возвращающие итератор на начало и конец (или за них), для универсального просмотра контейнерного класса.
Если они реализованы, то можно применять цикл foreach
(Тассов это называет так, а вообще правильно ”Range-based for loop”), который перебирает все элементы контейнера от begin()
до end()
:
-
Синтаксис
for (<тип>& <имя> : <container>) {}
-
Пример разложения
// Это: for (auto elem : obj) cout << elem; // Разложится в это: for (Iterator <Type> It = obj.begin(); It != obj.end(); ++It) { auto elem = *It; cout << elem; }
В контейнерном классе должны быть методы begin(
) и end()
, для итератора - !=
, ++
, *
Также могут быть реализованы в контейнере методы begin() const
, end() const
и cbegin()
, cend()
возвращающие константный итератор для константного объекта и константный для неконстантного соответственно