Поделиться через


Стандартные преобразования

В языке C++ определены преобразования между его основными типами. Также определяются преобразования для указателей, ссылочных типов и типов указателей на члены. Эти преобразования называются стандартными преобразованиями.

В этом разделе рассматриваются следующие стандартные преобразования:

  • Восходящие приведения целочисленных типов

  • Преобразования целочисленных типов

  • Преобразования типов с плавающей запятой

  • Преобразования типов с плавающей запятой и целочисленных типов

  • Арифметические преобразования

  • Преобразования указателей

  • Преобразования ссылок

  • Преобразования указателей на члены

    Примечание.

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

Следующий код вызывает преобразования (в данном примере это восходящее приведение целочисленных типов).

long  long_num1, long_num2;
int   int_num;

// int_num promoted to type long prior to assignment.
long_num1 = int_num;

// int_num promoted to type long prior to multiplication.
long_num2 = int_num * long_num2;

Результат преобразования является L-значением только в том случае, если получается ссылочный тип. Например, определяемое пользователем преобразование, объявленное как operator int&() возвращаемое ссылкой, и является l-значением. Однако преобразование, объявленное как operator int() возвращающее объект и не является l-значением.

Восходящие приведения целочисленных типов

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

  • Объекты, литералы и константы типа char и short int

  • Типы перечислений

  • int битовые поля

  • Перечислители

Акции C++ являются "сохранением ценностей", так как значение после продвижения гарантированно будет совпадать со значением перед повышением. При сохранении значений объекты более коротких целочисленных типов (например, битовые поля или объекты типа char) повышаются до типа int , если int он может представлять полный диапазон исходного типа. Если int не удается представить полный диапазон значений, объект будет повышен до типа unsigned int. Хотя эта стратегия совпадает с той, которая используется стандартом C, преобразования, сохраняющие значения, не сохраняют "подпись" объекта.

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

  • Операнды /, , %/=%=, <, <=>или>=

    Эти операторы зависят от знака для определения результата. При применении к этим операндам изменения значений и сохранения подписей результаты могут привести к различным результатам.

  • Левый >> операнды или >>=

    Эти операторы обрабатывают подписанные и неподписанные количества по-разному в операции смены. Для подписанных значений операция сдвига вправо распространяет бит входа в освобожденные битовые позиции, в то время как освобожденные битовые позиции заполняются без знака.

  • Аргумент перегруженной функции или операнда перегруженного оператора, который зависит от подписи типа операнда для сопоставления аргументов. Дополнительные сведения об определении перегруженных операторов см. в разделе "Перегруженные операторы".

Преобразования целочисленных типов

Целочисленные преобразования — это преобразования между целочисленными типами. Целочисленные типы: char( short илиshort int), intlongи long long. Эти типы могут быть квалифицированы или signedunsignedunsigned использоваться как сокращенные.unsigned int

Вход в неподписанный

Объекты целочисленных типов со знаком можно преобразовывать в соответствующие типы без знака. При выполнении этих преобразований фактический битовый шаблон не изменяется. Однако интерпретация изменений данных. Рассмотрим этот код:

#include <iostream>

using namespace std;
int main()
{
    short  i = -3;
    unsigned short u;

    cout << (u = i) << "\n";
}
// Output: 65533

В предыдущем примере определяется signed shortiи инициализируется в отрицательное число. Выражение (u = i) вызывает i преобразование в объект unsigned short перед назначением u.

Без знака для подписи

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

#include <iostream>

using namespace std;
int main()
{
short  i;
unsigned short u = 65533;

cout << (i = u) << "\n";
}
//Output: -3

В предыдущем примере u представляет собой целочисленный unsigned short объект, который должен быть преобразован в подписанное количество, чтобы оценить выражение (i = u). Так как его значение не может быть правильно представлено в объекте signed short, данные неправильно интерпретируются, как показано ниже.

Преобразование чисел с плавающей запятой

Объект плавающего типа можно безопасно преобразовать в более точный тип с плавающей запятой, то есть преобразование не приводит к потере значения. Например, преобразования из или из floatdoubledoublelong double нее безопасны, а значение не изменяется.

Объект плавающего типа также можно преобразовать в менее точный тип, если он находится в диапазоне, представляющего этот тип. (См. раздел Ограничения с плавающей запятой для диапазонов типов с плавающей запятой.) Если исходное значение не представляется точно, его можно преобразовать в следующее выше или следующее нижнее представляющее значение. Результат не определен, если такое значение не существует. Рассмотрим следующий пример:

cout << (float)1E300 << endl;

Максимальное значение, представляющееся типом float , равно 3.402823466E38, что гораздо меньше 1E300. Таким образом, число преобразуется в бесконечность, и результатом является inf.

Преобразования между целочисленным типом и типом с плавающей запятой

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

При преобразовании объекта плавающего типа в целочисленный тип дробная часть усечена или округляется до нуля. Число, например 1.3, преобразуется в 1 и -1,3 преобразуется в -1. Если усеченное значение выше, чем наибольшее представляющее значение, или меньше, чем наименьшее представляющее значение, результат не определен.

Арифметические преобразования

Многие двоичные операторы (обсуждаемые в выражениях с двоичными операторами) вызывают преобразование операндов и дают результаты таким же образом. Преобразования этих операторов вызываются обычными арифметическими преобразованиями. Арифметические преобразования операндов с различными собственными типами выполняются, как показано в следующей таблице. Типы typedef ведут себя в соответствии со своими базовыми собственными типами.

Условия преобразования типов

Выполненные условия Преобразование
Любой операнд имеет тип long double. Другой операнд преобразуется в тип long double.
Предыдущее условие не выполнено, и любой операнду имеет тип double. Другой операнд преобразуется в тип double.
Предыдущие условия не выполнены, и любой операнд имеет тип float. Другой операнд преобразуется в тип float.
Предыдущие условия не выполнены (ни один из операндов не является операндом с плавающей запятой). Операнды получают целые промоушены следующим образом:

— Если какой-либо операнд имеет тип unsigned long, другой операнд преобразуется в тип unsigned long.
— Если предыдущее условие не выполнено, и если один операнд имеет тип и другой тип longunsigned int, оба операнда преобразуются в тип unsigned long.
— Если предыдущие два условия не выполнены, и если один операнд имеет тип long, другой операнд преобразуется в тип long.
— Если предыдущие три условия не выполнены, и если какой-либо операнд имеет тип unsigned int, другой операнд преобразуется в тип unsigned int.
— Если ни одно из предыдущих условий не выполняется, оба операнда преобразуются в тип int.

В следующем коде демонстрируются правила преобразования, описанные в таблице.

double dVal;
float fVal;
int iVal;
unsigned long ulVal;

int main() {
   // iVal converted to unsigned long
   // result of multiplication converted to double
   dVal = iVal * ulVal;

   // ulVal converted to float
   // result of addition converted to double
   dVal = ulVal + fVal;
}

Первый оператор в приведенном выше примере представляет умножение двух целочисленных типов, iVal и ulVal. Условие соответствует тому, что ни один операнд не имеет плавающего типа, и один операнд имеет тип unsigned int. Таким образом, другой операнд iVal, преобразуется в тип unsigned int. Затем результат назначается dVal. Условие, выполняемая здесь, заключается в том, что один операнду имеет тип double, поэтому unsigned int результат умножения преобразуется в тип double.

Вторая инструкция в предыдущем примере показывает добавление float целочисленного типа fVal : и ulVal. Переменная ulVal преобразуется в тип float (третье условие в таблице). Результат добавления преобразуется в тип double (второе условие таблицы) и назначается dVal.

Преобразования указателей

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

Указатель на классы

Указатель на класс можно преобразовать в указатель на базовый класс в двух случаях.

Во-первых, когда указанный базовый класс доступен и преобразование однозначно. Дополнительные сведения о неоднозначных ссылках на базовый класс см. в разделе "Несколько базовых классов".

Доступность базового класса зависит от используемого типа наследования. Рассмотрим наследование, показанное на следующем рисунке:

Схема с графом наследования и специальными возможностями базового класса.

На схеме показан базовый класс A. Класс B наследует от A через частную защищенную общедоступную. Класс C наследует от B через public B.

Граф наследования, демонстрирующий специальные возможности базового класса

В следующей таблице показана доступность базового класса для ситуации, представленной на рисунке.

Тип функции Финансовый дериватив Допустимо ли преобразование из

B* по закону A* ?
Внешняя функция (без области видимости класса) Private No
Защищено No
Общедоступный Да
Функция-член B (в области B) Private Да
Защищено Да
Общедоступный Да
Функция-член C (в области C) Private No
Защищено Да
Общедоступный Да

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

Результатом такого преобразования является указатель на вложенный объект, часть объекта, полностью описываемого базовым классом.

В следующем примере кода определяется два класса: A и B, где B является производным от класса A. Дополнительные сведения о наследовании см. в разделе "Производные классы". Затем он определяет bObjectобъект типа Bи два указателя (pA и pB), которые указывают на объект.

// C2039 expected
class A
{
public:
    int AComponent;
    int AMemberFunc();
};

class B : public A
{
public:
    int BComponent;
    int BMemberFunc();
};
int main()
{
   B bObject;
   A *pA = &bObject;
   B *pB = &bObject;

   pA->AMemberFunc();   // OK in class A
   pB->AMemberFunc();   // OK: inherited from class A
   pA->BMemberFunc();   // Error: not in class A
}

Указатель имеет типpA, который можно интерпретировать как "указатель A * на объект типаA". bObject Члены (напримерBComponent, иBMemberFunc) являются уникальными для типа B и поэтому недоступны через pA. Указатель pA предоставляет доступ только к тем характеристикам (функциям-членам и данным) объекта, которые определены в классе A.

Указатель на функцию

Указатель на функцию можно преобразовать в тип void *, если тип void * достаточно велик для хранения этого указателя.

Указатель на void

Указатели на тип void можно преобразовать в указатели на любой другой тип, но только с явным приведением типов (в отличие от C). Указатель на любой тип можно преобразовать неявно в указатель на тип void. Указатель на неполный объект типа можно преобразовать в указатель void (неявно) и обратно (явным образом). Результат такого преобразования равен значению исходного указателя. Объект считается неполным, если он объявлен, но недостаточно информации, доступной для определения его размера или базового класса.

Указатель на любой объект, который не const является или volatile неявно преобразован в указатель типа void *.

Указатели с ключевыми словами const и volatile

C++ не предоставляет стандартное преобразование из const или volatile типа в тип, который не является или constнетvolatile. Однако можно указать любое преобразование с помощью явного приведения типов (включая небезопасные преобразования).

Примечание.

Указатели C++ на элементы, кроме указателей на статические элементы, отличаются от обычных указателей и не имеют одинаковых стандартных преобразований. Указатели на статические члены являются обычными, и для них имеются такие же преобразования, как и для обычных указателей.

Преобразование пустых (null) указателей

Целочисленное константное выражение, которое вычисляется до нуля, или такое выражение, приведение к типу указателя, преобразуется в указатель, называемый указателем NULL. Этот указатель всегда сравнивается с указателем на любой допустимый объект или функцию. Исключение — это указатели на основанные объекты, которые могут иметь одинаковое смещение и по-прежнему указывать на разные объекты.

В C++11 тип nullptr должен быть предпочтителен для указателя null в стиле C.

Преобразование выражений указателей

Любое выражение с типом массива можно преобразовать в указатель того же типа. Результатом преобразования будет указатель на первый элемент массива. В следующем примере показано такое преобразование.

char szPath[_MAX_PATH]; // Array of type char.
char *pszPath = szPath; // Equals &szPath[0].

Выражение, которое приводит к функции, возвращающей определенный тип, преобразуется в указатель на функцию, возвращающую этот тип, за исключением случая, когда:

  • Выражение используется в качестве операнда для адреса оператора (>).

  • выражение используется в качестве операнда для оператора вызова функции.

Преобразования ссылок

Ссылку на класс можно преобразовать в ссылку на базовый класс в следующих случаях:

  • Указанный базовый класс доступен.

  • Преобразование является однозначным. Дополнительные сведения о неоднозначных ссылках на базовый класс см. в разделе "Несколько базовых классов".

Результат преобразования — это указатель на вложенный объект, представляющий базовый класс.

Указатель на член

Указатели на члены класса можно преобразовать в ходе присвоения, инициализации, сравнения и выполнения других выражений. В этом разделе описаны следующие преобразования указателей в члены.

Указатель на член базового класса

Указатель на член базового класса можно преобразовать в указатель на член производного от него класса при выполнении следующих условий:

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

  • Производный класс не наследует практически от базового класса.

Если левый операнд является указателем на член, правый операнд должен иметь тип указателя на член или являться константным выражением со значением 0. Такое присваивание допустимо только в следующих случаях:

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

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

Указатель null на преобразования элементов

Целочисленное константное выражение, которое оценивается как нулевое, преобразуется в пустой указатель. Этот указатель всегда сравнивается с указателем на любой допустимый объект или функцию. Исключение — это указатели на основанные объекты, которые могут иметь одинаковое смещение и по-прежнему указывать на разные объекты.

В следующем примере кода демонстрируется определение указателя на член i в классе A. Указатель pai инициализируется со значением 0 и становится пустым указателем.

class A
{
public:
int i;
};

int A::*pai = 0;

int main()
{
}

См. также

Справочник по языку C++