Операции составного присваивания
Операции этой группы перечислены в следующей таблице:
Операция | Использование | Эквивалент |
*= | 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 (нет гарантии - зависит от реализации).