int i = 1024;
int k = -i; // i равно -1024
bool b = true;
bool b2 = -b; // b2 равно true!
В разделе 2.1.1 упоминалось, что значения типа bool не нужно использовать для вычислений. Результат -b — хороший пример того, что имелось в виду.
Для большинства операторов операнды типа bool преобразуются в тип int. В данном случае значение переменной b, true, преобразуется в значение 1 типа int (см. раздел 2.1.2). Это (преобразованное) значение преобразуется в отрицательное, -1. Значение -1 преобразуется обратно в тип bool и используется для инициализации переменной b2. Поскольку значение инициализатора отлично от нуля, при преобразовании в тип bool его значением станет true. Таким образом, значением b2 будет true!
Некоторые арифметические выражения возвращают неопределенный результат. Некоторые из этих неопределенностей имеют математический характер, например деление на нуль. Причиной других являются особенности компьютеров, например, переполнение, происходящее при превышении вычисленным значением размера области памяти, представленной его типом.
Предположим, тип short занимает на машине 16 битов. В этом случае переменная типа short способна хранить максимум значение 32767. На такой машине следующий составной оператор присвоения приводит к переполнению.
short short_value = 32767; // максимальное значение при short 16 битов
short_value += 1; // переполнение
cout << "short_value: " << short_value << endl;
Результат присвоения 1 переменной short_value непредсказуем. Для хранения знакового значения 32768 требуется 17 битов, но доступно только 16. Многие системы никак не предупреждают о переполнении ни во время выполнения, ни во время компиляции. Подобно любой ситуации с неопределенностью, результат оказывается непредсказуем. На системе авторов программа завершилась с таким сообщением:
short value: -32768
Здесь произошло переполнение переменной: предназначенный для знака разряд содержал значение 0, но был заменен на 1, что привело к появлению отрицательного значения. На другой системе результат мог бы быть иным, либо программа могла бы повести себя по-другому, включая полный отказ.
Примененные к объектам арифметических типов, операторы +, -, * и / имеют вполне очевидные значения: сложение, вычитание, умножение и деление. Результатом деления целых чисел является целое число. Получаемая в результате деления дробная часть отбрасывается.
int ival1 = 21/6; // ival1 равно 3; результат усекается
// остаток отбрасывается
int ival2 = 21/7; // ival2 равно 3; остатка нет;
// результат - целочисленное значение
Оператор % известен как остаток (remainder), или оператор деления по модулю (modulus). Он позволяет вычислить остаток от деления левого операнда на правый. Его операнды должны иметь целочисленный тип.
int ival = 42;
double dval = 3.14;
ival % 12; // ok: возвращает 6
ival % dval; // ошибка: операнд с плавающей запятой
При делении отличное от нуля частное позитивно, если у операндов одинаковый знак, и отрицательное в противном случае. Прежние версии языка разрешали округление отрицательного частного вверх или вниз; однако новый стандарт требует округления частного до нуля (т.е. усечения).
Оператор деления по модулю определен так, что если m и n целые числа и n отлично от нуля, то (m/n)*n + m%n равно m. По определению, если m%n отлично от нуля, то у него тот же знак, что и у m. Прежние версии языка разрешали результату выражения m%n иметь тот же знак, что и у m, причем на реализациях, у которых отрицательный результат выражения m/n округлялся не до нуля, но такие реализации сейчас запрещены. Кроме того, за исключением сложного случая, где -m приводит к переполнению, (-m)/n и m/(-n) всегда эквивалентны -(m/n), m%(-n) эквивалентно m%n и (-m)%n эквивалентно -(m%n). А конкретно: