Компилятор применяет эти преобразования типов при использовании значений одного арифметического типа там, где ожидается значение другого арифметического типа. Например, при использовании значения, отличного от логического в условии, арифметическое значение преобразуется в тип bool
таким же образом, как при присвоении арифметического значения переменной типа bool
:
int i = 42;
if (i) // условие рассматривается как истинное
i = 0;
При значении 0
условие будет ложным, а при всех остальных (отличных от нуля) — истинным.
К тому же при использовании значения типа bool
в арифметическом выражении оно всегда преобразуется в 0
или 1
. В результате применение логического значения в арифметическом выражении является неправильным.
Хотя мы сами вряд ли преднамеренно присвоим отрицательное значение объекту беззнакового типа, мы можем (причем слишком легко) написать код, который сделает это неявно. Например, если использовать значения типа unsigned
и int
в арифметическом выражении, значения типа int
обычно преобразуются в тип unsigned
. Преобразование значения типа int
в unsigned
выполняется таким же способом, как и при присвоении:
unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; // выводит -84
std::cout << u + i << std::endl; // при 32-битовом int,
// выводит 4294967264
Во втором выражении, прежде чем будет осуществлено сложение, значение -42
типа int
преобразуется в значение типа unsigned. Преобразование отрицательного числа в тип unsigned
происходит точно так же, как и при попытке присвоить это отрицательное значение объекту типа unsigned
. Произойдет "обращение значения" (wrap around), как было описано выше.
При вычитании значения из беззнакового объекта, независимо от того, один или оба операнда являются беззнаковыми, следует быть уверенным том, что результат не окажется отрицательным:
unsigned u1 = 42, u2 = 10;
std::cout << u1 - u2 << std::endl; // ok: результат 32
std::cout << u2 - u1 << std::endl; // ok: но с обращением значения
Тот факт, что беззнаковый объект не может быть меньше нуля, влияет на способы написания циклов. Например, в упражнениях раздела 1.4.1 (стр. 39) следовало написать цикл, который использовал оператор декремента для вывода чисел от 10
до 0
. Написанный вами цикл, вероятно, выглядел примерно так:
for (int i = 10; i >= 0; --i)
std::cout << i << std::endl;
Казалось бы, этот цикл можно переписать, используя тип unsigned
. В конце концов, мы не планируем выводить отрицательные числа. Однако это простое изменение типа приведет к тому, что цикл никогда не закончится:
// ОШИБКА: u никогда не сможет стать меньше 0; условие
// навсегда останется истинным
for (unsigned u = 10; u >= 0; --u)
std::cout << u << std::endl;
Рассмотрим, что будет, когда u
станет равно 0
. На этой итерации отображается значение 0
, а затем выполняется выражение цикла for
. Это выражение, --u
, вычитает 1
из u
. Результат, -1
, недопустим для беззнаковой переменной. Как и любое другое значение, не попадающее в диапазон допустимых, это будет преобразовано в беззнаковое значение. При 32-разрядном типе int
результат выражения --u
при u равном 0
составит 4294967295
.
Исправить этот код можно, заменив цикл for
циклом while
, поскольку последний осуществляет декремент прежде (а не после) отображения значения:
unsigned u = 11; // начать цикл с элемента на один больше
// первого, подлежащего отображению