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

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

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

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

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

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

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

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

В языках, поддерживающих ООП, с помощью приведения типа указатели на производные классы (англ. delivered class) преобразуется к указателям на базовые классы (англ. base class) (см. полиморфизм — возможность работы с базовыми классами также, как и с производными).

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

Неявное приведение типов происходит во время вычисления выражений:

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

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

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

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

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

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

#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;
};
  • приведение типа в шаблонах (компилятор уже при специализации шаблона решает, какие операции использовать);
  • приведение операндов тернарной условной операции «?:» к одному типу (значения 2-го и 3-го операндов должны иметь одинаковый тип);

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

Ограничения на type_to: должен существовать способ преобразования к типу 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 не является ссылкой

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

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