Wykład 12 - Biblioteka stadardowa.sxw 1 Przegląd biblioteki standardowej C++; W porównaniu z pierwszymi implementacjami i specyfikacjami C++ biblioteka standardowa języka C++ bardzo się rozrosła, a w latach 90-siątych ubiegłego stulecia wprowadzono szereg radykalnych rozszerzeń. Obecnie istnieje bardzo obszerna cześć tej biblioteki, która jest oparta na wzorcach (STL). W porównaniu z poprzednimi wersjami języka, dzięki wprowadzeniu mechanizmu przestrzeni nazw (namespace) zmianom uległo korzystanie z bardzo popularnej biblioteki strumieniowej, która obecnie jest umieszczona w przestrzeni nazw biblioteki standardowej nazywanej std. Odwołanie się wówczas do operacji zapisu lub odczytu wygląda następująco: #include <iostream> std::cout << "Hello, world!\n"; Zamiast używać nazw kwalifikowanych typu: std::string s = "Cztery nogi Dobrze; dwie nogi Źle!"; std::list<std::string> slogany; można używać następującego zapisu: #include<string> // dostęp do standardowego łańcucha C++ using namespace std; // porzucenie prefiksu std::. string s = "Ignorancja to rozkosz!"; // ok: string std::string W przypadku STL w specyfikacji nazwy pliku nagłówkowego brak rozszerzenia tj. <string> <string.h> zamiast Standardowa biblioteka dostarcza silnego typu łańcuchowego (wcześniej C i C++ nie posiadały wyróżnionego takiego typu i posługiwały się tablicą znaków oraz szeregiem funkcji na niej operującym, nie można było natomiast używać operatorów co jest możliwe obecnie: string s1 = "Hello"; string s2 = "world"; void m1() string s3 = s1 + ", " + s2 + "!\n"; cout << s3; void m2(string& s1, string& s2) s1 = s1 + \n ; // Dodaj znak nowej linii s2 += \n ; // analogiczna operacja. Typ łańcuchowy umożliwia również działanie na podciągach znaków: string name = "Niels Stroustrup"; // nazwisko="niels Stroustrup"; void m3() string s = name.substr(6,10) ; // s = "Stroustrup" name.replace(0,5,"nicholas") ; // A także umożliwia oczywiście porównywanie: string zaklęcie; void respond(const string& odpowiedź) if (odpowiedź == zaklęcie) // dokonaj cudów else if (odpowiedź == "tak") nazwisko = "Nicholas Stroustrup"
Wykład 12 - Biblioteka stadardowa.sxw 2 UWAGA: Oczywiście w rzeczywistym kodzie nie można używać polskich znaków diaktrycznych (ą,ć,... itp.) Ciekawą i często używaną cechą, jest uzyskanie ciągu znaków zakończonego \0 tak jak w standardzie C za pomocą metody c_str() (Identyczna metoda istnieje dla standardowo używanego w C++ Builder typu łańcuchowego AnsiString): printf("nazwisko: %s\n", name.cstr()) ; Dalej jest bezpośrednie przepisanie z tekstu wyświetlanego na wykładzie. Stąd potrzeba dopisywania notatek. Strumieniowe Wejście Ze strumieniowym wejściem i wyjściem spotkaliśmy się już wcześniej przy okazji omawiania wejścia i wyjścia w C. Teraz dodamy jeszcze kilka luźnych uwag. Poniżej obowiązują pliki nagłówkowe <string> i <iostream>; Strumień cin ma też ograniczenia przy wczytywaniu napisów: koniec wczytywania gdy napotkamy biały znak, np. spację. string nap; cout << "Halo! Kim jesteś?: \n"; cin >> nap; cout << "Witaj, " <<nap << "!\n"; W powyższej sytuacji gdy ktoś poda imię + nazwisko, mamy kłopot gdyż zostanie odczytany tylko jeden wyraz. Rozwiązaniem może być użycie funkcji getline wczytującej całą linijkę: string nap; cout << " Halo! Kim jesteś?: \n "; getline(cin, nap) ; cout << " Witaj, " <<nap <<"!\n"; Kontenery Kontenery = klasy utrzymujące kolekcje (zbiory) obiektów różnych typów. Przykład użycia: Program do przechowywania nazwisk i imion, oraz numerów telefonów: W klasycznym podejściu użylibyśmy typu strukturalnego w sposób poniższy.: struct Wpis string ImNazw; int numer; ; Wpis książka_tel[1000] ; void printwpis(int i) cout << książka_tel[i].imnazw<< << książka_tel[i].numer << \n ; Problem: co gdy chcemy dopisać 1001 osobę? WEKTORY: Problem z powyższym kodem polega na tym, że mamy ustaloną liczbę przechowywanych elementów i używając tablic musimy się liczyć z narzutem (tzn. dodatkową robotą) związanym z obsługą nadmiaru i niedomiaru. Biblioteka Standardowa (STL) dostarcza klasę vector, która zajmuje się takimi i podobnymi drobnymi problemami, dobierając dynamicznie rozmiar w zależności od potrzeb, a poza tym można jej używać tak jak standardowych tablic jednowymiarowych.
Wykład 12 - Biblioteka stadardowa.sxw 3 Przykład użycia: vector< Wpis > książka_tel(1000) ; void print_wpis (int i) // użycie jest podobne do użycia tablic cout <<książka_tel[i]. ImNazw << << książka_tel[i].numer << \n ; Funkcje składowe klasy vector umożliwiają dynamiczną zmianę rozmiaru. size() zwraca aktualny rozmiar wektora, a resize() zmienia jego rozmiar. void dodaj_wpisy (int n) // zwiększ rozmiar o n książka_tel.resize(książka_tel.size()+ n) ; Zauważmy różnice w zapisie: vector< Wpis > książka(1000); // wektor 1000 elementów vector< Wpis > książki[1000]; // 1000 pustych wektorów wektor jest pojedynczym obiektem, który można użyć w przypisaniu: Spróbujcie przypisać klasyczną tablicę!!!!. void f(vector< Wpis >& v) vector< Wpis > v2 = książka_tel; v = v2; Sprawdzanie zakresu: Klasycznie: int i = książka_tel[1001].numer; // Klęska: 1001 jest poza zakresem STL: Za pomocą klasy pomocniczej: template<class T> class Vec : public vector<t> public: Vec(): vector<t>() Vec(int s): vector<t>(s) T& operator[](int i) return at(i) ; // + sprawdzanie zakresu const T& operator[](int i) const return at(i) ; / / + sprawdzanie zakresu ; at(n) funkcja zwraca daną spod indeksu n, w wypadku przekroczenia zakresu zgłasza wyjątek out_of_range. Co wykorzystuje się następująco:
Wykład 12 - Biblioteka stadardowa.sxw 4 Vec< Wpis > książka_tel(1000) ; void f() try for (int i = 0; i<10000; i++) print_wpis(i) ; catch (outofrange) cout <<"Przekroczono zakres \n"; Ogólny schemat używania wyjątków: int main() try // twój kod catch (outofrange) cerr << "błąd zakresu \n"; catch (...) // dowolny wyjątek cerr << "Nieznany wyjątek \n ; //cerr to standardowy strumień błędu W zasadzie nie ma potrzeby używania indeksowania w implementacji książki telefonicznej. Czyli nie musimy znać numeru, pod którym przechowywana jest informacja o książce. Stąd lepiej tę funkcję spełniła by lista połączona. STL implementuje ją jako klasę list. list< Wpis > książka_tel; Elementy w liście wyszukujemy po ich wartości (nie indeksie). void print_wpis(const string& s) typedef list< Wpis >::constiterator LI; for (LI i =książka_tel.begin(); i!=książka_tel.end(); ++i) Wpis & e= *i; // referencja jako skrót if (s ==e. ImNazw) cout << e. ImNazw << << e.numer << \n ; Każdy kontener z STL dostarcza funkcji begin() i end(), które zwracają tzw. iterator (odpowiednio początku i końca). Iterator jest obiektem, za pomocą którego odwołujemy się do zawartości klasy kontenerowej i przesuwamy się pomiędzy elementami tej klasy. iterator++ przesuń się do następnego elementu. (iterator jest tutaj dowolną nazwą.) *iterator pobierz ten element (zmienna *iterator jest wówczas równa temu elementowi). Dodawanie do listy jest łatwe: void addwpis(wpis& e, list<wpis>::iterator i)
Wykład 12 - Biblioteka stadardowa.sxw 5 książka_tel.push front(e) ; // dodaj e na początku książka_tel.push back(e) ; // dodaj e na końcu książka_tel.insert(i,e) ; // dodaj e przed elementem // wskazywanym przez i Tablice asocjacyjne (słowniki) typ map Typ map służy do przechowywania par połączonych ze sobą elementów. Przy indeksowaniu wartością jednego elementu z pary, zwracana jest wartość drugiego. map<string,int> książka_tel; void printwpis(const string& s) if (int i = książka_tel[s]) cout << s << << i << \n ; Jeżeli nie zostanie znaleziona wartość kluczowa wówczas zwracana zostaje wartość domyślna dla danego typu. W tym przypadku 0. Co jak się wydaje jest dobrze określoną wartością nie będącą numerem telefonu. Biblioteka standardowa zawiera wiele klas kontenerowych, oto niektóre z nich: Kontenery Standardowe przegląd: vector T wektor o dynamicznym rozmiarze list T lista dwukierunkowa queue T kolejki stack T stos deque T podwójna kolejka priorityqueue T kolejka priorytetowa set T zbiór (elementy nie mogą się powtarzać) multiset T wielozbiór (może zawierać powtarzające się elementy) map key,val tablica asocjacyjna multimap key,val tablica asocjacyjna z kluczem wielokrotnym