Урок №55. Неявное преобразование типов данных

  Юрий  | 

  |

  Обновл. 13 Сен 2021  | 

 82781

 ǀ   7 

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

Преобразование типов

Разные типы данных могут представлять одно значение по-разному, например, значение 4 типа int и значение 4.0 типа float хранятся как совершенно разные двоичные шаблоны.

И как вы думаете, что произойдет, если сделать следующее:

Здесь компилятор не сможет просто скопировать биты из значения 4 типа int и переместить их в переменную f типа float. Вместо этого ему нужно будет преобразовать целое число 4 в число типа с плавающей точкой, которое затем можно будет присвоить переменной f.

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

Случай №1: Присваивание или инициализация переменной значением другого типа данных:

Случай №2: Передача значения в функцию, где тип параметра — другой:

Случай №3: Возврат из функции, где тип возвращаемого значения — другой:

Случай №4: Использование бинарного оператора с операндами разных типов:

Во всех этих случаях (и во многих других) C++ будет использовать преобразование типов.

Есть 2 основных способа преобразования типов:

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

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

Неявное преобразование типов


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

Есть 2 основных способа неявного преобразования типов:

   числовое расширение;

   числовая конверсия.

Числовое расширение

Когда значение из одного типа данных конвертируется в другой тип данных побольше (по размеру и по диапазону значений), то это называется числовым расширением. Например, тип int может быть расширен в тип long, а тип float может быть расширен в тип double:

В языке C++ есть два варианта расширений:

   Интегральное расширение (или «целочисленное расширение»). Включает в себя преобразование целочисленных типов, меньших, чем int (bool, char, unsigned char, signed char, unsigned short, signed short) в int (если это возможно) или unsigned int.

   Расширение типа с плавающей точкой. Конвертация из типа float в тип double.

Интегральное расширение и расширение типа с плавающей точкой используются для преобразования «меньших по размеру» типов данных в типы int/unsigned int или double (они наиболее эффективны для выполнения разных операций).

Важно: Числовые расширения всегда безопасны и не приводят к потере данных.

Числовые конверсии


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

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

Есть много правил по выполнению числовой конверсии, но мы рассмотрим только основные.

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

В этом примере мы присвоили огромное целочисленное значение типа int переменной типа char (диапазон которого составляет от -128 до 127). Это приведет к переполнению и следующему результату:

48

Однако, если число подходит по диапазону, конвертация пройдет успешно. Например:

Здесь мы получим ожидаемый результат:

3
0.1234

В случаях со значениями типа с плавающей точкой могут произойти округления из-за худшей точности в меньших типах. Например:

В этом случае мы наблюдаем потерю в точности, так как точность типа float меньше, чем типа double:

0.123456791

Конвертация из типа int в тип float успешна до тех пор, пока значения подходят по диапазону. Например:

Результат:

10

Аналогично, конвертация из float в int успешна до тех пор, пока значения подходят по диапазону. Но следует помнить, что любая дробь отбрасывается. Например:

Дробная часть значения (.6) игнорируется и выводится результат:

4

Обработка арифметических выражений

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

   Если операндом является целое число меньше (по размеру/диапазону) типа int, то оно подвергается интегральному расширению в int или в unsigned int.

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

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

   long double (самый высокий);

   double;

   float;

   unsigned long long;

   long long;

   unsigned long;

   long;

   unsigned int;

   int (самый низкий).

Мы можем использовать оператор typeid (который находится в заголовочном файле typeinfo), чтобы узнать решающий тип в выражении.

В следующем примере у нас есть две переменные типа short:

Поскольку значениями переменных типа short являются целые числа и тип short меньше (по размеру/диапазону) типа int, то он подвергается интегральному расширению в тип int. Результатом сложения двух int-ов будет тип int:

int 9

Рассмотрим другой случай:

Здесь short подвергается интегральному расширению в int. Однако int и double по-прежнему не совпадают. Поскольку double находится выше в иерархии типов, то целое число 2 преобразовывается в 2.0 (тип double), и сложение двух чисел типа double дадут число типа double:

double 5

С этой иерархией иногда могут возникать интересные ситуации, например:

Ожидается, что результатом выражения 5u − 10 будет -5, поскольку 5 − 10 = -5, но результат:

4294967291

Здесь значение signed int (10) подвергается расширению в unsigned int (которое имеет более высокий приоритет), и выражение вычисляется как unsigned int. А поскольку unsigned — это только положительные числа, то происходит переполнение, и мы имеем то, что имеем.

Это одна из тех многих веских причин избегать использования типа unsigned int вообще.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (482 оценок, среднее: 4,91 из 5)
Загрузка...

Комментариев: 7

  1. Денис:

    Добрый день, мб я что-то не так понимаю, но мне кажется есть ошибка в логике описанного, а именно в числовых расширениях. По контексту говорится , что числовые расширения делятся на два типа :
    1) Интегральные — преобразование меньшего значения чем int (bool, char, unsigned char, signed char, unsigned short, signed short) в int.
    2) Конвертация чисел типов с плавающей точкой — float в double.
    Но выше же приводиться пример с расширением int в long — это тогда какой тип расширения ? Или все таки существуют неявные преобразования типа расширение , которое уже делиться на числовые, интегральные и с плавающей точкой?
    Спасибо!

    1. Алекс:

      "Интегральное расширение и расширение типа с плавающей точкой используются для преобразования «меньших по размеру» типов данных в типы int/unsigned int или double (они наиболее эффективны для выполнения разных операций)." (цитата из текста)

      Расширение int -> long можно считать подтипом интегрального расширения, так как один целочисленный тип расширяется в другой, который имеет больший диапазон значений. Это происходит редко, так как обычно достаточно расширения в int, поэтому в скобках написано то, что написано. 🙂

  2. zashiki:

    "конвертация — "double d = 4; // конвертируем 4 (тип int) в double"
    А почему здесь называется конвертацией(т.е. от большего к меньшему), а не расширением?

    ведь double больше int

    1. Кекс:

      Вы путаете понятия "конвертация" и "конверсия"

  3. Алексей:

    Важно: Числовые расширения всегда безопасны и не приводят к потере данных.

    Это надо было бы прочитать создателям Civilization, об этом уже говорили ранее.

  4. Никита:

    Так а зачем тогда существует тип short int , если при любой арифметической операции, он всеравно превратится в int?

    1. Евгений:

      short int занимает в памяти в два раза меньше места.

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

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