Урок №48. Побітові оператори

  Юрій  | 

  Оновл. 5 Тра 2020  | 

 276

Примітка: Для деяких цей матеріал може здатися складним. Якщо ви застрягли або щось не зрозуміло — пропустіть цей урок (і наступний), в майбутньому зможете повернутися і розібратися детальніше. Він не настільки важливий для прогресу у вивченні C++, як інші уроки, і викладений тут, в більшій мірі, для загального розвитку.

Побітові оператори маніпулюють окремими бітами в межах змінної.

Навіщо потрібні побітові оператори?

У далекому минулому комп’ютерної пам’яті було дуже мало і нею дуже дорожили. Це було стимулом максимально розумно використовувати кожен доступний біт. Наприклад, в логічному типі даних bool є всього лише два можливих значення (true і false), які можуть бути представлені одним бітом, але по факту займають цілий байт пам’яті! А це, в свою чергу, через те, що змінні використовують унікальні адреси пам’яті, а вони виділяються тільки в байтах. Змінна типу bool займає 1 біт, а інші 7 витрачаються даремно.

Використовуючи побітові оператори, можна створювати функції, які дозволять вмістити 8 значень типу bool в змінній розміром 1 байт, що значно заощадить споживання пам’яті. У минулому такий трюк був дуже популярний. Але сьогодні, принаймні, в прикладному програмуванні, це не так.

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

Є 6 побітових операторів:

Оператор Символ Приклад Операція
Побітовий зсув вліво << x << y Всі біти в x зміщуються вліво на y біт
Побітовий зсув вправо >> x >> y Всі біти в x зміщуються вправо на y біт
Побітове НЕ ~ ~x Всі біти в x змінюються на протилежні
Побітове І & x & y Кожен біт в x І кожен біт в y
Побітове АБО | x | y Кожен біт в x АБО кожен біт в y
Побітове виключне АБО (XOR) ^ x ^ y Кожен біт в x XOR кожен біт в y

У побітових операціях слід використовувати тільки цілочисельні типи даних unsigned, так як C++ не завжди гарантує коректну роботу побітових операторів з цілочисельними типами signed.

Правило: При роботі з побітовими операторами використовуйте цілочисельні типи даних unsigned.

Побітовий зсув вліво (<<) і побітовий зсув вправо (>>)

Примітка: У наступних прикладах ми будемо працювати з 4-бітними двійковими значеннями.

У C++ кількість використовуваних біт ґрунтується на розмірі типу даних (в 1 байті знаходяться 8 біт). Оператор побітового зсуву вліво (<<) зрушує біти вліво. Лівий операнд є виразом, в якому вони зміщуються, а правий — на скільки місць потрібно змістити. Тому в виразі 3 << 1 мається на увазі “змістити біти вліво в літералі 3 на одне місце”.

Розглянемо число 3, яке в двійковій системі числення дорівнює 0011:

3 = 0011
3 << 1 = 0110 = 6
3 << 2 = 1100 = 12
3 << 3 = 1000 = 8

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

Оператор побітового зсуву вправо (>>) зміщує біти вправо, наприклад:

12 = 1100
12 >> 1 = 0110 = 6
12 >> 2 = 0011 = 3
12 >> 3 = 0001 = 1

У третьому випадку ми знову перемістили біт за межі літералу. Він також загубився назавжди.

Хоча в прикладах вище ми зміщуємо біти в літералах, ми також можемо зміщувати біти і в змінних:

Слід пам’ятати, що результати операцій з побітовими зсувами в різних компіляторах можуть відрізнятися.

Що!? Невже оператори << і >> використовуються не для виведення і введення даних?

І для цього також.

Зараз користь від використання побітових операторів не така велика, як це було раніше. Зараз в більшості випадків оператор побітового зсуву вліво використовується для виведення даних. Наприклад, розглянемо наступну програму:

Результат виконання програми:

8

А як компілятор розуміє, коли потрібно застосовувати оператор побітового зсуву вліво, а коли виводити дані? Все дуже просто. std::cout перевизначає значення оператора << за замовчуванням на нове (виведення даних в консоль). Коли компілятор бачить, що лівим операндом оператора << є std::cout, то він розуміє, що повинно відбутися виведення даних. Якщо лівим операндом є змінна цілочисельного типу даних, то компілятор розуміє, що повинен відбутися побітовий зсув вліво (операція за замовчуванням).

Побітовий оператор НЕ

Побітовий оператор НЕ (~), мабуть, найпростіший для пояснення і розуміння. Він просто змінює кожен біт на протилежний, наприклад: з 0 на 1 або з 1 на 0. Зверніть увагу, результати побітового НЕ залежать від розміру типу даних!

Припустимо, що розмір типу даних складає 4 біта:

4 = 0100
~ 4 = 1011 (двійкове) = 11 (десяткове)

Припустимо, що розмір типу даних складає 8 біт:

4 = 0000 0100
~ 4 = 1111 1011 (двійкове) = 251 (десяткове)

Побітові оператори І, АБО та виключне АБО (XOR)

Побітові оператори І (&) та АБО (|) працюють аналогічно логічним операторам І та АБО. Однак, побітові оператори застосовуються до кожного біту окремо! Наприклад, розглянемо вираз 5 | 6. У двійковій системі числення це 0101 | 0110. У будь-якій побітовій операції операнди найкраще розміщувати в такий спосіб:

0 1 0 1 // 5
0 1 1 0 // 6

А потім застосовувати операцію до кожному стовпчику з бітами окремо. Як ми вже знаємо, логічне АБО повертає true (1), якщо один з двох або відразу обидва операнди істинні (1). Таким чином працює і побітове АБО. Вираз 5 | 6 опрацьовується наступним чином:

0 1 0 1 // 5
0 1 1 0 // 6
-------
0 1 1 1 // 7

Результат:

0111 (двійкове) = 7 (десяткове)

Також можна опрацьовувати і комплексні вирази АБО, наприклад, 1 | 4 | 6. Якщо хоч один біт в стовпці дорівнює 1, то результат цілого стовпця — 1, наприклад:

0 0 0 1 // 1
0 1 0 0 // 4
0 1 1 0 // 6
--------
0 1 1 1 // 7

Результатом 1 | 4 | 6 є десяткове 7.

Побітове І працює аналогічно логічному І: повертається true, тільки якщо обидва біти в стовпці дорівнюють 1. Розглянемо вираз 5 & 6:

0 1 0 1 // 5
0 1 1 0 // 6
--------
0 1 0 0 // 4

Також можна вирішувати і комплексні вирази І, наприклад, 1 & 3 & 7. Тільки за умови, що всі біти в стовпці рівні 1, результатом стовпчика буде 1.

0 0 0 1 // 1
0 0 1 1 // 3
0 1 1 1 // 7
--------
0 0 0 1 // 1

Останній оператор — побітове виключне АБО (^) (англ. “XOR” від “eXclusive OR“). При обробці двох операндів, виключне АБО повертає true (1), тільки якщо один і тільки один з операндів є істинним (1). Якщо таких немає або всі операнди істинні (1), то результатом буде false (0). Розглянемо вираз 6 ^ 3:

0 1 1 0 // 6
0 0 1 1 // 3
-------
0 1 0 1 // 5

Також можна вирішувати і комплексні вирази XOR, наприклад, 1 ^ 3 ^ 7. Якщо одиниць в стовпці парна кількість, то результат — 0. Якщо непарна кількість, то результат — 1. Наприклад:

0 0 0 1 // 1
0 0 1 1 // 3
0 1 1 1 // 7
--------
0 1 0 1 // 5

Побітові оператори присвоювання

Як і у випадку з арифметичними операторами присвоювання, C++ надає побітові оператори присвоювання для полегшення внесення змін в змінні.

Оператор Символ Приклад Операція
Присвоювання з побітовим зсувом вліво <<= x <<= y Зміщуємо біти в x вліво на y біт
Присвоювання з побітовим зсувом вправо >>= x >>= y Зміщуємо біти в x вправо на y біт
Присвоювання з побітовою операцією АБО |= x |= y Присвоювання результату виразу x | y змінній x
Присвоювання з побітовою операцією І &= x &= y Присвоювання результату виразу x & y змінній x
Присвоювання з побітовою операцією виключного АБО ^= x ^= y Присвоювання результату виразу x ^ y змінній x

Наприклад, замість х = х << 1; ми можемо написати х <<= 1;.

Висновки

Працюючи з побітовими операторами (використовуючи метод стовпця) не забувайте про наступне:

   При обчисленні побітового АБО, якщо хоч один з бітів в стовпці дорівнює 1, то результат цілого стовпця — 1.

   При обчисленні побітового І, якщо всі біти в стовпці дорівнюють 1, то результат цілого стовпця — 1.

   При обчисленні побітового виключного АБО (XOR), якщо одиниць в стовпці непарна кількість, то результат — 1.

Тест

Приклад №1: Який результат виразу 0110 >> 2 в двійковій системі?

Приклад №2: Який результат виразу 5 | 12 в десятковій системі?

Приклад №3: Який результат виразу 5 & 12 в десятковій системі?

Приклад №4: Який результат виразу 5 ^ 12 в десятковій системі?

Відповіді

Відповідь №1

Результатом виразу 0110 >> 2 є двійкове 0001.

Відповідь №2

Вираз 5 | 12:

0 1 0 1
1 1 0 0
--------
1 1 0 1 // 13 (десяткове)

Відповідь №3

Вираз 5 & 12:

0 1 0 1
1 1 0 0
--------
0 1 0 0 // 4 (десяткове)

Відповідь №4

Вираз 5 ^ 12:

0 1 0 1
1 1 0 0
--------
1 0 0 1 // 9 (десяткове)

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (4 оцінок, середня: 5,00 з 5)
Loading...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *