Wstęp do biblioteki standardowej C++

Biblioteka standardowa C++ to zbiór narzędzi, „wbudowanych” w każdy kompilator C++, które ułatwiają pisanie. Na zbiór biblioteki standardowej C++ składają się:

  • Strumienie
  • Zbiorniki
  • Adaptery zbiorników
  • Iteratory
  • Algorytmy
  • Inne narzędzia
  • Biblioteka standardowa C

Każdy z typów i zmiennych biblioteki standardowej C++ jest zawarty w przestrzeni nazw std. Można się do nich odwoływać pełną nazwą np. (std::cout << 5;), zaimportować jeden z symboli (using std::cout; cout << 5;) albo wszystkie (using namespace std; cout << 5;), przy czym import wszystkich symboli jest niezalecany, bo:

  • przestrzeń nazw std posiada ogrom symboli.
  • upgrade kompilatora lub nowsza wersja C++ jest w stanie popsuć twój kod.
  • kiedy masz więcej niż jedną bibliotekę dołączoną, mogą pojawić się konflikty, które mogą spowodować, że twój kod może się skompilować, ale wykonać zupełnie inną rzecz niż się spodziewasz.

Strumienie

Strumienie służą za dostęp do sekwencyjnego ciągu danych – np. do odczytu danych z kasety, z pliku lub z ciągu znaków które człowiek wklepuje na klawiaturze.

Typ std::istream (Input STREAM) – strumień wejścia
Typ std::ostream (Output STREAM) – strumień wyjścia

Typ std::ifstream (Input File STREAM) – strumień wejścia pliku
Typ std::ofstream (Output File STREAM) – strumień wyjścia pliku
Typ std::stringstream (STRING STREAM) – strumień ciągu znaków
Zmienna std::cout (Console OUTput) robi za wypisywanie tekstu na ekran.
Zmienna std::cin (Console INput) robi za wczytywanie tekstu z ekranu.

Generalnie ze strumieni korzysta się w podobny sposób, a jedyne różnice występują w ustawieniach początkowych (np. strumień plikowy musimy utworzyć i ustalić nazwę tego pliku, a ekran jest „zawsze”):

#include <iostream>
#include <fstream>

int main()
{
    std::ofstream plik_z_liczbami("liczby.txt"); // otwieramy plik w trybie do odczytu
    int liczba;
    int suma = 0;
    while(plik_z_liczbami >> liczba)             // wczytujemy po kolei do końca pliku albo do wystąpienia błędu
    {
        suma += liczba;                          // sumujemy wartości
    }
    std::cout << "\n" << suma;                   // przechodzimy do następnej linii i wypisujemy sumę liczb
}

Tutaj obsługujemy strumień stringowy. Przykład trochę bez sensu, ale pokazuje, jak można tworzyć dłuższe ciągi znaków bez dłubania w funkcjach konwertujących na string.

#include <string>   
#include <sstream>
#include <iostream>

int main()
{
    unsigned int liczba;
    std::stringstream strstr;
    strstr << std::hex;                   //przestawiamy wypisywanie do strumienia na tryb heksadecymalny
    while(std::cin >> liczba)             //koniec pliku w przypadku ekranu oznacza ręcznie 
                                          //wpisany znak końca pliku (Kod ASCII 26) przez użytkownika
                                          //na windowsie robi się to poprzez wciśnięcie Ctrl+Z, na uniksach Ctrl+D
    {
        strstr << liczba;                 //łączymy ciąg liczb w jeden długi ciąg znaków
    }
    std::string ciag;
    ciag = strstr.str();            // str() zwraca string utworzony przez strumień
}

Ktoś może zarzucić, że taka konwersja liczby jest rozwlekła, i zbyt złożona. Pozwolę sobie się nie zgodzić z tym zdaniem. To co w Lazarusie robiło się poprzez

ciag:=imie + ' ' + nazwisko + ' ' + IntToStr(nr_z_dziennika) + ' srednia ocen: ' + FloatToStr(srednia);

To tu się robi

ss << imie << " " << nazwisko << " " << nr_z_dziennika << " srednia ocen: " << srednia;
ciag = ss.str();

Zbiorniki

Zbiorniki również przechowują dane – ale w odróżnieniu od strumieni – można wejść dwa razy w ten sam zbiornik (i oczekiwać podobnych rezultatów).
Poniżej krótki program który ma na celu przedstawienie sposobu korzystania z zbiorników.

#include <vector>
#include <iostream>

int main()
{
    std::vector<double> zaw(2, -5.0);  // tablica dynamiczna przechowująca elementy typu double
                                       // na początek ma 2 elementy, każdy równy -5.0
                                       // później możemy dodać kolejne elementy.
    zaw[0] = 3.1;         // odwołujemy się do elementu
    zaw.at(1) = 3.14;     // można alternatywnie tak - ten sposób przynajmniej nas ostrzeże,
                          // kiedy odwołujemy się do nieistniejącego elementu
    zaw.push_back(3.141); // wstawiamy element na koniec
    zaw.resize(5);        // zmieniamy rozmiar dwukolejki
    zaw.pop_back();       // usuwamy ostatni element
    zaw.push_front(3.0);  // wstawiamy element na początek
    zaw.back() = 3.1415;  // odwołujemy się do ostatniego elementu

    // wypiszemy zawartość
    for(int i = 0; i < zaw.size(); ++i)
    {
        std::cout << zaw[i] << " ";
    }
    std::cout << "\n";

    // alternatywnie można i tak - dłuższe i mniej czytelne
    // ale ma jedną zaletę - działa na każdym zbiorniku biblioteki standardowej
    for(std::vector<double>::iterator it = zaw.begin(); it != zaw.end(); ++it)
    {
        std::cout << *it << " ";
    }
    std::cout << "\n";

    // wypiszemy zawartość zbiornika od końca
    for(std::vector<double>::reverse_iterator it = zaw.rbegin(); it != zaw.rend(); ++it)
    {
        std::cout << *it << " ";
    }
}

Przykład wypisze:

3.0 3.1 3.14 3.141 3.1415
3.0 3.1 3.14 3.141 3.1415
3.1415 3.141 3.14 3.1 3.0 

std::vector tablica dynamiczna – jest to ciąg elementów ułożonych w pamięci jeden za drugim (tak jak zwykła tablica). Można wstawiać wartości z tyłu w czasie amortyzowanym stałym (czyli – zazwyczaj stały z wyjątkiem przypadków, w którym trzeba rozszerzyć tablicę, to wtedy liniowy), oraz wstawiać w środek w czasie liniowym.

vector

std::deque (doubly-ended queue, dwukolejka) to dość uniwersalny zbiornik. Umożliwia dodawanie elementów na obu końcach w czasie stałym, ale także dostęp do wartości od za pomocą indeksów – z użyciem notacji dwukolejka[indeks] lub dwukolejka.at(indeks).

deque

std::list lista łączona dwukierunkowa, umożliwia wstawienie elementu do środka w czasie stałym.

list

std::string – ciąg znaków typu char, z funkcjami specyficznymi dla ciągów znakowych.
std::set zbiór wartości – każdy element może wystąpić raz, odwołujemy się do elementu poprzez jego wartość.
std::multiset to samo co std::set, ale umożliwia występowanie duplikatów
std::map przyporządkowuje wartość jednego typu do wartości drugiego typu
std::multimap to samo co std::map, ale umożliwia występowanie duplikatów
std::valarray zbiornik specjalistyczny – służy do przechowywania wartości liczbowych. Potrafi wykonywać działania matematyczne na całym zakresie zbiornika, np. sin(zb) gdzie zb jest typu std::valarray<double> zwraca valarray z wartościami sinusów kolejnych argumentów w zb. Nie używałem, ale istnieje.
Tylko C++11:
std::array zwykła tablica taka jak ta wbudowana, ale z interfejsem typowym dla zbiorników – ma iteratory i można sprawdzać jej wielkość poprzez size() std::forward_list lista łączona jednokierunkowa, bardziej oszczędnie korzystająca z pamięci. Nie posiada metody size()

forward_list

std::unordered_set,
std::unordered_multiset,
std::unordered_map,
std::unordered_multimap – odpowiedniki typów bez unordered_ na początku, z tą różnicą, że wewnętrznie nie są zaimplementowane jako drzewa, a jako tablice haszujące. Co oznacza lepszą wydajność w praktycznych zastosowaniach.

Adaptery zbiorników

Adapter zbiornika ma podobny cel co przejściówka do kabli – potrzebujemy jakiejś funkcjonalności od zbiornika, ale interfejs nam nie pasuje, to ustawiamy adapter i już.

W bibliotece standardowej C++ występują trzy – std::stack, std::queue, std::priority_queue. Wszystkie trzy ograniczają dostęp do zbiornika, by użytkownik nie zepsuł czegoś w środku – std::stack na przykład ogranicza użytkownika przed wyjęciem elementu ze środka stosu. Z tego powodu potrzeba ich wykorzystania jest jedynie sporadyczna.

std::stack – stos
std::queue – kolejka
std::priority_queue – kolejka priorytetowa, kolejka z elementami umieszczonymi w kolejności określonej przez pewne kryterium. Często używana gdy chcemy dostęp do największego/najmniejszego elementu w czasie O(1)

Iteratory

Z iteratorów już tu korzystałem w tym tekście – kiedy trzeba było w pętli przejść po całym zbiorniku. Celem iteratorów jest udostępnienie możliwości przechodzenia po zbiorniku niezależnie od jego rodzaju. Iterator to uogólnienie pojęcia wskaźnika. Dla przypomnienia z wskaźnikiem można:

  • Uzyskać wartość wskazaną (wyłuskać wskaźnik): i = *wsk
  • (jeżeli jest odpowiedniego typu tzn. nie const typ*) modyfikować wartość wskazaną (po wyłuskaniu): *wsk = i
  • Przejść do następnego elementu (np. w tablicy): wsk++
  • Przejść do poprzedniego elementu: wsk--
  • Przejść ileśtam elementów w przód: wsk += 20
  • Przejść ileśtam elementów w tył: wsk -= 20
  • Porównywać dwa wskaźniki (czy wskazują na tą samą zmienną): wsk == wsk2, wsk != wsk2

I tak:
std::vector<T>::iterator spełnia wszystkie wymienione wymagania, więc jest iteratorem swobodnego dostępu (random access iterator)
std::list<T>::iterator nie może przechodzić ileśtam elementów w przód lub w tył, więc jest iteratorem dwukierunkowym (bidirectional iterator)
std::forward_list<T>::iterator do tego nie może się cofać, więc jest iteratorem jednokierunkowym (forward iterator)
std::ostream_iterator (iterator po strumieniu wyjścia) jest iteratorem wyjścia.
std::istream_iterator (iterator po strumieniu wejścia) jest iteratorem wejścia.

Algorytmy

Algorytmy w sensie biblioteki standardowej C++ to są poszczególne procedury które operują na zbiornikach. Tutaj wymienie kilka, które według mnie są pożyteczne.

std::sort(poczatek, koniec, funkcja_porownujaca) – sortuje elementy pomiędzy (przedział <poczatek, koniec)) jednym iteratorem a drugim. Podanie funkcji porównującej jest opcjonalne.
std::stable_sort(poczatek, koniec, funkcja_porownujaca) – sortowanie z zachowaniem kolejności elementów które są równe.
std::shuffle(poczatek, koniec, funkcja_losujaca) – rozrzuca elementy losowo po tablicy. std::next_permutation(poczatek, koniec, funkcja_porownujaca) – generuje kolejną permutację zbioru wartości. Podanie funkcji porównującej jest opcjonalne.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax