Interested Article - Приведение типа

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

Описание

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

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

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

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

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

Неявное приведение типа

Неявное приведение типа в языках C/C++

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

  • после вычисления операндов бинарных арифметических, логических, битовых операций, операций сравнения, а также 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 {
   // конструктор с несколькими аргументами для приведения типа 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 код: в общем случае да (например, вызов перегруженной операции приведения типа или конструктора).

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

Примеры.

// Получить процент попаданий.
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 или его подтипом, приведение выполняется. Иначе:

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

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

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

Логические ошибки возможны, если операции передать аргумент, не имеющий тип type_to , и не проверить указатель на равенство NULL (соответственно не обработать исключение std :: bad_cast ).

Операция const_cast

Назначение: снятие/установка модификатора(ов) const , volatile и/или mutable . Часто это применяется, чтобы обойти неудачную архитектуру программы или библиотеки, для стыковки Си с Си++, для передачи информации через обобщённые указатели void* , для одновременного написания const- и не-const-версии функции Си++14 существует обход через decltype ( auto ) ).

Ограничения на 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. от 18 декабря 2014 на Wayback Machine .
  2. open-std.org от 22 июля 2020 на Wayback Machine 6.3.1.4 Real floating and integer.
  3. . Дата обращения: 20 августа 2021. 20 августа 2021 года.

Ссылки

  • Robert C. Seacord (англ.) , cert.org
Источник —

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