O.2 -- Operatory bitowe

Operatory bitowe

C++ udostępnia często 6-bitowe operatory manipulacji wywoływane operatory bitowe operatory:

OperatorSymbolFormularzOperacja zwraca wartość gdzie:
przesunięcie w lewo<<x << nbity z x są przesunięte w lewo o n pozycje, nowe bity to 0.
w prawo przesuń>>x >> nbity z x są przesunięte w prawo o n pozycje, nowe bity to 0.
bitowego NOT~~xkażdy bit z x jest odwracany.
bitowego AND&x i ykażdy bit jest ustawiany, gdy oba odpowiednie bity w x i y1.
bitowego LUB|x | ykażdy bit jest ustawiany, gdy którykolwiek odpowiadający mu bit w x i y Jest 1.
bitowego XOR^x ^ ykaż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ówtrue, 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.

OperatorSymbolFormularzOperacja modyfikuje lewy operand gdzie:
przesunięcie w lewo<<x <<= nbity in x są przesunięte w lewo o n pozycje, nowe bity to 0.
w prawo przesuń>>x >>= nbity in x są przesunięte w prawo o n pozycje, nowe bity to 0.
bitowego AND&x &= ykażdy bit jest ustawiany, gdy oba odpowiednie bity w x i y1.
bitowego LUB|x |= ykażdy bit jest ustawiany, gdy którykolwiek odpowiadający mu bit w x i y Jest 1.
bitowego XOR^x ^= ykaż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~ i operator<< 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 int lub unsigned int na 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?

Pokaż rozwiązanie

b) Do czego w systemie binarnym odnosi się następujący zapis: 0011 | 0101?

Pokaż rozwiązanie

c) Co w systemie binarnym oznacza następujący zapis: 0011 i 0101?

Pokaż rozwiązanie

d) Do czego można porównać w systemie binarnym (0011 | 0101) i 1001?

Pokaż rozwiązanie

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

Pokaż rozwiązanie

Pytanie nr 3

Dodatkowy punkt: Powtórz quiz nr 2, ale nie używaj funkcji test i set (użyj funkcji bitowej operatory).

Pokaż wskazówkę

Pokaż wskazówkę

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
537 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze