Неявные правила преобразования типов в операторах C ++


167

Я хочу быть лучше, зная, когда мне следует сыграть. Каковы неявные правила преобразования типов в C ++ при сложении, умножении и т. Д. Например,

int + float = ?
int * float = ?
float * int = ?
int / float = ?
float / int = ?
int / int = ?
int ^ float = ?

и так далее ...

Будет ли выражение всегда оцениваться как более точный тип? Различаются ли правила для Java? Пожалуйста, поправьте меня, если я сформулировал этот вопрос неточно.


16
Имейте в виду, ^это XOR.
GManNickG

16
@int ^ float = ошибка компиляции :)
Serge Dundich

Ответы:


223

В C ++ операторы (для типов POD) всегда действуют на объекты одного типа.
Таким образом, если они не совпадают, один будет повышен, чтобы соответствовать другому.
Тип результата операции совпадает с операндами (после преобразования).

If either is      long          double the other is promoted to      long          double
If either is                    double the other is promoted to                    double
If either is                    float  the other is promoted to                    float
If either is long long unsigned int    the other is promoted to long long unsigned int
If either is long long          int    the other is promoted to long long          int
If either is long      unsigned int    the other is promoted to long      unsigned int
If either is long               int    the other is promoted to long               int
If either is           unsigned int    the other is promoted to           unsigned int
If either is                    int    the other is promoted to                    int
Both operands are promoted to int

Заметка. Минимальный размер операций составляет int. Поэтому short/ charповышаются до intтого, как операция завершена.

Во всех ваших выражениях intсимвол повышается до floatдо выполнения операции. Результатом операции является float.

int + float =>  float + float = float
int * float =>  float * float = float
float * int =>  float * float = float
int / float =>  float / float = float
float / int =>  float / float = float
int / int                     = int
int ^ float =>  <compiler error>

1
«Минимальный размер операций - int.» - Это было бы очень странно (как насчет архитектур, которые эффективно поддерживают операции char / short?) Это действительно в спецификации C ++?
Рафал Доугирд

3
@Rafal: Да. Предполагается, что int является наиболее эффективным целочисленным типом для работы на конкретной платформе. char всегда должен быть 1, но short может быть того же размера, что и int.
Мартин Йорк,

1
@ Рафал: да, это очень странно, и это в стандарте. Во многих случаях описываемая вами архитектура может использовать свой суперэффективный charтип. Если значение char + charприсваивается a char, то он может просто выполнить арифметику charи, например, обернуть. Но если результат присваивается, intтогда он должен выполнять арифметику в типе, достаточно большом, чтобы получить правильный результат, когда он больше, чем CHAR_MAX.
Стив Джессоп

2
Я просто хочу подчеркнуть тот факт, что int переводится в unsigned int !!! Я боролся с ошибками в течение нескольких дней, потому что у меня сложилось впечатление, что оба будут повышены до int или long, чтобы возможный отрицательный результат не вызвал недопущение / смещение.
nitsas

10
Пример проблемы " int получает повышение до unsigned int ": ((int) 4) - ((unsigned int) 5)приводит 4294967295к 32-битным и 32-битным беззнаковым.
nitsas

33

Арифметические операции, связанные с floatрезультатами в float.

int + float = float
int * float = float
float * int = float
int / float = float
float / int = float
int / int = int

Для более подробного ответа. Посмотрите, что говорится в разделе §5 / 9 стандарта C ++.

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

Этот шаблон называется обычными арифметическими преобразованиями, которые определяются следующим образом:

- Если один из операндов имеет тип long double, другой должен быть преобразован в long double.

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

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

- В противном случае интегральные преобразования (4.5) должны выполняться для обоих операндов. 54)

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

- В противном случае, если один операнд является длинным int, а другой - без знака int, то, если long int может представлять все значения беззнакового int, беззнаковое int должно быть преобразовано в long int; в противном случае оба операнда должны быть преобразованы в unsigned long int.

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

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

[Примечание: в противном случае единственным оставшимся случаем является то, что оба операнда являются int]


3
... до тех пор, пока другой тип не является ни тем, doubleни другим long double.
CB Bailey

1
@ Чарльз: Верно. Я процитировал соответствующий раздел из Стандарта для уточнения.
Наваз

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

1
Этот ответ устарел. Предложить обновление. В частности long longи unsigned longне адресовано прямо здесь.
chux - Восстановить Монику

@MarcoA. 32-битному floatне хватает битов в мантиссе (24 бита для IEEE-754 ) для 32-битного int, поэтому возможна некоторая потеря данных. 64-битный doubleдолжен быть просто в порядке.
Марк Рэнсом

17

Поскольку другие ответы не говорят о правилах в C ++ 11, вот один. Из стандарта C ++ 11 (черновик N3337) §5 / 9 (подчеркнуто отличие):

Этот шаблон называется обычными арифметическими преобразованиями , которые определяются следующим образом:

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

- Если один из операндов имеет тип long double, другой должен быть преобразован в long double.

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

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

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

- Если оба операнда имеют одинаковый тип, дальнейшее преобразование не требуется.

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

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

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

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

Смотрите здесь список, который часто обновляется.


1
Эти правила были одинаковыми во всех версиях C ++, за исключением перечислений с областью действия, которые, конечно, были добавлены в C ++ 11
MM

6

Этот ответ в значительной степени направлен на комментарий, сделанный @ RafałDowgird:

«Минимальный размер операций - int.» - Это было бы очень странно (как насчет архитектур, которые эффективно поддерживают операции char / short?) Это действительно в спецификации C ++?

Имейте в виду, что стандарт C ++ имеет важнейшее правило «как будто». Смотрите раздел 1.8: Выполнение программы:

3) Это положение иногда называют правилом «как будто», потому что реализация может свободно игнорировать любое требование Стандарта при условии, что в результате будет выполнено требование, насколько это можно определить из наблюдаемого поведение программы.

Компилятор не может установить intразмер 8 бит, даже если бы он был самым быстрым, поскольку стандарт предписывает 16-битный минимум int.

Следовательно, в случае теоретического компьютера со сверхбыстрыми 8-битными операциями неявное повышение до intарифметики может иметь значение. Однако для многих операций вы не можете определить, действительно ли компилятор выполнял операции с точностью до a intи затем преобразовывал их в a charдля хранения в вашей переменной, или все эти операции выполнялись в char.

Например, рассмотрим unsigned char = unsigned char + unsigned char + unsigned char, где сложение будет переполнено (допустим, значение 200 для каждого). Если бы вы повысили до int, вы бы получили 600, что затем было бы неявно приведено к результату unsigned char, который обернул бы по модулю 256, таким образом, получив окончательный результат 88. Если вы не делали таких повышений, вам пришлось бы переходить между первым два дополнения, которые уменьшают проблему с 200 + 200 + 200до 144 + 200344, что уменьшает до 88. Другими словами, программа не знает разницы, поэтому компилятор может игнорировать мандат на выполнение промежуточных операций, intесли операнды имеют более низкий рейтинг, чем int.

Это верно в общем случае сложения, вычитания и умножения. Это вообще не верно для деления или модуля.


4

Если исключить неподписанные типы, существует упорядоченная иерархия: знаковый символ, short, int, long, long long, float, double, long double. Во-первых, все, что идет перед int в приведенном выше, будет преобразовано в int. Затем в бинарной операции тип с более низким рейтингом будет преобразован в более высокий, и в результате будет получен тип с более высоким рейтингом. (Вы заметите, что из иерархии, каждый раз, когда речь идет о плавающей точке и интегральном типе, целочисленный тип будет преобразован в тип с плавающей точкой.)

Неподписанные вещи немного усложняют: это мешает ранжированию, и части ранжирования становятся определяемыми реализацией. Из-за этого лучше не смешивать подписанные и неподписанные в одном выражении. (Кажется, что большинство экспертов по C ++ избегают использования без знака, если только не используются побитовые операции. Это, по крайней мере, то, что рекомендует Страуструп.)


3
Страуструп может порекомендовать то, что ему нравится, но использование знака intдля числа, которое никогда не должно быть отрицательным, является полной тратой полных 50% доступного диапазона. Я конечно не Страуструп, но я использую unsignedпо умолчанию и signedтолько тогда, когда у меня есть причина.
underscore_d

1
Это все хорошо, underscore_d, до того дня, когда вы должны вычесть. Основная проблема с числами без знака в C ++ состоит в том, что при выполнении вычитания они остаются без знака. Итак, предположим, что вы пишете функцию, чтобы увидеть, в порядке ли std :: vector. Вы могли бы написать, bool in_order(vector<T> vec) { for ( int i = 0; i < size() - 1; ++i) { if (vec[i + 1] < vec[i]) return false; } return true;и тогда вы будете раздражены, обнаружив, что он падает для пустых векторов, потому что size () - 1 возвращает 18446744073709551615.
jorgbrown

3

Мое решение к проблеме получил WA (неправильный ответ), то я изменил одну из них, intчтобы long long intи он дал AC (принять) . Раньше я пытался это сделать long long int += int * int, а после я исправлял это long long int += long long int * int. Погуглил я придумал,

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

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

Условия выполнены ---> Конверсия

  • Любой операнд имеет тип long double . ---> Другой операнд преобразуется в тип long double .

  • Предыдущее условие не выполнено, и любой из операндов имеет тип double . ---> Другой операнд преобразуется в тип double .

  • Предыдущие условия не выполнены, и любой из операндов имеет тип float . ---> Другой операнд преобразуется в тип float .

  • Предыдущие условия не выполнены (ни один из операндов не имеет плавающих типов). ---> Интегральные продвижения выполняются над операндами следующим образом:

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

2 Целочисленные правила преобразования

  • Целочисленные Акции:

Целочисленные типы, меньшие чем int, повышаются, когда над ними выполняется операция. Если все значения исходного типа могут быть представлены как int, значение меньшего типа преобразуется в int; в противном случае он конвертируется в беззнаковое целое. Целочисленные продвижения применяются как часть обычных арифметических преобразований для определенных выражений аргументов; операнды унарных операторов +, - и ~; и операнды операторов сдвига.

  • Целочисленный рейтинг конверсии:

    • Никакие два целых типа со знаком не должны иметь одинаковый ранг, даже если они имеют одинаковое представление.
    • Ранг целочисленного типа со знаком должен быть больше ранга целочисленного типа со знаком с меньшей точностью.
    • Ранг long long intдолжен быть больше, чем ранг long int, который должен быть больше, чем ранг int, который должен быть больше, чем ранг short int, который должен быть больше, чем ранг signed char.
    • Ранг любого целого типа без знака должен равняться рангу соответствующего целого типа со знаком, если таковой имеется.
    • Ранг любого стандартного целочисленного типа должен быть больше, чем ранг любого расширенного целочисленного типа с такой же шириной.
    • Звание charдолжно равняться званию signed charи unsigned char.
    • Ранг любого расширенного целочисленного типа со знаком относительно другого расширенного целочисленного типа со знаком с той же точностью определяется реализацией, но все еще подчиняется другим правилам для определения ранга целочисленного преобразования.
    • Для всех целочисленных типов T1, T2 и T3, если T1 имеет больший ранг, чем T2, и T2 имеет больший ранг, чем T3, то T1 имеет больший ранг, чем T3.
  • Обычные арифметические преобразования:

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

1

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

4.5 Интегральные продвижения [conv.prom] r-значение
типа char, знаковый char, unsigned char, short int или unsigned short int может быть преобразовано в r-значение типа int, если int может представлять все значения типа источника; в противном
случае исходное значение может быть преобразовано в значение типа unsigned int.
Значение типа wchar_t (3.9.1) или тип перечисления (7.2) может быть преобразовано в значение первого
из следующих типов, которые могут представлять все значения его базового типа: int, unsigned int,
long или unsigned длинный.
Значение r для интегрального битового поля (9.6) может быть преобразовано в значение типа int, если int может представлять все
значения битового поля; в противном случае его можно преобразовать в unsigned int, если unsigned int может
повторно передать все значения битового поля. Если битовое поле еще больше, к нему не применяется интегральное продвижение. Если
битовое поле имеет перечисляемый тип, оно рассматривается как любое другое значение этого типа в целях продвижения.
Rvalue типа bool может быть преобразовано в rvalue типа int, где false становится равным нулю, а true
становится единым.
Эти преобразования называются интегральными продвижениями.

4.6 Повышение с плавающей запятой [conv.fpprom] Значение r
с типом float может быть преобразовано в значение типа double. Значение не изменяется.
Это преобразование называется продвижением с плавающей запятой.

Поэтому все преобразования, включающие float - результат float.

Только тот, который включает оба int - результатом является int: int / int = int


1

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

В выражениях, в которых участвуют действительное число и целое число, целое число будет преобразовано в действительное число. Например, в int + float тип выражения является float.

Другое различие связано с возможностями типа. Например, выражение, включающее int и long int, будет иметь тип long int.


2
Это неправда. На майских платформах long"больше", чем, floatно какой тип long+ float?
CB Bailey

1
-1: Что ты имеешь в виду под самым большим ? Поплавок больше, чем int? Или наоборот ?
Пол Р

2
Спасибо за ваши Коментарии. Да, размер в байтах здесь не представляет никакого интереса. Как оказалось, для объяснения ответа недостаточно указать курсивом больше всего. В любом случае, нет смысла объяснять это более глубоко, так как теперь есть другие, очень подробные ответы.
Baltasarq

-2

Предостережение!

Преобразования происходят слева направо.

Попробуй это:

int i = 3, j = 2;
double k = 33;
cout << k * j / i << endl; // prints 22
cout << j / i * k << endl; // prints 0

9
Это не из-за преобразования, а из-за приоритета оператора. j + i * kприведет к 101.
gartenriese
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.