390 likes | 543 Views
C++ wykład 11 (30.05.2013). Własne biblioteki, standardowa biblioteka szablonów STL. Kompilacja i łączenie. Plik jako jednostka kompilacji. Preprocesing – obsługa makr i dyrektyw włączających – dostarcza kompilatorowi jednostkę translacji.
E N D
C++wykład 11 (30.05.2013) Własne biblioteki, standardowa biblioteka szablonów STL
Kompilacja i łączenie • Plik jako jednostka kompilacji. • Preprocesing – obsługa makr i dyrektyw włączających – dostarcza kompilatorowi jednostkę translacji. • Kompilator analizuje jednostkę translacji w izolacji od reszty programu. • Fizyczna struktura programu (podział na pliki) powinna wynikać z logicznej struktury programu. • Rola linkera przy budowaniu programu albo biblioteki.
Moduły • Każdy większy program składa się z pewnej liczby oddzielnych części – modułów. • Moduł to kompletny fragment programu (moduł obliczeniowy, moduł we/wy, moduł prezentacji, itp). • Podział kodu na moduły porządkuje logikę programu. • Należy minimalizować zależności między modułami.
Biblioteki • Moduły, z których może korzystać wiele programów umieszcza się w oddzielnych skompilowanych plikach, zwanych bibliotekami. • Typy bibliotek w C++: • biblioteka statyczna jest dołączana do programu wynikowego na etapie linkowania; • biblioteka współdzielona jest dołączana do programu w trakcie ładowania programu do pamięci; • biblioteka dynamiczna jest dołączana do uruchomionego procesu w trakcie działania programu.
Biblioteki • Biblioteka to zbiór klas, funkcji i zmiennych, z których mogą korzystać różne programy. • Biblioteka ma postać binarną – jej poszczególne fragmenty są skompilowane (biblioteka jest kolekcją plików obiektowych). • Korzystanie z bibliotek ułatwia programowanie (korzystamy z gotowych i sprawdzonych fragmentów kodu) i przyspiesza proces rekompilacji.
Biblioteki • Wynikiem samej kompilacji pliku źródłowego (plik.c albo plik.cpp) jest plik plik.o pod Linuxem albo plik.obj pod Windowsem. • Biblioteki statyczne mają nazwy libmodul.a pod Linuxem albo modul.lib pod Windowsem. • Biblioteki dynamiczne mają nazwy libmodul.so pod Linuxem (tak jak biblioteki współdzielone) albo modul.dll pod Windowsem.
Biblioteka statyczna • Używając biblioteki statycznej przekazujemy jej archiwum linkerowi w czasie kompilacji. Linker wyszukuje w nim tylko tych plików obiektowych, które są niezbędne do działania naszego programu i kopiuje je bezpośrednio do programu. • Program wynikowy korzystający z biblioteki statycznej jest obszerniejszy ale szybciej się ładuje do pamięci. • Program wynikowy zlinkowany z biblioteką statyczną jest niezależny od plików zewnętrznych. • Uaktualnienie biblioteki wymaga rekompilacji programu.
Biblioteka statyczna lib.cpp g++ lib.o prog.cpp ar g++ static library prog.o linker g++ a.out loader ssh memory
Biblioteka współdzielona • Programy korzystające biblioteki współdzielonej nie zawierają bezpośrednio kodu z tej biblioteki a tylko referencję do niej. • Program wynikowy korzystający z biblioteki współdzielonej jest chudszy ale wolniej ładuje się do pamięci (biblioteki współdzielone są odszukiwane i ładowane do pamięci razem z programem). • Program wynikowy skompilowany z biblioteką współdzieloną jest zależny od plików zewnętrznych. • Zmodyfikowanie biblioteki współdzielonej spowoduje zmianę w działaniu programu ale bez jego ponownej kompilacji.
Biblioteka współdzielona prog.cpp g++ lib.cpp prog.o g++ lib.o linker g++ g++ shared library a.out loader ssh memory
Biblioteka dynamiczna • Programy korzystające bibliotek dynamicznych nie zawierają bezpośrednio kodu z tej biblioteki ale muszą korzystać ze specjalnych metod włączania takich bibliotek w trakcie działania programu (plik nagłówkowy <dlfcn.h>). • Program wynikowy korzystający z biblioteki dynamicznej jest chudszy i szybciej ładuje się do pamięci, ale działa wolniej (biblioteki dynamiczne można załadować w dowolnym momencie w trakcie działania programu). • Program wynikowy skompilowany z biblioteką dynamiczną jest zależny od plików zewnętrznych. • Zmodyfikowanie biblioteki dynamicznej spowoduje zmianę w działaniu programu ale bez jego ponownej kompilacji.
Biblioteka dynamiczna prog.cpp g++ prog.o lib.cpp linker g++ g++ lib.o a.out g++ dynamic library loader a.out ssh memory
Tworzenie bibliotek (pod Linuxem) Tworzenie programu bez dołączanych bibliotek. • Załóżmy, że mamy pliki src1.cpp, src2.cpp i src3.cpp, które stanowią moduł obliczeniowy oraz plik prog.cpp, który będzie korzystał z funkcji i klas zdefiniowanych w module obliczeniowym. • Aby skompilować cały program razem z modułem obliczeniowym należy wydać polecenie:$ g++ -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp prog.cpp -o calculation • Aby skompilować cały program razem z modułem obliczeniowym i statycznie zlinkować z innymi bibliotekami (rozmiar programu wynikowego będzie znacznie większy) należy wydać polecenie:$ g++ -static … • Aby uruchomić skompilowany program należy wydać polecenie:$ ./calculation • Aby sprawdzić z jakimi bibliotekami jest linkowany program i jakie symbole są w nim użyte należy wydać polecenie:$ ldd calculation$ nm calculation
Tworzenie bibliotek (pod Linuxem) Program korzystający z biblioteki statycznej. • Najpierw kompilujemy pliki źródłowe do plików obiektowych:$ g++ -c -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp • Następnie łączymy pliki obiektowe do jednego pliku bibliotecznego libsrc.a:$ ar crs libsrc.a src1.o src2.o src3.o • Na koniec należy skompilować plik z programem i zlinkować go z biblioteką:$ g++ -c -Wall -ansi -pedantic prog.cpp$ g++ -o calculation prog.o –L. –lsrc • Teraz można uruchomić skompilowany program:$ ./calculation • Wyjaśnienie: • opcja -Lścieżka określa ścieżkę do biblioteki, • opcja -lbiblioteka określa nazwę biblioteki.
Tworzenie bibliotek (pod Linuxem) Program korzystający z biblioteki współdzielonej. • Najpierw kompilujemy pliki źródłowe z opcją -fpic do plików obiektowych:$ g++ -fpic –c -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp • Następnie łączymy pliki obiektowe do jednego pliku bibliotecznego libsrc.so:$ g++ –fpic -shared -o libsrc.so src1.o src2.o src3.o • Na koniec należy skompilować plik z programem i wlinkować do niego informacje o bibliotece:$ g++ -Wall -ansi -pedantic prog.cpp$ g++ -o calculation prog.o –L. –lsrc • Przed uruchomieniem programu trzeba zapisać w zmiennej środowiskowej LD_LIBRARY_PATH ścieżkę do biblioteki:$ export LD_LIBRARY_PATH="LD_LIBRARY_PATH:ścieżka" • Teraz można uruchomić skompilowany program:$ ./calculation
Tworzenie bibliotek (pod Linuxem) Program korzystający z biblioteki dynamicznej. • Bibliotekę dynamiczną przygotowujemy tak samo jak bibliotekę współdzieloną libsrc.so:$ g++ -fpic –c -Wall -ansi -pedantic src1.cpp src2.cpp src3.cpp $ g++ –fpic -shared -o libsrc.so src1.o src2.o src3.o • Na koniec należy skompilować plik z programem i dołączyć do niego dynamicznego linkera opcją -ldl:$ g++ -Wall -ansi -pedantic prog.cpp$ g++ -o calculation prog.o –ldl • Teraz można uruchomić skompilowany program:$ ./calculation • Wyjaśnienie: • aby skorzystać z dynamicznego linkera należy do programu włączyć plik nagłówkowy <dlfcn.h>; • aby załadować dynamiczną bibliotekę trzeba skorzystać z funkcji dlopen, dlsym, dlerror i dlclose.
Nazwy zewnętrzne • Nazwa jest łączona zewnętrznie jeśli można jej używać w jednostkach translacji innej niż ta, w której ją zdefiniowano. • Nazwę zewnętrzną deklaruje się za pomocą słowa extern. • Funkcja wbudowana musi być zdefiniowana w każdej jednostce translacji za pomocą identycznej definicji; ta sama reuła odnosi się do funkcji i klas szablonowych.
Nowości z C++11 – szablony zewnętrzne i aliasy szablonów • C++ musi stworzyć instancję szablonu zawsze kiedy napotka w pełni określony szablon w jednostce translacyjnej. W starszym C++ nie jest bowiem możliwe wstrzymanie tworzenia instancji szablonu w takiej sytuacji. • C++11 wprowadza ideę szablonów zewnętrznych w celu zablokowania tworzenia instancji w jednostce translacyjnej. Przykład:extern template class std::vector<MojaKlasa>; • W C++11 można używać aliasów dla szablonów. Przykład:template <typename T> using Vec = std::vector<T,MyAlloc<T>>;
STL • STL (ang. Standard Template Library) to standardowa biblioteka wzorców w C++. • Wszystkie składniki STL są wzorcami. • STL jest rdzeniem biblioteki standardowej C++. • Wszystkie identyfikatory w bibliotece standardowej C++ zdefiniowane są w przestrzeni nazw std. • Zaleta STL – mnóstwo gotowych szablonów klas pozwalających programować na wyższym poziomie abstrakcji. • Wada STL – brak oczywistości (koniecznie czytaj dokumentację)
STL • Biblioteka STL składa się z kilku modułów: • strumienie we/wy, • łańcuchy i wyrażenia regularne, • kontenery i iteratory, • algorytmy i obiekty funkcyjne, • wielowątkowość i przetwarzanie współbieżne, • internacjonalizacja, • klasy narzędziowe.
Pary • Szablon struktury pair<> (zdefiniowany w <utility>) umożliwia potraktowanie dwóch wartości jako pojedynczego elementu. • Para posiada dwa pola: first i second. • Para posiada konstruktor dwuargumentowy oraz domyślny i kopiujący. • Pary można porównywać (operatory == i <). • Istnieje szablon funkcji make_pair() do tworzenia pary (typy danych są rozpoznawane przez kompilator po typach argumentów). • Przykłady:void f (std::pair<int, const char *>);void g (std::pair<const int, std::string>);…std::pair<int, const char *> p(44,”witaj”);f(p); // wywołuje domyślny konstruktor kopiującyg(p); // wywołuje konstruktor wzorcowyg(std::make_pair(44,”witaj”)); // przekazuje dwie // wartości jako parę z wykorzystaniem konwersji // typów • Pary są wykorzystywane w kontenerach map i multimap.
Tuple • W C++11 zdefiniowano tuple do przechowywania wielu wartości a nie tylko dwóch (szablon tuple<> jest analogią do szblony pary pair<>). • Tupla posiada wiele ponumerowanych pól, do których dostęp mamy za pomocą metody get<i>. • Tupla posiada konstruktor wieloargumentowy oraz domyślny i kopiujący. • Tuple można porównywać za pomocą operatorów porównań (porównywanie leksykograficzne). • Istnieje szablon funkcji make_tuple() do tworzenia tupli (typy danych są rozpoznawane przez kompilator po typach argumentów). • Istnieje szablon funkcji tie() do tworzenia tupli z referencjami (jako argumenty podaje się zmienne). • Szablon tuple_size<tupletype>::value służy do podania liczby elementów w tupli. • Szablon tuple_element<idx,tupletype>::type służy do podania typu elementu o indeksie idx w tupli.
Sprytne wskaźniki • Sprytne wskaźniki są zdefiniowane w pliku nagłówkowym <memory>. • Zastąpienie szablonu auto_ptr<>. • Szablon klasy shared_pointer<> – wiele takich sprytnych wskaźników może przechowywać wskaźnik do tego samego obiektu, tak że obiekt ten oraz związane z nim zasoby zostaną zwolnione dopiero po likwidacji ostatniego sprytnego wskaźnika. • Szablon klasy unique_pointer<> – tylko jeden sprytny wskaźnik może przechowywać wskaźnik do tego danego obiektu.
Ograniczenia liczbowe • Typy numeryczne posiadają ograniczenia zależne od platformy i są zdefiniowane w szablonie numeric_limits<> (zdefiniowany w <limits>, stałe preprocesora są nadal dostępne w <climits> i <cfloat>). • Wybrane składowe statyczne szablonu numeric_limits<>: is_signed, is_integer, is_exact, is_bounded, is_modulo, has_infinity, has_quiet_NaN, min(), max(), epsilon(). • Przykłady:numeric_limits<char>::is_signed;numeric_limits<short>::is_modulo;numeric_limits<long>::max();numeric_limits<float>::min();numeric_limits<double>::epsilon();
Minimum i maksimum • Obliczanie wartości minimalnej oraz maksymalnej:template <class T>inline const T& min (const T &a, const T &b) { return b<a ? b : a; }template <class T>inline const T& max (const T &a, const T &b) { return a<b ? b : a; } • Istnieją też wersje tych szablonów z komparatorami (funkcja lub obiekt funkcyjny):template <class T, class C>inline const T& min (const T &a, const T &b, C comp) { return comp(b,a) ? b : a; }template <class T>inline const T& max (const T &a, const T &b, C comp) { return comp(a,b) ? b : a; }
Minimum i maksimum • Przykład 1:bool int_ptr_less (int *p, int *q) { return *p<*q; }…int x = 33, y = 44;int *px = &x, *py = &y;int *pmax = std::max(px,py,int_ptr_less); • Przykład 2:int i;long l;…l = max(i,l); // BŁĄD // niezgodne typy argumentówl = std::max<long>(i,l); // OK
Zamiana wartości • Zamiana dwóch wartości:template <class T>inline void swap (T &a, T &b) { T tmp(a); a = b; b = tmp; } • Przykład:int x = 33, y = 44;…std::swap(x,y);
Operatory porównywania • Cztery funkcje szablonowe (zdefiniowane w <utility>) na podstawie operatorów == i < definiują operatory porównań !=, <=, >= i >. • Funkcje te są umieszczone w przestrzeni nazw std::rel_ops. • Przykład:class X { …public: bool operator== (const X &x) const throw(); bool operator< (const X &x) const throw(); …};…void foo () { using namespace std::rel_ops; X x1, x2; … if (x1!=x2) { … } …}
Kontenery • Kontenery służą do przechowywania i zarządzania kolekcjami danych. • Rodzaje kontenerów: • Kontenery sekwencyjne, gdzie każdy element ma określoną pozycję. Na przykład: vector, deque, list. • Kontenery uporządkowane (w tym asocjacyjne), gdzie pozycja elementu zależy od jego wartości. Na przykład: set, multiset, map, multimap.
Elementy kontenerów • Elementy kontenerów muszą spełniać wymagania podstawowe: • element musi być kopiowalny (konstruktor kopiujący), • element musi być przypisywalny (przypisanie kopiujące), • element musi być zniszczalny (publiczny destruktor). • W pewnych sytuacjach elementy kontenerów muszą spełniać wymagania dodatkowe: • konstruktor domyślny (utworzenie niepustego kontenera), • operator porównywania == (wyszukiwanie), • operator porównywania < (kryterium sortowania).
Semantyka wartości a semantyka referencji • Kontenery STL realizują semantykę wartości: tworzą wewnętrzne kopie swoich elementów oraz zwracają kopie tych elementów. • Semantykę referencji można zaimplementować samodzielnie za pomocą inteligentnych wskaźników – wskaźniki te mają umożliwiać zliczanie referencji dla obiektów, do których odnoszą się wskaźniki.
Kontenery sekwencyjne– wektory • Wektor vector<> (zdefiniowany w <vector>) przechowuje swoje elementy w tablicy dynamicznej. • Uzyskujemy szybki dostęp do każdego elementu za pomocą indeksowania. • Dołączanie i usuwanie elementów na końcu wektora jest bardzo szybkie, ale wstawienie lub usunięcie elementu ze środka zabiera więcej czasu. • Przykład:vector<int> coll;…for (int i=1; i<=6; ++i) coll.push_back(i);…for (int i=0; i<coll.size(); ++i) cout << coll[i] << ’ ’;cout << endl;
Kontenery sekwencyjne– kolejki o dwóch końcach • Kolejka o dwóch końcach deque<> (zdefiniowana w <deque>) przechowuje swoje elementy w tablicy dynamicznej, która może rosnąć w dwie strony. • Uzyskujemy szybki dostęp do każdego elementu za pomocą indeksowania. • Dołączanie i usuwanie elementów na końcu i na początku kolejki jest bardzo szybkie, ale wstawienie lub usunięcie elementu ze środka zabiera więcej czasu. • Przykład:deque<float> coll;…for (int i=1; i<=6; ++i) coll.push_front(i*1.234);…for (int i=0; i<coll.size(); ++i) cout << coll[i] << ’ ’;cout << endl;
Kontenery sekwencyjne– listy • Lista list<> (zdefiniowana w <list>) przechowuje swoje elementy w liście dwukierunkowej. • W listach nie ma swobodnego dostępu do elementów kolekcji. • Dołączanie i usuwanie elementów na końcu i na początku listy jest bardzo szybkie, ale dostanie się do elementu ze środka zabiera dużo czasu. • Przykład:list<char> coll;…for (char c=’a’; c<=’z’; ++c) coll.push_back(c);…while (!coll.empty()) { cout << coll.front() << ’ ’; coll.pop_front(); }cout << endl;
Kontenery sekwencyjne– łańcuchy i tablice • Obiektów klas łańcuchowych, czyli basic_string<>, string i wstring, można używać jak kontenerów sekwencyjnych. Są one podobne w zachowaniu do wektorów. • Innym rodzajem kontenera może być tablica. Nie jest to klasa i nie ma żadnych metod ale konstrukcja STL umożliwia uruchamianie na tablicach różnych algorytmów (tak jak na kontenerach).
Kontenery uporządkowane • Kontenery uporządkowane wykonują automatycznie sortowanie swoich elementów. • Asocjacyjne kontenery uporządkowane przechowują pary klucz-wartość (odpowiednio first i second) i sortowanie następuje po kluczach. • Domyślnie elementy lub klucze są porządkowane przy pomocy operatora <. • Kontenery uporządkowane są implementowane w postaci zrównoważonych drzew BST (drzewa czerwono-czarne). • Wszystkie kontenery uporządkowane posiadają domyślny parametr wzorca służący sortowaniu (domyślnym jest operator <). • Rodzaje kontenerów: zbiory set<>, wielozbiory multiset<>, mapy map<> i multimapy multimap<>.
Adaptatory kontenerów • Adaptatory kontenerów to kontenery wykorzystujące ogólną strukturę innych kontenerów do realizacji pewnych specyficznych potrzeb. • Adaptatorami kontenerów są stosy stack<>, kolejki queue<> i kolejki priorytetowe priority_queue<>.
Iteratory • Iterator to specjalny obiekt, który potrafi iterować po elementach kolekcji. • Iterator ma zaimplementowaną semantykę wskaźnika – posiada operator wyłuskania elementu *, operatory przechodzenia do elementówsąsiednich ++ i -- oraz operatory porównywania pozycji == i !=. • Wszystkie kontenery udostępniają funkcje tworzące iteratory do nawigowania po ich elementach – funkcja begin() zwraca iterator wskazujący na pozycję z pierwszym elementem w kolekcji a funkcja end() zwraca iterator wskazujący pozycję za ostatnim elementem. • Każdy kontener definiuje dwa typy iteratorów – kontener::iterator przeznaczony do iterowania po elementach z możliwością odczytu i zapisu oraz kontener::const_iterator przeznaczony do iterowania po elementach tylko z możliwością odczytu.
Iteratory • Przykład 1:list<char> coll;…list<char>::const_iterator pos;for (pos=coll.begin(); pos!=coll.end(); ++pos) cout << *pos << ’ ’;cout << endl; • Przykład 2:list<char> coll;…list<char>::iterator pos;for (pos=coll.begin(); pos!=coll.end(); ++pos) *pos = toupper(*pos);