Приведе́ние (преобразование) ти́па (, ) — в информатике преобразование значения одного типа в значение другого типа.
Описание Выделяют приведения типов: явные (); неявные ( cast, coercion).
Явное приведение задаётся программистом в тексте программы с помощью: конструкции языка; встроенных функций, принимающей значение одного типа и возвращающей значение другого типа.
Неявное приведение выполняется транслятором (компилятором или интерпретатором) по правилам, описанным в стандарте языка. Стандарты большинства языков запрещают неявные преобразования.
В объектно-ориентированных языках, таких как C++, механизм наследования реализуется посредством приведения типа указателя или ссылки на текущий объект к базовому классу (в типобезопасных, таких как OCaml, понятие о приведении типов отсутствует принципиально, и допустимость обращения к компоненту подтипа контролируется механизмом проверки согласования типов на этапе компиляции, а в машинном коде остаётся прямое обращение).
Неявное приведение типа
Неявное приведение типа в языках C/C++ Неявное приведение типов происходит в следующих случаяхcppreference.com. Implicit conversions .: после вычисления операндов бинарных арифметических, логических, битовых операций, операций сравнения, а также 2-го или 3-го операнда операции «?:»; значения операндов приводятся к одинаковому типу; перед выполнением присваивания; перед передачей аргумента функции; перед возвратом функцией возвращаемого значения; после вычисления выражения конструкции значение приводится к целочисленному типу; после вычисления выражений конструкций , , , - значение приводится к типу . Например, при выполнении бинарной арифметической операции значения операндов приводятся к одному типу. При наследовании указатели производного класса приводятся к указателям базового класса.
Рассмотрим пример на языке C.
double d; // вещественный тип long l; // целый тип int i; // целый тип
if ( d > i ) d = i; if ( i > l ) l = i; if ( d == l ) d *= 2;
При выполнении операций сравнения и при присваивании переменные разных типов неявно приводятся к одному типу.
При неявных преобразованиях возможны побочные эффекты. Например, при приведении числа вещественного типа к целому типу дробная часть отсекается (округление не выполняется)open-std.org ISO/IEC 9899:2018 C Programming Language Standard (C18) 6.3.1.4 Real floating and integer.. При обратном преобразовании возможно понижение точности из-за различий в представлении вещественных и целочисленных чисел. Например, в переменной типа (число с плавающей точкой одинарной точности по стандарту IEEE 754), нельзя сохранить число без потери точности, а в 32-битной переменной целого типа — можно. Из-за потери точности операции сравнения одного и того же числа, представленного целым и вещественным типами (например, и ), могут давать ложные результаты (числа могут быть не равны).
#include <stdio.h>
int main ( void ) { int i_value = 16777217; float f_value = 16777216.0; printf( "The integer is:%d\n", i_value ); printf( "The float is: %f\n", f_value ); printf( "Their equality:%d\n", i_value == f_value ); }
Приведённый код выведет следующее, если размер — 32 бита и компилятор поддерживает стандарт IEEE 754:
The integer is: 16777217 The float is: 16777216.000000 Their equality: 1
Явное приведение типа
Приведения типов в языке C Для явного приведения типов имя типа указывается в круглых скобках перед переменной или выражением. Рассмотрим пример.
int X; int Y = 200; char C = 30; X = (int)C * 10 + Y; // переменная С приводится к типу int
Для вычисления последнего выражения компилятор выполняет примерно следующие действия: сначала переменная символьного типа явно приводится к целочисленному типу путём расширения разрядности; выполняется вычисление операндов для операции умножения. Левый операнд имеет тип . Правый операнд — константа 10, а такие константы по умолчанию имеют тип . Так как оба операнда оператора «*» имеют тип , неявное приведение типов не выполняется. Результат умножения тоже имеет тип ; выполняется вычисление операндов операции сложения. Левый операнд — результат умножения имеет тип . Правый операнд — переменная имеет тип . Так как оба операнда оператора «+» имеют тип , неявное приведение к общему типу не выполняется. Результат сложения тоже имеет тип ; выполнение присваивания. Левый операнд — переменная имеет тип . Правый операнд — результат вычисления выражения, записанного справа от знака «=», тоже имеет тип . Так как оба операнда оператора «=» имеют одинаковый тип, неявное приведение типов не выполняется.
Но даже при этом возможны ошибки. Тип может быть как знаковым (), так и беззнаковым (); результат зависит от реализации компилятора и такое поведение разрешено стандартом. Значение беззнакового типа при преобразовании к знаковому типу может оказаться отрицательным из-за особенностей реализации машинных инструкций на некоторых процессорах. Чтобы избежать неоднозначностей, рекомендуется явно указывать знаковость для типа .
Приведения типов в языке C++ В языке C++ существует пять операций для явного приведения типа. Первая операция — круглые скобки () поддерживается для сохранения совместимости с C. Остальные четыре операции записываются в виде
xxx_cast< type_to >( expression_from )
Рассмотрим пример.
y = static_cast< signed short >( 65534 ); // переменной y будет присвоено значение -2
Громоздкие ключевые слова являются напоминанием программисту о том, что приведение типа чревато проблемами.
Операция
Назначение: допустимые приведения типов.
Операция аналогична операции «круглые скобки» с одним исключением: она не выполняет приведение указателей на неродственные типы (для этого применяется операция ).
Применение: преобразование между числовыми и enum, в том числе если неявное преобразование невозможно () или приводит к предупреждению «Возможная потеря точности» (); приведение указателей к типу и наоборот; приведение указателей на производные типы к указателям на базовые типы и наоборот; выбор одной из нескольких перегруженных функций; bool myLess(const wchar_t*, const wchar_t*); bool myLess(const std::wstring&, const std::wstring&);
std::vector<std::wstring> list; std::sort(list.begin(), list.end(), static_cast<bool(*)(const std::wstring&, const std::wstring&)>(myLess)); явный вызов конструктора с одним аргументом или перегруженной операции приведения типа; struct Type { // конструктор с одним аргументом для приведения типа int к типу Type Type ( int );
// перегруженная операция для приведения типа Type к типу double operator double () const;
int main () { Type x, y; int i; double d;
// вызов конструктора с одним аргументом x = y + static_cast< Type >( i );
// вызов перегруженной операции приведения типа d = static_cast< double >( x );
return 0; } конструктор может иметь большее число аргументов, но для них должны быть заданы значения по умолчанию; struct Type { // конструктор с несколькими аргументами для приведения типа int к типу Type; // для 2-го и последующих аргументов заданы значения по умолчанию Type ( int, int = 10, float = 0.0 );
приведение типа в шаблонах (компилятор уже при специализации шаблона решает, какие операции использовать); приведение операндов тернарной условной операции «?:» к одному типу (значения 2-го и 3-го операндов должны иметь одинаковый тип);
Ограничения на expression_from: нет.
Ограничения на type_to: должен существовать способ преобразования значения выражения expression_from к типу type_to, с помощью operator type_to или конструктора.
Производит ли операция код: в общем случае да (например, вызов перегруженной операции приведения типа или конструктора).
Источники логических ошибок: зависят от того, что собираетесь делать операцией. Возможны переполнения, выход за диапазон и даже (для преобразования указателей) порча памяти.
Пути избавления: зависят от задачи, и часто избавляться не нужно.
Примеры.
// Получить процент попаданий. double hitpercent ( const int aHitCount, // число попаданий const int aShotCount // число выстрелов ) { if ( aShotCount == 0 ) return 0.0; // Приведение типов к double выполняется для выполнения вещественного (не целочисленного) деления return static_cast< double >( aHitCount * 100 ) / static_cast< double >( aShotCount ); }
// следующие строчки эквивалентны
// использование операции static_cast string s = static_cast< string >( "Hello!" ); // вызов конструктора с одним аргументом string s = string( "Hello!" ); // использование операции «круглые скобки» string s = (string) "Hello!";
string s = static_cast< string >( 5 ); // не компилируется, компилятор не может найти подходящий конструктор
Операция
Назначение: приведение вниз по иерархии наследования, с особым поведением, если объект не имеет нужного типа. Используется именно в случаях, когда программист не уверен в типе.
Операция получает информацию о типе объекта expression_from с помощью RTTI. Если тип будет type_to или его подтипом, приведение выполняется. Иначе: для указателей возвращается NULL; для ссылок создаётся исключение .
Ограничения на expression_from: выражение должно быть ссылкой или указателем на объект, имеющий хотя бы одну виртуальную функцию.
Ограничения на type_to: любой другой виртуальный, также соответственно ссылка или указатель.
Производит ли операция код: да.
Логические ошибки возможны, если операции передать аргумент, не имеющий тип type_to, и не проверить указатель на равенство (соответственно не обработать исключение ).
Пути избавления: продуманная объектная система. Иногда (например, в обработке событий пользовательского интерфейса) dynamic_cast используют только как дополнительную самопроверку, когда и без того известно, какого типа объект, спровоцировавший событие.
Операция Назначение: снятие/установка модификатора(ов) , и/или . Часто это применяется, чтобы обойти неудачную архитектуру программы или библиотеки, для стыковки с другими языками, для передачи информации через обобщённые указатели void*, для одновременного написания const- и не-const-версии функции
Ограничения на expression_from: выражение должно возвращать ссылку или указатель.
Ограничения на type_to: тип type_to должен совпадать с типом выражения expression_from с точностью до модификатора(ов) , и .
Производит ли операция код: нет.
Источники логических ошибок: программа может изменить неизменяемый объект. Иногда это может привести к ошибке сегментации, иногда подпрограмма может не ожидать, что память, которую она предоставила для чтения, вдруг изменили.
Пути избавления: просчитанная const-корректность и удачный API: с Си++17 получила версию без const. С Си++14 — общая шаблонная функция, возвращающая , и функции с const и без вызывают её. С Си++23 эту функцию сразу можно использовать как метод.
Для примера рассмотрим код динамической библиотеки.
#include <string> // string using namespace std;
namespace { string s = "Wikipedia"; // Глобальная переменная // метод string::c_str() возвращает указатель типа const char * }
typedef char * PChar;
void __declspec( dllexport ) WINAPI SomeDllFunction ( PChar & rMessage ) { // преобразование char const * в char * rMessage = const_cast< char * >( s.c_str() ); }
При загрузке библиотеки в память процесса создаёт новый сегмент данных, в котором размещаются глобальные переменные. Код функции находится в библиотеке и при вызове возвращает указатель на строковые данные. Операция используется для удаления модификатора .
Операция Назначение: каламбур типизации — назначение ячейке памяти другого типа (не обязательно совместимого с данным) с сохранением битового представления.
Объект, возвращаемый выражением expression_from, рассматривается как объект типа type_to.
Ограничения на expression_from: выражение должно возвращать значение порядкового типа (любой из целых, логический или перечислимый ), указатель или ссылку.
Ограничения на type_to: Если expression_from возвращает значение порядкового типа или указатель, тип type_to может быть порядковым типом или указателем.