Содержание
Урок 1. Первый проект
Урок 2. Управление кнопками
Урок 3. Подключение LCD
Урок 4. Использование ШИМ
Урок 5. Таймеры
Урок 6.1. Статическая индикация
Урок 6.2. Динамическая индикация
Урок 7.1. Генерация звука
Урок 7.2. Генерация звука. Продолжение
Урок 8.1. Передача данных через UART
Урок 8.2. Передача данных через UART. Продолжение»
Урок 9. Передача данных через SPI
Урок 10. Изучение АЦП. Простой вольтметр
Урок 11. Получение синуса при помощи ШИМ
Урок 12. Измерение температуры
Урок 13. Внешние прерывания.
Урок 14. Использование отладчика
Урок 15.1. Управление инкрементальным энкодером
Урок 15.2. Управление громкостью, при помощи энкодера
Урок 16. Управление RGB светодиодом
Урок 17. Использование ИК
Урок 18.1. Знакомство с графическим дисплеем
Урок 18.2 Вывод изображения на графический дисплей
Урок 18.3 Вывод русскоязычного текста
Урок 19. Формирование сигнала, при помощи ЦАП (R2R)
Урок 20. Опрос матричной клавиатуры
Урок 21. Сторожевой таймер
Урок 22.1 Воспроизведение wav. Введение.
Урок 22.2 Воспроизведение wav. Продолжение.
Урок 23.1 Работа с внешней памятью
Урок 23.2 Работа с файловой системой Fat

Сегодня попробуем разобраться в том, как происходят преобразования типов в С++

Начнем с static_cast. Его проверки происходят на этапе компиляции. Кастовать можно базовые типы к друг другу

double y = 0.06;
int x = static_cast(y);

Так же можно преобразовывать связанные типы. Например, класс потомок к базовому типу — upcast, так и в обратную сторону от базового класса к потомку — downcast.

struct Base
{

};

struct Derived : public Base
{

};
int main(int argc, char *argv[])
{
    Derived derived;
    //upcast
    Base *base = static_cast(&derived);
    
    //downcast
    Derived *derived2 = static_cast(base);
    return 0;
}

Однако в данном случае нужно понимать, что проверяется только связанность типов. Возьмем пример ниже:

struct Base
{

};

struct Derived : public Base
{

};
int main(int argc, char *argv[])
{
    Base base;
    Derived *derived = static_cast(&base);
    
    return 0;
}

Мы создаем объект базового класса и его кастуем к классу потомку. Так как объекты связанные, то на этапе компиляции формально ошибок никаких не будет. Однако надо понимать, что в данных которые соответствуют Derived будет лежать мусор и использование его данных приведет к неопределенному поведению UB.

Следующий тип преобразований — const_cast. Может понадобиться для снятия константности у каких либо данных. Возьмем такой пример, который в теории может случиться на практике. У нас есть функция, которая на вход принимает тип char* и у нас есть строка string data.

void PrintData(char* data)
{
    cout << data << endl;
}

int main(int argc, char *argv[])
{
    string data = "hello";

    PrintData(data.c_str());
    return 0;
}

Хотя формально тут нет ошибки, но данный пример не скомпилируется, так как data.c_str() возвращает const char*, а не char*. Поэтому в данном случае можно воспользоваться const_cast.

PrintData(const_cast(data.c_str()));

Важно осознавать, что константные данные могут физически храниться в неизменяемой секции, например во флеше. Поэтому даже если обмануть компилятор и убрать проверку на этапе компиляции, то в рантайме это может привести к неопределенному поведению.

Еще один случай который нам нужно рассмотреть это reinterpret_cast. Допустим мы получили откуда нибудь из UART данные int data[] = {29, 5, 2022}; которые говорят нам о том, какой сейчас день, месяц и год. И допустим у нас есть структура, с которой можно удобно работать с датами struct Days. Раскладка по памяти одинаковая, а данные разные, как нам преобразовать одни данные в другие. Вот тут можно будет воспользоваться reinterpret_cast.

#include 

using namespace std;

struct Days
{
    int day;
    int month;
    int year;
};

void PrintData(Days& d)
{
    cout << "day=" << d.day << endl;
    cout << "month=" << d.month << endl;
    cout << "year=" << d.year << endl;
}

int main(int argc, char *argv[])
{
    int data[] = {29, 5, 2022};

    PrintData(reinterpret_cast(data));
    return 0;
}

Заключение. Сегодня мы рассмотрели 3 варианта преобразования типов. Про dynamic_cast уже упоминалось в статье про RTTI. Важно понимать, что reinterpret_cast, const_cast, static_cast проверяются в compile time, а dynamic_cast в runtime. Где и какое преобразование применять зависит от того какие необходимо производить манипуляции. Важно понимать что static_cast может сдвигать указатель при преобразованиях, а reinterpret_cast нет.

2 комментария: Type casting

  • Keroronsk:

    Зачем такие сложности, интересно. На С я пишу просто PrintData(&data); и всё работает и так…

  • admin:

    Некоторые из этих операций будут эквивалентны тому, что вы пишите на Си и более того взаимозаменяемы. До тех пор пока вы не будете использовать шаблонную магию. По сути static_cast и reinterpret_cast просто дополнительно говорят компилятору проверять, связаны эти типы или нет.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Счетчик
Яндекс.Метрика