6.16 – Явное преобразование (приведение) типов данных и static_cast

Добавлено 22 мая 2021 в 14:11

В предыдущем уроке «6.15 – Неявное преобразование (принуждение) типов данных» мы обсуждали, что компилятор может неявно преобразовывать значение из одного типа данных в другой с помощью системы, называемой неявным преобразованием типов. Если вы хотите выполнить продвижение значения одного типа данных до более крупного аналогичного типа данных, то неявное преобразование типа использовать можно.

Однако многие начинающие программисты пробуют что-то вроде этого: float f = 10/4;. Поскольку 10 и 4 принадлежат целочисленному типу int, продвижения не происходит. Выполняется целочисленное деление 10/4, в результате получается значение 2, которое затем неявно преобразуется в 2.0 и присваивается переменной f!

В случае, когда вы используете литеральные значения (например, 10 или 4), замена одного или обоих целочисленных литеральных значений на литеральное значение с плавающей запятой (10.0 или 4.0) приведет к преобразованию обоих операндов в значения с плавающей запятой, и деление будет выполнено с использованием математики с плавающей запятой (и, таким образом, сохранится дробная часть).

Но что, если вы используете переменные? Рассмотрим этот случай:

int i1 { 10 };
int i2 { 4 };

// Инициализация списком помешает этому.
// Прямая инициализация используется здесь только для демонстрации.
float f(i1 / i2);

Переменная f получит значение 2. Как сообщить компилятору, что мы хотим использовать деление с плавающей запятой вместо целочисленного деления? Ответ заключается в использовании оператора приведения типа (чаще называемого просто приведением), чтобы сообщить компилятору о необходимости явного преобразования типа. Приведение представляет собой запрос программиста на явное преобразование типа.

Приведение типа

В C++ существует 5 различных видов приведений типа: приведения в стиле C, статические приведения, константные приведения, динамические приведения и реинтерпретирующие приведения. Последние четыре иногда называют именованными приведениями.

В этом уроке мы рассмотрим приведение типа в стиле C и статическое приведение. Рассмотрение динамических приведений мы отложим до тех пор, пока не рассмотрим указатели и наследование в будущих уроках.

Константных и реинтерпретирующих приведений обычно следует избегать потому, что они полезны только в редких случаях и могут быть вредны при неправильном использовании.

Предупреждение


Избегайте константных приведений и реинтерпретирующих приведений, если у вас нет веской причины их использовать.

Приведение типа в стиле C

В стандартном программировании на C приведение типов выполняется с помощью оператора (), при этом имя типа, в который необходимо преобразовать значение, помещается в круглые скобки. Например:

int i1 { 10 };
int i2 { 4 };
float f { (float)i1 / i2 };

В приведенном выше коде мы используем приведение в стиле C для типа с плавающей запятой, чтобы указать компилятору, преобразовать i1 в значение с плавающей запятой. Поскольку левый операнд у operator/ теперь вычисляется как значение с плавающей запятой, правый оператор также будет преобразован в значение с плавающей запятой, и деление будет выполняться с использованием деления с плавающей запятой вместо целочисленного деления!

C++ также позволяет вам использовать приведение в стиле C с синтаксисом, более похожим на вызов функций:

int i1 { 10 };
int i2 { 4 };
float f { float(i1) / i2 };

Это работает идентично предыдущему примеру.

Хотя приведение в стиле C выглядит как единое преобразование, на самом деле оно может выполнять множество различных преобразований в зависимости от контекста. Оно может включать в себя статическое приведение, константное приведение или реинтерпретирующее приведение (последних двух из которых, как мы упоминали выше, следует избегать). В результате приведение типов в стиле C подвержено риску непреднамеренного неправильного использования и не приводит к ожидаемому поведению, чего легко избежать, если вместо этого использовать приведение типов согласно C++.

В качестве отступления...


Если вам интересно, в этой статье есть дополнительная информация о том, как на самом деле работают приведения в стиле C.

Предупреждение

Избегайте использования приведений в стиле C.

static_cast

В C++ появился оператор приведения типов static_cast, который можно использовать для преобразования значения одного типа в значение другого типа.

Ранее вы видели, как static_cast используется для преобразования char в int, чтобы std::cout печатал его как целое число, а не как символ:

char c { 'a' };
std::cout << c << ' ' << static_cast<int>(c) << '\n'; // печатает 97

Оператор static_cast принимает в качестве входного одно значение и выводит то же значение, преобразованное в тип, указанный в угловых скобках. static_cast лучше всего использовать для преобразования одного базового типа в другой.

int i1 { 10 };
int i2 { 4 };
 
// преобразовываем int во float, и поэтому получаем деление 
// с плавающей запятой, а не целочисленное деление
float f { static_cast<float>(i1) / i2 }; 

Основное преимущество static_cast заключается в том, что он обеспечивает проверку типа во время компиляции, что затрудняет случайную ошибку. static_cast также (намеренно) менее мощный, чем приведение типов в стиле C, поэтому вы не можете случайно удалить const или выполнить другие вещи, которые вы, возможно, не собирались делать.

Лучшая практика


Используйте static_cast, когда вам нужно преобразовать значение из одного типа в другой.

Использование приведения типов, чтобы сделать неявное преобразование типов явным

Компиляторы часто жалуются на небезопасное неявное преобразование типа. Например, рассмотрим следующую программу:

int i { 48 };
char ch = i; // неявное преобразование

Преобразование int (4 байта) в char (1 байт) потенциально небезопасно (поскольку компилятор не может определить, будет ли целое число выходить за пределы диапазона char или нет), поэтому компилятор обычно выводит предупреждение. Если бы мы использовали инициализацию списком, компилятор выдал бы ошибку.

Чтобы обойти это, мы можем использовать статическое приведение для явного преобразования нашего числа int в char:

int i { 48 };
 
// явное преобразование из int в char, и далее этот char присваивается переменной ch
char ch { static_cast<char>(i) };

Когда мы делаем так, мы явно сообщаем компилятору, что это преобразование преднамеренное, и принимаем на себя ответственность за последствия (например, за превышение диапазона char, если оно произойдет). Поскольку выходное значение этого static_cast имеет тип char, присвоение переменной ch не генерирует несоответствия типов и, следовательно, никаких предупреждений.

В следующей программе компилятор обычно будет жаловаться на то, что преобразование double в int может привести к потере данных:

int i { 100 };
i = i / 2.5;

Чтобы сообщить компилятору, что мы преднамеренно хотим это сделать, можно написать так:

int i { 100 };
i = static_cast<int>(i / 2.5);

Небольшой тест

Вопрос 1

В чем разница между неявным и явным преобразованием типов?

Неявное преобразование типа выполняется всякий раз, когда ожидается один тип данных, но предоставляется другой тип данных.

Явное преобразование типа происходит, когда пользователь использует приведение типа для явного преобразования значения из одного типа в другой тип.

Теги

C++ / CppLearnCppstatic_castДля начинающихОбучениеПриведение типа в стиле CПриведение типовПрограммированиеСтатическое приведениеЯвное преобразование типа

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.