(Пропрашивает Сумасшедшего Эдди за первый ответ, но я чувствую, что это можно сделать более ясным)
Преобразование типов
Почему это происходит?случиться по двум основным причинам.Во-первых, вы написали явное выражение, например static_cast<int>(3.5)
.Другая причина в том, что вы использовали выражение в месте, где компилятору нужен другой тип, поэтому он вставит преобразование для вас.Например, 2.5 + 1
приведет к неявному приведению от 1 (целое число) к 1,0 (двойное число). Явные формы
Существует только ограниченное количество явных форм.Прежде всего, C ++ имеет 4 именованные версии: static_cast
, dynamic_cast
, reinterpret_cast
и const_cast
.C ++ также поддерживает приведение в стиле C (Type) Expression
.И, наконец, приведен «стиль конструктора» Type(Expression)
.
4 именованные формы задокументированы в любом хорошем вводном тексте.Приведение в стиле C расширяется до static_cast
, const_cast
или reinterpret_cast
, а приведение в стиле конструктора является сокращением для static_cast<Type>
.Однако из-за проблем с синтаксическим анализом приведение типа конструктора требует одиночного идентификатора для имени типа;unsigned int(-5)
или const float(5)
недопустимы.
Неявные формы
Гораздо сложнее перечислить все контексты, в которых может происходить неявное преобразование.Поскольку C ++ является типизированным ОО-языком, во многих ситуациях у вас есть объект A в контексте, где вам нужен тип B. Примерами являются встроенные операторы, вызывающие функцию или перехватывающие исключение по значению.
Последовательность преобразования
Во всех случаях, неявных и явных, компилятор попытается найти последовательность преобразования .Последовательность преобразования - это последовательность шагов, которая переводит вас из типа A в тип B. Точная последовательность преобразования, выбранная компилятором, зависит от типа приведения.dynamic_cast
используется для выполнения проверенного преобразования Base-Derived, поэтому необходимо проверить, наследуется ли Derived от Base, через какой промежуточный класс (ы).const_cast
может удалить как const
, так и volatile
.В случае static_cast
возможные шаги являются наиболее сложными.Это сделает преобразование между встроенными арифметическими типами;он преобразует базовые указатели в производные указатели и наоборот, учитывает конструкторы классов (типа назначения) и операторы приведения классов (типа источника) и добавляет const
и volatile
.Очевидно, что некоторые из этих шагов являются ортогональными: арифметический тип никогда не является указателем или типом класса.Кроме того, компилятор будет использовать каждый шаг не более одного раза.
Как мы отмечали ранее, некоторые преобразования типов являются явными, а другие - неявными.Это важно для static_cast
, потому что в последовательности преобразования используются пользовательские функции.Некоторые шаги преобразования, представленные компилятором, могут быть помечены как explicit
(в C ++ 03 могут только конструкторы).Компилятор пропустит (без ошибок) любую функцию преобразования explicit
для неявных последовательностей преобразования.Конечно, если не осталось альтернатив, компилятор все равно выдаст ошибку.
Арифметические преобразования
Целочисленные типы, такие как char
и short
, можно преобразовать в "больший""типы, такие как int
и long
, и меньшие типы с плавающей точкой могут аналогично быть преобразованы в большие типы.Целочисленные типы со знаком и без знака могут быть преобразованы друг в друга.Целочисленные типы и типы с плавающей точкой могут быть заменены друг на друга.
Базовые и производные преобразования
Поскольку C ++ является языком OO, существует ряд приведений, при которых отношение между Base и Derived имеет значение.,Здесь очень важно понимать разницу между реальными объектами, указателями и ссылками (особенно, если вы пришли из .Net или Java).Во-первых, реальные объекты.Они имеют только один тип, и вы можете преобразовать их в любой базовый тип (на данный момент игнорируя частные базовые классы).Преобразование создает объект new базового типа.Мы называем это «нарезка»;производные части нарезаны.
Другой тип преобразования существует, когда у вас есть указатели на объекты.Вы всегда можете преобразовать Derived*
в Base*
, потому что внутри каждого производного объекта есть подобъект Base.C ++ автоматически применит правильное смещение Base с Derived к вашему указателю.Это преобразование даст вам новый указатель, но не новый объект.Новый указатель будет указывать на существующий подобъект.Таким образом, актерский состав никогда не отрежет производную часть вашего объекта.
Конверсия другим способом сложнее.В общем, не каждый Base*
будет указывать на базовый подобъект внутри производного объекта.Базовые объекты могут также существовать в других местах.Следовательно, возможно, что преобразование не получится.C ++ дает вам два варианта здесь.Либо вы сообщаете компилятору, что уверены, что указываете на подобъект внутри Derived с помощью static_cast<Derived*>(baseptr)
, либо просите компилятор проверить с помощью dynamic_cast<Derived*>(baseptr)
.В последнем случае результат будет nullptr
, если baseptr
фактически не указывает на производный объект.
Для ссылок на Base и Derived применяется то же самое, за исключением dynamic_cast<Derived&>(baseref)
: это будетбросить std::bad_cast
вместо возврата нулевого указателя.(Нет таких вещей, как нулевые ссылки).
Пользовательские преобразования
Существует два способа определения пользовательских преобразований: через тип источника и через тип назначения.Первый способ заключается в определении члена operator DestinatonType() const
в типе источника.Обратите внимание, что он не имеет явного возвращаемого типа (это всегда DestinatonType
), и что это const
.Преобразования никогда не должны изменять исходный объект.Класс может определять несколько типов, в которые он может быть преобразован, просто добавляя несколько операторов.
Второй тип преобразования, через тип назначения, опирается на определяемые пользователем конструкторы.Конструктор T::T
, который можно вызвать с одним аргументом типа U
, можно использовать для преобразования объекта U
в объект T.Не имеет значения, имеет ли этот конструктор дополнительные аргументы по умолчанию, и не имеет значения, передан ли аргумент U по значению или по ссылке.Однако, как отмечалось ранее, если T::T(U)
равно explicit
, то это не будет учитываться в последовательностях неявного преобразования.
возможно, что в результате действия пользователя возможны многочисленные последовательности преобразования между двумя типами.-определенные последовательности преобразования.Поскольку это, по сути, вызовы функций (для определенных пользователем операторов или конструкторов), последовательность преобразования выбирается путем разрешения перегрузки различных вызовов функций.