C++
Operatory bitowe
Szukaj…
Uwagi
Operacje przesunięcia bitów nie są przenośne we wszystkich architekturach procesorów, różne procesory mogą mieć różne szerokości bitów. Innymi słowy, jeśli napisałeś
int a = ~0;
int b = a << 1;
Ta wartość byłaby inna na komputerze 64-bitowym niż na komputerze 32-bitowym lub z procesora opartego na x86 na procesorze opartym na PIC.
Endianowość nie musi być brana pod uwagę przy samych operacjach bitowych, to znaczy, prawe przesunięcie ( >>
) spowoduje przesunięcie bitów w kierunku najmniej znaczącego bitu, a XOR wykona wyłączność lub na bitach. Endianowość musi być brana pod uwagę tylko w przypadku samych danych, to znaczy, jeśli endianność jest problemem dla twojej aplikacji, jest to problem niezależnie od nieco mądrzejszych operacji.
& - bitowe AND
int a = 6; // 0110b (0x06)
int b = 10; // 1010b (0x0A)
int c = a & b; // 0010b (0x02)
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Wynik
a = 6, b = 10, c = 2
Dlaczego
Nieco mądry AND
działa na poziomie bitowym i wykorzystuje następującą logiczną tabelę prawdy:
TRUE AND TRUE = TRUE
TRUE AND FALSE = FALSE
FALSE AND FALSE = FALSE
Kiedy wartość binarna dla a
( 0110
) i wartość binarna dla b
( 1010
) są AND
razem, otrzymujemy wartość binarną 0010
:
int a = 0 1 1 0
int b = 1 0 1 0 &
---------
int c = 0 0 1 0
Bitowe mądre AND nie zmienia wartości oryginalnych wartości, chyba że przypisano je konkretnie do użycia operatora złożonego bitowego przypisania &=
:
int a = 5; // 0101b (0x05)
a &= 10; // a = 0101b & 1010b
| - bitowe OR
int a = 5; // 0101b (0x05)
int b = 12; // 1100b (0x0C)
int c = a | b; // 1101b (0x0D)
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Wynik
a = 5, b = 12, c = 13
Dlaczego
Nieco mądra operacja OR
działa na poziomie bitowym i wykorzystuje następującą logiczną tabelę prawdy:
true OR true = true
true OR false = true
false OR false = false
Gdy wartość binarna dla a
( 0101
) i wartość binarna dla b
( 1100
) są OR
połączone, otrzymujemy wartość binarną 1101
:
int a = 0 1 0 1
int b = 1 1 0 0 |
---------
int c = 1 1 0 1
Bitowe mądre OR nie zmienia wartości oryginalnych wartości, chyba że przypisano je konkretnie do używania operatora złożonego bitowego przypisania |=
:
int a = 5; // 0101b (0x05)
a |= 12; // a = 0101b | 1101b
^ - bitowy XOR (wyłączny OR)
int a = 5; // 0101b (0x05)
int b = 9; // 1001b (0x09)
int c = a ^ b; // 1100b (0x0C)
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
Wynik
a = 5, b = 9, c = 12
Dlaczego
Nieco mądre XOR
(wyłączne lub) działa na poziomie bitowym i wykorzystuje następującą logiczną tabelę prawdy:
true OR true = false
true OR false = true
false OR false = false
Zauważ, że w przypadku operacji XOR true OR true = false
gdzie jak w przypadku operacji true AND/OR true = true
, stąd wyłączny charakter operacji XOR.
Korzystając z tego, gdy wartość binarna dla a
( 0101
) i wartość binarna dla b
( 1001
) są razem XOR
, otrzymujemy wartość binarną 1100
:
int a = 0 1 0 1
int b = 1 0 0 1 ^
---------
int c = 1 1 0 0
Bitowy XOR nie zmienia wartości pierwotnych wartości, chyba że jest przypisany konkretnie do użycia operatora złożonego przypisania bitowego ^=
:
int a = 5; // 0101b (0x05)
a ^= 9; // a = 0101b ^ 1001b
Mało bitowy XOR może być wykorzystywany na wiele sposobów i często jest wykorzystywany w operacjach maski bitowej do szyfrowania i kompresji.
Uwaga: Poniższy przykład jest często przedstawiany jako przykład dobrej sztuczki. Ale nie powinien być stosowany w kodzie produkcyjnym (istnieją lepsze sposoby std::swap()
aby osiągnąć ten sam wynik).
Możesz także użyć operacji XOR, aby zamienić dwie zmienne bez tymczasowego:
int a = 42;
int b = 64;
// XOR swap
a ^= b;
b ^= a;
a ^= b;
std::cout << "a = " << a << ", b = " << b << "\n";
Aby to zrobić, musisz dodać czek, aby upewnić się, że można go użyć.
void doXORSwap(int& a, int& b)
{
// Need to add a check to make sure you are not swapping the same
// variable with itself. Otherwise it will zero the value.
if (&a != &b)
{
// XOR swap
a ^= b;
b ^= a;
a ^= b;
}
}
Więc choć wygląda to na fajną sztuczkę w izolacji, nie jest użyteczny w prawdziwym kodzie. xor nie jest podstawową operacją logiczną, ale kombinacją innych: a ^ c = ~ (a & c) i (a | c)
również w kompilatorach 2015+ zmienne można przypisać jako binarne:
int cn=0b0111;
~ - bitowe NIE (jednoargumentowe uzupełnienie)
unsigned char a = 234; // 1110 1010b (0xEA)
unsigned char b = ~a; // 0001 0101b (0x15)
std::cout << "a = " << static_cast<int>(a) <<
", b = " << static_cast<int>(b) << std::endl;
Wynik
a = 234, b = 21
Dlaczego
Nieco mądre NOT
(jednoargumentowe uzupełnienie) działa na poziomie bitów i po prostu odwraca każdy bit. Jeśli jest to 1
, zmienia się na 0
, jeśli jest to 0
, zmienia się na 1
. Mądrze NIE ma takiego samego efektu jak XOR'owanie wartości względem wartości maksymalnej dla określonego typu:
unsigned char a = 234; // 1110 1010b (0xEA)
unsigned char b = ~a; // 0001 0101b (0x15)
unsigned char c = a ^ ~0;
Nieco mądry może być również wygodnym sposobem sprawdzenia maksymalnej wartości dla określonego typu całki:
unsigned int i = ~0;
unsigned char c = ~0;
std::cout << "max uint = " << i << std::endl <<
"max uchar = " << static_cast<short>(c) << std::endl;
Wręcz przeciwnie NIE zmienia wartości pierwotnej wartości i nie ma operatora przypisania złożonego, więc nie można na przykład wykonać a ~= 10
.
Nie należy mylić nieco logiki NOT ( ~
) z logicznym NOT ( !
); gdzie nieco mądre NIE odwróci każdego bitu, logiczne NIE użyje całej wartości, aby wykonać swoją operację, innymi słowy (!1) != (~1)
<< - lewy Shift
int a = 1; // 0001b
int b = a << 1; // 0010b
std::cout << "a = " << a << ", b = " << b << std::endl;
Wynik
a = 1, b = 2
Dlaczego
Przesunięcie w lewo do bitu przesunie bity wartości lewej ręki ( a
) o liczbę określoną po prawej stronie ( 1
), zasadniczo wypełniając najmniej znaczące bity zerami, przesuwając w ten sposób wartość 5
(binarnie 0000 0101
) w lewo 4 razy (np. 5 << 4
) da wartość 80
(dwójkowa 0101 0000
). Możesz zauważyć, że przesunięcie wartości w lewo 1 raz jest również takie samo jak pomnożenie wartości przez 2, przykład:
int a = 7;
while (a < 200) {
std::cout << "a = " << a << std::endl;
a <<= 1;
}
a = 7;
while (a < 200) {
std::cout << "a = " << a << std::endl;
a *= 2;
}
Należy jednak zauważyć, że operacja przesunięcia w lewo przesunie wszystkie bity w lewo, w tym bit znaku, przykład:
int a = 2147483647; // 0111 1111 1111 1111 1111 1111 1111 1111
int b = a << 1; // 1111 1111 1111 1111 1111 1111 1111 1110
std::cout << "a = " << a << ", b = " << b << std::endl;
Możliwe wyjście: a = 2147483647, b = -2
Chociaż niektóre kompilatory przynoszą wyniki, które wydają się oczekiwane, należy zauważyć, że jeśli opuścisz przesunięcie liczby ze znakiem, aby wpłynąć na bit znaku, wynik nie jest zdefiniowany . Nie jest również zdefiniowane, jeśli liczba bitów, o którą chcesz przesunąć, jest liczbą ujemną lub jest większa niż liczba bitów, jaką może pomieścić typ po lewej stronie, na przykład:
int a = 1;
int b = a << -1; // undefined behavior
char c = a << 20; // undefined behavior
Przesunięcie bitowe w lewo nie zmienia wartości oryginalnych, chyba że jest przypisane konkretnie do użycia operatora złożonego przypisania bitowego <<=
:
int a = 5; // 0101b
a <<= 1; // a = a << 1;
>> - przesunięcie w prawo
int a = 2; // 0010b
int b = a >> 1; // 0001b
std::cout << "a = " << a << ", b = " << b << std::endl;
Wynik
a = 2, b = 1
Dlaczego
Przesunięcie mądrego bitu spowoduje przesunięcie bitów wartości lewej ręki ( a
) o liczbę określoną po prawej stronie ( 1
); należy zauważyć, że chociaż operacja przesunięcia w prawo jest standardem, to, co dzieje się z bitami przesunięcia w prawo na podpisanej liczbie ujemnej, jest zdefiniowana implementacja, a zatem nie można zagwarantować, że będzie przenośna, na przykład:
int a = -2;
int b = a >> 1; // the value of b will be depend on the compiler
Nie jest również zdefiniowane, jeśli liczba bitów, o które chcesz przesunąć, jest liczbą ujemną, na przykład:
int a = 1;
int b = a >> -1; // undefined behavior
Przesunięcie bitowe w prawo nie zmienia wartości oryginalnych, chyba że przypisano je konkretnie do użycia operatora złożonego przypisania bitowego >>=
:
int a = 2; // 0010b
a >>= 1; // a = a >> 1;