Kontenery standardowe¶
Definicja kontenera¶
Kontener
- w bibliotece standardowej C++ to struktura służąca do przechowywania danych i posiadająca ściśle zdefiniowany interfejs (zgodny z wymogami standardu)
- kontroluje alokację i dealokację obiektów przy pomocy konstruktorów, destruktorów, operacji wstawiania i usuwania elementów
W kontenerach standardowych do C++11 można było przechowywać tylko typy o semantyce wartości. Od C++11 minimalnym wymogiem jest, aby typ był usuwalny (erasable).
Każdy kontener Cont<T>
(przechowujący typ T
):
definiuje typy cech
Typ Opis value_type
Typ T
elementu przechowywanego w kontenerzereference
T&
const_reference
const T&
iterator
Typ iteratora, który wskazuje na elementy typu T
const_iterator
Typ iteratora gwarantujący stałość wskazywanych elementów diference_type
int
size_type
unsigned int
posiada metody zwracające iteratory:
Operacja Efekt begin()
Zwraca iterator wskazujący na pierwszy element kolekcji end()
Zwraca iterator wskazujący pozycję następną po ostatnim elementem kolekcji cbegin()
Zwraca stały iterator wskazujący na pierwszy element kolekcji cend()
Zwraca stały iterator wskazujący pozycję następną po ostatnim elementem kolekcji implementuje operacje
Operacja Efekt Container c
Domyślny konstruktor - tworzy pusty kontener Container c(c2)
Konstruktor kopiujący (wszystkie elementy są kopiowane) Container c = c2
Konstruktor kopiujący (wszystkie elementy są kopiowane) Container c = r-value
Konstruktor przenoszący Container c(first, last)
Konstruuje kontener inicjując go kopiami wszystkich elementów z zakresu [first; last) Container c = {init_list}
Konstruuje kontener kopiując wszystkie elementy z listy inicjalizującej c.~Container()
Destruktor - niszczony jest każdy element kontenera i zwalniana jest pamięć c = c2
Kopiujący operator przypisania c = r-value
Przenoszący operator przypisania c.empty()
Zwraca czy kontener jest pusty c.size()
Zwraca ilość elementów przechowywanych w kontenerze c1 == c2
Zwraca czy dwa kontenery są sobie równe c2 != c2
Zwraca czy dwa kontenery są różne od siebie c1.swap(c2)
Wymienia zawartość dwóch kontenerów c1 i c2 swap(c1, c2)
Wymienia zawartość dwóch kontenerów c1 i c2
Iteracja po kontenerach¶
Kontenery standardowe są kompatybilne z pętlą range-based-for:
const std::vector<int> vec = { 1, 2, 3, 4, 5 };
for(const auto& item : vec)
{
std::cout << item << '\n';
}
Powyższa pętla jest rozwijana dla kontenerów standardowych do postaci:
{
auto&& __range = vec;
auto __begin = vec.begin();
auto __end = vec.end();
for( ; __begin != __end; ++__begin)
{
const auto& item = *__begin;
std::cout << item << '\n';
}
}
- Adaptory kontenerów
- szablony klas używające kontenerów do przechowywania elementów i udostępniające ograniczony interfejs. Adaptory kontenerów nie udostępniają iteratorów.
Kontenery sekwencyjne¶
- Kontenery sekwencyjne
- elementy są przechowywane w kolejności, w której zostały wstawione.
array<T, size_t>
- tablica o stałym rozmiarze (fixed size array).
- adaptuje dla statycznej tablicy pełny interfejs kontenera wymagany przez standard
- szybki dostęp do dowolnego elementu
- plik nagłówkowy:
<array>
vector<T>
- podobny do tablicy
- może się rozrastać w zależności od potrzeb
- szybkie dodawanie i usuwanie możliwe jedynie na końcu pojemnika
- szybki dostęp do dowolnego elementu
- plik nagłówkowy:
<vector>
deque<T>
- kolejka o dwóch końcach
- szybkie dodawanie z początku i końca
- szybki dostęp do poszczególnych elementów
- plik nagłówkowy:
<deque>
list<T>
- szybkie wstawianie i usuwanie elementów
- brak dostępu do dowolnego elementu na liście
- plik nagłówkowy: <list>
std::array¶
std::array<T, N>
implementuje tablicę o stałym rozmiarze (fixed size array).
Spełnia (prawie) wszystkie wymagania dla kontenera przez co jest dużo wygodniejsza w użyciu niż tablica typu C-style.
Plik nagłówkowy <array>
namespace std
{
template <typename T, size_t Size>
struct array
{
T _Elems[Size];
using value_type = T;
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = T *;
using const_pointer = const T *;
using reference = T&;
using const_reference = const T&;
using iterator = _Array_iterator<_Ty, _Size>;
using const_iterator = _Array_const_iterator<_Ty, _Size>;
using reverse_iterator = _STD reverse_iterator<iterator>;
using const_reverse_iterator = _STD reverse_iterator<const_iterator>;
constexpr reference operator[](size_type pos) noexcept
{
return _Elems[pos];
}
constexpr iterator begin() noexcept
{ // return iterator for beginning of mutable sequence
return (iterator(_Elems, 0));
}
constexpr iterator end() noexcept
{ // return iterator for end of mutable sequence
return (iterator(_Elems, _Size));
}
//... rest of implementation
};
}
Inicjalizacja¶
Ponieważ std::array
jest agregatem, możliwa jest inicjalizacja agregatowa.
Jest też jedynym kontenerem standardowym, którego elementy są inicjalizowane w sposób domyślny (default initialized).
std::array<int, 4> a; // items of a have undefined value
std::array<int, 4> arr1 = { {} }; // { 0, 0, 0, 0 }
std::array<int, 4> arr2 = { { 1, 2 } }; // { 1, 2, 0, 0 }
std::array<int, 4> arr3 = { { 1, 2, 3, 4 } }; // { 1, 2, 3, 4 };
std::array<int, 6> arr4 = { 1, 2, 3, 4, 5, 6 }; // possible warning
Od C++17 można w celu inicjalizacji wykorzystać mechanizm dedukcji parametrów klasy szablonowej:
std::array arr = { 1, 2, 3, 4, 5, 6 }; // std::array<int, 6>
static_assert(arr.size() == 6);
static_assert(is_same_v<decltype(arr)::value_type, int>);
for(const auto& item : arr)
std::cout << item << ' ';
std::cout << '\n';
Zwracanie tablicy z funkcji¶
Jedną z zalet tablic std::array
jest to, że można je zwracać z funkcji:
auto cross_product(const std::array<int, 3>& a, const std::array<int, 3>& b) -> std::array<int, 3>
{
return {{
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
}};
}
Interoperacyjność z tablicami C¶
Metoda data()
zwraca wskaźnik do typu danych przechowywanych w tablicy. Umożliwia to
współpracę z kodem używającym klasycznych tablic C.
namespace LegacyCode
{
void use_tab(int* tab, size_t size)
{
cout << "using tab: ";
for (int* it = tab; it != tab + size; ++it)
cout << *it << " ";
cout << '\n';
}
}
std::array arr1 = { 1, 2, 3, 4, 5 };
LegacyCode::use_tab(arr.data(), arr.size());
Można wykorzystać std::array<char>
jako bufor dla C-string’ów:
std::array<char,255> cstr; // create static array of 41 chars
strcpy(cstr.data(),"hello, world"); // copy a C-string into the array
printf("%s\n", cstr.data()); // print contents of the array as C-string
Zamiana danych - swap¶
Metoda swap()
umożliwia wymianę danych w tablicach tego samego typu. Złożoność tej operacji jest liniowa.
std::array<int, 4> arr1 = { { 1, 2, -1, 4} };
std::array<int, 4> arr2 = { {} };
utils::print(arr1, "arr1: ");
utils::print(arr2, "arr2: ");
arr1.swap(arr2);
cout << "\nAfter swap:\n";
utils::print(arr1, "arr1: ");
utils::print(arr2, "arr2: ");
arr1: [ 1 2 -1 4 ]
arr2: [ 0 0 0 0 ]
Swap:
arr1: [ 0 0 0 0 ]
arr2: [ 1 2 -1 4 ]
Interfejs krotki¶
Dla typu std::array
zaimplementowany jest interfejs krotki (tuple interface). W rezultacie
można traktować obiekty tablic jako homogeniczne krotki.
using ArrayAsTuple = std::array<int, 4>;
ArrayAsTuple t = { { 1, 2, 3, 4 } };
static_assert(std::tuple_size<ArrayAsTuple>::value == 4);
static_assert(std::is_same_v<std::tuple_element<1, ArrayAsTuple>::type, int>);
assert(std::get<1>(t) == 2);
Structured bindings (C++17)¶
Od C++17 std::array
może być dekomponowane do zmiennych za pomocą mechanizmu structured bindings:
array coord = { { 1, 2, 3 } };
auto [x, y, z] = coord;
assert(x == 1);
assert(y == 2);
assert(z == 3);
std::vector¶
- Plik nagłówkowy
<vector>
- Rozmiar może być ustalany w czasie wykonania programu
- Elementy przechowywane są w sposób ciągły w pamięci
- Może się rozrastać w zależności od potrzeb
- Szybkie dodawanie i usuwanie jedynie na końcu (stała zamortyzowana złożoność czasowa)
- Liniowa złożoność operacji wstawiania i usuwania elementów z dowolnego innego miejsca
- Bardzo szybki dostęp do n-tego elementu zapisanego w wektorze - implementuje iterator o dostępie swobodnym
std::vector - szablon¶
Szablon
namespace std
{
template <
class T,
class Allocator = std::allocator<T>
> class vector
{
//...
};
}
std::vector - konstruktory¶
Konstruktory - std::vector
-
vector
::
vector
() Tworzy pusty wektor
-
vector
::
vector
(const vector &vec) Tworzy wektor i kopiuje do niego wszystkie elementy z kontenera
vec
-
vector
::
vector
(sizeType n, const valueType &x) Tworzy kontener posiadający n kopii elementu
x
-
vector
::
vector
(InIter first, Inlter last) Tworzy kontener z elementami skopiowanymi z zakresu
[first,last)
określonego przez iteratory wejściowefirst
ilast
std::vector<int> vec_int;
std::vector<std::string> vec_str(10, "item"s);
std::vector<double> vec_dbl = { 3.14, 1.15, 7.66, 6.65 };
std::vector<int> vec_int_backup(vec_int);
std::vector<std::string> vec_str_backup(vec_str.begin(), vec_str.end());
Ostrzeżenie
Należy uważać na wywołania konstruktorów za pomocą nawiasów klamrowych.
vector<int> vec1(4, 1); // vec1 contains { 1, 1, 1, 1 }
vector<int> vec2{4, 1}; // vec2 contains { 4, 1 }
Inicjalizacja w C++17¶
Od C++17 można inicjalizować wektor w prostszy sposób wykorzystując mechanizm dedukcji typów dla argumentów klas szablonowych:
std::vector vec = { 1, 2, 3, 4 };
std::array arr = { 1, 2, 3, 4, 5 };
std::vector backup(begin(arr), end(arr)); // deduction of T from pair of iterators
std::vector - metody dostępu¶
Metody dostępowe wektora:
-
reference
vector
::
front
() zwraca pierwszy element wektora
-
reference
vector
::
back
() zwraca ostatni element wektora
-
reference
vector
::
operator[]
(sizeType n) zwraca element o indeksie
n
-
reference
vector
::
at
(sizeType n) zwraca element o indeksie
n
- rzuca wyjątkistd::out_of_range gdy n>=size()
Rozmiar, pojemność, maksymalny rozmiar¶
-
sizeType
vector
::
size
() const zwraca liczbę elementów znajdujących się w kontenerze
-
sizeType
vector
::
capacity
() const zwraca maksymalną liczbę elementów, które można zapisać w kontenerze bez zmiany jego rozmiaru
-
sizeType
vector
::
max_size
() const zwraca maksymalną liczbę elementów, które można zapisać w kontenerze
-
bool
vector
::
empty
() const jeżeli kontener jest pusty zwraca true
Przykład
std::vector<int> vec_int(3, 0);
vec_int[0] = 1;
vec_int.at(1) = 2;
vec_int[2] = 3;
std::cout << vec_int.front() << '\n';
try
{
vec_int.at(300) = 2;
}
catch (const std::out_of_range& e)
{
std::cerr << "out_of_range: " << e.what() << std::endl;
}
std::vector - iteratory¶
Metody zwracające iteratory
-
const_iterator
vector
::
begin
() const
-
iterator
vector
::
begin
()
-
const_iterator
vector
::
cbegin
() const zwraca iterator wskazujący na pierwszy element
-
iterator
vector
::
end
()
-
const_iterator
vector
::
end
() const
-
const_iterator
vector
::
cend
() const zwraca iterator wskazujący na pozycję znajdującą się z ostatnim elementem zapisanym w kontenerze
-
reverse_iterator
vector
::
rbegin
()
-
const_reverse_iterator
vector
::
rbegin
() const
-
const_reverse_iterator
vector
::
crbegin
() const zwraca wsteczny iterator wskazujący na ostatni element kontenera
-
reverse_iterator
vector
::
rend
()
-
const_reverse_iterator
vector
::
rend
() const
-
const_reverse_iterator
vector
::
crend
() const zwraca wsteczny iterator wskazujący na pozycję poprzedzającą pierwszy element wektora
Przykład
#include <vector>
using namespace std;
...
vector<int> vec_int(10);
for (auto cit = vec_int.cbegin(); cit != vec_int.cend(); ++cit)
cout << *cit << endl;
int count = 0;
for (auto it = vec_int.begin(); it != vec_int.end(); ++it)
*it = ++count;
std::vector - wstawianie elementów¶
Do wektora można wstawiać elementy za pomocą następujących metod:
-
void
vector
::
push_back
(const T &elem) wstawia na koniec wektora kopię wartości elementu
elem
.
-
void
vector
::
push_back
(T &&elem) wstawia na koniec wektora przenosząc wartość elementu
elem
.
-
iterator
vector
::
insert
(iterator pos, const T &elem) wartość elementu
elem
zostaje wstawiona do wektora przed pozycją wskazywaną przezpos
-
iterator
vector
::
insert
(iterator pos, T &&elem) wartość elementu
elem
zostaje przeniesiona do wektora przed pozycją wskazywaną przezpos
-
iterator
vector
::
insert
(iterator pos, InputIt first, InputIt last) wstawia do kontenera elementy z zakresu [first; last) przed pozycją
pos
-
iterator
vector
::
insert
(iterator, initializer_list<T> il) wstawia elementy z listy inicjalizacyjnej przed pozycją
pos
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.insert(v.begin(), 0)
v.insert(v.end(), {3, 4, 5});
// v -> 0, 1, 2, 3, 4, 5
Operacje emplace¶
Od wersji C++11 istnieją nowe metody:
-
reference
vector
::
emplace_back
(Args&&... args) wstawia na koniec obiekt skonstruowany „w miejscu” (in-place). Konstruktor elementu jest wywoływany z użyciem argumentów
args
.
-
iterator
vector
::
emplace
(const_iterator pos, Args&&... args) wstawia przed pozycją
pos
obiekt skonstruowany „w miejscu” (in-place). Konstruktor elementu jest wywoływany z użyciem argumentówargs
.
using namespace std;
struct Person
{
string name;
string surname;
bool operator==(const Person& p) const
{
return std::tie(name, surname) == std::tie(p.name, p.surname);
}
};
int main()
{
std::vector<Person> staff;
auto& person = staff.emplace_back("Jan", "Nowak");
assert(person == staff.front());
}
std::vector - usuwanie elementów¶
Operacje erase()
umożliwiają usuwanie elementów z wektora:
-
iterator
vector
::
erase
(iterator pos) usuwa element wskazywany przez
pos
.Iterator
pos
musi być prawidłowy i dereferencjowalny->
iterator zwracany przezend()
nie może być w tym przypadku użyty
-
iterator
vector
::
erase
(iterator first, iterator last) usuwa elementy w zakresie wskazywanym przez
[first, last)
W obu przypadkach zwracany jest iterator wskazujący na następną pozycję po ostatnim usuniętym elemencie.
std::vector<int> vec = { 1, 2, 3, 4, 5, 6, 7, 8 };
vec.erase(begin(vec) + 2, begin(vec) + 5);
// in vec after erase { 1, 2, 6, 7, 8 }
std::vector - ważność iteratorów¶
Wstawienie lub usunięcie elementów powoduje unieważnienie referencji, wskaźników oraz iteratorów odnoszących się do elementów następnych.
Jeśli wstawienie powoduje realokację unieważniane są wszystkie referencje, wskaźniki oraz iteratory odnoszące się do elementów kontenera.
Przykład nieprawidłowego kodu:
std::vector vec = { 1, 4, 5, 4, 2, 3, 4, 1 };
auto end = vec.end();
for (auto it = vec.begin(); it != end; ++it)
{
if (*it == 4)
v.erase(it); // WRONG!
}
Użycie funkcji reserve()
jeśli spowoduje realokację także unieważnia wszystkie referencje, wskaźniki oraz iteratory odnoszące się
do elementów kontenera
std::vector<string> words = { "hello", "stl", "!" };
std::string* ps = &words[0];
std::string& rs = words.back();
auto it = words.begin();
++it;
words.reserve(255); // reallocation
std::cout << *ps << " " << rs << " " << *it << '\n'; // undefined behavior
Efektywne stosowanie wektorów¶
Kontener typu std::vector
to ciągły bufor pamięci (tablica) o rozmiarze odpowiednim do przechowywania n
obiektów typu T
.
Klasa std::vector
posiada mechanizmy automatycznego zarządzania pamięcią bufora
- metody służące do wstawiania oraz usuwania elementów
- metody udostępniające zestaw metadanych o stanie kontenera
size()
capacity()
max_size()
Obiekt klasy vector
potrafi automatycznie powiększać swoją pojemność.
Algorytm rozrostu rozmiaru bufora jest zależny od implementacji.
Zwykle bufor o n
elementach jest zastępowany buforem o rozmiarze 2n
Procedura rozciągania bufora kontenera przedstawia się następująco:
- Przydział pamięci dla nowego bufora
- Skopiowanie (lub przeniesienie) elementów z starego do nowego bufora
- Zwolnienie dotychczasowego bufora
Rezerwacja pamięci dla bufora danych¶
Realokacja bufora w wektorze jest operacją czasochłonną. Należy się przed tym chronić rezerwując z góry bufor odpowiednio duży do planowanego rozmiaru kontenera.
std::vector<std::string> vec; // empty vec (size & capacity == 0)
vec.reserve(100);
for(int i = 0; i < 100; ++i)
vec.push_back(std::to_string(i)); // efficient push_backs
Efektywne usuwanie elementów¶
Usunięcie elementu ze środka kontenera ma złożoność liniową.
vector<int> vec = { 1, 2, 3, 4, 6, 7, 8, 9, 10 };
vec.erase(begin(vec) + 2);
Jeśli nie zależy nam na zachowaniu porządku elementów w wektorze możemy zastosować wydajniejszy idiom:
template<typename T>
void efficient_erase(std::vector<T>& vec, std::vector<T>::iterator pos)
{
std::swap(*pos, vec.back());
vec.pop_back();
}
efficient_erase(vec, begin(vec) + 2);
Przechowywanie wskaźników w wektorze¶
W kontenerach standardowych mogą być przechowywane smart-pointer’y.
class Gadget { /*...*/ };
vector<unique_ptr<Gadget>> gadgets;
auto g = make_unique<Gadget>(6, "mp3 player");
gadgets.push_back(make_unique<Gadget>(1, "ipad")); // C++14
gadgets.push_back(move(g));
gadgets.emplace_back(make_unique<Gadget>(4, "smartphone"));
// caution!!!
gadgets.emplace_back(new Gadget{4, "smartphone"}); // may leak in C++11
gadgets.clear(); // deleting all gadgets stored in the vector
Kopiowanie wektora¶
Kopiowanie wektorów można wykonać na dwa sposoby:
- konstruując obiekt kontenera
vector
jako kopię innego kontenera tego typu - konstruktor kopiujący - wywołując na rzecz istniejącego kontenera metodę
assign()
string words[] = { "Szkolenie", "ze", "Standard", "Template", "Library" };
vector<string> vec1(words, words + 5);
vector<string> vec2(vec1);
vector vec3(vec1); // since C++17
vec1.assign(vec3.rbegin(), vec3.rend());
Zamiana danych w wektorach¶
Efektywna zamiana danych między dwoma wektorami tego samego typu jest możliwa dzięki operacji swap()
std::vector<int> vec1 = { 1, 2, 3, 4, 5, 6, 7, 8 };
std::vector<int> vec2 = { 9, 10 };
vec1.swap(vec2); // zamiana danych w wektorach
Idiom zmniejszania bufora danych w wektorze wymaga użycia metody swap()
std::vector<int> vec(1000);
vec.erase(begin(vec) + 50, end(vec));
std::vector<int> temp(vec);
temp.swap(vec);
// krócej z wykorzystaniem obiektu tymczasowego
std::vector<int>(vec).swap(vec);
W C++11 możliwa jest zmiana wielkości zarezerwowanej przez wektor pamięci przy pomocy funkcji shrink_to_fit()
.
vec.shrink_to_fit(); // reallocation if necessary
Obsługa wyjątków¶
Jedyną funkcją, od której standard wymaga zgłaszania wyjątków, jest metoda at()
Jeśli funkcja wywołana przez wektor zgłasza wyjątki gwarantowane jest:
- Jeśli za pomocą
push_back()
wstawiany jest element i wystąpi wyjątek, funkcja ta nie spowoduje żadnego skutku - Jeśli operacje kopiowania elementów nie zgłaszają wyjątków, funkcja
insert()
albo kończy się powodzeniem, albo nie powoduje żadnego skutku - Funkcja
pop_back()
nie zgłasza żadnych wyjątków - Funkcja
swap()
nie zgłasza żadnych wyjątków* - Jeśli używane są typy POD, każda operacja kończy się powodzeniem, albo nie powoduje żadnego skutku
- Funkcje
erase()
orazclear()
nie zgłaszają wyjątków, jeśli operacje kopiowania nie zgłaszają wyjątków.
std::vector<bool>¶
Specjalizowana klasa przechowująca w wektorze wartości logiczne w postaci bitowej.
Posiada rozszerzony interfejs - operację flip()
std::vector<bool> vec_bool(7);
vec_bool[1] = true;
vec_bool[4] = true;
vec_bool.push_back(true);
std::copy(vec_bool.begin(), vec_bool.end(), std::ostream_iterator<int>(std::cout, " ") );
std::cout << '\n';
vec_bool[0].flip();
vec_bool.flip();
Problemy z typem vector<bool>¶
nie jest poprawnym kontenerem STL - nie spełnia wymagań zdefiniowanych przez standard
std::vector<int> vec = { 1, 2, 3 }; int* p = &c[0]; vector<bool> v(8); bool* pb = &v[0]; // compilation error
operator indeksowania nie zwraca typu referencyjnego (
bool&
) - zwraca tymczasowe obiekty proxytemplate <typename Allocator> class std::vector<bool, Allocator> { public: class reference { /*....*/ }; reference operator[](size_type n); };
W rezultacie aby iterować pętlą range-based-for po kontenerze z zamiarem zmiany elementów, należy użyć deklaracji
auto&&
:std::vector<bool> bits = { 0, 1, 1, 1, 0, 0, 0, 1 }; for(auto&& bit : bits) { bit.flip(); cout << bit; } cout << '\n';
std::deque¶
deque [„deck”] = double-ended queue - kolejka dwukierunkowa
namespace std
{
template<
class T,
class Allocator = std::allocator<T>
> class deque
{
//...
};
}
- Plik nagłówkowy:
<deque>
- Wstawianie elementów jest efektywne zarówno na początku jak i na końcu kontenera
- Posiada operator
[]
- Struktura wewnętrzna wprowadza dodatkowy etap pośredni w dostępie do elementów - operacje dostępu do elementów i ruchu iteratora są zwykle wolniejsze
- Iteratory są inteligentnymi wskaźnikami nawigującymi po segmentach pamięci
- Kontener
deque
nie oferuje możliwości sterowania pojemnością ani momentem realokacji - Bloki pamięci mogą zostać automatycznie zwolnione, gdy nie są już wykorzystywane
std::deque - operacje na końcach kolejki¶
Operacje wstawiania i usuwania na końcach kolejki
-
void
deque
::
push_front
(const valueType &x) wstawianie na początek
-
void
deque
::
pop_front
() usunięcie z początku
-
void
deque
::
push_back
(const valueType &x) wstawianie na koniec
-
void
deque
::
pop_back
() usunięcie ostatniego elementu
Podobnie jak wektor, od wersji C++11 mamy nowe metody wykorzystujące tworzenie obiektów „w miejscu”:
-
reference
deque
::
emplace_back
(Args &&args)
-
reference
deque
::
emplace_front
(Args &&args)
-
iterator
deque
::
emplace
(const_iterator pos, Args &&args) wstawiają elementy przekazując do konstruktora typu argumenty
args
std::deque<std::string> coll = { "middle" };
coll.push_back("last");
coll.emplace_back('!', 3);
coll.push_front("first");
coll.emplace_front('>', 3);
coll.emplace(coll.begin() + 2, '-', 3);
// coll { ">>>", "first", "---", "last", "!!!" }
coll.pop_back();
coll.pop_front();
// coll { "first", "---", "last" }
std::deque - ważność iteratorów¶
- Operacje wstawiania na końcach kolejki -
push_front()
,push_back()
,emplace_back()
,emplace_front()
- wszystkie związane z nią iteratory tracą ważność, natomiast wszystkie wskaźniki i referencje na elementy kolejki nie tracą ważności
- Operacje wstawiania w środku -
insert()
,emplace()
- unieważniają wszystkie iteratory i referencje (wskaźniki) do elementów kontenera
- Operacje usuwania ostatniego elementu -
pop_back()
- tracą ważność iterator past-the-end oraz iterator i referencje do usuwanego elementu
- Operacja usuwania pierwszego (ale nie ostatniego) elementu -
pop_front()
- unieważnia iteratory i referencje do usuwanego elementu
- Operacja usuwania ze środka deque’a -
erase()
- unieważnia wszystkie iteratory i referencje (wskaźniki) do elementów kontenera
std::list¶
- Jest kontenerem zoptymalizowanym do wstawiania lub usuwania elementów (stały czas)
- Posiada liniową złożoność obliczeniową znalezienia n-tego elementów
- Lista zapewnia iteratory dwukierunkowe.
- Plik nagłówkowy:
<list>
- brak iteratorów o dostępie swobodnym.
- Zaimplementowana została w postaci podwójnie łączonej listy (dwukierunkowej)
Definicja:
namespace std
{
template <
typename T,
typename Allocator = std::allocator<T>
> class list
{
//...
};
}
std::list - wstawianie i usuwanie elementów¶
Elementy można dodawać następującymi metodami:
-
void
list
::
push_back
(const valueType &x) wstawia
x
jako ostatni element w kontenerze
-
void
list
::
push_front
(const valueType &x) wstawia
x
jako pierwszy element w kontenerze
-
iterator
list
::
insert
(iterator pos, const valueType &x) wstawia
x
przed elementp
i zwraca iterator wskazujący nax
-
iterator
list
::
erase
(iterator p) usuwa element wskazywany przez
pos
i zwraca iterator do elementu następnego (po usuniętym)
Podobnie jak wektor, od wersji C++11 mamy nowe metody wykorzystujące tworzenie obiektów „w miejscu”:
-
reference
list
::
emplace_back
(Args &&args)
-
reference
list
::
emplace_front
(Args &&args)
-
iterator
list
::
emplace
(const_iterator pos, Args &&args) wstawiają elementy przekazując do konstruktora typu argumenty
args
std::list<std::string> lst = { "..." };
lst.push_back("C++");
lst.push_front("Szkolenie");
auto pos = find(lst.begin(). lst.end(), "C++");
pos = lst.erase(pos);
lst.insert(pos, { "z", "programowania", "w" });
std::list - ważność iteratorów¶
Operacje wstawiania i usuwania elementów nie powodują unieważnienia wskaźników, referencji ani iteratorów do innych elementów. Unieważniane są iteratory, referencje i wskaźniki do usuwanych elementów.
std::list - odporność na wyjątki¶
Lista realizuje obsługę wyjątków w taki sposób, że niemal każda operacja kończy się powodzeniem, albo nie powoduje zmiany stanu kontenera. Nie jest zatem możliwe wystąpienie stanu pośredniego, w którym zakończona zostałaby tylko połowa operacji.
Lista oferuje najlepsze gwarancje bezpieczeństwa w przypadku zgłaszania sytuacji wyjątkowych.
std::list - metody specjalne¶
Zawiera metody zoptymalizowane pod kątem operacji na liście dwukierunkowej
-
void
list
::
sort
() Sortuje elementy przechowywane na liście
-
void
list
::
merge
(list<T> &lst) Łączy aktualną posortowaną listę z inną, również posortowaną listą. Elementy z listy x są usuwane (po wyjściu z funkcji lista
lst
jest pusta)
-
void
list
::
reverse
() Odwraca kolejność elementów na liście
-
void
list
::
unique
() Usuwa z listy sąsiadujące ze sobą duplikaty elementów
-
void
list
::
remove
(const T &value) Usuwa z listy wszystkie wystąpienia podanej wartości
-
void
list
::
remove_if
(Predicate pred) Usuwa wszystkie elementy, dla których wywołanie
pred(item)
zwracatrue
-
void
list
::
splice
(Iterator where, list &lst) Przenosi wszystkie elementy kontenera
lst
do kontenera*this
na rzecz którego została wywołana metoda i umieszcza je przed pozycją iteratorawhere
. Żaden z elementów nie jest kopiowany lub przenoszony (przepinane są węzły).
-
void
list
::
splice
(Iterator where, list &lst, const_iterator it) Przenosi węzeł wskazywany iteratorem
it
do listy*this
. Węzeł jest wpinany przed elementem wskazywanym iteratoremit
-
void
list
::
splice
(Iterator where, list &lst, const_iterator first, const_iterator last) Przenosi węzły z zakresu
[first, last) listy ``lst
do listy*this
. Węzły są wpinane przed elementem wskazywanym iteratoremit
Przykład
list<string> lst_1 = { 5, 2, 5, 1, 7 };
list<string> lst_2 = {3, 5, 8, 2, 23, 5 };
// sorting
lst_1.sort();
lst_2.sort();
// merging sorted lists
lst_1.merge(lst_2); // lst_1 = { 1, 2, 2, 3, 5, 5, 5, 5, 7, 8, 23 }
assert(lst_2.size() == 0);
// removing elements
lst_1.remove(5); // lst_1 = { 1, 2, 2, 3, 7, 8, 23 }
// moving nodes between lists
list<int> lst_3 = { 101, 202 };
lst_3.splice(lst_3.begin(), lst_1); // lst3 = { 1, 2, 2, 3, 7, 8, 23, 101, 202 }
assert(lst_1.size() == 0);
Kontenery asocjacyjne¶
Kontenery asocjacyjne
- zbiory -
set<T>
,multiset<T>
- zbiory przechowujące klucze w kolejności określonej poprzez funktor porównujący (domyślnie
less<T>
) set<T>
przechowuje unikalne klucze- plik nagłówkowy:
<set>
- zbiory przechowujące klucze w kolejności określonej poprzez funktor porównujący (domyślnie
- mapy -
map<K, T>
,multimap<K, T>
- słowniki - przechowują pary: klucz i mapowany na klucz obiekt
- pary są przechowywane w kolejności określonej przez funktor porównujący wartości kluczy (domyślnie
less<K>
) - kontener
std::map
nie zezwala na przechowywanie duplikatów kluczy - plik nagłówkowy:
<map>
- Operacje wstawiania, wyszukiwania i usuwania w kontenerach
set
imap
mają złożoność logarytmiczną - Kontenery asocjacyjne wykonują automatyczne sortowanie swoich elementów zgodnie z określonym kryterium porównywania kluczy
Strict weak ordering¶
Kryterium sortowania musi definiować tzw. „ścisłe uporządkowanie słabe” (strict weak ordering):
Musi być antysymetryczne
- Dla operatora
<
oznacza to: jeślix < y
jest prawdziwe, toy < x
jest fałszywe
-Dla predykatu
op()
: jeśliop(x, y)
jest prawdziwe, toop(y, x)
jest fałszywe- Dla operatora
Musi być przechodnie
- Dla operatora
<
oznacza to: jeślix < y
jest prawdziwe orazy < z
jest prawdziwe, tox < z
jest prawdziwe - Dla predykatu
op()
: jeśliop(x, y)
jest prawdziwe orazop(y, z)
jest prawdziwe, toop(x, z)
jest prawdziwe
- Dla operatora
Musi być niezwrotne
- Dla operatora
<
oznacza to:x < x
jest zawsze fałszywe - Dla predykatu
op()
:op(x, x)
jest zawsze fałszywe
- Dla operatora
Równość a równoważność¶
Elementy e1 i e2 są równe, jeśli:
e1 == e2
Elementy e1 i e2 są równoważne, jeśli:
!(e1 < e2) && !(e2 < e1)
Równoważność można zinterpretować następująco:
- Jeśli
e1
nie poprzedza w sekwencjie2
i jednocześniee2
nie poprzedzae1
. - Ich relatywna pozycja w posortowanym ciągu jest taka sama (dwa obiekty trafią w to samo miejsce do posortowanej kolekcji).
Metody kontenerów asocjacyjnych¶
-
size_type
count
(const key_type &k) Zwraca liczbę elementów o wartości
k
-
size_type
erase
(const key_type &k) Usuwa wszystkie elementy o kluczu równoważnym z
k
. Złożoność funkcji: log(N) + count
-
iterator
erase
(iterator pos) -
iterator
erase
(iterator first, iterator last) Usuwa element wskazywany przez iterator lub zakres elementów [first, last). Zwracany jest iterator do następnego elementu po usuniętym.
-
iterator
find
(const key_type &k) Znajduje element równoważny z
k
i zwraca wskazujący na niego iterator. Złożoność: log(N)
-
pair<iterator, bool>
insert
(const value_type &x) Wstawia element
x
. Jeżeli kontener pozwala na istnienie duplikatów kluczy, to funkcja zwraca iterator wskazujący na wstawiony element Jeżeli kontener wymaga unikalnych kluczy, to funkcja zwraca wartośćpair<iterator, bool>
, w której pierwszy element to iterator na wartość równoważną zx
, a drugi to wartość logiczna określająca, czy elementx
został wstawiony, czy też istniał wcześniej w kontenerze
-
pair<iterator, bool>
emplace
(Args&&... args) Wstawia element w miejscu, utworzony przy pomocy parametrów konstruktora
args
. Pozwala to omijać kopiowanie lub przenoszenie elementu do kontenera.
-
iterator
emplace_hint
(const_iterator hint, Args&&... args) Wstawia element w miejscu, utworzony przy pomocy parametrów konstruktora
args
, w miejscu możliwie najbliższym pozycjihint
.
-
iterator
lower_bound
(const Key &k) zwraca iterator wskazujący na pierwszy element nie mniejszy niż klucz
k
-
iterator
upper_bound
(const Key &k) zwraca iterator wskazujący na pierwszy element, który jest większy od podanego klucza
k
-
std::pair<iterator, iterator>
equal_range
(const Key &k) zwraca zakres elementów równoważnych podanemu kluczowi - odpowiednik
pair(lower_bound(k), upper_bound(k))
std::set¶
std::set
- kontener asocjacyjny przechowujący klucze w porządku zgodnym z podanym kryterium porównywania
namespace std
{
template <
typename T,
typename Compare = std::less<T>,
typename Allocator = std::allocator<T>
> class set { /*...*/ };
}
- Plik nagłówkowy:
<set>
- Kontener zoptymalizowany dla operacji wstawiania, usuwania elementów oraz testowania, czy dany element jest przechowywany w kontenerze
- Implementacja - zrównoważone drzewo binarne bez duplikatów
- Zbiory implementują iteratory dwukierunkowe
- Jeśli nie zostanie podane żadne specjalne kryterium sortowania, użyty zostanie funktor
less<T>
, który wykorzystuje operator<
std::set - kryteria sortowania¶
Kryterium sortowania może zostać zdefiniowane na dwa sposoby:
- Jako parametr wzorca
std::set<int, std::greater<int> > coll;
Jako parametr konstruktora
funkcja
bool ptr_compare(int* x, int* y) { return *x > *y; } set<int*, bool (*)(int*, int*)> set_int( &ptr_compare );
std::function
set<Gadget, std::function<bool(const Gadget&, const Gadget&)>> gadgets([](const Gadget& g1, const Gadget& g2) { return g1.id() < g2.id(); }); gadgets.emplace(4, "AD1234"); gadgets.insert(Gadget(1, "AB2323")); gadgets.emplace(7, "AA4235"); for (const auto& g : gadgets) cout << g.id() << endl;
typ domknięcia
auto comp = [](const unique_ptr<int>&a , const unique_ptr<int>& b) { return *a < *b; }; set<unique_ptr<int>, decltype(comp)> numbers(comp);
std::set - wstawianie elementów¶
-
pair<iterator, bool>
set
::
insert
(const value_type &x) wstawia element
x
do kontenera, jeśli kontener nie zawiera już kluczax
. Zwraca obiektpair<iterator, bool>
, w którym składowafirst
zawiera iterator wskazujący na wartość równoważnąx
, a druga składowasecond
zawiera wartośćbool
, czy dany element został wstawiony, czy też nie.std::set<int> set_one = { 6, 7, 8, 4 }; set_one.insert(18); pair<set<int>::iterator, bool> result = set_one.insert(18); if (result.second) std::cout << "element został wstawiony" << '\n' else std::cout << "element nie został wstawiony" << '\n';
Od C++17 powyższy kod może zostać uproszczony z wykorzystaniem structured-bindings i formą instrukcji
if
z sekcją inicjalizacji:std::set<int> set_one = { 6, 7, 8, 4 }; if (auto [pos, action_performed] = set_one.insert(18); action_performed) std::cout << "element został wstawiony" << '\n' else std::cout << "element nie został wstawiony" << '\n'; for (const auto& key : set_one) std::cout << key << '\n';
-
pair<iterator, bool>
set
::
emplace
(Args&&... args) wstawia nowy element do kontenera, konstruując go in-place*`` za pomocą przekazanych argumentów, jeśli nie istnieje już element równoważny
struct Person { int id; std::string name; bool operator<(const Person& other) const { return std::tie(id, name) < std::tie(other.id, other.name); } }; set<Person> people = { {5, "Adamski"}, {3, "Nowakowski"} }; people.emplace(4, "Nowak");
-
iterator
set
::
emplace_hint
(const_iterator hint, Args&&... args) wstawia nowy element, konstruując go in-place jak najbliżej miejsca przed podpowiedzią
hint
. Jeśli wskazówka jest prawidłowa umożliwia osiągnięcie stałego, zamortyzowanego czasu wstawienia elementuauto hint = people.end(); people.emplace_hint(hint, 9, "Anonim");
std::set - operacje wyszukiwania¶
Zbiory są zoptymalizowane pod kątem szybkiego wyszukiwania elementów, więc udostępniają one specjalne funkcje wyszukiwania.
std::multiset<int> data = {1, 2, 4, 4, 4, 5, 6 };
auto lb = data.lower_bound(3);
assert(*lb == 4);
auto ub = data.upper_bound(4);
assert(*ub == 5);
assert(distance(lb, ub) == 3);
auto [first, last] = data.equal_range(4);
assert(first == lb);
assert(last == ub);
Gdy w zbiorze nie ma określonego klucza metody lower_bound()
i upper_bound()
zwracają iterator wskazujący na pozycję umożliwiającą efektywne wstawienie elementu.
std::set<int> data = { 1, 2, 4, 5 };
auto lb = data.lower_bound(3);
auto ub = data.upper_bound(3);
assert(lb == ub);
data.insert(lb, 3); // constant amortized time of insertion
std::map¶
- Kontener (mapa) zarządza parami złożonymi z klucza i odpowiadającej mu wartości.
- Przechowuje elementy typu
std::pair<const Key, T>
namespace std
{
template<
typename Key, typename T,
typename Compare = std::less<Key>,
typename Allocator = std::allocator<std::pair<const Key, T>>
> class map
{
//...
};
}
- Plik nagłówkowy:
<map>
- Elementy są identyfikowane i wstawiane (porządkowane) za pomocą klucza
- Każdy klucz może wystąpić w kontenerze tylko raz - po wstawieniu nie można zmienić jego wartości
- Mapy implementują iteratory dwukierunkowe
multimap<K, T>
- odmiana mapy, w której dozwolone są duplikaty kluczy
std::map - kryterium porządkowania elementów¶
Tworzenie mapy z domyślnym kryterium sortowania: less<>
std::map<int, std::string> day_of_week;
Kryterium sortowania inne niż domyślne może zostać przekazane
- Jako parametr wzorca
std::map<int, std::string, std::greater<int> > day_of_week_desc;
- Jako parametr konstruktora (podobnie jak w
std::set
)
std::map - konstruktory¶
// (1) default constructor
map<string, int> map1;
// (2) constructor using pair of iterators
map<string, int> iter(map1.find("anything"), map1.end());
// (3) copy constructor
map<string, int> copied(map1);
// (4) move constructor //C++11
map<string, int> moved(move(map1));
// (5) initializer list //C++11
const map<string, int> dict = {
{"one", 1},
{"two", 2},
{"three", 3}
};
std::map - wstawianie elementów¶
Wstawianie par klucz-wartość do mapy
- Użycie składowej value_type
map<string, float> coll;
...
coll.insert(map<string, float>::value_type("sty", 32.33));
- Użycie struktury
pair<K, T>
coll.insert(pair<string, float>("jan", 32.33));
coll.insert(pair<const string, float>("jan", 32.33));
coll.insert(pair("feb"s, 33.22)); // since C++17
- Użycie funkcji
make_pair()
coll.insert(make_pair("sty", 32.33));
Przykład:
std::map<string, string> days_of_week = { {"Saturday", "Sobota" } };
days_of_week.insert(make_pair("Monday", "Poniedziałek"));
days_of_week.insert(pair<string, string>("Tuesday", "Wtorek"));
days_of_week.insert(pair("Friday"s, "Piątek"s));
days_of_week["Wednesday"] = "Środa";
if (auto[where, was_inserted] = days_of_week.insert(pair("Thursday"s, "Czwartek"s)); was_inserted)
{
const auto& [key, value] = *where;
std::cout << "New entry was inserted: " << key << " - " << value << '\n';
}
else
{
std::cout << "Entry already exists in a dictionary...\n";
}
std::map - interfejs tablicy asocjacyjnej¶
Mapa umożliwia bezpośredni dostęp do elementów przy pomocy operatora []
-
T &
operator[]
(const Key &key) zwraca referencję do wartości elementu o kluczu równoważnym
key
wykonując wstawienie domyślnej wartości elementu jeśli podanego klucza nie ma w mapie
Instrukcja
std::map<std::string, float> constants;
constants["pi"] = 3.14;
jest przetwarzana następująco:
- Przetwarzaj wyrażenie
constants["pi"]
:- jeśli element o kluczu równoważnym
pi
istnieje, wyrażenie zwraca referencję do tego elementu - jeśli nie istnieje klucz równoważny
pi
, wyrażenie automatycznie wstawia nowy element z łańcuchempi
jako klucz o wartości domyślnej i zwraca referencję do wstawionego elementu
- jeśli element o kluczu równoważnym
- Przypisz wartość
3.14
Od C++17 efektywniejszego wstawienia lub uaktualnienia elementu można użyć funkcji:
-
template<class
M
>
pair<iterator, bool>map
::
insert_or_assign
(const key_type &k, M &&obj)
auto [pos, was_inserted] = constants.insert_or_assign("pi", 3.1415);
assert(was_inserted == false);
std::map - iteracja po elementach¶
Mapa map<Key, T>
przechowuje obiekty w parach klucz-wartość, tworzonych za pomocą klasy pair<const Key, T>
Oba elementy pary pair są dostępne za pomocą składowych
first
- pierwszy elementsecond
- drugi element
Iteracja po mapie to iteracja po parach pair<Key, T>
za pomocą iteratora:
template <typename Key, typename T>
void print_map(const map<Key, T>& m)
{
typename map<Key, T>::const_iterator cit;
cout << "[ ";
for (cit = m.begin(); cit != m.end(); ++cit)
cout << "(" << cit->first << " - " << cit->second << ") ";
cout << "]" << endl;
}
Iteracja po mapie w C++11:
template <typename Key, typename T>
void print_map(const map<Key, T>& m)
{
cout << "[ ";
for (const auto& kv : m)
cout << "(" << kv.first << " - " << kv.second << ") ";
cout << "]" << endl;
}
Jeszcze prościej można iterować po elementach mapy w C++17 wykorzystując structured bindings:
template <typename Key, typename T>
void print_map(const map<Key, T>& m)
{
cout << "[ ";
for (const auto& [key, value] : m)
cout << "(" << key << " - " << value << ") ";
cout << "]" << endl;
}
Transfer węzłów (C++17)¶
C++17 rozszerza API zbiorów i map (z haszowaniem) o możliwość przepinania węzłów pomiędzy kontenerami.
lepsza wydajność niż
erase()
iinsert()
aby przepiąć węzeł tylko typy klucza/wartości (oraz alokatora) muszą być zgodne ze sobą
- kryteria porównań kluczy, funkcje haszujące mogą być różne
- dozwolone jest przepinanie z kontenera nie zezwalającego na duplikaty do kontenera z duplikatami kluczy: np.
set <-> multiset
zdefiniowany jest typ węzła
container::node_type
- szczegółowa implementacja nie jest specyfikowana
- składowa
value()
zwraca wartość dla zbiorów - składowe
key()
orazmapped()
są dostępne dla map - wspierana jest semantyka przenoszenia (np. typy move-only)
nowe operacje w interfejsie kontenerów:
node_type extract(const_iterator pos);
node_type extract(const key_type& key);
merge(std::set& source);
merge(std::multiset& source);
insert_return_type insert(node_type&& nh);
iterator insert(const_iterator hint, node_type&& nh);
Przykład przepięcia węzłów między kontenerami asocjacyjnymi:
std::map<int, std::string> src{{1, "one"}, {2, "two"}, {3, "three"}};
std::map<int, std::string> dst{{3, "THREE"}};
auto pos_src = src.find(1);
dst.insert(src.extract(pos_src)); // splice using iterator
dst.insert(src.extract(2)); // splice using key value
auto result = dst.insert(src.extract(3));
// result.position == next(next(dst.begin()))
// result.inserted == false
// result.node.key() == 3
// result.node.mapped() == "three"
// instead the last statement
auto [pos, success, node] = dst.insert(src.extract(2)); // splice using key value
// pos == next(next(dst.begin()))
// success == false
// node.key() == 3
// node.mapped() == "three"
Kontenery haszujące¶
Kontenery haszujące gwarantują średnio stałą złożoność czasową operacji wyszukiwania, wstawiania oraz usuwania. Elementy w kontenerze haszującym nie są posortowane, ale są przechowywane w tzw. kubełkach (buckets). Element trafia do określonego kubełka na podstawie wartości skrótu (hash value) obliczanej dla elementu.
C++11 wprowadza następujące kontenery haszujące
- zbiory:
unordered_set
iunordered_multiset
- mapy:
unordered_map
iunordered_multimap
std::unordered_set¶
- Plik nagłówkowy
<unordered_set>
namespace std
{
template<
typename Key,
typename Hash = std::hash<Key>,
typename Pred = std::equal_to<Key>,
typename Alloc = std::allocator<Key>
> class unordered_set
{
//...
};
}
- Zbiór haszujący akceptujący duplikaty kluczy:
unordered_multiset
- Kontener
unordered_set
wymaga podania jako parametrów szablonu:- Funktora lub funkcji haszującej klucze - domyślnie
std::hash<T>
- Predykatu równości elementów - domyślnie
std::equal_to<T>
- Funktora lub funkcji haszującej klucze - domyślnie
Ważne
Dla kluczy typu Key
, które są sobie równe k1 == k2
, musi być spełniona równość std::hash<Key>(k1) == std::hash<Key>(k2)
.
Metody dostępowe dla kubełków¶
Kontenery z haszowaniem dostarczają zbiór metod, które umożliwiają zarządzanie strukturą kubełków w kontenerze.
-
size_type
bucket_count
() const Ilość kubełków
-
size_type
max_bucket_count
() const Górna granica ilości kubełków
-
size_type
bucket_size
(size_type n) Rozmiar kubełka numer
n
-
size_type
bucket
(const key_type &k) Indeks kubełka zawierającego klucz
k
-
float
load_factor
() const Średnia ilość elementów w kubełku
-
float
max_load_factor
() const Bieżąca maksymalna ilość elementów w kubełku
-
float
max_load_factor
(float ml) Zmiana poziomu zapełnienia kubełków (ml jest traktowane jako podpowiedź)
-
void
rehash
(size_type n) Zmienia ilość kubełków na co najmniej n
Przykład
#include <random>
#include <unordered_set>
std::random_device rd;
std::default_random_engine rnd_engine{rd};
std::uniform_int_distribution<> rnd_distr{rnd_engine};
std::unordered_set<int> uset1;
for(size_t i = 0; i < 1000; ++i)
uset1.insert(rnd_distr());
cout << "\nIlosc wystapien elementu o wartosci 50: "
<< uset1.count(50) << endl;
std::unordered_set<int>::iterator where = uset1.find(50);
if (where != uset1.end())
cout << "Znaleziony element: " << *where << endl;
std::unordered_map¶
- Plik nagłówkowy:
<unordered_map>
namespace std
{
template
<
typename Key, class T,
typename Hash = std::hash<Key>,
typename Pred = std::equal_to<Key>,
typename Alloc = std::allocator<std::pair<const Key, T> >
> class unordered_map
{
//...
};
}
- Jako parametry szablonu klasy przekazywane są typy:
Key
- kluczT
- mapowany typHash
- funktor wyliczający skrót dla kluczaPred
- predykat porównujący klucze
- Mapa haszująca dopuszczająca duplikaty kluczy:
unordered_multimap
Przykład:
std::unordered_map<int, string> umap1 = { { 7, "nd"}, {6, "sob"} };
umap1.insert(pair<int, string>(1, "pon"));
umap1.insert(make_pair(2, "wt"));
umap1.insert(std::unordered_map<int, string>::value_type(3, "sr"));
umap1[5] = "pt";
auto where = umap1.find(3);
if (where != umap1.end())
std::cout << "Znaleziono wpis: " << where->first << " - "
<< where->second << std::endl;
std::cout << "6: " << umap1[6] << std::endl;
std::hash¶
Struktura szablonowa std::hash
definiuje obiekt funkcyjny, który umożliwia wyliczenie wartości skrótu.
Operator wywołania funkcji spełnia poniższe wymagania:
- Akceptuje argument typu
Key
- Zwraca wartość typu
size_t
- Nie rzuca wyjątków
- Dla dwóch wartości
k1
ik2
, które są sobie równestd::hash<Key>()(k1) == std::hash<Key>()(k2)
- Dla dwóch wartości, które nie są sobie równe, prawdopodobieństwo, że wyliczona dla nich zostanie taka sama wartość skrótu powinna być bardzo mała.
Standard specjalizuje szablon std::hash
dla:
- wszystkich typów liczbowych
- typów wskaźnikowych:
T*
,std::unique_ptr
orazstd::shared_ptr
- typów łańcuchowych
std::bitset
std::vector<bool>
std::thread::id
std::type_index
Tworzenie funkcji haszujących¶
Jeżeli typem klucza kontenera z haszowaniem jest typ tworzony przez użytkownika, to należy dostarczyć jako parametr typu kontenera specjalizowaną funkcję haszującą. Może ty zostać zrealizowane na dwa sposoby:
Klasa funktora.
struct Person { std::string first_name; std::string last_name; bool operator==(const Person& other) const { return std::tie(first_name, last_name) == std::tie(other.first_name, other.last_name); } }; template <typename T> struct HashPerson { size_t operator()(const Person& p) const { std::size_t h1 = std::hash<std::string>()(p.first_name); std::size_t h2 = std::hash<std::string>()(p.last_name); return h1 ^ (h2 << 1); // or use boost::hash_combine } }; std::unordered_set<Person, HashPerson> people;
Specjalizacja klasy
std::hash
.namespace std { template<> struct hash<Person> { typedef Person argument_type; typedef std::size_t result_type; result_type operator()(argument_type const& p) const { result_type const h1 ( std::hash<std::string>()(p.first_name) ); result_type const h2 ( std::hash<std::string>()(p.last_name) ); return h1 ^ (h2 << 1); // or use boost::hash_combine } }; } std::unordered_set<Person> people;