Skip to content

CPP 17. Приведение типа в С : static_cast, dynamic_cast, const_cast, reinterpret_cast. Контейнерные классы и итераторы. Требования к контейнерам и итераторам. Категории итераторов. Операции над итераторами. Цикл for для работы с контейнерными объектами.

Kozlitin Maxim edited this page Jun 14, 2023 · 3 revisions

Приведение типа в С++: static_cast, dynamic_cast, const_cast, reinterpret_cast.

В С использовалась конструкция (новыйТип) ДанноеСтарогоТипа. Такое приведение небезопасно. Компилятор делает не задумываясь, не предупреждая нас возможно ли или не возможно.

В С++ указатель(или ссылка) на производный класс по умолчанию приводится к указателю на базовый. Если нужно обратное преобразование - проблема. В связи с этим появляется два оператора преобразования.

Один выполняется на этапе компиляции (static_cast), второй на этапе выполнения (dynamic_cast).

Для использование всех этих указателей надо

#include <memory>

static_cast

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

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_cast - убирает модификатор const. Некоторые компиляторы запрещают убирать const, если изначально объект был константным.

  • Пример

    class B
    {
       f() // неконстантный метод
    }
    const B obj;
    const B* p = &obj;
    const_cast<B*>(p)->f(); // снимаем константность и вызываем неконстантный метод

reinterpret_cast

reinterpret_cast - эквивалентен приведению в С

  • тык

    class A {};
    A* pA = new A;
    char* pByte = reinterpret_cast<char*>(pA); // Бандитизм, но reinterpret_cast позволяет

Контейнерные классы и итераторы. Работа с итераторами. Цикл for для работы с контейнерными объектами.

Общий механизм работы итераторов.

Итератор нужен для того, чтобы унифицировать работу с контейнерным классом.

Есть стандартный шаблон iterator, у которого есть специализации под разные виды работы с итераторами, у каждой есть свой тег:

  1. Итератор ввода - <input_iterator_tag> (можем менять то, на что он указывает)
  2. Итератор вывода - <output_iterator_tag> (не можем менять то, на что он указывает)
  3. Однонаправленный итератор на чтение и запись - <forward_iterator_tag> (*, ->, bool, ++(префиксный и постфиксный инкремент ), !=, ==)
  4. Двунаправленный итератор на чтение и запись - <bidirectional_iterator_tag> (*, ->, bool, ++(префиксный и постфиксный инкремент ), --, !=, ==)
  5. Итератор произвольного доступа - <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()

возвращающие константный итератор для константного объекта и константный для неконстантного соответственно

Clone this wiki locally