Расширяющее преобразование — это преобразование, при котором значение одного типа преобразуется в другой тип равного или большего размера. Сужающее преобразование — это преобразование, при котором значение одного типа преобразуется в другой тип меньшего размера. Таблицы в этом разделе описывают характеристики обоих типов преобразований.
Расширяющие преобразования
В следующей таблице описаны расширяющие преобразования, которые можно выполнять без потери данных.
Type
Можно без потери данных преобразовать в
Byte
UInt16, Int16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal
SByte
Int16, Int32, Int64, Single, Double, Decimal
Int16
Int32, Int64, Single, Double, Decimal
UInt16
UInt32, Int32, UInt64, Int64, Single, Double, Decimal
Char
UInt16, UInt32, Int32, UInt64, Int64, Single, Double, Decimal
Int32
Int64, Double, Decimal
UInt32
Int64, UInt64, Double, Decimal
Int64
Decimal
UInt64
Decimal
Single
Double
Некоторые расширяющие преобразования к типу Single или Double могут привести к потере точности. В следующей таблице описаны расширяющие преобразования, которые могут привести к частичной потере данных.
— Привет, Амиго! Тема сегодняшней лекции – расширение и сужение типов. С расширением и сужением примитивных типов ты познакомился уже давно. На 10 уровне. Сегодня мы расскажем, как это работает для ссылочных типов, т.е. для объектов классов.
Код
Описание
Тут мы видим три объявленных класса: животное, кот и тигр. Кот наследуется от Животного. А Тигр от Кота.
Объект класса Tiger всегда можно спокойно присвоить переменной с типом класса-родителя. Для класса Tiger – это Cat, Animal и Object.
Теперь рассмотрим, что же такое расширение и сужение типов.
Если в результате присваивания мы двигаемся по цепочке наследования вверх (к типу Object), то это — расширение типа (оно же — восходящее преобразование или upcasting), а если вниз, к типу объекта, то это — сужение типа (оно же — нисходящее преобразование или downcasting).
Движение вверх по цепочке наследования называется расширением, поскольку оно приводит к более общему типу. Но при этом теряется возможность вызвать методы, которые были добавлены в класс при наследовании.
Код
Описание
При сужении типа, нужно использовать оператор преобразования типа, то есть мы выполняем явное преобразование.
При этом Java-машина выполняет проверку, а действительно ли данный объект унаследован от Типа, к которому мы хотим его преобразовать.
Преобразование и приведение примитивных типов в Java
Иногда возникают ситуации, когда необходимо переменной одного типа присвоить значение переменной другого типа. Например:
1. Автоматическое преобразование типов Java
Рассмотрим сначала автоматическое преобразование. Если оба типа совместимы, их преобразование будет выполнено в Java автоматически. Например, значение типа byte всегда можно присвоить переменной типа int, как это показано в предыдущем примере.
Для автоматического преобразования типа должно выполняться два условия:
В этом случае происходит преобразование с расширением.
Следующая схема показывает расширяющее преобразование в Java:
Сплошные линии обозначают преобразования, выполняемые без потери данных. Штриховые линии говорят о том, что при преобразовании может произойти потеря точности.
2. Приведение типов Java
где параметр целевой_тип обозначает тип, в который нужно преобразовать указанное значение.
Например, в следующем фрагменте кода тип int приводится к типу byte :
Рассмотрим пример преобразования значений с плавающей точкой в целые числа. В этом примере дробная часть значения с плавающей точкой просто отбрасывается (операция усечения):
При приведении более емкого целого типа к менее емкому старшие биты просто отбрасываются:
При приведении более емкого значения с плавающей точкой в целое число происходит усечение и отбрасывание старших битов:
3. Автоматическое продвижение типов в выражениях
Помимо операций присваивания, определенное преобразование типов может выполняться и в выражениях.
В языке Java действуют следующие правила:
Следующий пример аналогичен предыдущему, но используется операция совмещенного присваивание, в которой приведение происходит автоматически:
8.2 – Продвижение целочисленных типов и типов с плавающей запятой
В уроке «4.3 – Размеры объектов и оператор sizeof » мы отметили, что в C++ есть гарантии минимального размера для каждого из базовых типов. Однако фактический размер этих типов может варьироваться в зависимости от компилятора и архитектуры.
Напоминание
Количество бит, которое использует тип данных, называется его шириной. Более широкий тип данных – это тот, который использует больше битов, а более узкий тип данных – тот, который использует меньше битов.
Но что происходит, когда мы хотим, чтобы наш 32-битный процессор изменял 8-битное значение (например, символ) или 16-битное значение? Некоторые 32-битные процессоры (например, серия x86) могут напрямую манипулировать 8-битными и 16-битными значениями. Однако часто это происходит медленнее, чем манипулирование 32-битными значениями! Другие 32-разрядные процессоры (например, PowerPC) могут работать только с 32-разрядными значениями, и для манипулирования более узкими значениями необходимо использовать дополнительные приемы.
Числовое продвижение
Поскольку C++ предназначен для портируемости и производительности в широком диапазоне архитектур, разработчики языка не хотели предполагать, что заданный CPU сможет эффективно манипулировать значениями, которые были уже, чем естественный размер данных для этого CPU.
Чтобы помочь решить эту проблему, C++ определяет категорию преобразований типов, неофициально называемую числовым продвижением. Числовое продвижение (расширяющее преобразование типа) – это преобразование более узкого числового типа (например, char ) в более широкий числовой тип (обычно int или double ), который может быть эффективно обработан и с меньшей вероятностью приведет к переполнению.
Все числовые продвижения сохраняют значения, что означает, что все значения в исходном типе могут быть представлены без потери данных или точности в новом типе. Поскольку такие продвижения безопасны, компилятор будет свободно использовать числовое продвижение по мере необходимости и при этом не будет выдавать предупреждение.
Числовое продвижение снижает избыточность
Цифровое продвижение решает и другую проблему. Рассмотрим случай, когда вы хотели написать функцию для печати значения типа int :
Здесь на помощь приходит числовое продвижение: мы можем писать функции с параметрами int и/или double (например, функция printInt() выше). Затем этот же код можно вызвать с аргументами типов, которые можно численно продвигать для совпадения с типами параметров функции.
Категории числового продвижения
Числовые правила продвижения делятся на две подкатегории: целочисленное продвижение и продвижение типов с плавающей запятой.
Продвижение типов с плавающей запятой
Начнем с более простого.
Целочисленные продвижения
Правила целочисленного продвижения более сложны.
Используя правила целочисленного продвижения, можно сделать следующие преобразования:
Есть еще несколько других неотъемлемых правил продвижения, которые используются реже. Их можно найти по адресу https://en.cppreference.com/w/cpp/language/implicit_conversion#Integral_promotion.
Не все преобразования, сохраняющие значения, являются числовыми продвижениями
Некоторые преобразования типов с сохранением значений (например, int в long или int в double ) в C++ не считаются числовыми продвижениями (это числовые преобразования, которые мы вскоре рассмотрим в уроке «8.3 – Числовые преобразования»).
Различие носит в основном академический характер. Однако в некоторых случаях компилятор предпочитает числовые продвижения числовым преобразованиям. Мы увидим примеры, в которых это имеет значение, когда мы рассмотрим разрешение перегрузки функций (в следующем уроке «8.11 – Разрешение перегрузки функций и неоднозначные совпадения»).
Каждое выражение в Java имеет тип, который определяется структурой выражения и типами составляющих его операндов (констант, переменных и методов). Однако, иногда нам может потребоваться явное преобразование выражения в другой тип. Кроме того, в некоторых ситуациях исполняющая система Java сама неявно проводит такие преобразования.
Преобразование типа Т1 в тип T2 позволяет выражению типа T1 трактоваться в период компиляции как выражение типа T2. В одних случаях это чисто синтаксическая конструкция, не влияющая на генерируемый код, в других преобразование типа требует дополнительных действий в период выполнения по изменению значения выражения или дополнительных проверок правильности применяемого преобразования. Примеры:
Далее в этой главе приведена классификация всех возможных преобразований типов, а затем классификация контекстов в которой они могут использоваться.
5.4.1.1. Расширяющие преобразования чисел
Расширяющие преобразования чисел это преобразования числового типа в «больший» числовой тип, которые считаются безопасными, т. к. не приводят к потере величины преобразуемого значения. Такими преобразованиями в Java являются:
В действительности, преобразование целого значения в плавающее может привести к потере точности, т. е. к потере значащих цифр. Так, следующий пример
5.4.1.2. Сужающие преобразования чисел
Сужающие преобразования чисел это преобразования числового типа в «меньший» числовой тип, которые могут привести как к потере величины, так и к потере точности. Такими преобразованиями в Java являются:
Мы не будем здесь подробно рассматривать правила, по которым происходят эти преобразования, поскольку интуитивно они понятны, а формально достаточно громоздки. При их применении важно помнить, что Java, в отличие от других языков, не генерирует ошибок при переполнении (overflow) или потере значения (underflow), поэтому контроль за корректностью преобразований полностью ложится на программиста.
5.4.1.3. Расширяющие преобразования ссылок
Расширяющие преобразования ссылок это преобразования производных ссылочных типов в типы их предков, которые не требуют никаких действий на этапе исполнения и никогда не генерируют ошибок. Такими преобразованиями в Java являются:
5.4.1.4. Сужающие преобразования ссылок
Сужающие преобразования ссылок это преобразования производных ссылочных типов в типы их потомков. Эти преобразования требуют проверки своей легитимности на этапе исполнения и могут генерировать исключение ClassCastException. Такими преобразованиями в Java являются:
5.4.1.5. Преобразования в строки
Любое выражение в Java, включая null, может быть преобразовано в тип String.
5.4.1.6. Недопустимые преобразования
Следующие преобразования типов в Java запрещены:
5.4.2. Контексты преобразований
5.4.2.1. Преобразование при присваивании
Преобразование при присваивании происходит, когда значение выражения присваивается переменной. При этом тип выражения преобразуется к типу переменной. При присваивании всегда возможны расширяющие преобразования типов (как числовых, так и ссылочных). Сужающее преобразование возможно только при соблюдении следующих условий:
Например, оператор byte x = 123; допустим, поскольку константа 123 (имеющая тип int) лежит в диапазоне допустимых значений типа byte.
Если тип выражения не может быть преобразован к типу переменной, то компилятор генерирует ошибку. В остальных случаях мы говорим, что тип выражения совместим по присваиванию с типом переменной. Так, следующий фрагмент
приведет к генерации ошибки, поскольку типы char и short несовместимы по присваиванию согласно данных выше определениям (первый реализован 16-битовыми словами без знака, а второй со знаком).
5.4.2.2. Преобразование аргументов метода
Преобразование аргументов метода происходит, когда фактические значения аргументов преобразуется к типу параметров метода или конструктора при его вызове. При этом всегда возможны расширяющие преобразования типов (как числовых, так и ссылочных) и недопустимы сужающие преобразования. Причины последнего запрета можно пояснить следующим примером:
5.4.2.3. Преобразование в строку
Преобразование в строку происходит только в одном случае: когда бинарная операция + применяется к двум операндам, один из которых имеет тип String. В этой ситуации второй операнд также преобразуется к типу String, и результатом операции является конкатенация полученных строк. Подробнее этот процесс описан в гл. 5.14.
5.4.2.4. Явное преобразование типа
Явное преобразование типа происходит, когда к операнду явно применяется операция приведения типа (type cast). В этой ситуации могут применяться все описанные выше виды преобразований типов, кроме преобразования в строку. Попытка явного преобразования к типу, отмеченная выше, как запрещенная, вызовет ошибку компиляции. Кроме того, на этапе выполнения возможна генерация исключения ClassCastException, если заданное преобразование недопустимо.
5.4.3. Преобразования типов числовых операндов
Преобразование типов в процессе вычисления числовых выражений имеет ряд особенностей. Они сводятся к двум случаям: преобразования операндов в унарных операциях и в бинарных операциях.