Синтаксис: приведение типов данных в C и C++
Cи-стиль приведения типов данных доступен и языке C++, но считается не самодостаточным по сравнению с приведением типов в C++. Так как Си-стиль приведения типов не так точен, как C++-стиль приведения и не так заметен. Cи-стиль приведения типов данных может быть использован для преобразования любого типа в любой другой тип, при этом неважно насколько это небезопасное преобразование, например, преобразование целого числа в указатель типа int ). Казалось бы, такое преобразование невозможно, однако компилятор с нами не согласен, но выполнит это приведение. И каков получится результат. ему совершенно не важно. Смотрим синтаксис приведения типов данных согласно Си-стилю:
- type — тип данных к которому преобразуется значение val
причем тип данных обязательно указывается в круглых скобочках.
Рассмотрим следующий пример использования Си-стиля приведения типа данных int к типу double .
Этот пример приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления. Если вы плохо знакомы с приведением типов данных, прочтите статью: Явное и неявное приведение типов данных.
static_cast — унарная операция приведения типов данных в С++
Операция static_cast доступна только в языке C++. static_cast может быть использована для преобразования одного типа в другой, но она не должна быть использована для выполнения недопустимого преобразования, например, преобразование значения в указатель или наоборот. Рекомендуется пользоваться операцией static_cast , нежели Cи-стилем приведения, потому что static_cast ограничивает недопустимое приведение типов и, следовательно — безопаснее.
Операция static_cast , грубо говоря, — это шаблон функции, в которой необходимо явно указать тип данных для преобразования, то есть задать параметр шаблона.
Итак, в треугольных скобочках указывается тип данных, к которому необходимо преобразовать значение value , которое стоит в круглых скобочках. Смотрим все тот же пример, который приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления.
dynamic_cast — унарная операция приведения типов данных в С++
Операция dynamic_cast доступна только в C++ и имеет смысл только, применительно к членам класса иерархии «полиморфных типов». Динамическое приведение типов данных может быть использовано для безопасного приведения указателя (или ссылки) на суперкласс, в указатель (или ссылку) на подкласс в иерархии классов. Если динамическое приведение типов — недопустимо, так как реальный тип объекта, указывает не на тот тип подкласса, приведение типов не выполнится.
Динамическое приведение указателя
При приведении указателя, в случае неудачи, dynamic_cast возвращает нулевой указатель NULL . Такое поведение обеспечивает быстрый способ определения, является ли данный объект частностью динамического типа.
Синтаксис указателя динамического приведения:
Динамическое приведение ссылки
При приведении ссылочной переменной, не возможно вернуть указатель, в случае неудачи. Поэтому будет вызвано исключение std::bad_cast (из заголовочного файла <typeinfo> ).
Чтобы безопасно пользоваться динамическим приведением, все вызовы dynamic_cast должны быть обрамлены в блок Try/Catch .
const_cast — константное приведение типов данных
Операция const_cast доступна только в C++. Константное приведение используются, чтобы константную переменную преобразовать в неконстантную. При этом, константным становится возвращаемое значение операции const_cast , а не сама переменная.
Следующий пример преобразует константный указатель на символьную строку в неконстантный указатель на эту же строку.
reinterpret_cast — операция приведения типов данных
Операция reinterpret_cast доступна только в C++ и является наименее безопасной формой приведения типов данных в С++, она позволяет интерпретировать значение в другой тип данных. reinterpret_cast не должна быть использована для приведения иерархии классов или преобразования константных переменных.
Рассмотрим пример использования этой операции приведения, например, чтобы преобразовать целое значение в указатель, нужно написать следующее:
Более подробно о приведении типов данных читайте в статье: Явное и неявное преобразование типов данных.
8.5 – Явное преобразование (приведение) типов данных и static_cast
В предыдущем уроке «8.1 – Неявное преобразование (принуждение) типов данных» мы обсуждали, что компилятор может неявно преобразовывать значение из одного типа данных в другой с помощью системы, называемой неявным преобразованием типов. Если вы хотите выполнить числовое продвижение значения одного типа данных до более крупного аналогичного типа данных, то неявное преобразование типа использовать можно.
Однако многие начинающие программисты пробуют что-то вроде этого:
Поскольку 10 и 4 принадлежат целочисленному типу int , целочисленного продвижения не происходит. Выполняется целочисленное деление 10/4, в результате получается значение 2, которое затем неявно преобразуется в 2.0 и присваивается переменной d ! Скорее всего, это не то, что было задумано.
В случае, когда вы используете литеральные значения (например, 10 или 4), замена одного или обоих целочисленных литеральных значений на литеральное значение с плавающей запятой (10.0 или 4.0) приведет к преобразованию обоих операндов в значения с плавающей запятой, и деление будет выполнено с использованием математики с плавающей запятой (и, таким образом, сохранится дробная часть).
Но что, если вы используете переменные? Рассмотрим этот случай:
Переменная d получит значение 2.0. Как сообщить компилятору, что мы хотим использовать деление с плавающей запятой вместо целочисленного деления? Суффиксы литералов не могут использоваться с переменными. Нам нужен способ преобразовать переменные одного (или обоих) операндов в тип с плавающей запятой, чтобы использовалось деление с плавающей запятой.
К счастью, C++ поставляется с рядом различных операторов приведения типов (чаще называемых приведениями или англоязычный термин «cast»), которые могут использоваться программистом для запроса компилятора на выполнение преобразования типа. Поскольку приведение типов является явным запросом программиста, эту форму преобразования типа часто называют явным преобразованием типа (в отличие от неявного преобразования типа, когда компилятор выполняет преобразование типа автоматически).
Приведение типа
В C++ существует 5 различных видов приведений типа: приведения в стиле C, статические приведения, константные приведения, динамические приведения и реинтерпретирующие приведения. Последние четыре иногда называют именованными приведениями.
В этом уроке мы рассмотрим приведение типа в стиле C и статическое приведение.
Связанный контент
Динамические приведения мы рассмотрим в уроке «18.10 – Динамическое преобразование типов» после того, как рассмотрим другие необходимые темы.
Константных и реинтерпретирующих приведений обычно следует избегать потому, что они полезны только в редких случаях и могут быть вредны при неправильном использовании.
Предупреждение
Избегайте константных приведений и реинтерпретирующих приведений, если у вас нет веской причины их использовать.
Приведение типа в стиле C
В стандартном программировании на C приведение типов выполняется с помощью оператора () , при этом имя типа, в который необходимо преобразовать значение, помещается в круглые скобки. Вы всё еще можете увидеть, что они используются в коде, преобразованном из C.
В приведенном выше коде мы используем приведение в стиле C для типа с плавающей запятой, чтобы указать компилятору, преобразовать x в double . Поскольку левый операнд у operator/ теперь вычисляется как значение с плавающей запятой, правый оператор также будет преобразован в значение с плавающей запятой, и деление будет выполняться с использованием деления с плавающей запятой вместо целочисленного деления!
C++ также позволяет вам использовать приведение в стиле C с синтаксисом, более похожим на вызов функций:
Это работает идентично предыдущему примеру, но тут преимущество в том, что преобразуемое значение заключено в скобки (что упрощает определение того, что конвертируется).
Хотя приведение в стиле C выглядит как единое преобразование, на самом деле оно может выполнять множество различных преобразований в зависимости от контекста. Оно может включать в себя статическое приведение, константное приведение или реинтерпретирующее приведение (последних двух из которых, как мы упоминали выше, следует избегать). В результате приведение типов в стиле C подвержено риску непреднамеренного неправильного использования и не приводит к ожидаемому поведению, чего легко избежать, если вместо этого использовать приведение типов согласно C++.
Связанный контент.
Если вам интересно, в статье «За кулисами C++: статическое, реинтерпретирующее приведения типов и приведение типов в стиле C» есть дополнительная информация о том, как на самом деле работают приведения в стиле C.
Лучшая практика
Избегайте использования приведений в стиле C.
static_cast
В C++ появился оператор приведения типов static_cast , который можно использовать для преобразования значения одного типа в значение другого типа.
Ранее вы видели, как static_cast используется для преобразования char в int , чтобы std::cout печатал его как целое число, а не как символ:
Оператор static_cast принимает в качестве входного одно значение и выводит то же значение, преобразованное в тип, указанный в угловых скобках. static_cast лучше всего использовать для преобразования одного базового типа в другой.
Основное преимущество static_cast заключается в том, что он обеспечивает проверку типа во время компиляции, что затрудняет случайную ошибку. static_cast также (намеренно) менее мощный, чем приведение типов в стиле C, поэтому вы не можете случайно удалить const или выполнить другие вещи, которые вы, возможно, не собирались делать.
Лучшая практика
Используйте static_cast , когда вам нужно преобразовать значение из одного типа в другой.
Использование приведения типов, чтобы сделать неявное преобразование типов явным
Компиляторы часто жалуются на небезопасное (сужающее) неявное преобразование типа. Например, рассмотрим следующую программу:
Преобразование int (4 байта) в char (1 байт) потенциально небезопасно (поскольку компилятор не может определить, будет ли целое число выходить за пределы диапазона char или нет), поэтому компилятор обычно выводит предупреждение. Если бы мы использовали инициализацию списком, компилятор выдал бы ошибку.
Чтобы обойти это, мы можем использовать статическое приведение для явного преобразования нашего числа int в char :
Когда мы делаем так, мы явно сообщаем компилятору, что это преобразование преднамеренное, и принимаем на себя ответственность за последствия (например, за превышение диапазона char , если оно произойдет). Поскольку выходное значение этого static_cast имеет тип char , присвоение переменной ch не генерирует несоответствия типов и, следовательно, никаких предупреждений или ошибок.
В следующей программе компилятор обычно будет жаловаться на то, что преобразование double в int может привести к потере данных:
Чтобы сообщить компилятору, что мы преднамеренно хотим это сделать, можно написать так:
Небольшой тест
Вопрос 1
В чем разница между неявным и явным преобразованием типов?
Неявное преобразование типа выполняется всякий раз, когда ожидается один тип данных, но предоставляется другой тип данных.
Явное преобразование типа происходит, когда пользователь использует приведение типа для явного преобразования значения из одного типа в другой тип.
Еще раз про приведение типов в языке С++ или расстановка всех точек над cast
Этот пост попытка кратко оформить все, что я читал или слышал из разных источников про операторы приведения типов в языке C++. Информация ориентирована в основном на тех, кто изучает C++ относительно недолго и, как мне кажется, должна помочь понять cпецифику применения данных операторов. Старожилы и гуру С++ возможно помогут дополнить или скорректировать описанную мной картину. Всех интересующихся приглашаю под кат.
Приведение типов в стиле языка C (C-style cast)
Приведение типов в стиле языка C может привести выражение любого типа к любому другому типу данных (исключение это приведение пользовательских типов по значению, если не определены правила их приведения, а также приведение вещественного типа к указателю или наоборот). К примеру, unsigned int может быть преобразован к указателю на double. Данный метод приведения типов может быть использован в языке C++. Однако, метод приведения типов в стиле языка C не делает проверки типов на совместимость, как это могут сделать static_cast и dynamic_cast на этапе компиляции и на этапе выполнения соответственно. При этом все, что умеют const_cast и reinterpret_cast данный метод приведения типов делать может.
Общий вид приведения:
(new_type)exp
, где new_type – новый тип, к которому приводим, а exp – выражение, которое приводится к новому типу.
Т.к. данный оператор не имеет зарезервированного ключевого слова (например, static_cast) найти все места приведения типов в тексте программы будет не очень удобно, если это потребуется.
const_cast
Оператор приведения const_cast удаляет или добавляет квалификаторы const и volatile с исходного типа данных (простые типы, пользовательские типы, указатели, ссылки). Например, был const int, а после преобразования стал int или наоборот. Квалификаторы const и volatile называют cv-квалификаторы (cv-qualifiers). Данные квалификаторы указываются перед именами типов. Как ни трудно догадаться квалификатор const задает константность, т.е. защищает переменную от изменения. Квалификатор volatile говорит о том, что значение переменной может меняться без явного выполнения присваивания. Это обеспечивает защиту от оптимизации компилятором операций с данной переменной.
Общий вид приведения:
const_cast<new_type>(exp)
Дополнительный пример от пользователя 5nw
Квалификаторы const и volatile можно удалить или добавить только с помощью оператора приведения const_cast и приведения типов в стиле языка C. Другие операторы приведения типов не влияют на квалификаторы const и volatile (reinterpret_cast, static_cast, dynamic_cast).
reinterpret_cast
Оператор приведения reinterpret_cast используется для приведения несовместимых типов. Может приводить целое число к указателю, указатель к целому числу, указатель к указателю (это же касается и ссылок). Является функционально усеченным аналогом приведения типов в стиле языка С. Отличие состоит в том, что reinterpret_cast не может снимать квалификаторы const и volatile, а также не может делать небезопасное приведение типов не через указатели, а напрямую по значению. Например, переменную типа int к переменной типа double привести при помощи reinterpret_cast нельзя.
Общий вид приведения:
reinterpret_cast<new_type>(exp)
static_cast
Оператор приведения static_cast применяется для неполиморфного приведения типов на этапе компиляции программы. Отличие static_cast от приведения типов в стиле языка C состоит в том, что данный оператор приведения может отслеживать недопустимые преобразования, такие как приведение указателя к значению или наоборот (unsigned int к указателю на double не приведет), а также приведение указателей и ссылок разных типов считается корректным только, если это приведение вверх или вниз по одной иерархии наследования классов, либо это указатель на void. В случае фиксации отклонения от данных ограничений будет выдана ошибка при компиляции программы. При множественном наследовании static_cast может вернуть указатель не на исходный объект, а на его подобъект.
Общий вид приведения:
static _cast<new_type>(exp)
dynamic_cast
Оператор приведения dynamic_cast применяется для полиморфного приведения типов на этапе выполнения программы (класс считается полиморфным, если в нем есть хотя бы одна виртуальная функция). Если указатель, подлежащий приведению, ссылается на объект результирующего класса или объект класса производный от результирующего то приведение считается успешным. То же самое для ссылок. Если приведение невозможно, то на этапе выполнения программы будет возвращен NULL, если приводятся указатели. Если приведение производится над ссылками, то будет сгенерировано исключение std::bad_cast. Несмотря на то, что dynamic_cast предназначен для приведения полиморфных типов по иерархии наследования, он может быть использован и для обычных неполиморфных типов вверх по иерархии. В этом случае ошибка будет получена на этапе компиляции. Оператор приведения dynamic_cast может приводить указатель на полиморфный тип к указателю на void, но не может приводить указатель на void к другому типу. Способность dynamic_cast приводить полиморфные типы обеспечивается системой RTTI (Run-Time Type Identification), которая позволяет идентифицировать тип объекта в процессе выполнения программы. При множественном наследовании dynamic_cast может вернуть указатель не на исходный объект, а на его подобъект.
Для чего нужен static_cast, как он работает и где его применяют?
Для чего нужен static_cast, как он работает и где его применяют?
![]()
У static_cast очень много различных применений. Его идея состоит в следующем: это ограниченный по мощи C-style cast. Ограничение нужно потому, что C-style cast может привести что угодно к чему угодно (ну, почти), и тем самым может скрыть ошибку. Например, вы можете случайно закастить const char* в char* , получив крэш в некоторых системах с аппаратной поддержкой const-памяти. static_cast вам такого не позволит.
Большую часть времени, когда вы хотите сделать явное преобразование типов (а я надеюсь, это бывает достаточно редко), вы хотите именно static_cast .
Формальный список всего, что умеет static_cast , очень большой, я приведу лишь наиболее важные вещи, которые он умеет (а также которые он не умеет):
- Преобразование указателя на родительский класс к указателю на дочерний класс. Объект по указателю обязан быть правильного дочернего класса, иначе undefined behaviour. Если вы не уверены и хотите проверить, тот ли подкласс у объекта, пользуйтесь dynamic_cast (он специально для этого предназначен).
- Преобразования между числовыми типами. int , long , char , unsigned int — все их можно кастить друг в друга при помощи static_cast .
- Можно закастить любое выражение в void . Результат будет вычислен и отброшен (но побочные эффекты, разумеется, выполнятся).
- static_cast может привести константу nullptr к любому типу-указателю. Обычно это не нужно и можно полагаться на неявное преобразование типов, но иногда (например, для выбора нужной перегрузки функции) такое может пригодится.
- Преобразование между указателями на в принципе несовместимые типы. Например, указатель на double нельзя привести к указателю на int . Для трюков с нарушением type safety пользуйтесь reinterpret_cast .
- Указатели на типы, а также сами типы с несовместимыми атрибутами const и/или volatile . Если вам необходимо нарушить const-корректность, пользуйтесь const_cast .
- Разумеется, вы не сможете привести указатель на функцию-член к указателю на обычную функцию, или указатель на код к указателю на данные. Для подобных грязных хаков пользуйтесь reinterpret_cast .
Ещё одной причиной использования static_cast (как и других C++-специфических преобразований типов) является лёгкость его поиска в исходниках, как глазами, так и поисковыми утилитами. Сишный каст (особенно его функциональную разновидность) очень легко пропустить в коде.
Для сравнения, «привычное» преобразование типов (C-style cast) равносильно следующей последовательности: