W nowoczesnych architekturach komputerów najmniejszą adresowalną jednostką pamięci jest bajt. Ponieważ wszystkie obiekty muszą mieć unikalne adresy pamięci, oznacza to, że obiekty muszą mieć rozmiar co najmniej jednego bajta. W przypadku większości typów zmiennych jest to w porządku. Jednak w przypadku wartości logicznych jest to trochę marnotrawstwo (gra słów zamierzona). Typy logiczne mają tylko dwa stany: prawda (1) lub fałsz (0). Ten zestaw stanów wymaga przechowywania tylko jednego bitu. Jeśli jednak zmienna musi mieć co najmniej bajt, a bajt ma 8 bitów, oznacza to, że wartość logiczna wykorzystuje 1 bit, a pozostałych 7 pozostawia nieużywanych.
W większości przypadków jest to w porządku — zwykle nie mamy tak dużego obciążenia pamięcią, że musimy przejmować się 7 zmarnowanymi bitami (lepiej jest zoptymalizować pod kątem zrozumiałości i łatwości konserwacji). Jednakże w niektórych przypadkach wymagających dużej ilości pamięci przydatne może być „spakowanie” 8 indywidualnych wartości logicznych w jeden bajt w celu zwiększenia wydajności pamięci.
Wykonywanie tych czynności wymaga możliwości manipulowania obiektami na poziomie bitów. Na szczęście C++ daje nam narzędzia, które pozwalają nam to zrobić. Modyfikowanie poszczególnych bitów w obiekcie nazywa się manipulacją bitami.
Nota autora
Manipulacją bitami stosuje się często w niektórych kontekstach programistycznych (np. grafika, szyfrowanie, kompresja, optymalizacja), ale w mniejszym stopniu w programowaniu ogólnym.
Z tego powodu cały ten rozdział można przeczytać opcjonalnie. Możesz go pominąć lub przejrzeć i wrócić później.
Flagi bitowe
Do tego momentu używaliśmy zmiennych do przechowywania pojedynczych wartości:
int foo { 5 }; // assign foo the value 5 (probably uses 32 bits of storage)
std::cout << foo; // print the value 5Jednak zamiast postrzegać obiekty jako posiadające pojedynczą wartość, możemy zamiast tego traktować każdy bit w obiekcie jako niezależną wartość logiczną. Kiedy pojedyncze bity obiektu są używane jako wartości logiczne, bity te nazywane są flagami bitowymi.
Nomenklatura
Bit przechowujący 0 wartość jest nazywany „fałszywym”, „wyłączonym” lub „nieustawionym”.
Bit przechowujący 1 wartość jest nazywany „prawdą”, „włączonym” lub „set”.
Gdy bit zostaje zmieniony z 0 do 1 lub a 1 do 0, mówimy, że został „odwrócony” lub „odwrócony”.
Na marginesie…
W obliczeniach, a flaga jest wartością sygnalizującą wystąpienie jakiegoś warunku w programie. W przypadku flagi bitowej wartość true oznacza, że warunek istnieje.
Analogicznie w Stanach Zjednoczonych wiele skrzynek pocztowych ma z boku małe (zwykle czerwone) fizyczne flagi. Kiedy poczta wychodząca czeka na odbiór przez przewoźnika, flaga jest podnoszona, aby zasygnalizować, że jest poczta wychodząca.
Aby zdefiniować zestaw flag bitowych, zazwyczaj używamy liczby całkowitej bez znaku o odpowiednim rozmiarze (8 bitów, 16 bitów, 32 bity itp.… w zależności od tego, ile mamy flag) lub std::bitset.
#include <bitset> // for std::bitset
std::bitset<8> mybitset {}; // 8 bits in size means room for 8 flagsNajlepsza praktyka
Manipulacja bitami jest jednym z niewielu przypadków, w których należy jednoznacznie użyj liczb całkowitych bez znaku (lub std::bitset).
W tej lekcji pokażemy, jak w prosty sposób manipulować bitami za pomocą std::bitset. W następnej serii lekcji odkryjemy, jak to zrobić w trudniejszy, ale uniwersalny sposób.
Numeracja bitów i pozycje bitów
Biorąc pod uwagę sekwencję bitów, zazwyczaj numerujemy bity od prawej do lewej, zaczynając od 0 (nie 1). Każda liczba oznacza pozycję bitu.
76543210 Bit position 00000101 Bit sequence
Biorąc pod uwagę sekwencję bitów 0000 0101, bity znajdujące się na pozycjach 0 i 2 mają wartość 1, a pozostałe bity mają wartość 0.
Manipulacja bitami za pomocą std::bitset
W lekcji 5.3 — Systemy liczbowe (dziesiętny, binarny, szesnastkowy i ósemkowy) pokazaliśmy już, jak używać std::bitset do drukowania wartości binarnych. Jednak nie jest to jedyna przydatna rzecz, jaką std::bitset można zrobić.
std::bitset zapewnia 4 kluczowe funkcje składowe, które są przydatne do manipulacji bitami:
- test() pozwala nam sprawdzić, czy bit ma wartość 0 czy 1.
- set() pozwala nam włączyć nieco (to nic nie da, jeśli bit jest już włączony).
- reset() pozwala nam wyłączyć bit (nie zrobi to nic, jeśli bit jest już włączony) off).
- flip() pozwala nam zamienić wartość bitu z 0 na 1 lub odwrotnie.
Każda z tych funkcji przyjmuje jako jedyny argument położenie bitu, na którym chcemy wykonać operację.
Oto przykład:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<8> bits{ 0b0000'0101 }; // we need 8 bits, start with bit pattern 0000 0101
bits.set(3); // set bit position 3 to 1 (now we have 0000 1101)
bits.flip(4); // flip bit 4 (now we have 0001 1101)
bits.reset(4); // set bit 4 back to 0 (now we have 0000 1101)
std::cout << "All the bits: " << bits<< '\n';
std::cout << "Bit 3 has value: " << bits.test(3) << '\n';
std::cout << "Bit 4 has value: " << bits.test(4) << '\n';
return 0;
}Wypisuje:
All the bits: 00001101 Bit 3 has value: 1 Bit 4 has value: 0
Przypomnienie
Funkcje składowe przedstawiliśmy w lekcji 5.7 — Wprowadzenie do std::string. W przypadku normalnych funkcji wywołujemy function(object). W przypadku funkcji składowych wywołujemy object.function().
W lekcji 0b omówiliśmy <<<M11>>>przedrostek literału binarnego i ' separator cyfr 5.3 — Systemy liczbowe (dziesiętny, binarny, szesnastkowy i ósemkowy).
Nadanie nazw bitom może pomóc w udoskonaleniu naszego kodu czytelne:
#include <bitset>
#include <iostream>
int main()
{
[[maybe_unused]] constexpr int isHungry { 0 };
[[maybe_unused]] constexpr int isSad { 1 };
[[maybe_unused]] constexpr int isMad { 2 };
[[maybe_unused]] constexpr int isHappy { 3 };
[[maybe_unused]] constexpr int isLaughing { 4 };
[[maybe_unused]] constexpr int isAsleep { 5 };
[[maybe_unused]] constexpr int isDead { 6 };
[[maybe_unused]] constexpr int isCrying { 7 };
std::bitset<8> me{ 0b0000'0101 }; // we need 8 bits, start with bit pattern 0000 0101
me.set(isHappy); // set bit position 3 to 1 (now we have 0000 1101)
me.flip(isLaughing); // flip bit 4 (now we have 0001 1101)
me.reset(isLaughing); // set bit 4 back to 0 (now we have 0000 1101)
std::cout << "All the bits: " << me << '\n';
std::cout << "I am happy: " << me.test(isHappy) << '\n';
std::cout << "I am laughing: " << me.test(isLaughing) << '\n';
return 0;
}Powiązana treść
Omawiamy [maybe_unused] w lekcji 1.4 — Przypisywanie zmiennych i inicjalizacja.
W lekcji 13.2 — Wyliczenia bez zakresu, pokazujemy, jak moduły wyliczające tworzą jeszcze lepszy zbiór nazwanych bitów.
A co, jeśli chcemy uzyskać lub ustawić wiele bitów na raz
std::bitset nie da się tego zrobić łatwe. Aby to zrobić lub jeśli chcemy używać flag bitowych typu unsigned Integer zamiast std::bitset, musimy zwrócić się do bardziej tradycyjnych metod. Omówimy je w ciągu następnych kilku lekcji.
Rozmiar std::bitset
Jedną potencjalną niespodzianką jest to, że std::bitset jest zoptymalizowany pod kątem szybkości, a nie oszczędności pamięci. Rozmiar a std::bitset to zazwyczaj liczba bajtów potrzebnych do przechowywania bitów, zaokrąglona w górę do najbliższego sizeof(size_t), czyli 4 bajty na maszynach 32-bitowych i 8 bajtów na maszynach 64-bitowych.
Zatem a std::bitset<8> będzie zazwyczaj używać 4 lub 8 bajtów pamięci, chociaż technicznie rzecz biorąc, potrzebuje tylko 1 bajtu do przechowywania 8 bitów. Dlatego std::bitset jest najbardziej przydatna, gdy zależy nam na wygodzie, a nie na oszczędności pamięci.
Wykonywanie zapytań std::bitset
Istnieje kilka innych funkcji składowych, które są często przydatne:
- size() zwraca liczbę bitów w zestawie bitów.
- count() zwraca liczbę bitów w zestawie bitów, które są ustawione na
true. - all() zwraca wartość logiczną wskazującą, czy wszystkie bity są ustawione na
true. - any() zwraca wartość logiczną wskazującą, czy jakiekolwiek bity są ustawione na
true. - none() zwraca wartość logiczną wskazującą, czy żadne bity nie są ustawione na
true.
#include <bitset>
#include <iostream>
int main()
{
std::bitset<8> bits{ 0b0000'1101 };
std::cout << bits.size() << " bits are in the bitset\n";
std::cout << bits.count() << " bits are set to true\n";
std::cout << std::boolalpha;
std::cout << "All bits are true: " << bits.all() << '\n';
std::cout << "Some bits are true: " << bits.any() << '\n';
std::cout << "No bits are true: " << bits.none() << '\n';
return 0;
}Wypisuje:
8 bits are in the bitset 3 bits are set to true All bits are true: false Some bits are true: true No bits are true: false

