В информатике преобразование типов , [1] [2] приведение типов , [1] [3] приведение типов , [3] и жонглирование типами [4] [5] — это различные способы изменения выражения из одного типа данных в другой. Примером может служить преобразование целочисленного значения в значение с плавающей точкой или его текстовое представление в виде строки и наоборот. Преобразования типов могут использовать определенные особенности иерархий типов или представлений данных . Два важных аспекта преобразования типов — это то, происходит ли это неявно (автоматически) или явно , [1] [6] и преобразуется ли базовое представление данных из одного представления в другое, или данное представление просто переинтерпретируется как представление другого типа данных. [6] [7] В общем, как примитивные , так и составные типы данных могут быть преобразованы.
Каждый язык программирования имеет свои правила преобразования типов. Языки со строгой типизацией обычно выполняют мало неявных преобразований и препятствуют повторной интерпретации представлений, в то время как языки со слабой типизацией выполняют много неявных преобразований между типами данных. Язык со слабой типизацией часто позволяет заставить компилятор произвольно интерпретировать элемент данных как имеющий разные представления — это может быть неочевидной ошибкой программирования или техническим методом для прямого взаимодействия с базовым оборудованием.
В большинстве языков слово coercion используется для обозначения неявного преобразования, либо во время компиляции, либо во время выполнения . Например, в выражении, смешивающем целые числа и числа с плавающей точкой (например, 5 + 0,1), компилятор автоматически преобразует целочисленное представление в представление с плавающей точкой, чтобы дроби не терялись. Явные преобразования типов указываются либо путем написания дополнительного кода (например, добавления идентификаторов типов или вызова встроенных процедур ), либо путем кодирования процедур преобразования для использования компилятором, когда он в противном случае остановился бы из-за несоответствия типов.
В большинстве языков типа ALGOL , таких как Pascal , Modula-2 , Ada и Delphi , преобразование и приведение — это совершенно разные понятия. В этих языках преобразование относится либо к неявному, либо к явному изменению значения из одного формата хранения типа данных в другой, например, 16-битного целого числа в 32-битное целое число. Потребности в хранении могут измениться в результате преобразования, включая возможную потерю точности или усечение. С другой стороны, слово приведение относится к явному изменению интерпретации битовой комбинации, представляющей значение из одного типа в другой. Например, 32 смежных бита могут рассматриваться как массив из 32 булевых значений, 4-байтовая строка, беззнаковое 32-битное целое число или значение с плавающей точкой одинарной точности IEEE. Поскольку сохраненные биты никогда не изменяются, программист должен знать детали низкого уровня, такие как формат представления, порядок байтов и потребности выравнивания, чтобы осмысленно приводить.
В семействе языков C и ALGOL 68 слово cast обычно относится к явному преобразованию типа (в отличие от неявного преобразования), что приводит к некоторой двусмысленности относительно того, является ли это переинтерпретацией битовой последовательности или реальным преобразованием представления данных. Более важным является множество способов и правил, которые применяются к тому, какой тип данных (или класс) находится указателем, и как указатель может быть скорректирован компилятором в таких случаях, как наследование объекта (класса).
Ada предоставляет универсальную библиотечную функцию Unchecked_Conversion. [8]
Неявное преобразование типов, также известное как приведение или жонглирование типами , — это автоматическое преобразование типов компилятором . Некоторые языки программирования позволяют компиляторам обеспечивать приведение, другие требуют этого.
В выражении смешанного типа данные одного или нескольких подтипов могут быть преобразованы в супертип по мере необходимости во время выполнения , чтобы программа работала правильно. Например, следующий код является допустимым на языке C :
двойной d ; длинный l ; целочисленный i ; если ( d > i ) d = i ; если ( i > l ) l = i ; если ( d == l ) d *= 2 ;
Хотя d , l и i принадлежат к разным типам данных, они будут автоматически преобразованы в одинаковые типы данных каждый раз при выполнении сравнения или присваивания. Это поведение следует использовать с осторожностью, так как могут возникнуть непреднамеренные последствия. Данные могут быть потеряны при преобразовании представлений из числа с плавающей точкой в целое число, так как дробные компоненты значений с плавающей точкой будут усечены (округлены до нуля). И наоборот, точность может быть потеряна при преобразовании представлений из целого числа в число с плавающей точкой, так как тип с плавающей точкой может быть неспособен точно представить все возможные значения некоторого целочисленного типа. Например, может быть типом одинарной точности IEEE 754 , который не может точно представить целое число 16777217, в то время как 32-битный целочисленный тип может. Это может привести к неинтуитивному поведению, как показано в следующем коде:float
#include <stdio.h> int main ( void ) { int i_value = 16777217 ; float f_value = 16777216.0 ; printf ( "Целое число равно: %d \n " , i_value ); printf ( "Плавающее число равно: %f \n " , f_value ); printf ( "Их равенство: %d \n " , i_value == f_value ); }
На компиляторах, реализующих float как числа с одинарной точностью IEEE, а int как минимум 32-битные, этот код выдаст такую необычную распечатку:
Целое число: 16777217Число с плавающей точкой: 16777216.000000Их равенство: 1
Обратите внимание, что 1 представляет равенство в последней строке выше. Это странное поведение вызвано неявным преобразованием в i_value
float при сравнении с f_value
. Преобразование приводит к потере точности, что делает значения равными до сравнения.
Важные выводы:
float
вызывает int
усечение , т.е. удаление дробной части.double
вызывает float
округление цифры.long
вызывает int
отбрасывание лишних битов более высокого порядка.Одним из особых случаев неявного преобразования типа является повышение типа, когда объект автоматически преобразуется в другой тип данных, представляющий надмножество исходного типа. Повышение обычно используется с типами, меньшими, чем собственный тип арифметико-логического устройства (АЛУ) целевой платформы, перед арифметическими и логическими операциями, чтобы сделать такие операции возможными или более эффективными, если АЛУ может работать с более чем одним типом. C и C++ выполняют такое повышение для объектов булевых, символьных, широких символьных, перечислений и коротких целочисленных типов, которые повышаются до int, и для объектов типа float, которые повышаются до double. В отличие от некоторых других преобразований типов, повышение никогда не теряет точность и не изменяет значение, хранящееся в объекте.
На Яве :
int x = 3 ; double y = 3.5 ; System.out.println ( x + y ) ; // Вывод будет 6.5
Явное преобразование типов, также называемое приведением типов, — это преобразование типов, которое явно определено в программе (вместо того, чтобы выполняться автоматически в соответствии с правилами языка для неявного преобразования типов). Оно запрашивается пользователем в программе.
double da = 3.3 ; double db = 3.3 ; double dc = 3.4 ; int result = ( int ) da + ( int ) db + ( int ) dc ; // result == 9 // если бы использовалось неявное преобразование (как в случае "result = da + db + dc"), результат был бы равен 10
Существует несколько видов явного преобразования.
В объектно-ориентированных языках программирования объекты также могут быть преобразованы : ссылка на базовый класс преобразуется в один из его производных классов.
В C# преобразование типов может быть выполнено безопасным или небезопасным (т. е. C-подобным) способом, первый из которых называется проверенным приведением типа . [9]
Животное животное = новый Кот (); Бульдог b = ( Бульдог ) животное ; // если (животное - Бульдог), stat.type(животное) - Бульдог, иначе исключение b = животное как Бульдог ; // если (животное - Бульдог), b = (Бульдог) животное, иначе b = null животное = null ; b = животное как Бульдог ; // b == null
В C++ аналогичного эффекта можно добиться, используя синтаксис приведения в стиле C++ .
Животное * животное = новый Кот ; Bulldog * b = static_cast < Bulldog *> ( animal ); // компилируется только если либо Animal, либо Bulldog являются производными от другого (или одного и того же) b = dynamic_cast < Bulldog *> ( animal ); // если (animal is Bulldog), b = (Bulldog*) animal, иначе b = nullptr Bulldog & br = static_cast < Bulldog &> ( * animal ); // то же, что и выше, но будет выдано исключение, если должен быть возвращен nullptr // это не наблюдается в коде, где избегается обработка исключений удалить животное ; // всегда свободные ресурсы животное = nullptr ; b = dynamic_cast < Bulldog *> ( животное ); // b == nullptr
В Eiffel понятие преобразования типа интегрировано в правила системы типов. Правило присваивания гласит, что присваивание, например:
х := у
является допустимым, если и только если тип его исходного выражения, y
в данном случае, совместим с типом его целевой сущности, x
в данном случае. В этом правиле совместимость с означает, что тип исходного выражения либо соответствует , либо преобразуется в тип целевого выражения. Соответствие типов определяется знакомыми правилами полиморфизма в объектно-ориентированном программировании . Например, в назначении выше тип y
соответствует типу , x
если класс, на котором y
он основан, является потомком того, на котором x
он основан.
Действия по преобразованию типов в Eiffel, в частности преобразования в и преобразования из, определяются следующим образом:
Тип, основанный на классе CU, преобразуется в тип T, основанный на классе CT (а T преобразуется из U), если либо
- CT имеет процедуру преобразования, использующую U в качестве типа преобразования, или
- В запросе конверсии CU указан тип конверсии T.
Eiffel — полностью совместимый язык для Microsoft .NET Framework . До разработки .NET в Eiffel уже были обширные библиотеки классов. Использование библиотек типов .NET, особенно с часто используемыми типами, такими как строки, создает проблему преобразования. Существующее программное обеспечение Eiffel использует строковые классы (например, STRING_8
) из библиотек Eiffel, но программное обеспечение Eiffel, написанное для .NET, должно использовать строковый класс .NET ( System.String
) во многих случаях, например, при вызове методов .NET, которые ожидают, что элементы типа .NET будут переданы в качестве аргументов. Таким образом, преобразование этих типов туда и обратно должно быть максимально плавным.
my_string : STRING_8 — Собственная строка Eiffel my_system_string : SYSTEM_STRING — Собственная строка .NET ... моя_строка := моя_системная_строка
В коде выше объявлены две строки, по одной каждого типа ( SYSTEM_STRING
— это совместимый с Eiffel псевдоним для System.String). Поскольку System.String
не соответствует STRING_8
, то указанное выше назначение допустимо только в том случае, если System.String
преобразуется в STRING_8
.
Класс Eiffel STRING_8
имеет процедуру преобразования make_from_cil
для объектов типа System.String
. Процедуры преобразования также всегда обозначаются как процедуры создания (аналогично конструкторам). Ниже приведен фрагмент из STRING_8
класса:
класс STRING_8 ... создать make_from_cil ... преобразовать make_from_cil ({ SYSTEM_STRING }) ...
Наличие процедуры преобразования делает назначение:
моя_строка := моя_системная_строка
семантически эквивалентно:
создать my_string . make_from_cil ( my_system_string )
в котором my_string
создается как новый объект типа STRING_8
с содержимым, эквивалентным содержимому my_system_string
.
Чтобы выполнить задание, в котором исходный источник и цель поменялись местами:
моя_системная_строка := моя_строка
класс STRING_8
также содержит запрос преобразования to_cil
, который создаст System.String
из экземпляра STRING_8
.
класс STRING_8 ... создать make_from_cil ... преобразовать make_from_cil ({ SYSTEM_STRING }) в_cil : { SYSTEM_STRING } ...
Задание:
моя_системная_строка := моя_строка
тогда становится эквивалентным:
my_system_string := my_string . to_cil
В Eiffel настройка для преобразования типа включена в код класса, но затем, по-видимому, происходит так же автоматически, как явное преобразование типа в клиентском коде. Это включает не только назначения, но и другие типы вложений, такие как подстановка аргумента (параметра).
Rust не обеспечивает неявного преобразования типов (приведения) между примитивными типами. Но явное преобразование типов (приведение) может быть выполнено с использованием as
ключевого слова. [10]
пусть x = 1000 ; println! ( "1000 как u16 это: {}" , x как u16 );
Связанная концепция в системах статических типов называется утверждением типа , которое предписывает компилятору обрабатывать выражение определенного типа, игнорируя его собственный вывод. Утверждение типа может быть безопасным (выполняется проверка во время выполнения) или небезопасным. Утверждение типа не преобразует значение из одного типа данных в другой.
В TypeScript утверждение типа выполняется с помощью as
ключевого слова: [11]
const myCanvas = document.getElementById ( " main_canvas " ) as HTMLCanvasElement ;
В приведенном выше примере document.getElementById
объявлено, что возвращает HTMLElement
, но вы знаете, что он всегда возвращает HTMLCanvasElement
, который является подтипом HTMLElement
, в данном случае. Если это не так, последующий код, который полагается на поведение , HTMLCanvasElement
не будет работать правильно, так как в Typescript нет проверки времени выполнения для утверждений типа.
В Typescript нет общего способа проверить, имеет ли значение определенный тип во время выполнения, поскольку отсутствует поддержка типов во время выполнения. Однако можно написать пользовательскую функцию, с помощью которой пользователь сообщает компилятору, имеет ли значение определенный тип или нет. Такая функция называется type guard и объявляется с возвращаемым типом x is Type
, где x
— параметр или this
, вместо boolean
.
Это позволяет содержать небезопасные утверждения типов в функции проверки, а не разбрасывать их по кодовой базе.
В Go утверждение типа может использоваться для доступа к конкретному значению типа из значения интерфейса. Это безопасное утверждение, что оно вызовет панику (в случае одного возвращаемого значения) или вернет нулевое значение (если используются два возвращаемых значения), если значение не принадлежит к этому конкретному типу. [12]
т := я .( Т )
Утверждения этого типа сообщают системе, что i
тип T
. Если это не так, она паникует.
Многие языки программирования поддерживают типы объединений , которые могут содержать значение нескольких типов. Немаркированные объединения предусмотрены в некоторых языках со слабой проверкой типов, таких как C и PL/I , но также и в оригинальном Pascal . Их можно использовать для интерпретации битовой комбинации одного типа как значения другого типа.
В хакерстве приведение типов — это неправильное использование преобразования типов для временного изменения типа данных переменной по сравнению с тем, как он был изначально определен. [13] Это дает хакерам возможности, поскольку при преобразовании типов после того, как переменная «приводится к типу» и становится другим типом данных, компилятор будет рассматривать эту взломанную переменную как новый тип данных для этой конкретной операции. [14]