Wprowadzenie do STL-a

Michał Czardybon


Wprowadzenie do STL-a

Wstęp

STL (Standard Template Library) jest biblioteką dla języka C++ dostarczającą funkcjonalności wspólnej dla niemal wszystkich nietrywialnych programów - w tym przede wszystkim klas uniwersalnych kontenerów (list, drzew binarnych). Od 1998 roku jest ona częścią standardu języka C++. Wcześniej powstało wiele analogicznych bibliotek, właściwie każda firma programistyczna miała swoją własną - przykłady można znaleźć w bibliotekach MFC, wxWindows czy w Borland C++ Builderze. Ty sam zapewne w swoich projektach programistycznych nie raz implementowałeś listy, sterty itp. Dobrze więc stało się, że raz na zawsze rozwiązano ten problem i teraz wszyscy mogą używać tej samej, uniwersalnej bibliteki.

W tym krótkim wprowadzeniu opisuję ogólne koncepcje, potrzebne do rozpoczęcia pracy z biblioteką STL. Więcej informacji, w tym pełną dokumentację, można znaleźć na stronie http://www.sgi.com/tech/stl (lektura obowiązkowa). Warto także zajrzeć do bardziej opisowego opracowania autorstwa: Alexander Stepanov, Meng Lee (wygoogluj).

Krótko o wzorcach

W celu zrozumienia działania biblioteki STL trzeba wcześniej zrozumieć czym są wzorce. Jeśli jednak chcesz szybko zacząć zabawę z STL'em, wystarczy krótkie przedstawienie. Otóż wzorce klas są elementem języka C++ służącymi do opisywania operacji typu copy-paste, czyli tam, gdzie potrzebujesz takiego samego kodu jak ten, który już napisałeś, z tę jedną różnicą, że wszędzie zamiast intów chcesz mieć double. Ta funkcjonalność jest bardzo potrzebna w celu utworzenia klas kontenerów, które działałyby dla różnych typów obiektów zawieranych - np. możesz chcieć mieć listę intów, a w innym miejscu programu listę wskaźników na jakieśtam obiekty.

Używa się tego (w dużym skrócie) w ten sposób, że przed definicją klasy dołącza się deklarację:

   template <class T>

a następnie w miejsce typów, które mają być dowolne, wpisuje się T. Tak zdefiniowany wzorzec używa się podając po nazwie klasy, nazwę typu ujętą w nawiasy ostre (<, >).

Przykład:

   template <class T>
   class Array
   {
   public:
      ...
      T& getItem(int k) { return data[k]; }
   private:
      T* data;
   };
   
   Array<int>my_array_of_int;

Iteratory

Aby nie było zbyt prosto, potrzebujemy jeszcze jednej nowej koncepcji w języku programowania. Jest nią pojęcie iteratora. Iterator jest uogólnieniem wskaźnika (tj. każdy wskaźnik jest iteratorem). Aby zrozumieć dlaczego uogólnienie jest potrzebne, rozważmy przykład tablicy i listy. Zmienna typu tablicowego (w języku C) jest równoważna wskaźnikowi na jej pierwszy element. Dostęp do kolejnych elementów tej tablicy możemy uzyskać za pomocą indeksowania, ale także, co jest zazwyczaj bardziej efektywne, za pomocą inkrementowania wskaźnika. I tak można na przykład zdefiniować funkcję strcpy (kopiowanie stringów w języku C) następująco:

   void strcpy(char* p, char* q)
   {
      while (*q != 0) *p++ = *q++;
   }

Operacji takiej nie można jednak wykonać dla innych typów kontenerów - np. dla listy. Nie można oczywiście tylko wtedy, gdy nie mamy iteratorów, bo iteratory zostały wymyślone właśnie po to, aby było to możliwe. A dlaczego to właściwie jest potrzebne? No nie tylko dla większej wydajności. Przede wszystkim po to, aby można było uogólnić operacje na kontenerach takie, jak na przykład wspomniane kopiowanie, na kontenery dowolnych typów - można wtedy kopiować w dokładnie ten sam sposów tablicę do listy i drzewo binarne do tablicy haszującej! Oczywiście kopiowanie jest tylko przykładem. Za pomocą iteratorów można łatwo wyrazić bardziej skomplikowane operacje takie, jak wyszukiwanie elementu czy sortowanie.

Podsumowując:

Iteratory są po to aby można było używać różnych kontenerów w ten sam sposób.

W bibliotece STL iteratory są klasami wewnętrznymi klas kontenerów, a same kontenery posiadają metody zwracające iteratory - np. metody begin() oraz end() zwracające początek i koniec sekwencji elementów zawieranych. Jak już wspomniałem, iteratorem posługujemy się tak, jak zwykłym wskaźnikiem w przypadku tablicy - czyli aby przejść do następnego elementu korzystamy z operatora “++”, a w celu dostania się do zawartości operatora “*”.

Typowa pętla wypisująca wszystkie elementy kontenera (tutaj tablica liczb) wygląda następująco:

   vector<int>::iterator i;
   for (i = v.begin(); i != v.end(); i++)
   {
      printf(“%d “, (*i));
   }

Jest jeszcze jedna sprawa, o której powinienem wspomnieć - istnieją różne iteratory. Najważniejsze są: Forward Iterator (np. dla listy jednokierunkowej, może przesuwać się tylko w jednym kierunku), Bidirectional Iterator (dla listy dwukierunkowej, może się cofać - operator “--”) i Random Access Iterator (dla tablicy, ale nie dla listy, może się poruszać o dowoną liczbę elementów). Nie wszystkie algorytmy da się wykonać na wszystkich typach iteratorów - np. sortowanie może być wykonane tylko dla iteratorów o dostępie swobodnym.

Kontenery

Biblioteka STL dostarcza następujące kontenery:

  • vector - tablica, która może dynamicznie się rozszerzać

  • list - lista dwukierunkowa

  • set - kontener o logarytmicznym czasie wstawiania i wyszukiwania

  • map - tak jak set, ale składa się z par klucz-wartość

  • multiset - tak jak set, ale może zawierać zduplikowane elementy

  • multimap - tak jak map, ale może zawierać zduplikowane elementy

  • priority_queue - kolejka priorytetowa, zwraca element największy w czasie logarytmicznym

Algorytmy

Korzystając z abstrakcji iteratora na powyżych kontenerach lub ogólniej na danych wyznaczonych zakresem iteratorów można wykonywać algorytmy, tak zwane uogólnione. Na przykład:

  • sort - wiadomo, sortowanie

  • find - znajduje (lub przynajmniej się stara) wskazany element

  • copy - kopiowanie elementów wyznaczonych zakresem iteratorów

  • unique - wyrzuca zduplikowane elementy

  • random_shuffle - wymieszanie elementów (zmiana kolejności na losową)

Co dalej?

W tym krótkim wprowadzeniu przedstawiłem jedynie motywację i ogólne koncepcję potrzebne do rozpoczęcia nauki biblioteki STL. Uznałem, że nie ma sensu kopiować tutaj opisu metod i tym podobnych rzeczy, które można znaleźć w dokumentacji. Ściągnij więc teraz sobie dokumentację ze strony Silicon Graphics i zobacz jak używa się poszczególnych klas. Powodzenia!


20.5.2024 - 02:05:23