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

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

Приведе́ние (преобразование) ти́па (англ. Шаблон:Lang-en2, Шаблон:Lang-en2) — в информатике преобразование значения одного типа в значение другого типа.

Описание

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

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

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

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

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

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

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

Неявное приведение типов происходит в следующих случаях<ref>cppreference.com. Implicit conversions Шаблон:Wayback.</ref>:

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

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

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

<source lang="c"> double d; // вещественный тип long l; // целый тип int i; // целый тип

if ( d > i ) d = i; if ( i > l ) l = i; if ( d == l ) d *= 2; </source>

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

При неявных преобразованиях возможны побочные эффекты. Например, при приведении числа вещественного типа к целому типу дробная часть отсекается (округление не выполняется)<ref>open-std.org ISO/IEC 9899:2018 C Programming Language Standard (C18) Шаблон:Wayback 6.3.1.4 Real floating and integer.</ref>. При обратном преобразовании возможно понижение точности из-за различий в представлении вещественных и целочисленных чисел. Например, в переменной типа Шаблон:Cpp (число с плавающей точкой одинарной точности по стандарту IEEE 754), нельзя сохранить число 16 777 217 без потери точности, а в 32-битной переменной целого типа Шаблон:Cpp — можно. Из-за потери точности операции сравнения одного и того же числа, представленного целым и вещественным типами (например, Шаблон:Cpp и Шаблон:Cpp), могут давать ложные результаты (числа могут быть не равны).

<source lang="c">

  1. 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 );

} </source>

Приведённый код выведет следующее, если размер Шаблон:Cpp — 32 бита и компилятор поддерживает стандарт IEEE 754:

 The integer is: 16777217
 The float is: 16777216.000000
 Their equality: 1

Явное приведение типа

Приведения типов в языке C

Для явного приведения типов имя типа указывается в круглых скобках перед переменной или выражением. Рассмотрим пример.

<source lang="c"> int X; int Y = 200; char C = 30; X = (int)C * 10 + Y; // переменная С приводится к типу int </source>

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

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

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

Приведения типов в языке C++

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

xxx_cast< type_to >( expression_from )

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

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

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

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

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

Применение:

  • преобразование между числовыми и enum, в том числе если неявное преобразование невозможно (Шаблон:Cpp) или приводит к предупреждению «Возможная потеря точности» (Шаблон:Cpp);
  • приведение указателей к типу Шаблон:Cpp и наоборот;
  • приведение указателей на производные типы к указателям на базовые типы и наоборот;
  • выбор одной из нескольких перегруженных функций;

<source lang="cpp"> 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)); </source>

  • явный вызов конструктора с одним аргументом или перегруженной операции приведения типа;

<source lang="cpp"> 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;

} </source>

конструктор может иметь большее число аргументов, но для них должны быть заданы значения по умолчанию;

<source lang="cpp"> struct Type {

  // конструктор с несколькими аргументами для приведения типа int к типу Type;
  // для 2-го и последующих аргументов заданы значения по умолчанию
  Type ( int, int = 10, float = 0.0 );

}; </source>

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

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

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

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

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

Пути избавления: зависят от задачи, и часто избавляться не нужно.

Примеры.

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

Шаблон:Main Назначение: приведение вниз по иерархии наследования, с особым поведением, если объект не имеет нужного типа. Используется именно в случаях, когда программист не уверен в типе.

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

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

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

Производит ли операция Шаблон:Cpp код: да.

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

Пути избавления: продуманная объектная система. Иногда (например, в обработке событий пользовательского интерфейса) dynamic_cast используют только как дополнительную самопроверку, когда и без того известно, какого типа объект, спровоцировавший событие.

Назначение: снятие/установка модификатора(ов) Шаблон:Cpp, Шаблон:Cpp и/или Шаблон:Cpp. Часто это применяется, чтобы обойти неудачную архитектуру программы или библиотеки, для стыковки с другими языками, для передачи информации через обобщённые указатели void*, для одновременного написания const- и не-const-версии функции<ref name="weekly">Шаблон:Cite web</ref>

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

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

Производит ли операция Шаблон:Cpp код: нет.

Источники логических ошибок: программа может изменить неизменяемый объект. Иногда это может привести к ошибке сегментации, иногда подпрограмма может не ожидать<ref name="weekly" />, что память, которую она предоставила для чтения, вдруг изменили.

Пути избавления: просчитанная const-корректность и удачный API: с Си++17 Шаблон:Cpp получила версию без const. С Си++14 — общая шаблонная функция, возвращающая Шаблон:Cpp, и функции с const и без вызывают её<ref name="weekly" />. С Си++23 эту функцию сразу можно использовать как метод.

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

<source lang="cpp">

  1. 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() );

} </source>

При загрузке библиотеки в память процесса создаёт новый сегмент данных, в котором размещаются глобальные переменные. Код функции Шаблон:Cpp находится в библиотеке и при вызове возвращает указатель на строковые данные. Операция Шаблон:Cpp используется для удаления модификатора Шаблон:Cpp.

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

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

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

Ограничения на type_to:

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

Производит ли операция Шаблон:Cpp код: нет.

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

Пути избавления: поскольку функция предназначена для работы на низком уровне и обхода ошибок чужой архитектуры, практически нет. Одну из задач — низкоуровневую работу с битами и байтами памяти — можно и нужно заменять на функцию Шаблон:Cpp Си++20.

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

<source lang="cpp"> // Возвращает 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 не является ссылкой </source>

См. также

Примечания

Шаблон:Примечания

Ссылки

Шаблон:Нет источников Шаблон:Перевести