Interested Article - Приведение типа
- 2021-10-14
- 2
Приведе́ние (преобразование) ти́па ( англ. 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
или его подтипом, приведение выполняется. Иначе:
- для указателей возвращается NULL ;
-
для ссылок создаётся
исключение
std :: bad_cast
.
Ограничения на
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 не является ссылкой
См. также
Примечания
- cppreference.com. от 18 декабря 2014 на Wayback Machine .
- open-std.org от 22 июля 2020 на Wayback Machine 6.3.1.4 Real floating and integer.
- ↑ . Дата обращения: 20 августа 2021. 20 августа 2021 года.
Ссылки
- Robert C. Seacord (англ.) , cert.org
|
В другом языковом разделе
есть более полная статья
(англ.)
.
|
- 2021-10-14
- 2