Основою програм є набір послідовно виконуваних команд. Ці команди формуються у вирази і використовують оператори для виконання певних обчислень або дій.
На сьогоднішньому зайнятті.
■ Що таке вирази.
■ Що таке блоки, або складені вирази.
■ Що таке оператори.
■ Як виконувати прості арифметичні і логічні операції.

 Вирази

Мови, розмовні або програмування, складаються з виразів (statement), які йдуть одно за іншим. Давайте проаналізуємо перше вираження, яке ви вивчили :

cout << "Hello World" << endl;

Вираження використовує оператор cout для відображення тексту на консоль (тобто екран). Усі вирази в мові C++   закінчуються крапкою з комою (;), що визначає межу вираження. Це подібно до точки (.), яку ви додаєте в кінці речення розмовної мови. Наступне вираження може початися безпосередньо після крапки з комою, але для зручності і легкості для читання програмісти записують вирази з нового рядка. Ось, наприклад, два вираження в одному рядку:

cout << "Hello World" << endl; cout << "Another hello" << endl;

// Один рядок, два вираження

ПРИМІТКА

Відступи зазвичай не сприймаються компілятором. До них відносяться пропуски, табуляція, символи нового рядка і так далі. Проте відступи в межах строкових літералів відображаються у виведенні.

Тому наступний код був би недопустимий:

cout << "Hello

World" << endl;

// символ нового рядка в строковому літералі недопустимий

Такий код зазвичай закінчується повідомленням про помилку, що вказує, що компілятор не виявив в першому рядку закриваючу лапку (") і крапку, що завершує вираження, з комою (;). Якщо з якихось причин необхідно розповсюдити оператор на декілька рядків, досить додати в кінець символ оберненої косої риски (\) :

cout << "Hello \

World" << endl; // розділення рядка на дві цілком допустимо

Ще один спосіб написати приведений вище оператор в двох рядках - це застосувати два строкові літерали замість одного:

cout << "Hello "

"World" << endl; // два строкові літерали теж цілком допустимі

Зустрівши такий код, компілятор зверне увагу на два сусідні строкові літерали і сам об'єднає їх.

ПРИМІТКА

Розділення виразів на декілька рядків може бути дуже корисним, коли у вас є довгі текстові елементи або складні вирази, що складаються з безлічі змінних, які роблять вираження набагато довше, ніж може вмістити більшість екранів.

Складені вирази, або блоки

Згрупувавши оператори у фігурних дужках {...}, ви створюєте складений оператор (compound statement), або блок (block).

{

int Number = 365;

cout << "This block contains an integer and a cout statement"

<< endl;

}

Як правило, блок об'єднує безліч операторів. Блоки особливо корисні при застосуванні умовного оператора if і циклів, які розглядаються на зайнятті 6, 'Галуження процесу виконання програм".

Використання операторів

Оператори (operator) - це інструментальні засоби, що надаються мовою C   для роботи з даними, їх перетворенням, обробкою і ухвалення рішень на їх основі.

Оператор привласнення (=)

Оператор привласнення (assignment operator) ми вже використали в цій книзі, він цілком інтуїтивно зрозумілий:

int Mylnteger = 101;

Приведене вище вираження використовує оператор привласнення для ініціалізації цілочисельної змінної значенням 101. Оператор привласнення замінює значення, що міститься операндом ліворуч (званого 1-значением (1 - value)), значенням операнда справа (що називається r- значенням (r - value)).

Поняття l- і r-значень

l-значення частенько називають областями пам'яті. Така змінна, як Mylnteger, з наведеного вище прикладу фактично є дескриптором (описувачем) області пам'яті і відповідно l-значенням, r- значенням, навпаки, можуть бути самим вмістом області пам'яті.

Усі l-значення можуть бути r- значеннями, але не усі r- значення можуть бути l-значеннями. Щоб зрозуміти це краще, розглянемо наступний приклад, який не має ніякого сенсу, а тому не компілюватиметься:

101 = Mylnteger;

Оператори суми ( ), віднімання (-), множення (*), ділення (/) і ділення по модулю (%)

Ви можете виконувати арифметичні операції між двома операндами використовуючи оператор   для складання, оператор - для віднімання, оператор * для множення, оператор / для ділення і оператор % ділення по модулю:

int Numl = 22;

int Num2 = 5;
int addition = Numl + Num2; // 27
int subtraction = Numl - Num2; // 17
int multiplication = Numl * Num2; // 110 int division = Numl / Num2; // 4
int modulo = Numl % Num2; // 2
Оператор деления (/) возвращает результат деления

Оператор ділення (/) повертає результат ділення двох операндів. Проте у разі цілих чисел результат не містить десяткової частини, оскільки цілі числа за визначенням не можуть її містити. Оператор ділення по модулю (повертає тільки залишок ділення і застосуємо тільки до цілочисельних значень. Лістинг 5.1 містить просту програму, що демонструє виконання арифметичних дій з двома введеними користувачем числами.

ЛІСТИНГ 5.1. Демонстрація арифметичних операторів з введеними користувачем цілими числами

1: #include <iostream>
2: using namespace std;
3:
4: int main()
5: {
6:    cout << "Enter two integers:" << endl;
7:    int Numl = 0, Num2 = 0;
8:    cin >> Numl;
9:    cin >> Num2;
10:
11:   cout << Num1 << " + " << Num2 << " = " << Num1 + Num2 << endl;
12:   cout << Num1 << " - "  << Num2 << " = " << Num1 - Num2 << endl;
13:   cout << Num1 << " * " << Num2 << " = " << Num1 * Num2 << endl;
14:   cout << Num1 << " / "  << Num2 << " = " << Num1 / Num2 << endl;
15:   cout << Num1 << " % " << Num2 << " = " << Num1 % Num2 << endl;
16:
17:      return 0;
18: }

Результат

Enter two integers:
365
25
365 + 25 = 390
365 - 25 = 340
365 * 25 = 9125
365 / 25 = 14
365 % 25 = 1

Аналіз

Велика частина програми говорить сама за себе. Найцікавіше, ймовірно, рядок, що використовує оператор ділення по модулю Вона повертає залишок ділення значення змінної Numl (365) на значення змінної Num2 (25).

Використання операторів 93

Оператори інкремента (  ) і декремента (-)

Іноді потрібний інкремент (increment), або приріст, значення на одиницю. Це особливо важливо для змінних, контролюючих цикли, де значення змінної повинне збільшуватися або зменшуватися на одиницю при кожному виконанні циклу.

Для вирішення цього завдання мова C   надає оператори     (інкремента) і - (декремента).

Синтаксис їх використання наступний:

int Numl = 101;

int Num2 = Numl  ; // Постфіксний оператор інкремента

int Num2 =   Numl; // Префіксний оператор інкремента

int Num2 = Numl--; // Постфіксний оператор декремента

int Num2 = --Numl; // Префіксний оператор декремента

Приклад коду демонструє два різні способи застосування операторів інкремента і декремента : до і після операнда. Оператори, які розташовуються перед операндом, називаються префіксними (prefix) операторами інкремента або декремента, а ті, які розташовуються після,, - постфіксними (postfix) операторами інкремента або декремента.

 Що означає постфіксний і префіксний

Спочатку слід зрозуміти відмінність між префіксом і постфіксом, а потім використати те, що треба в даному випадку. Результат виконання постфіксних операторів полягає в тому, що спочатку 1-значение привласнюється r- значенню, а потім г-значення збільшується або зменшується. Це означає, що в усіх випадках використання постфіксного оператора значенням змінної Num2 буде колишнє значення змінної Numl (тобто те значення, яке вона мала до операції інкремента або декремента).

Дія префіксних операторів прямо протилежно: спочатку змінюється г-значення, а потім воно привласнюється 1-значению. У цих випадках змінні Num2 і Numl мають однакове значення. Лістинг 5.2 демонструє результат виконання префіксних і постфіксних операторів інкремента і декремента на прикладі цілого числа.

ЛІСТИНГ 5.2. Відмінності між постфіксними і префіксними операторами

1:  #include <iostream>
2:  using namespace std;
3:
4:  int main()
5:  {
6:     int MyInt = 101;
7:     cout << "Start value of integer being operated: " << MyInt
            << endl;
8:     
9:     int PostFixInc = MyInt++;
10:    cout << "Result of Postfix Increment = " << PostFixInc << endl; 
11:    cout << "After Postfix Increment, MyInt = " << MyInt << endl;
12:
13:    MyInt = 101; // Переустановка
14:    int PreFixInc = ++MyInt;
15:    cout << "Result of Prefix Increment = " << PreFixInc << endl; 
16:    cout << "After Prefix Increment, MyInt = " << MyInt << endl;
17:    
18:    MyInt = 101;
19:    int PostFixDec = MyInt--;
19:    cout << "Result of Postfix Decrement = " << PostFixDec << endl;
20:    cout << "After Postfix Decrement, MyInt = " << MyInt << endl;
21:
22:    MyInt = 101;
23:    int PreFixDec = --MyInt;
24:    cout << "Result of Prefix Decrement = " << PreFixDec << endl;
25:    cout << "After Prefix Decrement, MyInt = " << MyInt << endl;
26:
27:    return 0;
28: }

Результат

Start value of integer being operated: 101

Result of Postfix Increment = 101

After Postfix Increment, Mylnt = 102

Result of Prefix Increment = 102

After Prefix Increment, Mylnt = 102

Result of Postfix Decrement = 101

After Postfix Decrement, Mylnt = 100

Result of Prefix Decrement = 100

After Prefix Decrement, Mylnt = 100

Аналіз

Результати показують, чим постфіксні оператори відрізнялися від префіксних, привласнювані в рядках 8 і 18.1-значения містять початкові значення цілого числа, то, яким воно було до операцій інкремента або декремента. Префіксні оператори в рядках 13 і 23, навпаки, присвоїли результат інкремента або декремента. Ця найважливіша відмінність, про яку слід пам'ятати, вибираючи правильний тип оператора.

У наступних виразах префіксні або постфіксні оператори ніяк не впливають на результат:

Mylnt  ; // Те ж, що і ...

  Mylnt;

Річ у тому, що тут немає привласнення початкового значення і кінцевий результат в обох випадках - збільшене на одиницю ціле число.

ПРИМІТКА   Нерідко доводиться чути про ситуації, коли префіксні оператори інкремента або декремента є прийнятнішими з точки зору продуктивності, тобто   Myint прийнятніше, ніж Myint  .

Це правда, принаймні, теоретично, оскільки при постфіксних операторах компілятор повинен тимчасово зберігати початкове значення на випадок його привласнення. Вплив на продуктивність у разі цілих чисел незначний, але у разі деяких класів цей аргументі міг би мати сенс.

Уникайте переповнювання, обдумано вибираючи типи даних

Такі типів даних, як short, int, long, unsigned short, unsigned int, unsigned long і так далі, мають обмежену місткість для утримання чисел. Коли в ході арифметичної операції ви перевищуєте визначену типом межу, відбувається переповнювання (overflow).

Візьмемо, приміром, тип unsigned short. Тип даних short використовує 16 бітів, а отже, може містити значення від 0 до 65535. Коли ви додаєте 1 до значення 65535, що зберігається в змінній типу unsigned short, відбувається переповнювання і значення перетворюється на 0. Це дуже схоже на спідометр автомобіля : в нім теж відбувається механічне переповнювання, коли він може відображати тільки п'ять цифр, а автомобіль пройшов 99 999 кілометрів.

В даному випадку тип unsigned short виявився б непідходящим типом для такого лічильника. Для утримання чисел більше 65 535 слід використати тип unsigned int.

У разі типу signed short допустимі цілі числа в діапазоні від - 32768 до 32767. Додавання 1 до числа 32767 в змінній типу signed integer частенько дає в результаті найбільше негативне значення, але це вже залежить від компілятора.

Лістинг 5.3 демонструє помилки переповнювання, які можна з необережності отримати при арифметичних операціях.

ЛІСТИНГ 5.3. Помилка переповнювання у знакових і беззнакових цілочисельних змінних

1:  #include <iostream>
2:  using namespace std;
3:
4:  int main ()
5:  {
6:     unsigned short UShortValue = 65535;
7:     cout << "Incrementing unsigned short " << UShortValue
            << " gives: ";
8:     cout << ++UShortValue << endl;
9:
10:     short SignedShort = 32767;
11:    cout << "Incrementing signed short " << SignedShort<< " gives: ";
12:    cout << ++SignedShort << endl;
13:
14:    return 0;
15: }

Результат


Incrementing unsigned short 65535 gives: 0

Incrementing signed short 32767 gives: -32768

Аналіз

Як можна помітити, неумисні ситуації переповнювання призводять до непередбачуваної і не інтуїтивно зрозумілої поведінки додатка. Якщо з причин економії пам'яті ви використали тип unsigned short, то коли знадобиться число 65 536, ви фактично отримаєте число 0.

Оператори рівності (=) і нерівності (!=)

Частенько необхідно перевірити виконання або не виконання певної умови перш, ніж зробити деяку дію. Оператори рівності ==  (операнди рівні) і нерівності ! = (операнди не рівні) дозволяють зробити саме це.

Результат перевірки рівності має логічний тип bool, тобто true (істина) або false (брехня).

int MyNum = 20;

bool CheckEquality =  (MyNum == 20); // true

bool Checklnequality =  (MyNum != 100); // true

bool CheckEqualityAgain =  (MyNum == 200); // false

bool ChecklnequalityAgain =  (MyNum != 20); // false

Оператори порівняння

Окрім перевірки на рівність і нерівність, може виникнути необхідність в порівнянні, значення якої змінної більше, а якої менше. Для цього мова C   надає оператори порівняння, приведені в таблицю. 5.1.

ТАБЛИЦЯ 5.1. Оператори порівняння

Оператор Опис
Менше (<) Повертає значення true, якщо один операнд менше іншого (Ор1 <Ор2), інакше повертає значення false
Більше (>) Повертає значення true, якщо один операнд більше іншого (Opl > Ор2), інакше повертає значення false
Менше або рівне (<=) Повертає значення true, якщо один операнд менше або дорівнює іншому, інакше повертає значення false
Більше або рівне (>=) Повертає значення true, якщо один операнд більше або дорівнює іншому, інакше повертає значення false

   Як свідчить таблиця. 5.1, результатом операції порівняння завжди є значення true або false, іншими словами, тип bool. Наступний приклад коду демонструє застосування операторів порівняння, приведених в таблицю. 5.1:
int MyNum =20; // приклад цілочисельного
bool CheckLessThan =  (MyNum < 100);
bool CheckGreaterThan =  (MyNum > 100);
bool CheckLessThanEqualTo =  (MyNum <= 20);
bool CheckGreaterThanEqualTo =  (MyNum >= 20);
bool CheckGreaterThanEqualToAgain =  (MyNum >= 100);
значення // true // false // true // true // false
Код лістингу 5.4 демонструє використання цих операторів при відображенні результату на екрані.

ЛІСТИНГ 5.4. Оператори рівності і порівняння

1:  #include  <iostream>
2:  using namespace std;
3:  int main()
4:  {
5:      cout << "Enter two integers:" << endl; 
6:      int Numl = 0, Num2 = 0; 
7:		cin >> Numl; 
8:		cin >> Num2;
9:
10:     bool Equality = (Numl == Num2);
11:     cout << "Result of equality test: " << Equality << endl; 
12:
13:     bool Inequality = (Numl != Num2);
14:     cout << "Result of inequality test: " << Inequality << endl;
15:
16:     bool GreaterThan = (Numl > Num2);
17:     cout << "Result of " << Numl << " > " << Num2;
18:     cout << " test: " << GreaterThan << endl;
19:
20:     bool LessThan = (Numl < Num2);
21:     cout << "Result of " << Numl << " < " << Num2 << " test: " 
22:          << LessThan << endl;
23:
24:     bool GreaterThanEquals = (Numl >= Num2); 
25:     cout << "Result of " << Numl << " >= " << Num2;
26:     cout << " test: " << GreaterThanEquals << endl;
27:
28:     bool LessThanEquals = (Numl <= Num2);
29:     cout << "Result of " << Numl << " <= " << Num2;
30:     cout << " test: " << LessThanEquals << endl;
31:     return 0;
32:  }

Результат
Enter two integers:
365
-24
Result of equality test: 0 Result of inequality test: 1 Result of 365 > -24 test: 1 Result of 365 < -24 test: 0 Result of 365 >= -24 test: 1 Result of 365 <= -24 test: 0
Следующий запуск:
Enter two integers:
101
101
Result of equality test: 1 Result of inequality test: 0

Result of 101 > 101 test: 0

Result of 101 < 101 test: 0

Result of 101 >= 101 test: 1

Result of 101 <= 101 test: 1

Аналіз

Програма відображає результат різних операцій в двійковому виді. Цікаво відмітити у виведенні випадок, де порівнюються два однакові цілі числа. Оператори ==,>= і <= дають ідентичний результат.

Той факт, що виведення операторів рівності і порівняння є двійковим, робить їх відмінно відповідними для використання в операторах ухвалення рішення і у виразах умов циклів, що гарантують виконання циклу, тільки доки умова істинна. Детальніша інформація про умовне виконання і цикли приведена на зайнятті 6, "Галуження процесу виконання програм".

Логічні операції NOT, AND, OR і XOR

Логічна операція NOT забезпечується оператором ! і виконується над одним операндом. Таблиця істинності для логічної операції NOT, яка просто інвертує значення логічного прапора, приведена в таблицю. 5.2.

ТАБЛИЦЯ 5.2. Таблиця істинності логічної операції NOT

Операнд Результат операції NOT (Операнд)
False True
True False

   Для інших операцій, таких як AND, OR або XOR, потрібні два операнди. Логічна операція AND повертає значення true тільки тоді, коли кожен операнд містить значення true. Дія логічної операції AND приведена в таблицю. 5.3.

ТАБЛИЦЯ 5.3. Таблиця істинності логічної операції AND

Операнд Операнд 2 Результат операції
False False False
True False False
False True False
True True True

Логічна операція AND забезпечується оператором &&.
Логічна операція OR повертає значення true тоді, коли принаймні один з операндів містить значення true. Дія логічної операції OR приведена в таблицю. 5.4.
Логічна операція OR забезпечується оператором | |.
Логічна операція XOR (exclusive OR, або що виключає АБО) трохи відрізняється від логічної операції OR і повертає значення true тоді, коли будь-який з операндів містить значення true, але не обоє. Дія логічної операції XOR приведена в таблицю. 5.5.

Таблиця 5.4. Таблиця істинності операції OR

Операнд Операнд 2 Результат операції
False False False
True False True
False True True
True True True

Таблиця 5.5. істинності логічної операції XOR

Операнд Операнд 2 Результат операції
False False False
True False True
False True True
True True
False

Логічна операція OR забезпечується в мові C   оператором А. Результат виходить при виконанні операції XOR над бітами операндів.

Використання логічних операторів C   NOT (!), AND (&&) і OR (| |)

Розглянемо наступні вирази.

■ "Якщо йде дощ І якщо немає автобуса, то я не можу потрапити на роботу".

■ "Якщо є велика знижка АБО якщо я отримаю премію, то зможу купити цей автомобіль".

У програмуванні потрібна деяка логічна конструкція, де результат двох операцій використовується в логічному контексті для ухвалення рішення про подальший напрям потоку програми. Мова C   надає логічні оператори AND і OR, які можна використати в умовних операторах, а отже, умовно змінювати потік виконання програми.

Лістинг 5.5 демонструє роботу логічних операторів AND і OR.

ЛІСТИНГ 5.5. Аналіз логічних операторів C++   && і | |

1: #include <iostream>
2: using namespace std;
3:
4: int main()
5: {
6:    cout << "Enter true(l) or false(0) for two operands:" << endl;
7:    bool Opl = false, Op2 = false;
8:    cin >> Opl;
9:    cin >> Op2;
10:
11:    cout << Opl << " AND " << Op2 << " = " << (Opl && Op2) << endl;
12:    cout << Opl << " OR " << Op2 << " = " << (Opl || Op2) << endl;
13:
14:    return 0;
15: }

Результат

Enter true(l) or false(0) for two operands:
1
0
1 AND 0=0 1 OR 0 = 1
Слідуючий запуск:
Enter true(l) or false(0) for two operands:
1
1
1 AND 1 = 1 1 OR 1 = 1

Аналіз

Програма фактично демонструє, що дозволяють логічні операції AND і OR. Проте вона не показують, як їх використати для ухвалення рішень.
У лістингу 5.6 представлена програма, яка, використовуючи умовні і логічні оператори, виконує різні рядки коду залежно від значень, що містилися в змінних.

ЛІСТИНГ 5.6. Використання логічних операторів NOT (!) і AND (&&) в умовних операторах для зміни потоку виконання

1: #include <iostream>
2: using namespace std;
3: 
4: int main()
5: {
6:   cout << "Use boolean values(0 / 1) to answer the questions"
7:		  << endl;
8:	 cout << "Is it raining? ";
9:	 bool Raining = false;
10:	 cin >> Raining;
11:
12:	 cout << "Do you have buses on the streets? ";
13:	 bool Buses = false;
14:	 cin >> Buses;
15:
16:	 // Условный оператор использует логические операторы AND и NOT
17:	 if (Raining && !Buses)
18:		cout << "You cannot go to work" << endl;
19:	 else
20:		cout << "You can go to work" << endl;
21:
22:	 if (Raining && Buses)
23:		cout << "Take an umbrella" << endl;
24:
25:	 if ((!Raining) && Buses)
26:		cout << "Enjoy the sun and have a nice day" << endl;
27:
28:	 return 0;
29: }

Результат

Use boolean values(0 / 1) to answer the questions Is it raining? 1
Do you have buses on the streets? 1 You can go to work Take an umbrella

Слідуючий запуск:
Use boolean values (0 / 1) to answer the questions Is it raining? 1
Do you have buses on the streets? 0 You cannot go to work

Наступний запуск:
Use boolean values(0 / 1) to answer the questions Is it raining? 0
Do you have buses on the streets? 1
You can go to work
Enjoy the sun and have a nice day

Аналіз

Код лістингу 5.6 використовує умовні оператори у формі конструкції if, яка доки ще не розглядалася. Але все таки спробуйте зрозуміти поведінку цієї конструкції, зіставивши її з виведенням. Рядок 15 містить логічний вираз (Raining && !Buses), який можна прочитати як "йде дощ І НЕМАЄ автобусів". Логічний оператор AND тут використаний для об'єднання відсутності автобусів (позначеного логічним оператором NOT перед наявністю автобусів) і присутності дощу.

ПРИМІТКА  Детальніша інформація про конструкцію if буде приведена на зайнятті 6, "Галуження процесу виконання програм".

Код лістингу 5.7 використовує логічні оператори NOT (!) і OR (| |) для демонстрації умовної обробки.

ЛІСТИНГ 5.7. Використання логічних операторів NOT і OR для прийняття рішення про те, чи можете ви купити автомобіль своєї мрії

1: #include 
2: using namespace std;
3:
4: int main()
5: {
6:    cout << "Answer questions with 0 or 1" << endl;
7:    cout << "Is there a deep discount on your favorite car? ";
8:    bool Discount = false;
9:    cin >> Discount;
10:
11:   cout << "Did you get a fantastic bonus? ";
12:   bool FantasticBonus = false;
13:   cin >> FantasticBonus;
14:
15:   if (Discount || FantasticBonus)
16:   cout << "Congratulations, you can buy that car!" << endl;
17:   else
18:   cout << "Sorry, waiting a while is a good idea" << endl;
19:
20:   return 0;
21: }

Результат

Answer questions with 0 or 1
Is there a deep discount on your favorite car? 0 Did you get a fantastic bonus? 1 Congratulations, you can buy that car!

Слідуючий запуск:
Answer questions with 0 or 1
Is there a deep discount on your favorite car? 0 Did you get a fantastic bonus? 0 Sorry, waiting a while is a good idea

Послідуючий запуск:
Answer questions with 0 or 1
Is there a deep discount on your favorite car? 1 Congratulations, you can buy that car!

Аналіз

У рядку 14 конструкція if використовується разом з конструкцією else в рядку 16. Конструкція if виконує наступний оператор в рядку 15, коли умова (Discount I | FantasticBonus) повертає значення true. Цей вираз містить логічний оператор OR і повертає значення true, навіть якщо немає ніякої знижки на ваш улюблений автомобіль. Коли вираження повертає значення false, виконується оператор в рядку 17 після конструкції else.

Побітові оператори NOT (~), AND (&), OR (|) і XOR (А)

Відмінність між логічними і побітовими операторами в тому, що вони повертають не логічний результат, а значення, окремі біти якого отримані в результаті виконання оператора над бітами операндів. Мова C   дозволяє виконувати такі операції, як NOT, OR, AND і OR (XOR), що виключає, в побітовому режимі, дозволяючи маніпулювати окремими бітами, інвертуючи їх за допомогою оператора застосовуючи

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

Деякі бітові операції корисні в тих випадках, коли кожен з бітів, що містяться в цілому числі, наприклад, визначає стан деякого прапора. Так, ціле число розміром в 32 біта можна використати для зберігання 32-х логічних прапорів. Використання побітових операторів показане в лістингу 5.8.

ЛІСТИНГ 5.8. Використання побітових операторів для виконання операцій NOT, AND, OR і XOR з окремими бітами цілого числа

1: #include <iostreame>
2: #include <bitset>
3: using namespace std;
4:
5: int main()
6: {
7:     cout << "Enter a number (0 - 255): "; 
8:     unsigned short InputNum = 0; 
9:     cin >> InputNum;
10:
11:    bitset<8> InputBits (InputNum);
12:    cout << InputNum << " in binary is " << InputBits << endl;
13:
14:    bitset<8> BitwiseNOT = (-InputNum);
15:    cout << "Logical NOT |" << endl;
16:    cout << "-" << InputBits << " = " << BitwiseNOT << endl;
17:
18:    cout << "Logical AND, & with 00001111" << endl; 
19:    bitset<8> BitwiseAND = (OxOF & InputNum);
                    // OxOF шестнадцатеричная форма числа 0001111 
20:    cout << "0001111 & " << InputBits << " = "<< BitwiseAND << endl;
21:
22:    cout << "Logical OR, | with 00001111" << endl;
23:    bitset<8> BitwiseOR = (OxOF | InputNum);
24:    cout << "00001111 | " << InputBits << " = " << BitwiseOR << endl;
25:
26:    cout << "Logical XOR, л with 00001111" << endl;
27:    bitset<8> BitwiseXOR = (OxOF A InputNum);
28:    cout << "00001111 л " << InputBits << " = " << BitwiseXOR
            << endl;
29:
30:   return 0;
31: }

Результат

Enter a number (0 - 255): 181

181 in binary is 10110101

Logical NOT |
-10110101 = 01001010

Logical AND, & with 00001111

0001111 S 10110101 = 00000101

Logical OR, | with 00001111 00001111 | 10110101 = 10111111

Logical XOR, A with 00001111 00001111 A 10110101 = 10111010

Аналіз

Ця програма використовує набір бітів (bitset) - тип, який ще не розглядався, - для полегшення відображення двійкових даних. Роль класу std:: bitset тут виключно допоміжна - він допомагає з відображенням, і не більше того. У рядках 10, 13, 17 і 22 ви фактично привласнюєте ціле число об'єкту набору бітів, використовуваному для відображення того ж цілочисельного значення в двійковому виді. Операції виконуються з цілими числами.  Спочатку зосередьтеся на виведенні, що відображає введене користувачем початкове число 181 в двійковому виді, а потім перейдіть до результату виконання різних побітових операторів &, | і А з цим цілим числом. Як можна помітити, побітове NOT, використовуване в рядку 13, просто інвертує окремі біти. Програма демонструє також роботу операторів &, | і А, що використовують кожен біт двох операндів для створення результату. Зіставте отримані результати з приведеними раніше таблицями істинності, і вони стануть зрозуміліші.

ПРИМІТКА    Якщо хочете дізнатися більше про маніпулювання бітовими прапорами в мові C, звернетеся до зайняття 25, "Робота з бітовими прапорами при використанні бібліотеки STL", там клас std::bitset обговорюється детальніше.

Побітові оператори зрушення вправо (") і вліво (")

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

А це типовий приклад застосування оператора зрушення для множення на два:

int DoubledValue = Num << 1; // для подвоєння значення біти

// зрушуються на одну позицію вліво

Ось типовий приклад застосування оператора зрушення для ділення на два:

int HalvedValue = Num >> 2; // для ділення значення на дві біти

// зрушуються на одну позицію управо

Використання операторів зрушення для швидкого множення і ділення цілочисельних значень приведене в лістингу 5.9.

ЛІСТИНГ 5.9  Використання побітового оператора зрушення вправо (") для отримання чверті і половини значення, а також оператора зрушення вліво (") для подвоєння значення і множення його на чотири

1: #include <iostream>
2: using namespace std;
3:
4: int main()
5: {
6:    cout << "Enter a number: "; 
7:    int Input = 0; 
8:    cin >> Input;
9:
10:   int Half = Input >> 1; 
11:   int Quarter = Input >> 2; 
12:   int Double = Input << 1; 
13:   int Quadruple = Input << 2;
14:
15:   cout << "Quarter: " << Quarter << endl; 
16:   cout << "Half: " << Half << endl; 
17:   cout << "Double: " << Double << endl; 
18:   cout << "Quadruple: " << Quadruple << endl;
19:   return 0;
20: }

Результат

Enter a number: 16

Quarter: 4

Half: 8

Double: 32

Quadruple: 64

Аналіз

Користувач вводить число 16, яке в двійковому уявленні виглядає як 1000. У рядку 9 здійснюється його зміщення управо на один біт, і виходить 0100, що в десятковому виді складає 8 - фактично половина початкового значення. У рядку 10 здійснюється зміщення управо на два біти, 1000 перетворюється на 00100, що складає 4. Результат операторів зрушення вліво в рядках 11 і 12 прямо протилежний. Зміщення на один біт вліво дає значення 10000 або 32, а на два біти - відповідно до 100000 або 64, фактично подвоюючи і почетверяючи початкове значення!

ПРИМІТКА   Побітові оператори зрушення не переносять значення. Крім того, результат зрушення знакових чисел залежить від конкретного компілятора. На деяких з них самий старший розряд при зрушенні вліво не привласнюється самому молодшому біту; останній набуває значення нуль.

Складені оператори приcвоювання

Складені оператори присвоювання (compound assignment operator) - це оператори привласнення, де результат операції привласнюється операнду ліворуч.

Розглянемо наступний код:

int Num1 = 22;

int Num2 = 5;

Num1  += Num2; // після операції Numl містить значення 27

Це еквівалентно наступному рядку коду :
Numl = Numl   Num2;
Таким чином, результат оператора  = - це сума цих двох операндів, присвоєна потім операнду ліворуч (Numl). Короткий перелік складених операторів привласнення з поясненням їх роботи приведений в таблицю. 5.6.

ТАБЛИЦЯ 5.6. Складені оператори присвоювання

Оператор Застосування Еквівалент
Присвоювання з додаванням Num1 += Num2; Num1 = Num1 + Num2;
Присвоювання з відніманням Num1 -= Num2; Num1 = Num1 - Num2;
Присвоювання з множенням Num1 *= Num2; Num1 = Num1 * Num2;
Присвоювання з діленням Num1 /= Num2; Num1 = Num1 / Num2;
Присвоювання з діленням по модулю Num1 %= Num2; Num1 = Num1 % Num2;
Присвоювання з побітовим зсувом вліво Num1 <<= Num2; Num1 = Num1 << Num2;
Присвоювання з побітовим зсувом вправо Num1 >>= Num2; Num1 = Num1 >> Num2;
Присвоювання з побітовим AND Num1 &= Num2; Num1 = Num1 & Num2;
Присвоювання з побітовим OR Num1 |= Num2; Num1 = Num1 | Num2;
Присвоювання з побітовим XOR Num1 ^= Num2; Num1 = Num1 ^ Num2;
 

Застосування цих операторів демонструє лістинг 5.10.

ЛІСТИНГ 5.10. Використання складених операторів привласнення для додавання, віднімання, ділення і ділення по модулю, а також побітових операцій зрушення, OR, AND і X0R

1:  #include <iostream>
2:  using namespace std;
3:
4:  int main()
5:  {
6:	  cout << "Enter a number: ";
7:	  int Value = 0;
8:	  cin >> Value;
9:
10:	  Value += 8;
11:	  cout << "After += 8, Value = " << Value << endl;
12:	  Value -= 2;
13:	  cout << "After -= 2, Value = " << Value << endl;
14:	  Value /= 4;
15:	  cout << "After /= 4, Value = " << Value << endl;
16:	  Value *= 4;
17:	  cout << "After *= 4, Value = " << Value << endl;
18:	  Value %= 1000;
19:	  cout << "After %= 1000, Value = " << Value << endl;
20:	
21:  // Примечание: далее присвоение происходит в пределах cout
22:	  cout << "After <<= 1, value = " << (Value <<= 1) << endl;
23:	  cout << "After <<= 2, value = " << (Value >>= 2) << endl;
24:
25:	  cout << "After |= 0x55, value = " << (Value |= 0x55) << endl;
26:	  cout << "After ^= 0x55, value = " << (Value ^= 0x55) << endl;
27:	  cout << "After &= 0x55, value = " << (Value &= 0x0F) << endl;
28:
29:	  return 0;
30:  }

Результат

Enter a number: 440

After += 8, Value = 448

After -= 2, Value = 446

After /= 4, Value = 111

After *= 4, Value = 444

After %= 1000, Value = 444

After «= 1, value = 888

After »= 2, value = 222

After |= 0x55, value = 223

After A= 0x55, value = 138

After &= OxOF, value = 10

Аналіз

Зверніть увагу, як послідовно змінюється значення змінної Value у міру застосування в програмі різних операторів привласнення. Кожна операція здійснюється з використанням змінної Value, а її результат знову привласнюється змінній Value. Отже, в рядку 9 введене користувачем значення 440 додається до 8, а результат, 448, знову привласнюється змінній Value. При наступній операції в рядку 11 з 448 віднімається 2, що дає значення 446, яке знову привласнюється змінній Value, і так далі

Використання оператора sizeof для визначення об'єму пам'яті, зайнятого змінною

Цей оператор повертає об'єм пам'яті у байтах, використаної певним типом або змінною. Оператор sizeof має наступний синтаксис:

sizeof (змінна); чи

sizeof (тип);

ПРИМІТКА

Оператор sizeof (...) виглядає як виклик функції, але це не функція, а оператор. Цей оператор не може бути визначений програмістом, а отже, не може бути переобтяжений.

Детальніша інформація про власних операторів приведена на зайнятті 12, "Типи операторів і їх перевантаження".

Лістинг 5.11 демонструє застосування оператора sizeof для визначення об'єму пам'яті, зайнятого масивом. Крім того, можна повернутися до лістингу 3.4 і проаналізувати застосування оператора sizeof для визначення об'єму пам'яті, зайнятого змінними найбільш поширених типів.

ЛІСТИНГ 5.11. Використання оператора sizeof для визначення кількостібайтів, зайнятих масивом з 100 цілих чи сел і кожним його елементом

1: #include <iostream>
2: using namespace std;
3:
4: int main ()
5: {
6:    cout << "Use sizeof of determine memory occupied by arrays"
           << endl;
7:    int MyNumbers [100] = {0};
8:
9:    cout << "Bytes occupied by an int: " << sizeof(int) << endl;
10:   cout << "Bytes occupied by array MyNumbers: "
           << sizeof(MyNumbers) << endl;
11:   cout << "Bytes occupied by each element: "
           << sizeof(MyNumbers[0]) << endl;
12:
13:   return 0;
14: }

Результат
Use sizeof of determine memory occupied by arrays Bytes occupied by an int: 4 Bytes occupied by array MyNumbers: 400 Bytes occupied by each element: 4

Аналіз

Програма демонструє, як оператор sizeof повертає розмір у байтах масиву з 100 цілих чисел, що становить 400 байтів, а також що розмір кожного його елементу складає 4 байти.

Оператор sizeof може бути дуже корисний, коли необхідно динамічно розмістити в пам'яті N об'єктів, особливо коли їх тип створений вами самостійно. Ви використали б результат виконання оператора sizeof для визначення об'єму пам'яті, зайнятого кожним об'єктом, а потім динамічно зарезервували б для нього пам'ять, використовуючи оператор new.

Детальніша інформація про динамічний розподіл пам'яті приведена на зайнятті 8, "Покажчики і посилання".

Пріоритет операторів

Можливо, ви пам'ятаєте з шкільного курсу, що арифметичні операції мають порядок, в якому вони повинні виконуватися в складному арифметичному вираженні.

У мові C   використовуються такі оператори і вирази :

int MyNumber = 10 * 30  20 - 5 * 5<<2;

Питання: яке значення міститиме змінна MyNumber? Тут немає місця ніяким припущенням. Порядок виконання різних операторів строго визначений стандартом C  . Цей порядок визначається пріоритетом операторів, приведеним в таблицю. 5.7.

ТАБЛИЦЯ 5.7. Пріоритет операторів

Назва Оператор                  
1  Область видимості  ::
2  Пряме та непряме звертання до члена класу, виклик функції, постфіксний інкремент та декремент  . -> () ++ --
3  Префіксний інкремент та декремент, інверсія та унарні "мінус" та "плюс", отримання адреси та посилання, а також оператори new, new[], delete, delete[], casting, sizeof()  ++ -- ^ ! - + &
4  Звертання до елемента по вказівнику  . * -> *
5  Множення, ділення, ділення по модулю  * / %
6  Додавання, віднімання  + -
7  Зсув вліво, зсув вправо  <<  >>
8  Менше, менше або рівно, більше, більше або рівне  < <= > >=
9  Рівне, не рівне  == !=
10  Побітове AND  &
11  Побітове виключне OR  ^
12  Побітове OR  |
13  Логічне AND  &&
14  Логічне OR  ||
15  Трійковий умовний оператор  ? :
16  Оператори присвоєння  = *= /= %= += -= <<= >>=
17  Кома  ,

Давайте ще раз розглянемо складне вираження, приведене для прикладу раніше :

int MyNumber = 10 * 30   20 - 5 * 5 << 2;

При обчисленні результату цього виразу необхідно використати правила пріоритету операторів, приведені в таблицю. 5.7, щоб зрозуміти, як їх виконує компілятор. Так, множення і ділення мають більш високий пріоритет, ніж складання і віднімання, пріоритет яких, у свою чергу, вище за пріоритет оператора зрушення. В результаті все зводиться до наступного:

int MyNumber = 300  20-25 << 2;

Оскільки складання і віднімання мають пріоритет над зрушенням, подальше зводиться до наступного:

int MyNumber = 295 << 2;

І нарешті, виконується операція зрушення. Знаючи, що зрушення вліво на один біт подвоює число, а зрушення вліво на два біти множить його на 4, можна сказати, що вираження зводиться до 2 95 * 4, а результат складає 1180.

УВАГА!   Щоб зробити код зрозуміліше, використайте круглі дужки. Приведене вище вираження просто погано написане. Це компілятору все просто зрозуміти, але написаний код має бути зрозумілий і людям. Той же вираз буде набагато зрозуміліше, якщо записати його так:

int MyNumber =  ((10 * 30) - (5*5)   20) "2; // не залишайте ніяких приводів для сумніву

РЕКОМЕНДУЄТЬСЯ  Використайте круті дужки, щоб зробити ваш код зрозуміліше

Використайте правильні типи змінних щоб уникнути ситуацій переповнювання

Пам'ятайте, що усі l- значення (наприклад, змінні) можуть бути r- значеннями, але не усі r- значення (наприклад, "Hello World") можуть бути l-значеннями

НЕ РЕКОМЕНДУЄТЬСЯ  Не створюйте складні вирази, що покладаються на таблицю пріоритету операторів; ваш код має бути зрозумілий і людям

Не помиляйтеся, що вирази ++Змінна і Змінна++   рівнозначні. Вони розрізняються при використанні в привласненні

Резюме

На цьому зайнятті ви дізналися, що таке команди, оператори і вирази мови C++  . Ви навчилися виконувати прості арифметичні операції, такі як складання, віднімання, множення і ділення. Був також приведений короткий огляд таких логічних операторів, як NOT, AND, OR і XOR. Ми розглянули логічні оператори !, & & і | |, використовувані в умовних виразах, і такі побітові оператори, як ~, &, | і А, які позволякУг маніпулювати даними по одному біту за раз.

Ви дізналися про пріоритет операторів, а також про те, чому так важливо використати круглі дужки при написанні коду, який має бути зрозумілий також підтримувальним його програмістам. Було дано загальне уявлення про переповнювання цілочисельних змінних і про способи уникнути його.