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

  Юрій  | 

  Оновл. 4 Вер 2021  | 

 434

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

Примітка: Для деяких цей матеріал може здатися складним. Якщо ви застрягли або щось не зрозуміло — пропустіть цей урок (і наступний), в майбутньому ви зможете повернутися і розібратися детальніше. Він не настільки важливий для прогресу у вивченні мови 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.

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

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

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

Розглянемо число 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 Зірок (6 оцінок, середня: 5,00 з 5)
Loading...

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

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