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

Добавлено 22 мая 2021 в 14:11
Последнее редактирование 20 июня 2021 в 05:30

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

Однако многие начинающие программисты пробуют что-то вроде этого:

double d = 10/4; // выполняет целочисленное деление, инициализируя d значением 2.0

Поскольку 10 и 4 принадлежат целочисленному типу int, целочисленного продвижения не происходит. Выполняется целочисленное деление 10/4, в результате получается значение 2, которое затем неявно преобразуется в 2.0 и присваивается переменной d! Скорее всего, это не то, что было задумано.

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

double d = 10.0 / 4.0; // выполняет деление с плавающей запятой,
                       // инициализируя d значением 2.5

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

int x { 10 };
int y { 4 };

// выполняет целочисленное деление, инициализируя d значением 2.0
double d = x / y;

Переменная d получит значение 2.0. Как сообщить компилятору, что мы хотим использовать деление с плавающей запятой вместо целочисленного деления? Суффиксы литералов не могут использоваться с переменными. Нам нужен способ преобразовать переменные одного (или обоих) операндов в тип с плавающей запятой, чтобы использовалось деление с плавающей запятой.

К счастью, C++ поставляется с рядом различных операторов приведения типов (чаще называемых приведениями или англоязычный термин «cast»), которые могут использоваться программистом для запроса компилятора на выполнение преобразования типа. Поскольку приведение типов является явным запросом программиста, эту форму преобразования типа часто называют явным преобразованием типа (в отличие от неявного преобразования типа, когда компилятор выполняет преобразование типа автоматически).

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

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

В этом уроке мы рассмотрим приведение типа в стиле C и статическое приведение.

Связанный контент


Динамические приведения мы рассмотрим в уроке «18.10 – Динамическое преобразование типов» после того, как рассмотрим другие необходимые темы.

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

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


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

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

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

Например:

#include <iostream>
 
int main()
{
    int x { 10 };
    int y { 4 };
 
    // преобразует x в double, поэтому получаем деление с плавающей запятой
    double d { (double)x / y }; 
    std::cout << d; // prints 2.5
 
    return 0;
}

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

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

// преобразует x в double, поэтому получаем деление с плавающей запятой
double d { double(x) / y };

Это работает идентично предыдущему примеру, но тут преимущество в том, что преобразуемое значение заключено в скобки (что упрощает определение того, что конвертируется).

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

Связанный контент...


Если вам интересно, в статье «За кулисами C++: статическое, реинтерпретирующее приведения типов и приведение типов в стиле C» есть дополнительная информация о том, как на самом деле работают приведения в стиле C.

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

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

static_cast

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

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

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

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

#include <iostream>
 
int main()
{
    int x { 10 };
    int y { 4 };
 
    // преобразовываем  x в double, и поэтому
    // получаем деление с плавающей запятой
    double d { static_cast<double>(x) / y };  
    std::cout << d; // prints 2.5
 
    return 0;
}

Основное преимущество 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) для публикации комментария требуется время на премодерацию.