std::array<T,dim>

std::array<T, dim> je polje elemenata tipa T i dimenzije dim. Dimenzija mora biti konstanta poznata za vrijeme kompilacije. Tip je definiran u zaglavlju <array>.

Spremnik std::array ne dozvoljava promjenu broja elemenata. Njegovi se elementi (ako je moguće) alociraju na stogu, a ne u dinamičkoj memoriji (heap-u). Garantirano je da se elementi nalaze na konsekutivnim lokacijama.

Elementima u polju pristupamo ili pomoću indeksa ili pomoću iteratora.

Primjer.

#include <array>

std::array<int, 4> a{2,4,6,1};
std::array<int, 6> b; // vrijednost elemenata nedefinirana
b = a;  // sve elemente iz a pridruži elementima iz b

for(auto x : a) std::cout << x << ",";
std::cout << std::endl;


std::array<double, 4> b{3.14, 2.71}; // zadnja dva elementa
                                     //inicijalizirana su nulom.

// Koristima reverzne iteratore.
for(auto it=b.rbegin(); it != b.rend(); ++it)
    std::cout << *it <<",";
std::cout << std::endl;

std::cout << b[0] << std::endl;    // pristup preko indeksa
std::cout << b.at(4) << std::endl; // pristup preko indeksa s provjerom
                                   // indeksa -- izbacuje izuzetak

Operacije

Tabela 1. Konstrukcija
Operacija Značenje

array<Elem,N> c

Defaultni konstruktor

array<Elem,N> c(c2)

Konstruktor kopije

array<Elem,N> c = c2

Konstruktor kopije

array<Elem,N> c(rv)

Konstruktor kopije premještanjem

array<Elem,N> c = rv

Konstruktor kopije premještanjem

array<Elem,N> c = initlist

Konstrukcija iz inicijalizacijske liste

Tabela 2. Uspoređivane
Operacija Značenje

c1 == c2

Jednakost

c1 != c2

Različitost

Tabela 3. Pridruživanje
Operacija Značenje

c1 = c2

Pridruži sve elemente iz c2 elementima iz c1

c1 = rv

Pridruživane premještanjem

c.fill(val)

Postavi sve elemente na val

c1.swap(c2)

Podatke iz c1 zamijeni izmijeni s podacima iz c2

Tabela 4. Dohvat elemenata
Operacija Značenje

c[idx]

Vraća element s indeksom idx bez provjere indeksa

c.at(idx)

Vraća element s indeksom idx. Izbacuje range-error izuzetak ako je indeks van granica

c.front()

Vraća prvi element

c.back()

Vraća zadnji element

Tabela 5. Iteratori
Operacija Značenje

c.begin()

Vraća iterator na poziciju prvog elementa

c.end()

Vraća iterator na poziciju iza zadnjeg elementa

c.cbegin()

Vraća konstantan iterator na poziciju prvog elementa

c.cend()

Vraća konstantan iterator na poziciju iza zadnjeg elementa

c.rbegin()

Vraća reverzni iterator na poziciju prvog elementa reverznog niza

c.rend()

Vraća reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

c.crbegin()

Vraća konstantan reverzni iterator na poziciju prvog elementa reverznog niza

c.crend()

Vraća konstantan reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

std::vector<T> - konstrukcija

std::vector<T> je dinamički alocirano polje tipa T. vector se brine o dinamičkoj alokaciji i dealokaciji memorije. Elementi u vektoru se nalaze u konsekutivnim lokacijama, što je važno radi efikasnosti pristupa.

Da bismo mogli koristiti vector treba uključiti njegovu datoteku zaglavlja:

#include <vector>

Konstruktor koji ne uzima argumente konstruira prazan vektor.

std::vector<std::string> svec; // prazan vector stringova

Konstruktor može uzeti broj elemenata i inicijalizacijsku vrijednost.

vector<double> a;           // prazan vektor. Ne sadrži niti jedan element
vector<double> b(10);       // vektor od 10 double-ova, inicijaliziranih nulom
vector<std::string> e(16)   // vektor od 16 praznih stringova
vector<double> c(128,0.77); // Vektor od 128 double-ova,
                            // inicijaliziranih sa 0.77
vector<double> d{1.1,1.2,1.3,1.4}; // inicijalizacija popisom elemenata

Za inicijalizaciju možemo koristiti i raspon elemenata iz standardnog polja elemenata:

char niz[] = {'a','b','c'};
vector<char> vniz(niz,niz+3);  //  {'a','b','c'}

Ako želimo elemente incijalizirati elementima spremnika drugog tipa trebamo koristiti iteratore:

std::list<int> li{1,2,3,4};
std::vector<double> vd(li.begin(), li.end());  // {1,2,3,4}
Tabela 6. Konstrukcija
Operacija Značenje

vector<Elem> c

Dodijeljeni konstruktor; kreira prazan vektor

vector<Elem> c(c2)

Konstruktor kopije

vector<Elem> c = c2

Konstruktor kopije

vector<Elem> c(rv)

Konstruktor kopije premještanjem

vector<Elem> c = rv

Konstruktor kopije premještanjem

vector<Elem> c(n)

Kreira vektor od n elemenata inicijaliziranih dodijeljenim konstruktorom (elementa)

vector<Elem> c(n,elem)

Kreira vektor od n elemenata inicijaliziranih s elem

vector<Elem> c(beg,end)

Kreira vektor inicijaliziran elementima iz raspona [beg,end)

vector<Elem> c(initlist)

Kreira vektor inicijaliziran elementima iz inicijalizacijske liste initlist

vector<Elem> c = initlist

Kreira vektor inicijaliziran elementima iz inicijalizacijske liste initlist

c.~vector()

Destruktor

std::vector<T> - size i capacity

Vektor raste automatski kada nema više mjesta za novi element u vektoru. Metoda size() daje broj elemenata u vektoru, a metoda capacity() daje broj elemenata koji vektor može primiti bez realokacije.

std::vector<int> a;
std::cout << a.size() << std::endl;     //  ispisuje nulu
std::cout << a.capacity() << std::endl; //  ispisuje nulu
assert(a.empty()); // a je prazan

std::vector<int> b(6);
std::cout << b.size() << std::endl;     //  ispisuje 6
std::cout << b.capacity() << std::endl; //  ispisuje 6

Metoda empty() vraća istinu ako je vektor prazan, odnosno laž ako nije. Makro assert() je definiran u zaglavlju <cassert>.

Metodom push_back() ubacujemo novi element iz zadnjeg. Ako nema dovoljno mjesta za novi element vektor se realocira: osigurava se dovoljno mjesta drugdje u memoriji, kopiraju se svi stari element i dodaje se novi.

// Ubaci na kraj vektora novi element = 11
b.push_back(11);
std::cout << b.size() << std::endl;     //  ispisuje 7
std::cout << b.capacity() << std::endl; //  ispisuje 12

Pri realokaciji veličina vektora se približno podvostručuje.

Kapacitet vektora se može povećati i pomoću reserve() metode koja osigurava da vektor ima dovoljno prostora (ali ne mijenja broj elemenata u njemu).

b.reserve(16);
std::cout << b.size() << std::endl;     //  ispisuje 7
std::cout << b.capacity() << std::endl; //  ispisuje 16

Broj elemenata se može promijeniti pomoću metode resize(n). Ako se povećava broj elemenata, novi elementi će biti inicijalizirani nulom, odnosno bit će pozvan konstruktor koji ne uzima argumente. Ako se broj elementa smanjuje elementi koji ostaju u spremniku čuvaju svoju vrijednost. Kapacitet vektora najčešće ostaje isti.

Napomena
Treba razlikovati resize() i reserve(). Ako na praznom vektoru v pozovemo v.reserve(100) on ostaje prazan, ali ima rezerviranu memoriju za 100 elemenata. S druge strane, ako na praznom vektoru v pozovemo v.resize(100) on postaje vektor od 100 elementa inicijaliziranih dodijeljenim konstruktorom.
Tabela 7. Manipulacije veličinom vektora
Operacija Značenje

c.empty()

Vraća true ako je vektor praza, a inače false

c.size()

Vraća broj elemenata u vektoru

c.max_size()

Vraća maksimalni mogući broj elemenata u vektoru

c.capacity()

Vraća maksimalni mogući broj elemenata u vektoru bez realokacije

c.reserve(num)

Povećava kapacitet na num ako je bio manji od num

c.resize(num)

Promijeni broj elemenata spremnika na num (ako spremnik pri tome raste nove elemente inicijaliziraj dodijeljenim konstruktorom)

c.resize(num,elem)

Promijeni broj elemenata spremnika na num (ako spremnik pri tome raste nove elemente inicijaliziraj kopijom od elem)

c.shrink_to_fit()

Reducira kapacitet na broj elemenata u vektoru

c1 == c2

Vraća true ako je c1 jednak c2 (zove == na svakom elementu)

c1 != c2

Vraća true ako c1 nije jednak c2 (isto što i !(c1==c2))

std::vector<T> - operacije na čitavom spremniku

Ako spremniku pridružujemo spremnik istog tipa za to možemo koristiti operator pridruživanja. Ako vektor na lijevoj strani nije prazan njegovi elementi će biti uništeni i zamijenjeni elementima vektora desne strane.

std::vector<double> b{1.0,2.0,3.0}
std::vector<double> d{4.0,3.0,2.0,1.0,0.0}; // 5 elemnata
d = b;
std::cout << "Size = " << b.size();  // ispisuje 3

Kada želimo vektoru pridružiti elemente iz spremnika drugog tipa trebamo koristiti metodu assign().

std::array<double, 3>  ar{1.0,2.0,3.0};
std::vector<double> b;   // prazan vektor
b.assign(5, 1.0);        // sada ima 5 elementa jednakih 1.0
b.assign(ar.begin(),ar.end()); // Sada ima 3 elementa: 1.0,2.0 i 3.0
std::cout << "Size = " << b.size(); // ispisuje 3

U svim ovim operacijama ako spremnik na lijevoj strani nije prazan njegovi elementi se uništavaju.

Tabela 8. Operacije na čitavom spremniku
Operacija Značenje

c = c2

Pridruži sve elemente spremika c2 spremniku c

c = rv

Pridruži premještanjem sve elemente spremika c2 spremniku c

c = initlist

Pridruži sve elemente inicijalizacijske liste initlist spremniku c

c.assign(n,elem)

Pridruži n kopija elementa elem`

c.assign(beg,end)

Pridruži elemente iz raspona [beg,end)

c.assign(initlist)

Pridruži sve elemente inicijalizacijske liste initlist spremniku c

c1.swap(c2)

Zamjeni sadržaj spremnika c1 i c2

swap(c1,c2)

Zamjeni sadržaj spremnika c1 i c2

Napomena
C++ smatra iste spremnike s različitim tipom elemenata različitim tipovima. Tako su std::vector<double> i std::vector<float> različiti tipovi i između njih ne možemo koristiti operator pridruživanja.

std::vector<T> - dohvat elemenata

Dohvat elemenata ide kroz cjelobrojni indeks ili iterator.

Elemente vektora možemo dohvatiti pomoću uglatih zagrada i cjelobrojnih indeksa kao i kod običnog polja.

for(unsigned int i=0; i != b.size(); ++i) b[i] = std::log(2*i+1);
std::cout << "b[1] = "<< b[1] << std::endl; // nema provjere indeksa
double xx = b.at(14); // at() umjesto []: dohvat pomoću indeksa s
                      // provjerom granica - izbacuje izuzetak

Indeks počinje od nule; metoda size() iz klase vector<T> nam daje broj elemenata u polju.

Iteratori služe za kretanje kroz spremnike i ponašaju se kao pokazivači. Svaki spremnik ima metodu begin() koja vraća iterator koji pokazuje na prvi element spremnika i metodu end() koja vraća iterator koji pokazuje na jedno mjesto iz zadnjeg u spremiku.

for(auto it = b.begin(); it != b.end(); ++it)
    std::cout << *it << std::endl; // dohvat pomoću iteratora
assert(*b.begin() == b[0]);

Moguće je direktno dobiti referencu na prvi odnosno zadnji element vektora kroz metode front() i back():

b.front() += 1;
b.back() = 7;

Ne provjerava se da li element postoji.

Metode data() vraća pokazivač na prvi elemet u spremniku, dakle pokazivač na memoriju koja sadrži sve elemente. To omogućava da vector koristimo kao C-polje.

int * pb = b.data();
assert(pb == &b[0]);
Tabela 9. Dohvat elemenata
Operacija Značenje

c[idx]

Vraća element s indeksom idx bez provjere indeksa

c.at(idx)

Vraća element s indeksom idx. Izbacuje range-error izuzetak ako je indeks van granica

c.front()

Vraća prvi element

c.back()

Vraća zadnji element

c.data()

Vraća pokazivač na prvi element vektora.

Tabela 10. Iteratori
Operacija Značenje

c.begin()

Vraća iterator na poziciju prvog elementa

c.end()

Vraća iterator na poziciju iza zadnjeg elementa

c.cbegin()

Vraća konstantan iterator na poziciju prvog elementa

c.cend()

Vraća konstantan iterator na poziciju iza zadnjeg elementa

c.rbegin()

Vraća reverzni iterator na poziciju prvog elementa reverznog niza

c.rend()

Vraća reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

c.crbegin()

Vraća konstantan reverzni iterator na poziciju prvog elementa reverznog niza

c.crend()

Vraća konstantan reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

std::vector<T> - ubacivanje elementa

Kod vektora ubacivanje novog elementa je efikasno samo na kraju vektora. Za to koristimo metodu push_back(). Zadnji se element može efikasno izbaciti iz vektor metodom pop_back().

Kada veličina vektora dosegne njegov kapacitet, pri ubacivanju novog elementa dolazi do realokacije vektora. Rezervira se nova memorijska lokacija za vektor, veća od trenutne. Svi se stari elementi vektora kopiraju na novu lokaciju i dodaje se novi element. Memorija koju je vektor ranije koristio se dealocira.

Elemente možemo ubacivati u vektor na bilo koje mjestu pomoću metode insert(). Ta operacija ima linearnu složenost. Za brisanje koristimo metodu erase().

std::vector<int> b; // b = prazan vektor
b.push_back(1);
b.push_back(2);
b.push_back(3);  // b = 1,2,3
b.pop_back();    // b = 1,2
auto it = b.begin();
it++;
b.insert(it,3);  // b = 1,3,2
--it;
auto it_beg = b.erase(it); // b = 3,2
assert(*it_beg == 3);
Tabela 11. Ubacivanje i izbacivanje elemenata
Operacija Značenje

c.push_back(elem)

Dodaj element elem na kraj spremnika

c.pop_back()

Ukloni zadnji element iz spremnika (ne vraća ga)

c.insert(pos,elem)

Ubaci kopiju elemente elem ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju novoubačenog elementa

c.insert(pos,n,elem)

Ubaci n kopija elementa elem ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju prvog novoubačenog elementa

c.insert(pos,beg,end)

Ubaci kopiju elemenata iz raspona [beg,end) ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju prvog novoubačenog elementa (ili pos ako je raspon bio prazan)

c.insert(pos,initlist)

Ubaci kopiju elemenata inicijalizacijske liste initlist ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju prvog novoubačenog elementa (ili pos ako je lista bila prazna) iterator position pos and returns the position of the f rst new element (or pos if there is no new element;

c.erase(pos)

Obriši element na koji pokazuje iterator pos i vrati iterator koji pokazuje na poziciju sljedećeg elementa

c.erase(beg,end)

Obriši elemente u rasponu [beg,end) i vrati iterator koji pokazuje na poziciju sljedećeg elementa

c.clear()

Obriši sve elemente u spremniku.

std::vector<T> - brisanje elementa

Brisanje elementa vrši metoda erase(). Metoda uzima iterator koji pokazuje na element za brisanje ili raspon iteratora koji određuje elemente za brisanje:

std::vector<unsigned int> b(7);
for(unsigned int i = 0; i < b.size(); ++i)
    b[i] = 2*i+1;
// sada je b = 1,3,5,7,9,11,13

b.erase(b.begin()+1); // obriši drugi element, b = 1,5,7,9,11,13
b.erase(b.begin(), b.begin()+3); // obriši prva tri elementa, b = 9,11,13

Brisanje elementa zadanog po vrijednosti vršimo pomoću std::remove algoritma (zaglavlje <algorithm>) i metode erase() koristeći erase-remove idiom. Algoritam std::remove(it_begin, it_end, val) pronalazi val u rasponu [it_begin, it_end) izbacuje ga iz tog raspona i vraća iterator koji pokazuje na kraj skraćenog raspona. Kako algoritmi ne mogu vršiti operacije na spremniku on ne izbacuje element iz spremnika. Za taj dio posla moramo pozvati erase() metodu.

// remove-erase idiom.
auto it = std::remove(b.begin(), b.end(), 11);  // it je novi kraj raspona
b.erase(it, b.end());  // b = 9,13

Zadnji element se može ukloniti metodom pop_back().

Svi elementi se mogu obrisati metodom clear():

vd.clear();
assert(vd.empty());

std::vector<T> - smještavanje elementa

Kada se element u vektor ubacuje pomoću push_back() metode vrši se konstrukcija elementa kopiranjem. Element se može direktno konstruirati u vektoru pomoću emplace_back() metode. Ona konstruira element iza zadnjeg elementa u vektoru pozivajući direktno konstruktor; metoda zato uzima argumente konstruktora.

struct X{
    X(int i = 0, double y = 0.0) : ii(i), yy(y) {}
    int ii;
    double yy;
};


X xa, xb;
std::vector<X> xvec;
xvec.push_back(xa); // kopiranje
xvec.push_back(xb); // kopiranje
xvec.emplace_back(1, 2.2); // konstrukcija
std::cout << xvec.at(2).yy << std::endl; // ispisuje 2.2
Tabela 12. Smještavanje elementa
Operacija Značenje

c.emplace(pos,args...)

Ubacuje kopiju elementa inicijaliziranog pomoću args ispred pozicije na koju pokazuje iterator pos i vraća iterator koji pokazuje na novi element.

c.emplace_back(args...)

Dodaje kopiju elementa inicijaliziranog pomoću args na kraj spremnika. Ne vraća ništa

Svojstva vektora

  • Vektor čuva elemente na uzastopnim lokacijama u jednom bloku memorije i stoga je memorijski efikasan.

  • Brine o dinamičkoj alokaciji i dealokaciji memorije te predstavlja vektor neograničene veličine.

  • Pri ekspanziji može biti realociran što obezvrijeđuje iteratore i reference na elemente.

  • Direktno može dohvatiti svaki element u konstantnom vremenu.

  • Daje iteratore izravnog dohvata.

  • Efikasan je kod ubacivanja i brisanja elemenata na kraju vektora.

  • Nije efikasan kod ubacivanja i brisanja elemenata na početku i u sredini vektora.

  • Ako je izbačen izuzetak u metodi push_back() onda ta metoda nema efekta; pop_back() ne izbacuje izuzetke.

  • Ako tip podatka koji koristimo u vektoru ne izbacuje izuzetke pri kopiranju i premještanju, onda sve operacije na vektoru imaju svojstvo da su ili uspješne ili nemaju efekta.

std::vector<bool>

Specijalizacija vektora std::vector<bool> je specifična jer optimizira dinamički alociranu memoriju. Umjesto da za svaki element vektora alocira jedan bool (jedan bajt) std::vector<bool> svaki svoj element reprezentira jednim bitom.

Posljedica toga je da std::vector<bool> nema sva svojstva pravog sekvencijalnog spremnika. Pored toga, ima metodu flip() koja mijenja poljedini bit iz nule u jedan ili obratno.

std::vector<bool> vec(12, true);
// Ispis vektora.
for(auto x : vec) std::cout << x;  // 111111111111
std::cout << std::endl;
// Promijeni sve jedinice u nulu i obratno.
vec.flip(); // 0->1, 1->0
for(auto x : vec) std::cout << x;  // 000000000000
std::cout << std::endl;

// Promijeni samo 4. i 11. element
vec[3].flip();
vec[10].flip();
vec[0] = true; // standardno prodruživanje
for(auto x : vec) std::cout << x;  // 100100000010
std::cout << std::endl;

// Dodaj nove elemente na kraj spremnika.
vec.push_back(true);
vec.push_back(false);
vec.push_back(true);

for(auto x : vec) std::cout << x;  // 100100000010101
std::cout << std::endl;

Operator indeksiranja vraća proxy objekt a ne referencu na bool tako da je sljedeći kod neispravan:

auto & v  = vec[0];        // greška, [] vraća privremeni proxy objekt
auto & vv = *vec.begin();  // greška, izraz vraća privremeni proxy objekt

std::bitset<N>

Ako trebamo vektor bitova konačne duljine onda je bolje koristiti std::bitset<N>. Spremnik bitset kao parametar predloška uzima broj bitova, a kao i std::vector<bool> pamti bitove na optimalan način. Sučelje bitset-a se može vidjeti u ovom primjeru:

#include <iostream>
#include <bitset>

int main() {
  std::bitset<32> flags;  // flags = 00000000000000000000000000000000
  std::cout << "flags = "
             << flags.to_string() // to_string() pretvara bitset u string
             << std::endl;        // nula i jedinica
  flags.set(11); // postavi dvanaesti bit
  flags[10] = true; // postavi jedanaesti bit
  flags.flip(9);    // deseti bit promijeni iz nule u jedinicu
  // sada je flags = 00000000000000000000111000000000
  flags.flip(); // invertiraj sve bitove
  // sada je flags = 11111111111111111111000111111111

  std::bitset<4> fl1("0011"); // konstruiraj bitset iz stringa
  std::bitset<4> fl2("0101");

  // Logičke operacije nad bitset objektima
  std::cout << "fl1 & fl2: " << (fl1 & fl2) << std::endl; // fl1 & fl2: 0001
  std::cout << "fl1 | fl2: " << (fl1 | fl2) << std::endl; // fl1 | fl2: 0111
  std::cout << "fl1 ^ fl2: " << (fl1 ^ fl2) << std::endl; // fl1 ^ fl2: 0110

  // Ispiši vrijednost kao broj
  std::cout << fl1.to_ulong() << std::endl;  // ispisuje 3
  return 0;
}

std::deque<T>

Da bismo mogli koristiti deque (double-ended queue) treba uključiti njegovu datoteku zaglavlja:

#include <deque>

Spremnik deque je vrlo sličan vektoru. Dinamički alocira i dealocira memoriju te dozvoljava direktan pristup elementima pomoću operatora indeksiranja i iteratora.

Za razliku od vektora deque dozvoljava efikasno ubacivanje elemenata na oba kraja spremnika. Stoga ima, pored metode push_back() i metodu push_front(); zatim pop_back() i pop_front() te emplace_back() i emplace_front().

deque se obično implementira kao više blokova memorije koji ne čine nužno jedan kontinuirani blok memorije, kao što je to slučaj kod vektora. Stoga je memorijski manje efikasan od vektora.

std::deque<float> dec;

dec.push_back(1.0);
dec.push_front(-1.0);

std::cout << dec[0] << std::endl; // ispisuje -1.0
std::cout << dec[1] << std::endl; // ispisuje 1.0
  • deque ima gotovo isto sučelje kao i vector. Iznimka je što nema metode capacity() i reserve(). Ima metode push_front() i pop_front() koje vektor nema. Sve ostale metode su iste kao i kod vektora.

  • Ubacivanje i brisanje elementa na oba kraja spremnika je brzo, ali je u sredini spremnika sporo.

  • Daje iteratore izravnog dohvata.

  • Iteratori su mu sporiji od vektorovih iteratora. Svako ubacivanje elementa i svako brisanje elementa obezvrijeđuje sve iteratore i reference na elemente spremnika.

  • Ako je izbačen izuzetak u metodi push_back() ili push_front() onda ta metoda nema efekta; pop_back() i pop_front() ne izbacuju izuzetke.

  • Kao i kod vektora, ako tip podatka koji koristimo ne izbacuje izuzetke pri kopiranju i premještanju, onda sve operacije na deque-u imaju svojstvo da su ili uspješne ili nemaju efekta.

Spremnik std::deque<T> treba koristiti samo kada je bitno da se elementi mogu ubacivati/izbacivati na oba kraja spremnika (dakle, kada nam treba red). Bez toga std::deque<T> nema nikakve prednosti pred vektorom.

std::list<T> - konstrukcija

Da bismo mogli koristiti list treba uključiti njegovu datoteku zaglavlja:

#include <list>

Ovaj spremnik predstavlja dvostruko povezanu listu. Stoga nudi samo dvosmjerne iteratore, a ne iteratore izravnog dohvata kao vector i deque.

Elementi liste mogu se nalaziti bilo gdje u memoriji, a povezani su pokazivačima, te stoga lista nije memorijski efikasna.

Lista ima iste konstruktore kao i vector:

std::list<int> li1; // prazna lista
std::list<char> lc1(16); // lista od 16 elemenata (inicijalizirani nulama)
std::list<float> lf1(8, 1.2f); // lista od 8 elemenata inicijaliziranih sa 1.2f
std::list<int> li2(lf1.begin(), lf1.end()); // lista inicijalizirana elementima
                                            // drugog spremnika
std::list<int> li3{1,2,3,4,5};  // inicijalizacija navođenjem elemenata
Tabela 13. Konstrukcija
Operacija Značenje

list<Elem> c

Dodijeljeni konstruktor; kreira praznu listu

list<Elem> c(c2)

Konstruktor kopije

list<Elem> c = c2

Konstruktor kopije

list<Elem> c(rv)

Konstruktor kopije premještanjem

list<Elem> c = rv

Konstruktor kopije premještanjem

list<Elem> c(n)

Kreira listu od n elemenata inicijaliziranih dodijeljenim konstruktorom (elementa)

list<Elem> c(n,elem)

Kreira listu od n elemenata inicijaliziranih s elem

list<Elem> c(beg,end)

Kreira listu inicijaliziran elementima iz raspona [beg,end)

list<Elem> c(initlist)

Kreira listu inicijaliziran elementima iz inicijalizacijske liste initlist

list<Elem> c = initlist

Kreira listu inicijaliziran elementima iz inicijalizacijske liste initlist

c.~list()

Destruktor

Kao i kod ostalih sekvencijalnih spremnika postoje sljedeće operacije koje ne mijenjaju stanje spremika.

Tabela 14. Manipulacije veličinom vektora
Operacija Značenje

c.empty()

Vraća true ako je lista prazna, a inače false

c.size()

Vraća broj elemenata u listi

c.max_size()

Vraća maksimalni mogući broj elemenata u listi

c1 == c2

Vraća true ako je c1 jednak c2 (zove == na svakom elementu)

c1 != c2

Vraća true ako c1 nije jednak c2 (isto što i !(c1==c2))

Metode capacity(), reserve() i shrink_to_fit() ne postoje jer nema potrebe za njima.

Operacije na čitavom spremniku su iste kao i kod ostalih sekvencijalnih spremnika:

Tabela 15. Operacije na čitavom spremniku
Operacija Značenje

c = c2

Pridruži sve elemente spremika c2 spremniku c

c = rv

Pridruži premještanjem sve elemente spremika c2 spremniku c

c = initlist

Pridruži sve elemente inicijalizacijske liste initlist spremniku c

c.assign(n,elem)

Pridruži n kopija elementa elem`

c.assign(beg,end)

Pridruži elemente iz raspona [beg,end)

c.assign(initlist)

Pridruži sve elemente inicijalizacijske liste initlist spremniku c

c1.swap(c2)

Zamjeni sadržaj spremnika c1 i c2

swap(c1,c2)

Zamjeni sadržaj spremnika c1 i c2

std::list<T> - dohvat elemenata

Dohvat elemenata ide kroz pokazivače (nema operatora dohvata []) te kroz metode front() i back().

// Dohvat elemenata kroz pokazivač
for(auto it=lf1.begin(); it != lf1.end(); ++it)
    std::cout << *it << ",";
std::cout << std::endl;

// range-for petlja
for(auto x : li2)
     std::cout << x << ",";
std::cout << std::endl;

if(!li3.empty()){  // ili li3.size() != 0
    std::cout << li3.front() << std::endl; // nema provjere da li
    std::cout << li3.back() << std::endl;  // element postoji
}
Tabela 16. Dohvat elemenata
Operacija Značenje

c.front()

Vrati prvi element (ne provjerava je li lista prazna)

c.back()

Vrati zadnji element (ne provjerava je li lista prazna)

Dohvat proizvoljnog elementa ide kroz iteratore. Funkcije koje vraćaju iteratore su iste kao i kod ostalih sekvencijalnih spremnika. Razlika je ovdje ta što vraćaju dvosmjerni iterator koji nije iterator izravnog pristupa.

Tabela 17. Iteratori
Operacija Značenje

c.begin()

Vraća iterator na poziciju prvog elementa

c.end()

Vraća iterator na poziciju iza zadnjeg elementa

c.cbegin()

Vraća konstantan iterator na poziciju prvog elementa

c.cend()

Vraća konstantan iterator na poziciju iza zadnjeg elementa

c.rbegin()

Vraća reverzni iterator na poziciju prvog elementa reverznog niza

c.rend()

Vraća reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

c.crbegin()

Vraća konstantan reverzni iterator na poziciju prvog elementa reverznog niza

c.crend()

Vraća konstantan reverzni iterator na poziciju iza zadnjeg elementa reverznog niza

std::list<T> - ubacivanje i izbacivanje elemenata

Za ubacivanje koristimo push_back(), push_front() i insert() (koji ima više verzija). Za izbacivanje koristimo pop_back(), pop_front() i remove(). Ubacivanje i izbacivanje je efikasno na svakom mjestu. Radi efikasnosti umjesto remove() algoritma uvijek treba koristiti remove() metodu klase std::list<T>.

// li2 = {1,2,3,4,5}
li3.push_back(6);  // 1,2,3,4,5,6
li3.push_front(0); // 0,1,2,3,4,5,6
li3.remove(4);  // ukloni sve elemente jednake 4 -> 0,1,2,3,5,6
li3.remove_if([](int x){ return x % 2; }); // ukloni neparne elemente
                                           // 0,2,6
li3.pop_back();  // 0,2
li3.pop_front(); // 2
li3.insert(li3.end(), 2, 7); // ubaci na kraj liste dva puta element 7.
                             // 2,7,7
li3.insert(std::prev(li3.end()), 17);  // ubaci 17 ispred zadnjeg elementa
                                       // 2,7,17,7
li3.resize(5);   // odredi novu veličinu liste. Nove elemente stavi na nulu.
                 // 2,7,17,7,0

Metoda remove() eliminira elemente zadane vrijednosti. Metoda erase() eleiminira element na koji pokazuje iterator (ili sve elemente iz raspona).

li3.erase(std::next(li3.begin())); // obriši drugi element -> 2,17,7,0
li3.erase(std::next(li3.begin(),2), li3.end()); // obriši od trećeg do
                                        // zadnjeg elementa -> 2,17
Tabela 18. Ubacivanje i izbacivanje elemenata
Operacija Značenje

c.push_back(elem)

Dodaj element elem na kraj spremnika

c.pop_back()

Ukloni zadnji element iz spremnika (ne vraća ga)

c.push_front(elem)

Dodaj element elem na početak spremnika

c.pop_front()

Ukloni prvi element iz spremnika (ne vraća ga)

c.insert(pos,elem)

Ubaci kopiju elemente elem ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju novoubačenog elementa

c.insert(pos,n,elem)

Ubaci n kopija elementa elem ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju prvog novoubačenog elementa

c.insert(pos,beg,end)

Ubaci kopiju elemenata iz raspona [beg,end) ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju prvog novoubačenog elementa (ili pos ako je raspon bio prazan)

c.insert(pos,initlist)

Ubaci kopiju elemenata inicijalizacijske liste initlist ispred pozicije na koju pokazuje iterator pos i vrati iterator koji pokazuje na poziciju prvog novoubačenog elementa (ili pos ako je lista bila prazna) iterator position pos and returns the position of the f rst new element (or pos if there is no new element;

c.erase(pos)

Obriši element na koji pokazuje iterator pos i vrati iterator koji pokazuje na poziciju sljedećeg elementa

c.erase(beg,end)

Obriši elemente u rasponu [beg,end) i vrati iterator koji pokazuje na poziciju sljedećeg elementa

c.clear()

Obriši sve elemente u spremniku.

Prisutne su i operacije smještavanja elementa u spremnik. Metode emplace uzimaju argumnete konstruktora i konstruiraju element na odgovarajućem mjestu.

Tabela 19. Smještavanje elementa
Operacija Značenje

c.emplace(pos,args...)

Ubacuje kopiju elementa inicijaliziranog pomoću args ispred pozicije na koju pokazuje iterator pos i vraća iterator koji pokazuje na novi element.

c.emplace_back(args...)

Dodaje kopiju elementa inicijaliziranog pomoću args na kraj spremnika. Ne vraća ništa

c.emplace_front(args...)

Dodaje kopiju elementa inicijaliziranog pomoću args na početak spremnika. Ne vraća ništa

Lista ima iste resize metode kao i vektor:

Tabela 20. Resize metode
Operacija Značenje

c.resize(num)

Promijeni broj elemenata spremnika na num (ako spremnik pri tome raste nove elemente inicijaliziraj dodijeljenim konstruktorom)

c.resize(num,elem)

Promijeni broj elemenata spremnika na num (ako spremnik pri tome raste nove elemente inicijaliziraj kopijom od elem)

Konačno remove algoritam je implementiran u samoj listi i efikasniji je od generalnog remove algoritma.

Tabela 21. Uklanjane elemenata
Operacija Značenje

c.remove(val)

Ukloni iz spremnika sve elemente koji imaju vrijednost val

c.remove_if(op)

Ukloni iz spremnika sve elemente za koje op(elem) daje istinu

Algoritmi u std::list<T>

Klasa std::list<T> implementira neke algoritme kao funkcije članice jer na taj način postiže veću efikasnost od generalih algoritama, ili se generalni algoritmi ne mogu primijeniti jer traže iteratore izravnog dohvata (kao sort).

Algoritmi implementirani u klasi list su efikasniji od generalih algoritama jer manipuliraju samo sa pokazivačim. Na primjer, algoritam sort sortira listu, unique eliminira uzastopne duplikate, a reverse invertira listu:

list<int> li{2,5,1,7,5,4,9,7};
li.sort(); // 1,2,4,5,5,7,7,9,
li.unique(); // 1,2,4,5,7,9,
li.reverse(); // 9,7,5,4,2,1,
li.reverse(); // nazad na 1,2,4,5,7,9,

Algoritam merge vrši spajanje dvije sortirane liste u jednu sortiranu list. Pri tom nema kopiranja elemenata i lista koja se uključuje u polaznu listu ostaje prazna.

list<int> li2{5, 8, 4, 11};
li2.sort();
li.merge(li2);  //  1,2,4,4,5,5,7,8,9,11,
assert(li2.empty());

Metoda splice dozvoljava da se elementi jednog spremnika transferiraju u drugi na danom mjestu. Pri tome nema kopiranja elemenata već se samo manipuliraju pokazivači. Može se transferirati čitav spremnik, samo jedan element ili raspon elemenata.

// li2 je prazan
li2.push_back(17);
li2.push_back(18);
li2.push_back(19);
li.splice(next(li.begin(),3), li2);  //1,2,4,17,18,19,4,5,5,7,8,9,11,

Transfer se dešava ispred pokazivača next(li.begin(),3), što znači ispred četvrtog elementa.

Tabela 22. Resize metode
Operacija Značenje

c.unique()

Ukloni sve duplikate kod konsekutivnih elemenata iste vrijednosti

c.unique(op)

Ukloni sve duplikate kod konsekutivnih elemenata za koje op() daje istinu

c.splice(pos,c2)

Premjesti sve elemente iz c2 u c ispred elementa na koji pokazuje iterator pos

c.splice(pos,c2,c2pos)

Premjesti element na poziciji c2pos u spremniku c2 ispred pozicije na koju referira pos u listi c (c i c2 mogu biti isti)

c.splice(pos,c2,c2beg,c2end)

Premjesti sve elemente iz raspona [c2beg,c2end) u c2 ispred pos u listi c (c i c2 mogu biti isti)

c.sort()

Sortiraj elemente spremnika pomoću operatora <

c.sort(op)

Sortiraj elemente spremnika pomoću operatora uspoređivanja op()

c.merge(c2)

Uz pretpostavku da su oba spremnika sortirana, premjesti sve elemente iz c2 u c tako da elementi spojene liste budu sortirani

c.merge(c2,op)

Uz pretpostavku da su oba spremnika sadrže elemente sortirane pomoću operatora uspoređivanja op(), premjesti sve elemente iz c2 u c tako da elementi spojene liste budu sortirani pomoću operatora uspoređivanja op()

c.reverse()

Invertiraj poredak svih elemenata

std::forward_list<T>

Da bismo mogli koristiti forward_list treba uključiti njegovu datoteku zaglavlja:

#include <forward_list>

Ovaj je spremnik uveden standardom iz 2011. godine i predstavlja jednostruko povezanu list. On zauzima manje memorije od std::list<T> spremnika i nudi podskup funkcionalnosti koju nudi std::list<T>.

std::forward_list<T> ima sljedeća svojstva:

  • Nudi samo jednosmjerne iteratore;

  • Nema metode size(), push_back(), back() i pop_back();

  • Metode za konstrukciju su iste kao i kod dvostruko povezane liste.

forward_list<int> fli{3,5,1,6,4,6};

cout << "fli size = " // nema metode size,
     << distance(fli.begin(), fli.end()) << endl;
     // distance je u zaglavlju <iterator>

std::forward_list<T> insert metode

insert metode u sekvencijalnim spremnicima vrše insertiranje ispred mjesta na kojem želimo ubacivanje. Kod std::forward_list<T> to nije moguće zbog nemogućnosti kretanja unazad. Stoga je metoda insert preimenovana u insert_after i ubacuje element(e) iz lokacije na koju pokazuje dani iterator. Da bi bilo moguće ubacivati na početak liste metoda before_begin() vraća pokazivač na jedno mjesto prije prvog u spremniku.

list<int> li{0,0,0};
fli.insert_after(fli.before_begin(), li.begin(), li.end());
// dobivamo 0,0,0,3,5,1,6,4,6,

Na isti način su metode emplace i erase zamijenjene sa emplace_after i erase_after:

fli.erase_after(fli.begin()); //  0,2,3,5,1,6,4,6,
fli.erase_after(next(fli.begin(),5), fli.end()); //  0,2,3,5,1,6,

erase_after briše element nakon onog na koji pokazuje iterator. Verzija koja uzima raspon iteratora briše sve elemente u rasponu osim prvog i zadnjeg.

Metode splice su također preimenovane u splice_after. Ova metoda prebacuje elemente iz jednog forward_list<> spremnika u drugi forward_list<>, iza dane lokacije:

forward_list<int> fli2{2,2,2};
fli.splice_after(next(fli.begin(),3), fli2); //  0,2,3,5,2,2,2,1,6,

Kao i std::list<T>, spremnik std::forward_list<T> ima algoritme sort, unique, reverse, merge i remove.