5.7 Wektory (std::vector)

Tablice statyczne, o których mówiliśmy, należy znać (i czasem stosować...), ale nie są one zbyt wygodne. Tworząc je, musimy z góry znać ich wymiar i to już na etapie pisania kodu źródłowego, bo wymiarem może być tylko stała kompilacji. Niewygodne jest też przesyłanie zwykłych C-tablic do funkcji, bo trzeba osobno przesyłać wymiar, jako że do funkcji przesyłany jest tak naprawdę tylko adres pierwszego elementu. Znacznie wygodniejszym typem tablicy jest wektor (vector z nagłówka vector). Jest to typ kolekcji bardzo podobny, i podobnie implementowany, jak ArrayList, znany nam z Javy. Tak jak array jest to właściwie szablon, który musimy parametryzować (w nawiasach kątowych) typem elementów. Wektory, w porównaniu z C-tablicami, mają szereg zalet:

O kolekcjach, w tym wektorach, będziemy mówić później, bo na razie nie wiemy co to są klasy, metody i szablony, ale poniższy przykład (i dokumentacja) pozwolą nam już je stosować.


P29: vecsimple.cpp     Wektory

      1.  #include <iostream>
      2.  #include <string>
      3.  #include <vector>
      4.  
      5.  int main() {
      6.      using std::vector; using std::cout;
      7.  
      8.      vector<int> v1{2,1};                            
      9.      vector<int> v2(3,1);                            
     10.      v1.push_back(0);
     11.      v2.push_back(1);
     12.      cout << "v1: size = " << v1.size() << " -> ";
     13.      for (const auto& e : v1) cout << e << " ";
     14.      cout << "\n";
     15.      cout << "v2: size = " << v2.size() << " -> ";
     16.      for (const auto& e : v2) cout << e << " ";
     17.      cout << "\n";
     18.  
     19.      vector<std::string> v3;                         
     20.      // v3[0] = "A";  // WRONG!!
     21.      v3.push_back("A");
     22.      for (int i = 1; i < 5; ++i)
     23.          v3.push_back(v3.at(i-1) + char('A' + i));   
     24.      for (const auto& e : v3) cout << e << " ";
     25.      cout << "\n";
     26.  
     27.      cout << "First: " << v3.front() << ", last : "  
     28.           << v3.back() << "\n";
     29.      while (v3.size() > 0) {
     30.          cout << "Removing " << v3.back() << "\n";
     31.          v3.pop_back();                              
     32.      }
     33.  }

Powyższy program drukuje

    v1: size = 3 -> 2 1 0
    v2: size = 4 -> 1 1 1 1
    A AB ABC ABCD ABCDE
    First: A, last : ABCDE
    Removing ABCDE
    Removing ABCD
    Removing ABC
    Removing AB
    Removing A
Stosując nawiasy klamrowe, możemy utworzyć wektor o z góry zadanej zawartości. Na przykład wektor v1, utworzony w linii , zawiera dwie liczby typu int o wartościach 2 i 1. Można też do zainicjowania wektora użyć nawiasów okrągłych (jak w linii ). Taki zapis znaczy jednak co innego: zainicjuj trzy elementy wektora (pierwszy argument 3) wartością 1 (drugi argument 1). Można w końcu utworzyć wektor pusty, jak w linii . Zauważmy, że wtedy nie zawiera on żadnego elementu, więc przypisanie v3[0]="A" byłoby nielegalne, bo v3[0] znaczy pierwszy element, ale przecież w pustym wektorze nie ma ani jednego! Nowe elementy można dodawać do wektora za pomocą push_back, jak w przykładzie (albo, jeszcze lepiej, emplace_back, którą to funkcję poznamy później). Elementy są wtedy dodawane – co, oczywiście, wiąże się ze zmianą wymiaru – na końcu kolekcji. Istnieją co prawda sposoby, aby dodać nowe elementy na dowolnej pozycji, ale należy ich unikać, bo są bardzo nieefektywne. Dostęp do poszczególnych elementów wektora mamy za pomocą składni vec[i] (bez kontroli zakresu indeksu) albo vec.at(i) (, z kontrolą indeksu). Do pierwszego i ostatniego elementu można też się odwołać poprzez vec.front()vec.back() (). Ostatni element możemy usunąć za pomocą vec.pop_back() ().

T.R. Werner, 23 lutego 2019; 23:59