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

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Приведе́ние (преобразование) ти́па (англ. type conversion, typecasting, coercion) — в информатике преобразование значения одного типа в значение другого типа.

Описание[править | править вики-текст]

Выделяют приведения типов:

Явное приведение задаётся программистом в тексте программы с помощью:

  • конструкции языка;
  • функции, принимающей значение одного типа и возвращающей значение другого типа.

Неявное приведение выполняется транслятором (компилятором или интерпретатором) по правилами, описанным в стандарте языка. Стандарты некоторых языков запрещают неявные преобразования.

В некоторых объектно-ориентированных языках, например, C++, полиморфизм подтипов реализуется посредством приведения типа указателя на текущий объект к базовому классу. В других, например, OCaml, операция приведения типов отсутствует принципиально, а допустимость обращения к компоненту подтипа контролируется механизмом проверки согласования типов, и в машинном коде остаётся прямое обращение.

Неявное приведение[править | править вики-текст]

Неявное приведение типа в языках C/C++[править | править вики-текст]

Неявное приведение типов происходит в следующих случаях[1]:

  • после вычисления операндов бинарных арифметических, логических, битовых операций, операций сравнения, а также 2-го или 3-го операнда операции «?:»; значения операндов приводятся к одинаковому типу;
  • перед выполнением присваивания;
  • перед передачей аргумента функции;
  • перед возвратом функцией возвращаемого значения;
  • после вычисления выражения конструкции switch значение приводится к целочисленному типу;
  • после вычисления выражений конструкций if, for, while, do-while значение приводится к типу bool.

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

Рассмотрим пример на языке C.

double  d;  // вещественный тип
long    l;  // целый тип
int     i;  // целый тип
 
if ( d > i )      d  = i;
if ( i > l )      l  = i;
if ( d == l )     d *= 2;

При выполнении операций сравнения и при присваивании переменные разных типов неявно приводятся к одному типу.

При неявных преобразованиях возможны побочные эффекты. Например, при приведении числа вещественного типа к целому типу дробная часть отсекается (округление не выполняется). При обратном преобразовании возможно понижение точности из-за различий в представлении вещественных и целочисленных чисел. Например, в переменной типа float (число с плавающей точкой одинарной точности по стандарту IEEE 754), нельзя сохранить число 16 777 217 без потери точности, а в 32-х битной переменной целого типа int — можно. Из-за потери точности операции сравнения одного и того же числа, представленного целым и вещественным типами (например, int и float), могут давать ложные результаты (числа могут быть не равны).

#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 );
}

Приведённый код выведет следующее, если размер int — 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

Для вычисления последнего выражения компилятор выполняет примерно следующие действия:

  • сначала переменная C целочисленного типа char явно приводится к целочисленному типу int путём расширения разрядности;
  • выполняется вычисление операндов для операции умножения. Левый операнд имеет тип int. Правый операнд — константа 10, а такие константы по умолчанию имеют тип int. Так как оба операнда оператора «*» имеют тип int, неявное приведение типов не выполняется. Результат умножения тоже имеет тип int;
  • выполняется вычисление операндов операции сложения. Левый операнд — результат умножения имеет тип int. Правый операнд — переменная Y имеет тип int. Так как оба операнда оператора «+» имеют тип int, неявное приведение к общему типу не выполняется. Результат сложения тоже имеет тип int;
  • выполнение присваивания. Левый операнд — переменная X имеет тип int. Правый операнд — результат вычисления выражения, записанного вправа от знака «=», тоже имеет тип int. Так как оба операнда оператора «=» имеют одинаковый тип, неявное приведение типов не выполняется.

Но даже при этом возможны ошибки. Тип char может быть как знаковым (signed char), так и беззнаковым (unsigned char); результат зависит от реализации компилятора и такое поведение разрешено стандартом. Значение беззнакового типа char при преобразовании к знаковому типу int может оказаться отрицательным из-за особенностей реализации машинных инструкций на некоторых процессорах. Чтобы избежать неоднозначностей, рекомендуется явно указывать знаковость для типа char.

Приведения типов в языке C++[править | править вики-текст]

В языке C++ существует пять операторов для явного приведения типа. Первый оператор — круглые скобки ((type_to)expression_from) поддерживается для сохранения совместимости с C. Остальные четыре оператора записываются в виде

xxx_cast< type_to >( expression_from )

Рассмотрим пример.

y = static_cast< signed short >( 65534 ); // переменной y будет присвоено значение -2

Громоздкие ключевые слова являются напоминанием программисту о том, что приведение типа чревато проблемами.

Оператор static_cast[править | править вики-текст]

Назначение: допустимые приведения типов.

Оператор static_cast аналогичен оператору «круглые скобки» с одним исключением: оператор не выполняет приведение указателей на базовые типы к указателям на производные типы (для этого применяется оператор reinterpret_cast).

Применение:

  • приведение целых типов к вещественным и наоборот;
  • преобразование значений типа с меньшей разрядностью к значению типа с большей разрядностью и наоборот (увеличение и уменьшение разрядности);
  • отключение предупреждений компилятора вида «Возможная потеря точности» при преобразовании с уменьшением разрядности;
  • приведение указателей к типу void* и наоборот;
  • приведение указателей на производные типы к указателям на базовые типы (но не наоборот);
  • явный вызов конструктора с одним аргументом или перегруженного оператора приведения типа;
struct Type {
   // конструктор с одним аргументом для приведения типа Type к типу int
   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 {
   // конструктор с несколькими аргументами для приведения типа Type к типу int;
   // для 2-го и последующих аргументов заданы значения по умолчанию
   Type ( int, int = 10, float = 0.0 );
};
  • приведение типа в шаблонах (компилятор уже при специализации шаблона решает, какие операции использовать);
  • приведение операндов тернарной условной операции «?:» к одному типу (значения 2-го и 3-го операндов должны иметь одинаковый тип);

Ограничения на expression_from: нет.

Ограничения на type_to: должен существовать способ преобразования значения выражения expression_from к типу type_to.

Производит ли оператор static_cast код: в общем случае, да (например, вызов перегруженного оператора приведения типа или конструктора).

Ошибки возможны в следующих случаях:

  • приведение к неправильному типу;
  • отсутствие необходимого приведения.

Не исключено, что после преобразования типа значения может появится временный объект. Для создания объекта вызывается конструктор. Объект будет благополучно уничтожен вместе со всеми изменениями. Большинство компиляторов при этом выдают предупреждение.

Примеры.

// Получить процент попаданий.
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 ); // не компилируется, компилятор не может найти подходящий конструктор

Оператор dynamic_cast[править | править вики-текст]

Назначение: приведение с проверкой типа.

Оператор получает информацию о типе объекта expression_from с помощью RTTI. Если тип равен типу type_to, приведение выполняется. Иначе:

  • для указателей возвращается NULL;
  • для ссылок создаётся исключение std::bad_cast.

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

Ограничения на type_to: ссылка или указатель на дочерний по отношению к expression_from тип.

Производит ли оператор dynamic_cast код: да.

Ошибки возможны, если оператору передать аргумент, не имеющий тип type_to, и:

  • не проверить указатель на равенство NULL;
  • не обработать исключение std::bad_cast.

Оператор const_cast[править | править вики-текст]

Назначение: снятие/установка модификатора(ов) const и/или volatile.

Ограничения на expression_from: выражение должно возвращать ссылку или указатель.

Ограничения на type_to: тип type_to должен совпадать с типом выражения expression_from с точностью до модификатора(ов) const и/или volatile.

Производит ли оператор const_cast код: нет.

Возможные ошибки: изменение неизменяемого объекта.

Для примера рассмотрим код динамической библиотеки.

#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() );
}

При загрузке библиотеки в память процесса создаёт новый сегмент данных, в котором размещаются глобальные переменные. Код функции SomeDllFunction() находится в библиотеке и при вызове возвращает указатель на скрытый член глобального объекта класса string. Оператор const_cast используется для удаления модификатора const.

Оператор reinterpret_cast[править | править вики-текст]

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

Объект, возвращаемый выражением expression_from, рассматривается как объект типа type_to.

Ограничения на expression_from: выражение должно возвращать значение порядкового типа (логического (bool), символьного (char), целого (int) или перечисления (enum)), указатель или ссылку.

Ограничения на type_to: если выражение expression_from возвращает значение порядкового типа или указатель, тип type_to может быть порядковым типом или указателем; если выражение expression_from возвращает ссылку, тип type_to должен быть ссылкой.

Производит ли оператор reinterpret_cast код: нет.

Возможные ошибки. Объект, возвращаемый выражением expression_from, может не иметь типа type_to. Нет никакой возможности проверить это. Всю ответственность за корректность преобразования программист берёт на себя.

Рассмотрим примеры.

// Возвращает true, если число x конечное.
// Возвращает false, если число x равно ∞ или NaN.
bool isfinite ( double const x )
{
 
   // преобразование double const -> uint64_t const &
   uint64_t const & y = reinterpret_cast< uint64_t const & >( x );
 
   return ( ( y & UINT64_C( 0x7FF0000000000000 ) ) != UINT64_C( 0x7FF0000000000000 ) );
}
 
// попытка получения адреса временного значения
long const & y = reinterpret_cast< long const & >( x + 5.0 );
// ошибка: выражение x + 5.0 не является ссылкой

См. также[править | править вики-текст]

Примечания[править | править вики-текст]