Явное приведение задаётся программистом в тексте программы с помощью:
конструкции языка;
встроенных функций, принимающей значение одного типа и возвращающей значение другого типа.
Неявное приведение выполняется транслятором (компилятором или интерпретатором) по правилам, описанным в стандарте языка. Стандарты большинства языков запрещают неявные преобразования.
В объектно-ориентированных языках, таких как C++, механизм наследования реализуется посредством приведения типа указателя или ссылки на текущий объект к базовому классу (в типобезопасных, таких как OCaml, понятие о приведении типов отсутствует принципиально, и допустимость обращения к компоненту подтипа контролируется механизмом проверки согласования типов на этапе компиляции, а в машинном коде остаётся прямое обращение).
Неявное приведение типа
Неявное приведение типа в языках C/C++
Неявное приведение типов происходит в следующих случаях[1]:
после вычисления операндов бинарных арифметических, логических, битовых операций, операций сравнения, а также 2-го или 3-го операнда операции «?:»; значения операндов приводятся к одинаковому типу;
перед выполнением присваивания;
перед передачей аргумента функции;
перед возвратом функцией возвращаемого значения;
после вычисления выражения конструкции switch значение приводится к целочисленному типу;
после вычисления выражений конструкций if, for, while, do-while значение приводится к типу bool.
Например, при выполнении бинарной арифметической операции значения операндов приводятся к одному типу. При наследовании указатели производного класса приводятся к указателям базового класса.
doubled;// вещественный типlongl;// целый типinti;// целый типif(d>i)d=i;if(i>l)l=i;if(d==l)d*=2;
При выполнении операций сравнения и при присваивании переменные разных типов неявно приводятся к одному типу.
При неявных преобразованиях возможны побочные эффекты. Например, при приведении числа вещественного типа к целому типу дробная часть отсекается (округление не выполняется)[2]. При обратном преобразовании возможно понижение точности из-за различий в представлении вещественных и целочисленных чисел. Например, в переменной типа float (число с плавающей точкойодинарной точности по стандарту IEEE 754), нельзя сохранить число 16 777 217 без потери точности, а в 32-битной переменной целого типа int — можно. Из-за потери точности операции сравнения одного и того же числа, представленного целым и вещественным типами (например, int и float), могут давать ложные результаты (числа могут быть не равны).
#include<stdio.h>intmain(void){inti_value=16777217;floatf_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);}
Приведённый код выведет следующее, если размер int — 32 бита и компилятор поддерживает стандарт IEEE 754:
The integer is: 16777217
The float is: 16777216.000000
Their equality: 1
Явное приведение типа
Приведения типов в языке C
Для явного приведения типов имя типа указывается в круглых скобках перед переменной или выражением. Рассмотрим пример.
intX;intY=200;charC=30;X=(int)C*10+Y;// переменная С приводится к типу int
Для вычисления последнего выражения компилятор выполняет примерно следующие действия:
сначала переменная C символьного типа char явно приводится к целочисленному типу int путём расширения разрядности;
выполняется вычисление операндов для операции умножения. Левый операнд имеет тип int. Правый операнд — константа 10, а такие константы по умолчанию имеют тип int. Так как оба операнда оператора «*» имеют тип int, неявное приведение типов не выполняется. Результат умножения тоже имеет тип int;
выполняется вычисление операндов операции сложения. Левый операнд — результат умножения имеет тип int. Правый операнд — переменная Y имеет тип int. Так как оба операнда оператора «+» имеют тип int, неявное приведение к общему типу не выполняется. Результат сложения тоже имеет тип int;
выполнение присваивания. Левый операнд — переменная X имеет тип int. Правый операнд — результат вычисления выражения, записанного справа от знака «=», тоже имеет тип int. Так как оба операнда оператора «=» имеют одинаковый тип, неявное приведение типов не выполняется.
Но даже при этом возможны ошибки. Тип char может быть как знаковым (signedchar), так и беззнаковым (unsignedchar); результат зависит от реализации компилятора и такое поведение разрешено стандартом. Значение беззнакового типа char при преобразовании к знаковому типу int может оказаться отрицательным из-за особенностей реализации машинных инструкций на некоторых процессорах. Чтобы избежать неоднозначностей, рекомендуется явно указывать знаковость для типа char.
Приведения типов в языке C++
В языке C++ существует пять операций для явного приведения типа. Первая операция — круглые скобки ((type_to)expression_from) поддерживается для сохранения совместимости с C. Остальные четыре операции записываются в виде
xxx_cast< type_to >( expression_from )
Рассмотрим пример.
y=static_cast<signedshort>(65534);// переменной y будет присвоено значение -2
Громоздкие ключевые слова являются напоминанием программисту о том, что приведение типа чревато проблемами.
Операция static_cast аналогична операции «круглые скобки» с одним исключением: она не выполняет приведение указателей на неродственные типы (для этого применяется операция reinterpret_cast).
Применение:
преобразование между числовыми и enum, в том числе если неявное преобразование невозможно (int→enumclass) или приводит к предупреждению «Возможная потеря точности» (double→float);
приведение указателей к типу void* и наоборот;
приведение указателей на производные типы к указателям на базовые типы и наоборот;
явный вызов конструктора с одним аргументом или перегруженной операции приведения типа;
structType{// конструктор с одним аргументом для приведения типа int к типу TypeType(int);// перегруженная операция для приведения типа Type к типу doubleoperatordouble()const;};intmain(){Typex,y;inti;doubled;// вызов конструктора с одним аргументомx=y+static_cast<Type>(i);// вызов перегруженной операции приведения типаd=static_cast<double>(x);return0;}
конструктор может иметь большее число аргументов, но для них должны быть заданы значения по умолчанию;
structType{// конструктор с несколькими аргументами для приведения типа int к типу Type;// для 2-го и последующих аргументов заданы значения по умолчаниюType(int,int=10,float=0.0);};
приведение типа в шаблонах (компилятор уже при специализации шаблона решает, какие операции использовать);
приведение операндов тернарной условной операции «?:» к одному типу (значения 2-го и 3-го операндов должны иметь одинаковый тип);
Ограничения на expression_from: нет.
Ограничения на type_to: должен существовать способ преобразования значения выражения expression_from к типу type_to, с помощью operator type_to или конструктора.
Производит ли операция static_cast код: в общем случае да (например, вызов перегруженной операции приведения типа или конструктора).
Источники логических ошибок: зависят от того, что собираетесь делать операцией. Возможны переполнения, выход за диапазон и даже (для преобразования указателей) порча памяти.
Примеры.
// Получить процент попаданий.doublehitpercent(constintaHitCount,// число попаданийconstintaShotCount// число выстрелов){if(aShotCount==0)return0.0;// Приведение типов к double выполняется для выполнения вещественного (не целочисленного) деленияreturnstatic_cast<double>(aHitCount*100)/static_cast<double>(aShotCount);}// следующие строчки эквивалентны// использование операции static_caststrings=static_cast<string>("Hello!");// вызов конструктора с одним аргументомstrings=string("Hello!");// использование операции «круглые скобки»strings=(string)"Hello!";strings=static_cast<string>(5);// не компилируется, компилятор не может найти подходящий конструктор
Ограничения на expression_from: выражение должно быть ссылкой или указателем на объект, имеющий хотя бы одну виртуальную функцию.
Ограничения на type_to: ссылка или указатель на дочерний по отношению к expression_from тип.
Производит ли операция dynamic_cast код: да.
Логические ошибки возможны, если операции передать аргумент, не имеющий тип type_to, и не проверить указатель на равенство NULL (соответственно не обработать исключение std::bad_cast).
Операция const_cast
Назначение: снятие/установка модификатора(ов) const, volatile и/или mutable. Часто это применяется, чтобы обойти неудачную архитектуру программы или библиотеки, для стыковки Си с Си++, для передачи информации через обобщённые указатели void*, для одновременного написания const- и не-const-версии функции[3] (в Си++14 существует обход через decltype(auto)[3]).
Ограничения на expression_from: выражение должно возвращать ссылку или указатель.
Ограничения на type_to: тип type_to должен совпадать с типом выражения expression_from с точностью до модификатора(ов) const, volatile и mutable .
Производит ли операция const_cast код: нет.
Источники логических ошибок: программа может изменить неизменяемый объект. Иногда это может привести к ошибке сегментации, иногда подпрограмма может не ожидать[3], что память, которую она предоставила для чтения, вдруг изменили.
#include<string> // stringusingnamespacestd;namespace{strings="Wikipedia";// Глобальная переменная// метод string::c_str() возвращает указатель типа const char *}typedefchar*PChar;void__declspec(dllexport)WINAPISomeDllFunction(PChar&rMessage){// преобразование char const * в char *rMessage=const_cast<char*>(s.c_str());}
При загрузке библиотеки в память процесса создаёт новый сегмент данных, в котором размещаются глобальные переменные.
Код функции SomeDllFunction() находится в библиотеке и при вызове возвращает указатель на скрытый член глобального объекта класса string. Операция const_cast используется для удаления модификатора const.
Операция reinterpret_cast
Назначение: каламбур типизации — назначение ячейке памяти другого типа (не обязательно совместимого с данным) с сохранением битового представления.
Объект, возвращаемый выражением expression_from, рассматривается как объект типа type_to.
Ограничения на expression_from: выражение должно возвращать значение порядкового типа (любой из целых, логический bool или перечислимый enum), указатель или ссылку.
Ограничения на type_to:
Если expression_from возвращает значение порядкового типа или указатель, тип type_to может быть порядковым типом или указателем.
Если expression_from возвращает ссылку, тип type_to должен быть ссылкой.
Производит ли операция reinterpret_cast код: нет.
Источники логических ошибок. Объект, возвращаемый выражением expression_from, может не иметь типа type_to. Нет никакой возможности проверить это, всю ответственность за корректность преобразования программист берёт на себя.
Рассмотрим примеры.
// Возвращает true, если число x конечное.// Возвращает false, если число x равно ∞ или NaN.boolisfinite(doubleconstx){// преобразование double const -> uint64_t const &uint64_tconst&y=reinterpret_cast<uint64_tconst&>(x);return((y&UINT64_C(0x7FF0000000000000))!=UINT64_C(0x7FF0000000000000));}// попытка получения адреса временного значенияlongconst&y=reinterpret_cast<longconst&>(x+5.0);// ошибка: выражение x + 5.0 не является ссылкой