Operatory bitowe
C++ udostępnia często 6-bitowe operatory manipulacji wywoływane operatory bitowe operatory:
| Operator | Symbol | Formularz | Operacja zwraca wartość gdzie: |
|---|---|---|---|
| przesunięcie w lewo | << | x << n | bity z x są przesunięte w lewo o n pozycje, nowe bity to 0. |
| w prawo przesuń | >> | x >> n | bity z x są przesunięte w prawo o n pozycje, nowe bity to 0. |
| bitowego NOT | ~ | ~x | każdy bit z x jest odwracany. |
| bitowego AND | & | x i y | każdy bit jest ustawiany, gdy oba odpowiednie bity w x i y są 1. |
| bitowego LUB | | | x | y | każdy bit jest ustawiany, gdy którykolwiek odpowiadający mu bit w x i y Jest 1. |
| bitowego XOR | ^ | x ^ y | każdy bit jest ustawiany, gdy odpowiadające mu bity w x i y są różne. |
Są to operatory niemodyfikujące (nie modyfikują swoich argumentów).
Nota autora
W poniższych przykładach będziemy głównie pracować z 4-bitowe wartości binarne. Robimy to dla wygody i prostoty przykładów. W rzeczywistych programach liczba używanych bitów opiera się na rozmiarze obiektu (np. obiekt 2-bajtowy przechowuje 16 bitów).
Aby poprawić czytelność, możemy również pominąć 0b przedrostek literałów binarnych poza przykładami kodu (np. zamiast 0b0101 możemy zdecydować się na użycie 0101).
Operatory bitowe są zdefiniowane dla całki typy i std::bitsetBędziemy używać std::bitset w naszych przykładach, ponieważ łatwiej jest wydrukować dane wyjściowe w formacie binarnym.
Unikaj używania operatorów bitowych z argumentami całkowitymi ze znakiem, ponieważ wiele operatorów zwróci wyniki zdefiniowane w implementacji przed C++20 lub będzie miało inne potencjalne problemy, których można łatwo uniknąć używając operandów bez znaku (lub std::bitset).
Najlepsza praktyka
Aby uniknąć niespodzianek, użyj operatory bitowe z operandami całkowitymi bez znaku lub std::bitset.
Operandy bitowego przesunięcia w lewo (<<) i bitowe przesunięcia w prawo (>>)
Klasa bitowe przesunięcie w lewo (<<) Operator przesuwa bity w lewo Lewy operand jest wyrażeniem zapewniającym początkową sekwencję bitów, a prawy operand jest liczbą całkowitą określającą liczbę pozycji bitowych, o które należy przesunąć bity. Na przykład, gdy piszemy x << 2, mówimy „tworzą wartość, w której bity z x zostały przesunięte o 2 pozycje w lewo.”
Lewy operand nie jest modyfikowany, a nowe bity przesunięte z prawidłowego rozmiaru to 0.
Oto kilka przykładów przesunięcia sekwencji bitów w lewo 0011:
0011 \<\< 1 is 0110 0011 \<\< 2 is 1100 0011 \<\< 3 is 1000
Zauważ, że w trzecim przypadku przesunęliśmy 1 bit na końcu liczby! Bity przesunięte poza koniec sekwencji bitów zostają utracone na zawsze.
Klasa operator bitowego przesunięcia w prawo (>>) działa podobnie, ale przesuwa bity w prawo.
Oto kilka przykładów przesunięcia sekwencji bitów w prawo 1100:
1100 />/> 1 is 0110 1100 />/> 2 is 0011 1100 />/> 3 is 0001
Zauważ, że w trzecim przypadku przesunęliśmy się nieco w prawo koniec liczby, więc zostaje ona utracona.
Zróbmy przykład w C++, który można skompilować i uruchomić:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> x { 0b1100 };
std::cout << x << '\n';
std::cout << (x >> 1) << '\n'; // shift right by 1, yielding 0110
std::cout << (x << 1) << '\n'; // shift left by 1, yielding 1000
return 0;
}Wypisuje:
1100 0110 1000
Dla zaawansowanych czytelników
Przesunięcie bitów w C++ jest endian-niezależne. Przesunięcie w lewo jest zawsze skierowane w stronę najbardziej znaczącego bitu, a przesunięcie w prawo w stronę najmniej znaczącego.
Co!? Czy operator<< i operator>> nie są używane do wprowadzania i wyprowadzania danych?
Na pewno tak.
Współczesne programy zazwyczaj nie wykorzystują zbytnio operatorów przesunięcia bitowego w lewo i w prawo do przesuwania bitów. Zamiast tego operator przesunięcia bitowego w lewo jest częściej używany z std::cout (lub innymi obiektami strumienia wyjściowego) do wyświetlania tekstu. Rozważmy następujący program:
#include <bitset>
#include <iostream>
int main()
{
unsigned int x { 0b0100 };
x = x << 1; // use operator<< for left shift
std::cout << std::bitset<4>{ x } << '\n'; // use operator<< for output
return 0;
}Ten program wypisuje:
1000
W powyższym programie czy operator<< wie, jak przesuwać bity w jednym przypadku i wyprowadzać x w innym? Odpowiedź jest taka, że sprawdza typ operandów. Jeśli lewy operand jest typem całkowitym, to operator<< wie, jak to zrobić w typowy sposób przesuwania bitów, jeśli lewy operand jest obiektem strumienia wyjściowego, jak std::cout, to wie, że zamiast tego powinien wysyłać dane wyjściowe.
To samo dotyczy operator>>.
Powiązana treść
Ta możliwość zmiany zachowania operatorów w zależności od typu argumentów wykorzystuje funkcję zwaną przeciążaniem operatora, którą przedstawimy w dalszej części lekcji 13.5 — Wprowadzenie do przeciążania operatorów I/O.
Pamiętaj, że jeśli używasz operator<< zarówno do wyjścia, jak i lewego przesunięcia, w przypadku przesunięcia w lewo wymagane jest użycie nawiasów:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> x{ 0b0110 };
std::cout << x << 1 << '\n'; // print value of x (0110), then 1
std::cout << (x << 1) << '\n'; // print x left shifted by 1 (1100)
return 0;
}Wypisuje:
01101 1100
Pierwsza linia wypisuje wartość x (0110), a następnie literał 1. Druga linia wypisuje wartość x przesuniętą w lewo przez 1 (1100).
Bitowo NOT
Klasa bitowego NOT operator (~) jest koncepcyjnie prosta: po prostu odwraca każdy bit z 0 do 1 lub odwrotnie.
~0011 is 1100 ~0000 0100 is 1111 1011
Dla zaawansowanych czytelników
Liczba bitów w wyniku bitowego NOT wpływa na wygenerowaną wartość.
Następujący program ilustruje to:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> b4{ 0b100 }; // b4 is 0100
std::bitset<8> b8{ 0b100 }; // b8 is 0000 0100
std::cout << "Initial values:\n";
std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
std::cout << "Values: " << b4.to_ulong() << ' ' << b8.to_ulong() << "\n\n";
b4 = ~b4; // flip b4 to 1011
b8 = ~b8; // flip b8 to 1111 1011
std::cout << "After bitwise NOT:\n";
std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
std::cout << "Values: " << b4.to_ulong() << ' ' << b8.to_ulong() << '\n';
return 0;
}Wypisuje:
Initial values: Bits: 0100 00000100 Values: 4 4 After bitwise NOT: Bits: 1011 11111011 Values: 11 251
Początkowo b4 i b8 oba są ustawione na 0b100. Po dodaniu zer wiodących b4 kończy się jako 0100 i b8 jak 00000100, co jest wypisywane w następnym wierszu.
Następnie używamy funkcji składowej to_ulong() do interpretacji wartości bitów, interpretowanej jako long liczba całkowita. Widać, że oba b4 i b8 drukują wartość 4. Pomimo różnej liczby bitów, oba reprezentują tę samą wartość. Dzieje się tak, ponieważ początkowe bity zerowe nie wnoszą żadnej wartości do interpretowanej liczby całkowitej.
Następnie używamy bitowego NOT, aby odwrócić bity każdego z nich, więc b4 teraz mamy bity 1011 i b8 teraz mamy bity 1111 1011. W przypadku wydrukowania jako liczba całkowita wyświetla wartości 11 i 251. Jak widać, wartości te nie są już identyczne. Dzieje się tak, ponieważ jedynki wiodące rzeczywiście wnoszą wartość do interpretowanej liczby całkowitej, a b8 ma więcej jedynek wiodących niż b4.
Bitowo LUB
Bitowo LUB (|) działa podobnie jak jego logiczny odpowiednik OR. Jeśli pamiętasz, logiczne OR ma wartość true (1), jeśli którykolwiek z operandów są true, w przeciwnym razie ma wartość false (0).
Jednakże, podczas gdy logiczne OR jest stosowane do całego operandu (w celu uzyskania pojedynczego wyniku prawdziwego lub fałszywego), bitowe OR jest stosowane do każdej pary bitów w operandach (w celu uzyskania pojedynczego prawdziwego lub fałszywego wyniku dla każdego bit).
Zilustrujmy to przykładem. Rozważmy wyrażenie 0b0101 | 0b0110.
Wskazówka
Aby ręcznie wykonać dowolną binarną operację bitową, najłatwiej jest ustawić oba operandy w następujący sposób:
0 1 0 1 OR (or whatever bitwise operation you are doing) 0 1 1 0
Następnie zastosuj tę operację do każdej kolumny bitów i zapisz wynik poniżej.
W pierwszej kolumna, 0 LUB 0 Jest 0, więc pod linią wstawiamy 0.
0 1 0 1 OR 0 1 1 0 ------- 0
Druga kolumna, 1 LUB 1 Jest 1. Trzecia kolumna 0 lub 1 Jest 1. I czwarta kolumna, 1 lub 0 Jest 1.
0 1 0 1 OR 0 1 1 0 ------- 0 1 1 1
Nasz wynik jest 0111 binarny.
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0101 } | std::bitset<4>{ 0b0110 }) << '\n';
return 0;
}Wypisuje:
0111
Możemy zrobić to samo, aby złożyć bitowe wyrażenia OR, takie jak 0b0111 | 0b0011 | 0b0001. Jeśli którykolwiek z bitów w kolumnie to 1, wynikiem tej kolumny jest 1:
0 1 1 1 OR 0 0 1 1 OR 0 0 0 1 -------- 0 1 1 1
Oto kod powyższego:
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0111 } | std::bitset<4>{ 0b0011 } | std::bitset<4>{ 0b0001 }) << '\n';
return 0;
}Wypisuje:
0111
Bitowe AND
Bitowe AND (&) działa podobnie do powyższego, z tą różnicą, że używa logiki AND zamiast logiki OR. Oznacza to, że dla każdej pary bitów w operandach funkcja Bitowe ORAZ ustawia wynikowy bit na true (1), jeśli oba sparowane bity są 1, I false (0). W przeciwnym razie.
Rozważ wyrażenie 0b0101 & 0b0110. Wyrównanie każdego bitu i zastosowanie bitowego AND do każdej kolumny bitów:
0 1 0 1 AND 0 1 1 0 -------- 0 1 0 0
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0101 } & std::bitset<4>{ 0b0110 }) << '\n';
return 0;
}Wypisuje:
0100
Podobnie możemy zrobić to samo, aby złożyć wyrażenia bitowe AND, takie jak 0b0001 & 0b0011 & 0b0111. Jeśli wszystkie bity w kolumnie są 1, wynikiem tej kolumny jest 1.
0 0 0 1 AND 0 0 1 1 AND 0 1 1 1 -------- 0 0 0 1
#include <bitset>
#include <iostream>
int main()
{
std::cout << (std::bitset<4>{ 0b0001 } & std::bitset<4>{ 0b0011 } & std::bitset<4>{ 0b0111 }) << '\n';
return 0;
}Wypisuje:
0001
Bitowego XOR
Ostatni operator to bitowego XOR (^), znany również jako wyłączny lub.
Dla każdej pary bitów w operandach Bitwise XOR ustawia wynikowy bit na true (1), gdy dokładnie jeden z sparowanych bitów jest 1, I false (0) w przeciwnym razie. Innymi słowy, Bitwise XOR ustawia wynikowy bit na true kiedy sparowane bity są różne (jeden to a 0 a drugi a 1).
Rozważ wyrażenie 0b0110 ^ 0b0011:
0 1 1 0 XOR 0 0 1 1 ------- 0 1 0 1
Możliwe jest również oszacowanie stylu kolumny złożonego wyrażenia XOR, takiego jak 0b0001 ^ 0b0011 ^ 0b0111. Jeśli w kolumnie jest parzysta liczba 1 bitów, wynikiem są 0. Jeśli w kolumnie znajduje się nieparzysta liczba 1 bitów, wynikiem są 1.
0 0 0 1 XOR 0 0 1 1 XOR 0 1 1 1 -------- 0 1 0 1
Bitowe operatory przypisania
Podobnie jak arytmetyczne operatory przypisania, C++ udostępnia bitowe operatory przypisania. Modyfikują one lewy operand.
| Operator | Symbol | Formularz | Operacja modyfikuje lewy operand gdzie: |
|---|---|---|---|
| przesunięcie w lewo | << | x <<= n | bity in x są przesunięte w lewo o n pozycje, nowe bity to 0. |
| w prawo przesuń | >> | x >>= n | bity in x są przesunięte w prawo o n pozycje, nowe bity to 0. |
| bitowego AND | & | x &= y | każdy bit jest ustawiany, gdy oba odpowiednie bity w x i y są 1. |
| bitowego LUB | | | x |= y | każdy bit jest ustawiany, gdy którykolwiek odpowiadający mu bit w x i y Jest 1. |
| bitowego XOR | ^ | x ^= y | każdy bit jest ustawiany, gdy odpowiadające mu bity w x i y są różne. |
Na przykład zamiast pisać x = x >> 1; można napisać x >>= 1;.
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> bits { 0b0100 };
bits >>= 1;
std::cout << bits << '\n';
return 0;
}Ten program wypisuje:
0010
Na marginesie…
Nie ma bitowego operatora przypisania NOT. Dzieje się tak, ponieważ pozostałe operatory bitowe są binarne, ale bitowe NOT jest jednoargumentowe (więc co znalazłoby się po prawej stronie operatora ~= ?). Jeśli chcesz odwrócić wszystkie bity obiektu, możesz użyć normalnego przypisania: x = ~x;
Operatory bitowe wykonują promocję całkową na mniejszych typach całkowitych Zaawansowane
Jeśli operand(y) operatora bitowego są typem całkowitym mniejszym niż int, te operandy zostaną promowane (konwertowane) do int lub unsigned int, a zwrócony wynik będzie również int lub unsigned int. Na przykład, jeśli nasze operandy to unsigned short, zostaną one awansowane (konwertowane) na unsigned int, a wynik operacji zostanie zwrócony jako unsigned int.
W wielu przypadkach nie będzie to miało znaczenia.
Powiązana treść
Promocję całkową omawiamy na lekcji 10.2 -- Promocja zmiennoprzecinkowa i całkowa.
Jednak w przypadku używania operatorów bitowych na węższych typach całkowitych niż int lub unsigned int, należy zwrócić uwagę na dwa przypadki:
operator~ioperator<<są wrażliwe na szerokość i mogą dawać różne wyniki w zależności od szerokości operandu.- Inicjowanie lub przypisanie wyniku do zmiennej o mniejszym typie całkowitym jest konwersją zawężającą (ponieważ konwersja
intlubunsigned intna mniejszy typ całkowity może spowodować utratę danych). Jest to niedozwolone podczas inicjalizacji listy i Twój kompilator może narzekać na zawężanie przypisania lub nie.
W poniższym programie występują następujące problemy (przy założeniu 32-bitowych wartości całkowitych):
#include <bitset>
#include <cstdint>
#include <iostream>
int main()
{
std::uint8_t c { 0b00001111 };
std::cout << std::bitset<32>(~c) << '\n'; // incorrect: prints 11111111111111111111111111110000
std::cout << std::bitset<32>(c << 6) << '\n'; // incorrect: prints 0000000000000000001111000000
std::uint8_t cneg { ~c }; // error: narrowing conversion from unsigned int to std::uint8_t
c = ~c; // possible warning: narrowing conversion from unsigned int to std::uint8_t
return 0;
}Problemy te można rozwiązać, używając static_cast do konwersji wyniku operacji bitowej z powrotem na węższy typ całkowity. Poniższy program daje poprawne wyniki:
#include <bitset>
#include <cstdint>
#include <iostream>
int main()
{
std::uint8_t c { 0b00001111 };
std::cout << std::bitset<32>(static_cast<std::uint8_t>(~c)) << '\n'; // correct: prints 00000000000000000000000011110000
std::cout << std::bitset<32>(static_cast<std::uint8_t>(c << 6)) << '\n'; // correct: prints 0000000000000000000011000000
std::uint8_t cneg { static_cast<std::uint8_t>(~c) }; // compiles
c = static_cast<std::uint8_t>(~c); // no warning
return 0;
}Ostrzeżenie
Operandy bitowe będą promować operandy o węższych typach całkowitych, int lub unsigned int.
operator~ i operator<< są wrażliwe na szerokość i mogą dawać różne wyniki w zależności od szerokości operandu. static_cast Wynik takich operacji bitowych z powrotem do węższego typu całkowitego przed użyciem, aby zapewnić poprawne wyniki.
Najlepsza praktyka
Unikaj przesuwania bitów w przypadku mniejszych typów całkowitych niż int jeśli to możliwe.
Streszczenie
Podsumowując, jak oceniać operacje bitowe przy użyciu metody kolumnowej:
Podczas obliczania bitowego LUB, jeśli dowolny bit w kolumnie wynosi 1, wynikiem dla tej kolumny jest 1.
Podczas obliczania bitowego ORAZ, jeśli wszystkie bity w kolumnie mają wartość 1, wynikiem dla tej kolumny jest 1.
Podczas obliczania bitowego XOR, jeśli w kolumnie znajduje się nieparzysta liczba 1 bitów, wynikiem dla tej kolumny jest 1.
W następnej lekcji odkryjemy, jak można użyć tych operatorów w połączeniu z maskami bitowymi, aby ułatwić manipulację bitami.
Czas quizu
Pytanie nr 1
a) Do czego równa się wartość 0110 >> 2 w formacie binarnym?
b) Do czego w systemie binarnym odnosi się następujący zapis: 0011 | 0101?
c) Co w systemie binarnym oznacza następujący zapis: 0011 i 0101?
d) Do czego można porównać w systemie binarnym (0011 | 0101) i 1001?
Pytanie nr 2
Obrót bitowy przypomina przesunięcie bitowe, z tą różnicą, że bity przesunięte z jednego końca są dodawane z powrotem na drugi koniec. Na przykład 0b1001 << 1 byłoby 0b0010, ale obrót w lewo o 1 skutkowałby zamiast tego 0b0011 . Zaimplementuj funkcję, która wykonuje obrót w lewo na std::bitset<4>. W tym przypadku dobrze jest użyć test() i set().
Należy wykonać następujący kod:
#include <bitset>
#include <iostream>
// "rotl" stands for "rotate left"
std::bitset<4> rotl(std::bitset<4> bits)
{
// Your code here
}
int main()
{
std::bitset<4> bits1{ 0b0001 };
std::cout << rotl(bits1) << '\n';
std::bitset<4> bits2{ 0b1001 };
std::cout << rotl(bits2) << '\n';
return 0;
}i wydrukować następujący tekst:
0010 0011
Pytanie nr 3
Dodatkowy punkt: Powtórz quiz nr 2, ale nie używaj funkcji test i set (użyj funkcji bitowej operatory).

