Операции составного присваивания

Операции этой группы перечислены в следующей таблице:

Операция Использование Эквивалент
*= a *= b a = a * b
/= a /= b a = a / b
%= a %= b a = a % b
+= a += b a = a + b
-= a -= b a = a - b
<<= a <<= b a = a << b
>>= a >>= b a = a >> b
&= a &= b a = a & b
|= a |= b a = a | b
^= a ^= b a = a ^ b

Общий формат записи выражений с использованием этих операций: < Переменная>< Операция >= < Выражение>

Эквивалентом этого формата в общем случае является:< Переменная> = <Переменная>< Операция >< Выражение>

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

17. Понятие L – значения.

Операция присваивания в свою очередь является выражением, значением которого является значение переменной в левой части после присваивания (эту переменную часто называют L-значением (от слова left– левая сторона)).Например:

#include<iostream>

usingnamespacestd;

Intmain()

{

inti = 7, j = 30, k;

cout<<i * j<<endl;

cout<< (k = i * j) <<endl;

cout<< k <<endl;

system("Pause");

return 0;

}

Результат работы программы: 210,210, 210.

18. Преобразование типов данных.

Рассмотрим пример:

Определены переменные

inta = 5;

doubleb = 7.6;

В программе необходимо подсчитать их сумму a + b.

Внутреннее (машинное) представление типа intи типа double существенно различаются. Существенно различаются и процедуры сложения целых значений и процедуры сложения вещественных значений. Как же тогда сложить целое и вещественное? Выход – преобразовать оба значения к одному и тому же типу данных, а затем выполнить соответствующую операцию. Но если преобразовать значение переменной b к целому типу данных (отбросить дробную часть или округлить до ближайшего целого) результат будет равен либо 12, либо 13, то есть произошла потеря точности. А вот если сначала преобразовать значение a к типу double и сложить их как вещественные значения, тогда точность потеряна не будет (результат будет равен 12.6 и будет вещественного типа). На самом деле так и происходит.

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

В языке C++ различают неявное (автоматическое) и явное преобразование типов данных.

Неявное преобразование типов данных при выполнении операций, подобной рассмотренной выше (и в ряде других случаев), выполняется компилятором по определенным правилам автоматически. В чем же состоят эти правила?

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

1. Все данные типов char и shortint преобразуются к типу int.

2. Если хотя бы один из операндов имеет тип double, то и другой операнд преобразуется к типу double (если он другого типа); результат вычисления имеет тип double.

3. Если хотя бы один из операндов имеет тип float, то и другой операнд преобразуется к типу float (если он другого типа); результат вычисления имеет тип float.

4. Если хотя бы один операнд имеет тип long, то и другой операнд преобразуется к типу long (если он другого типа); результат имеет тип long.

5. Если хотя бы один из операндов имеет тип unsigned, то и другой операнд преобразуется к типу unsigned (если его тип не unsigned); результат имеет тип unsigned.

Если ни один из случаев 1-5 не имеет места, то оба операнда должны иметь тип int; такой же тип будет и у результата.

Следует отметить, что компиляторы языка C++ достаточно свободно выполняют подобные преобразования, что может в ряде случаев привести к неожиданным результатам. Например:

#include <iostream>

using namespace std;

Int main()

{

unsigned a = 5;

int b = 10;

cout<< a << " - " << b << " = " << a - b <<endl;

system("Pause");

return 0;

}

Результат работы программы: 5 – 10 = 4294967291

Таким образом, несмотря на то, что язык C++ достаточно «снисходителен» к действиям программиста, это требует от программиста еще большей дисциплины в его действиях и четких знаний нюансов языка программирования.

Для исправления ошибки в работе предыдущей программы можно, например, изменить вычисление выражения a–bследующим образом: (int) a – bили int(a) – b.В этом случае мы получим правильный результат: 5 – 10 = -5.

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

Явное преобразование типов данных осуществляется с помощью соответствующей операции преобразования типов данных, которая имеет один из двух следующих форматов:(<тип данных>) <выражение>или <тип данных> (<выражение>). Например:

(int) 3.14int (3.14)

(double) aилиdouble (a)

(long) (a + 1e5f) long (a + 1e5f)

Подобные преобразования имеют своим исходом три ситуации:

· преобразование без потерь;

· с потерей точности;

· с потерей данных.

Преобразование происходит без потерь, если преобразуемое значение принадлежит множеству значений типа, к которому осуществляется преобразование. Например:

shorta = 100;

cout<< (int) a<<endl; // На экран выведено 100

cout<< (char) a<<endl; // Выведена буква d(ее десятичный эквивалент - 100)

cout<< (float) a<<endl; // На экран выведено 100

cout<< (double) a<<endl; // На экран выведено 100

float b = 3.14f;

cout<< (double) b <<endl; // Наэкранвыведено3.14

double d = 3.14;

cout<< (float) d<<endl; // На экран выведено 3.14

Преобразование любого вещественного типа к целому осуществляется путем отбрасывания дробной части вещественного значения, поэтому практически всегда такие преобразования приводят к потере точности (осуществляются приближенно). Например:

doubled = 3.74;

cout<< (int) d<<endl; // Наэкранвыведено3

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

int a = -100;

cout<< (unsigned) a <<endl; // Наэкранвыведено4294967196

int a = 50000;

cout<< (short) a <<endl; // Наэкранвыведено-15536

float b = 3e+9f;

cout<< (int) b <<endl; // Наэкранвыведено-2147483648

double d = 3e+9;

cout<< (int) d <<endl; // Наэкранвыведено-2147483648

double d = 3e+40;

cout<< (float) d <<endl; // Наэкранвыведено1.#INF- переполнение

double d = -3e+40;

cout<< (float) d<<endl; // На экран выведено -1.#INF- переполнение

Рассмотренная операция преобразования типов перешла в C++ изC. В C++ имеются свои операции преобразования типов данных. Например, рассмотренные выше преобразования в C++ можно было бы выполнить с помощью операции static_cast, имеющей следующий формат:static_cast<тип данных> (выражение).Например:static_cast<double> (a + 2e+40f)

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

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

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

Унарный плюс (+) и унарный минус (-)Единственный операнд любого арифметического типа. Формат записи: + < Выражение >,- < Выражение >

Унарный плюс возвращает значение операнда без изменений.

Унарный минус (его иногда называют арифметическим отрицанием) меняет знак операнда на обратный.

Умножение - * - бинарная операция. Примеры записи: a * b 2 * 3 a * 0.56

Операнды могут быть любого арифметического типа данных. Тип данных результата операции определяется правилами неявного преобразования типов.

При выполнении возможен выход реального значения результата за допустимый диапазон значений типа данных – при этом значение результата операции трудно предсказать. Например: cout<< 1000000 * 1000000; // Результат: -727379968,cout<< 1е20f * 1e20f; // Результат: 1.#INF –переполнение (+ бесконечность), cout<< 1е20f * -1e20f; // Результат: -1.#INF –переполнение (- бесконечность).

Деление - / - бинарная операция. Примеры записи: a / b 2 / 3 a / 0.56

Если оба операнда являются целыми, то результат деления будет целым. В этом случае целый результат получается отбрасыванием дробной части от полученного реального значения (не округление). Например:5 / 3 - результат равен 1.

Если все же в результате выполнения этой операции требуется получить значение с дробной частью, необходимо чтобы хотя бы один из операндов был вещественного типа. Например:5.0 / 3или 5 / 3.или5.0 / 3.0 или5 / 3fили 5f / 3или 5f / 3fили float (5) / 3илиdouble (5) / 3

Результатом вычисления всех этих выражений будет значение 1.6666… одного из вещественных типов.

Однако, например, выражение double (5 / 3)хотя и будет вещественного типа, но его значение все равно будет равно 1, поскольку сначала будет вычислено выражение в скобках, результат которого будет целого типа и будет равен 1, а затем это значение будет приведено к вещественному типу.

При выполнении операции возможен выход реального значения результата за допустимый диапазон значений типа данных – при этом значение результата операции трудно предсказать. Например: cout<< 1е35f / 0.0001f; // Результат: 1.#INF –переполнение (+ бесконечность), cout<< 1е20f / -0.0001f; // Результат: -1.#INF –переполнение (- бесконечность), cout<< 1е200 / 1e-200; // Результат: 1.#INF –переполнение (бесконечность).

Если первый операнд вещественного типа, то деление на 0 дает значение 1.#INF или -1.#INF. Если же он целого типа, возникает ошибка режима исполнения (деление на 0).

Остаток от деления - % - бинарная операция. Операнды только целого типа. Результат операции целого типа. Например: 5 % 1 - результат 0,5 % 2 - результат 1,5 % 3 - результат 2,5 % 4 - результат 1, 5 % 5 - результат 0, 5 % 6 - результат 5…..

Если второй операнд равен 0, возникает ошибка режима исполнения (деление на 0).

Сложение (+) и вычитание (-) – бинарные операции. Операнды могут быть любых арифметических типов данных. Примеры записи:a + b, a – b

Тип результата операций определяется правилами неявного преобразования типов.

При выполнении операций возможны ошибки переполнения и некорректного преобразования типов данных операндов.Например:

unsignedshortn = 0, m;

m = n - 1;

cout<<m<<endl; //На экран будет выведено65535

n = m + 1;

cout<<n<<endl; //На экран будет выведено0

20. Операции инкремента и декремента, их разновидности

Инкремент (++) и декремент (--) – унарные операции увеличения и уменьшения операнда на 1соответственно. Операнд может быть любого арифметического типа данных.

Операции имеют две формы – префиксную(++a, --a) ипостфиксную(a++, a--).

Независимо от формы операция инкремента эквивалентна следующему оператору: a = a + 1; аоперациядекрементаследующему:a = a - 1; Например:

#include <iostream>

using namespace std;

Int main()

{

inti = 7, j = 10;

++ i;

j ++;

cout<<i<< "\t" <<j<<endl; // На экран выведено 8 11

-- i;

j --;

cout<<i<< "\t" <<j<<endl; // На экран выведено 7 10

system("Pause");

return 0;

}

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

Немного изменим текст программы:

#include <iostream>

using namespace std;

Int main()

{

inti = 7, j = 10;

cout<< ++ i<< "\t " <<j ++ <<endl; // На экран выведено 8 10

cout<<i<< "\t " <<j<<endl; // На экран выведено 8 11

cout<< -- i<< "\t " <<j -- <<endl; // На экран выведено 7 11

cout<<i<< "\t " <<j<<endl; // На экран выведено 7 10

system("Pause");

return 0;

}

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

В первом примере операторы ++ i; j ++;просто изменяютзначения переменных iи jбез какой-либо другой обработки и только после окончания работы этих операторов на экран выводятся эти измененные значения. Поэтому различие между префиксной и постфиксной формами не чувствуется. То же самое происходит и при выполнении операторов -- i; j--;.

Во втором примерепрефиксные и постфиксные формыопераций инкремента и декремента участвуют в выполнении оператора вывода данных на экран. Поэтому при выполнении префиксных операций сначала происходит изменение значений переменной i, потом эти измененные значения выводятся на экран, а при выполнении постфиксных операций сначала выводятся неизмененные значения переменной j, а уже после этого производятся изменения значения этой переменной.

А как интерпретировать такие выражения:a+++b, a---b

Эти выражения следует интерпретировать так:(a++) + b,(a--) - b

Но не так: a + (++b), a - (--b)

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

21. Операции отношения.

Операции этой группы служат для сравнения значений. Сюда входят следующие операции:

· == - равно;

· != - не равно;

· >- больше;

· >= - больше или равно;

· < - меньше;

· <= - меньше или равно.

Все эти операции бинарные. В качестве операндов могут быть использованы выражения любых арифметических типов данных. Результат этих операций всегда логического типа (bool).

Примеры: a == b,a != b,a> 10,(a - 3) >= (b + 10).

22. Логические операции.

Эти операции используются при построении сложных логических выражений. В эту группу входят 3 операции:

· !- логическое отрицание (логическое НЕ);

· && - конъюнкция (логическое И);

· || - дизъюнкция (логическое ИЛИ).

Первая операция унарная, две остальные – бинарные. Операнды – выражения любого арифметического типа данных, значения которых интерпретируются как значения логического типа (отличное от 0 значение – true; 0 - false) . Результат этих операций - логического типа.

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

a b !a a&&b a||b

Пусть, например, имеется математическое неравенство:0 <x< 10. На языке C++ это неравенство следует записывать так:(0 <x) && (10 >x)или (х > 0) && (x< 10). А математическое неравенство 0 >x> 10должно выглядеть следующим образом: (0 >x) || (10 <x)или (х < 0) || (x> 10).

Особенностью выполнения операций &&и || является то, что второй операнд (в правой части операций) вычисляется не всегда. Он вычисляется только в том случае, если значения первого операнда недостаточно для получения результата операций &&или ||.

Например. Если в выражении (a + 10) && (b – 1)значение первого (левого) операнда a + 10равно 0 (false) (это будет при значении a = -10), то вычисление второго (правого) операнда b – 1не выполняется, так как и без его вычисления, значение результата операции &&уже известно – это false. А в выражении (a + 10) || (b – 1) второй операнд не будет вычисляться в том случае, если первый операнд не равен 0– в этом случае результат операции ||и так уже известен – он равен true.

23. Побитовые операции сдвига

Побитовые операции рассматривают операнды как упорядоченные наборы битов,
каждый бит может иметь одно из двух значений - 0 или 1 (наборы двоичных значений).Такие операции позволяютпрограммисту манипулировать значениями отдельных битов. Объект, содержащий наборбитов, иногда называют битовым вектором. Он позволяет компактно хранить наборфлагов- переменных, принимающих значение "да" "нет".

Операции сдвига - <<и >> - бинарные операции. Операнды целого типа. Результат также целого типа. Формат записи:< Операнд 1 ><<< Операнд 2 > -сдвиг влево, < Операнд 1 >>>< Операнд 2 > -сдвиг вправо

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

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

unsigned a = 20, n = 3, r;

r = a << n;

cout<< r <<endl; //Наэкранвыведено160

r = a >> n;

cout<<r<<endl; //На экран выведено 2

Иллюстрация:

Номер разряда: 31 30 … 8 7 6 5 4 3 2 1 0

Значение a: 0 0 … 0 0 0 0 1 0 1 0 0 = 20

Операция: a<<n

Значение r: 0 0 … 0 1 0 1 0 0 0 0 0 = 160

Операция: a>>n

Значение r: 0 0 … 0 0 0 0 0 0 0 1 0 = 2

Операция сдвига влево осуществляет перемещение битов левого операнда a в сторону больших разрядов на количество разрядов, равное значению правого операнда n. Это эквивалентно умножению значения a на 2 в степени n(20 * 8 = 160).

Операция сдвига вправо осуществляет перемещение битов левого операнда a в сторону меньших разрядов на количество разрядов, равное значению правого операнда n. Это эквивалентно делению значения a на 2 в степени n(целочисленное деление 20 / 8 = 2).

Используя операцию сдвига влево очень просто получить любую целую степень двойки в диапазоне степеней равной количеству двоичных разрядов правого операнда без 1. Например, так:1U<< 20 - равно 2 в степени 20, то есть 1048576

При сдвиге влево (в сторону старших разрядов), освобождающиеся младшие разряды замещаются 0 (нулями). При сдвиге вправо возможны две ситуации: если первый операнд беззнаковый (unsigned), то освобождающиеся старшие разряды замещаются 0; если же первый операнд знаковый, то освобождающиеся старшие разряды замещаются либо знаковым разрядом, либо 0 (нет гарантии - зависит от реализации).

Наши рекомендации