Kontenery w C++ – std::array, czyli ładniejsza tablica.
W standardowych i popularnych kursach języka C++ zazwyczaj nikt nie rusza tematów poświęconych szczegółom takim jak kontenery, czy też inne pierdoły z referencji języka – mogą one się wydać rozpraszające lub po prostu trudne dla początkującego, lub komuś po prostu się nie chce. Dlatego postanowiłem napisać serię poradników poświęconych właśnie takim szczegółom, które potrafią bardzo ułatwić życie a o których rzadko kiedy się pisze. Zakładam oczywiście, że czytelnik posiada podstawową wiedzę na temat C++’a, bo bez tego może być mu ciężko ugryźć ten poradnik.
Zacznę od podstawowego kontenera, jakim jest
std::array
.
Zakładam, że wiesz co to tablica, prawda? Ot, zbiór elementów jednego typu poukładanych w pamięci jeden za drugim. Nic strasznego, nic trudnego. Jest to bardzo prosta rzecz, można by rzec nawet, że aż za prosta – standardowy C-style array nie posiada w sobie nic ciekawego, żadnych iteratorów, żadnych fajnych metod dostępu do niego, wszystko trzeba robić „ręcznie”.
I tutaj na scenie pojawia się
std::array
z biblioteki
array
, dodany w standardzie
C++11
Ważne – (zazwyczaj) domyślnie każdy kompilator korzysta ze standardu C++03. Żeby skorzystać z nowszych, należy to sprecyzować przy użyciu flagi -std=c++11/14/17 (zależnie od standardu), albo w opcjach kompilatora – ale to zostawiam już do samodzielnego ogarnięcia).
Jest to bardzo przyjemna alternatywa, posiadająca dużą wartość użytkową – po prostu ułatwia programiście życie. No dobra, ale co w tym takiego fajnego? Dlaczego miałbym się przestawić nagle na
std::array
, zamiast używać starego dobrego c-style arraya?
But… why?
Używanie
std::array
jest prawie takie samo jak używanie zwykłego c-style arraya – losowy dostęp do elementów umożliwia standardowy operator
[]
, ale oprócz tego ten kontener posiada kilka bajerów:
Po pierwsze –
std::array
posiada tak zwany
iterator
. Iterator to obiekt wskazujący na jakiś element z zakresu elementów, który potrafi iterować (czyli, potocznie mówiąc,
„przeskakiwać”
) między elementami przy pomocy na przykład operatorów inkrementacji czy dekrementacji (ale częściej iteratorów używa się ich w range-based for’ach na przykład, o czym za chwilę). Jest to duże ułatwienie w sytuacjach gdzie musimy na przykład przelecieć całą tablicę od początku do końca (lub też od końca do początku, bo
std::array
posiada także reverse iterator!)
Po drugie – posiada przeładowane operatory porównania
==
,
!=
,
<
,
<=
,
>
,
>=
. Czyli możemy w łatwy sposób porównywać dwa std::array’e. Dodatkowo, ma przeładowania dla
std::swap
i
std::get
, gdybyśmy potrzebowali w fikuśny sposób dostać się do danego elementu tablicy lub zamienić jej zawartość z zawartością innej.
Po trzecie – posiada kilka użytkowych metod, takich jak
fill
(wypełnienie tablicy pewną wartością),
front
/
back
(dostęp do pierwszego/ostatniego elementu),
empty
(sprawdzenie czy tablica jest pusta),
size
/
max_size
(sprawdzenie aktualnej i maksymalnej ilości elementów). Jest to też typ agregowalny, czyli przykładowo można go inicjalizować poprzez klamry
{}
, tak jak zwykłą tablicę.
Podsumowując –
std::array
jest prawie taki sam w użytkowaniu jak zwykły c-style array, tylko fajniejszy oraz często wygodniejszy.
No fajnie, tylko jak tego użyć?
Jak już wcześniej mówiłem, można używać
std::array
tak samo jak zwykłej tablicy. Inaczej wygląda deklaracja, ponieważ jest to zwyczajna struktura z szablonowymi typami w konstruktorze:
template<class T, std::size_t N> struct array;
Gdzie T to typ danych przechowywanych w naszym kontenerze, a N to jego początkowa wielkość.
std::size_t
to ogólnie mówiąc typ danych dla liczb całkowitych bez znaku.
Warto o tym pamiętać, polecam przy odnoszeniu się do wielkości kontenerów używać
std::size_t
zamiast int’a czy jakiegokolwiek innego typu danych, oszczędzi nam to ostrzeżeń kompilatora mówiących o porównywaniu typów ze znakiem i bez niego.
Przykładowe deklaracje takich kontenerów wyglądają następująco
No i fajnie, mamy jakieśtam tablice. Co teraz z nimi zrobić? Wykorzystajmy dla przykładu iteratory.
Ten kod chyba wymaga tłumaczenia, szczególnie jeśli ktoś wcześniej nie miał kontaktu z nowszymi standardami C++.
Więc tak – pierwszy przykład wyświetla nam zawartość
std::array
przy użyciu range-based for’a.
const auto &v
to stała referencja do kolejnych wartości kontenera, iterator automatycznie inkrementuje się co iterację pętli. Dlaczego odnoszę się do elementów w ten sposób? Mógłbym to zrobić klasycznie, poprzez
int v
, ale w ten sposób każdy z elementów byłby kopiowany do tymczasowej zmiennej
v
, co w przypadku większych kontenerów stwarzało by duży problem wydajnościowy. Odniesienie się poprzez referencję pozwala na kopiowanie jedynie adresu do elementu, unikając kopiowania jego samego, a
const
uniemożliwia zmianę danego elementu (bo nie chcemy go przecież zmieniać). W tym wypadku jest to na tyle mała skala że w praktyce nie zauważymy żadnych wzrostów wydajności, ale warto wyrobić sobie praktykę używania referencji. Jeśli chodzi o
auto
, to jest to tylko kwestia wygody – ten keyword i tak zostanie zamieniony przez kompilator na odpowiedni typ danych.
W następnym kawałku kodu wypełniamy sobie tablicę bezpośrednio odnosząc się do iteratorów –
b.begin()
wskazuje na początek,
b.end()
na koniec naszego kontenera (analogicznie,
b.rbegin()
wskazuje na koniec, a
b.rend()
na początek). Jak wcześniej mówiłem, iteratory mają przeładowane operatory – inkrementacja, dekrementacja i tego typu rzeczy, które są bardzo wygodne.
Dalej, modyfikacja elementów przy użyciu range-based for’a, tutaj używam referencji bez
const
ponieważ modyfikuję elementy (i w tym wypadku nie mogę odnieść się bez referencji, ponieważ bez niej operował bym na kopiach elementów tablicy)
I na koniec smaczek, przelatuję sobie tablicę od końca co drugi element.
Ale jak by to wyglądało na normalnej tablicy?
Oczywiście to tylko kilka przykładowych porównań, ale widzimy, że
std::array
jest o wiele bardziej czytelniejszy niż standardowa tablica.
Podsumowanie – plusy i minusy
Przyszedł czas na krótkie podsumowanie naszego kontenera
- + Czytelny – posiada wiele funkcji które pozwalają na pisanie czytelnego kodu z wykorzystaniem naszego arraya
- + Wygodny – Iteratory, przeładowania operatorów, metody pozwalające na sprawdzenie wielkości i dostęp do krańcowych elementów oraz wiele więcej metod i bajerów ułatwiających życie
- + Kompatybilny – Można go używać w ten sam sposób, jak normalnej tablicy. Ale nie trzeba.
-
– Stała wielkość
– Jeśli potrzebujemy kontenera o zmiennej wielkości, należy użyć na przykład
std::vector
I w sumie tyle. Jeśli masz jakieś wątpliwości, nadal czegoś nie rozumiesz, zapomniałem o czymś, masz jakąś sugestię czy cokolwiek – pisz śmiało w komentarzu.
A po pełną referencję i więcej przykładów dla tego kontenera zapraszam tutaj: http://en.cppreference.com/w/cpp/container/array .
Następny rozdział będzie poświęcony kontenerowi
std::vector.
Wojciech Olech
Samouk, teleinformatyk, programista. Bawię się elektronicznym stuffem. Ulubione platformy: Arduino, AVR, RaspberryPi, Linux, Windows. Ulubione języki: C/C++/C#/Python.
Zobacz wszystkie posty tego autora →
Komentarze