WikiSort.ru - Программирование

ПОИСК ПО САЙТУ | о проекте

Приведе́ние (преобразование) ти́па (англ. 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 ).

Применение:

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

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

Ограничения на type_to: должен существовать способ преобразования значения выражения expression_from к типу type_to, с помощью operator 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 и/или mutable . Часто это применяется, чтобы обойти неудачную архитектуру программы или библиотеки, для стыковки Си с Си++, для передачи информации через обобщённые указатели void*.

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

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

Производит ли операция 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 или перечислимый 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 не является ссылкой

См. также

Примечания

  1. cppreference.com. Implicit conversions.

Ссылки

Данная страница на сайте WikiSort.ru содержит текст со страницы сайта "Википедия".

Если Вы хотите её отредактировать, то можете сделать это на странице редактирования в Википедии.

Если сделанные Вами правки не будут кем-нибудь удалены, то через несколько дней они появятся на сайте WikiSort.ru .




Текст в блоке "Читать" взят с сайта "Википедия" и доступен по лицензии Creative Commons Attribution-ShareAlike; в отдельных случаях могут действовать дополнительные условия.

Другой контент может иметь иную лицензию. Перед использованием материалов сайта WikiSort.ru внимательно изучите правила лицензирования конкретных элементов наполнения сайта.

2019-2024
WikiSort.ru - проект по пересортировке и дополнению контента Википедии