Rozdział 27.
Programowanie w C++
Rick McMullin
W tym rozdziale:
Język C++
Klasy i metody
Opcje kompilatora GCC
Opcje współpracy z debugerem i programem profilującym
Wyszukiwanie błędów w aplikacjach C++
Wyszukiwanie błędów w funkcjach wirtualnych
Wyszukiwanie błędów w funkcjach obsługi wyjątków
Polecenia gdb specyficzne dla C++
Biblioteki klas GNU C++
Język C++
C++ to wersja języka C rozszerzona o obsługę obiektów. Język ten opracowany został w Bell Labs na początku lat osiemdziesiątych i szybko zyskał sobie uznanie i popularność wśród programistów. Na rynku istnieje obecnie kilkanaście różnych kompilatorów tego języka. Najpopularniejsze z kompilatorów przeznaczonych dla platformy PC to Borland C++, Microsoft Visual C++, Zortech C++ i Watcom C++. Przeznaczone są one do tworzenia programów działających w systemach DOS i Windows, choć niektóre z nich potrafią również tworzyć programy pracujące pod kontrolą Windows NT i OS/2. Oczywiście istnieje również wiele kompilatorów języka C++ przeznaczonych dla innych platform sprzętowych.
W przypadku większości systemów UNIX-owych kompilatory języka C++ rozprowadzane są przez dystrybutorów systemu. Podobnie jest w przypadku Linuxa. Kompilator C++, który kiedyś nazywał się g++, jest bardzo blisko spokrewniony z GCC. W wersji 2.0 oba te kompilatory zostały scalone w jeden program.
Obecnie GCC to połączenie kompilatorów języków C, C++ oraz C z obiektami. W systemie nadal znajduje się plik o nazwie g++, ale jest on tylko skryptem wywołującym kompilator GCC z odpowiednimi opcjami.
Dlaczego C++?
C++ i programowanie obiektowe (ang. object-oriented programming, OOP) nie powstały od razu. Wiele lat temu, gdy na świecie panowały komputery ośmiobitowe, programiści zamiast języka maszynowego zaczęli używać asemblera, wykorzystując nieco większe możliwości obliczeniowe następców komputerów czterobitowych. Umożliwiło to przerzucenie części pracy związanej z tworzeniem programu na komputer.
Z upływem czasu moc obliczeniowa komputerów rosła i możliwe stało się pisanie coraz bardziej skomplikowanych programów. Było to jednak coraz trudniejsze. Powstawać zaczęły kompilatory języków wyższego poziomu, które brały ogrom „czarnej roboty” na siebie. Pierwsze języki były językami strukturalnymi - na przykład FORTRAN, COBOL, Pascal czy C. Strukturalizacja programów pozwalała na ich uproszczenie, zwiększenie przejrzystości, a przede wszystkim zmniejszenie liczby błędów dzięki podzieleniu zadania na mniejsze problemy (funkcje i procedury), łatwiejsze do rozwiązania.
Programowanie strukturalne dobrze spełniało swoją funkcję, ale znów tylko do pewnego czasu. W miarę wzrostu rozmiarów aplikacji zaczęło zawodzić. Programowanie zorientowane obiektowo powstało jako odpowiedź na problemy stwarzane przez programowanie strukturalne.
Główne nowe pojęcia wprowadzane przez OOP to:
hermetyzacja danych,
dziedziczenie,
polimorfizm.
Hermetyzacja danych
Przy programowaniu strukturalnym problemy pojawiały się najczęściej wtedy, gdy różne funkcje czy moduły programu korzystały ze wspólnych danych. Dowolny fragment programu mógł odwoływać się do danych bez poinformowania o tym innego fragmentu.
Hermetyzacja danych ma na celu zabezpieczenie przed tego typu problemami. Polega ona na zgrupowaniu wspólnych danych, zapisaniu ich w odpowiednim typie i zapewnieniu spójnego interfejsu dostępu do nich. Dzięki temu nikt nie może skorzystać z danych, pomijając ten interfejs.
Najważniejszą zaletą takiego rozwiązania jest zabezpieczenie danych przed ich nieautoryzowaną bezpośrednią modyfikacją. Często bowiem zmiana danych musi wiązać się z jakimiś ściśle określonymi czynnościami. Poza tym zmiana struktury danych nie jest widziana na zewnątrz samej struktury, o ile tylko interfejs dostępu do danych pozostanie niezmieniony. Znacząco upraszcza to tworzenie i poprawianie bardziej złożonych programów.
W języku C++ hermetyzację danych zapewnia mechanizm klas.
Dziedziczenie
Dziedziczenie pozwala na ponowne użycie części kodu. Zwykle stosowane jest w przypadku, gdy jakaś część programu posiada zasadniczo wszystkie cechy posiadane przez inną część, plus kilka innych, na przykład gdy jeden obiekt jest szczególnym przypadkiem drugiego.
Dziedziczenie jest w języku C++ zaimplementowane jako dziedziczenie klas.
Polimorfizm
Polimorfizm umożliwia zdefiniowanie funkcji wykonujących różne działania w zależności od typu danych, na których operują. Moc tego mechanizmu ujawnia się, gdy informacje przesyłane są poprzez klasę bazową do klas pochodnych, i dla każdej z nich mogą one oznaczać coś innego.
Polimorfizm w języku C++ zaimplementowany jest poprzez funkcje wirtualne.
Klasy i metody
Klasy w języku C++ można w zasadzie wyobrażać sobie podobnie jak struktury w języku C, z tym, że oprócz danych mogą one zawierać również funkcje, które na tych danych mogą być wykonywane. Rozpatrzmy na przykład typ danych reprezentujący bryłę geometryczną. Bryły mogą mieć najróżniejsze kształty, ale wszystkie mają kilka wspólnych atrybutów, na przykład pole powierzchni czy objętość. Można zatem zdefiniować w języku C strukturę o nazwie bryla:
struct bryla {
float pole;
float obj;
}
Jeśli do tej definicji dołączymy jeszcze kilka funkcji, otrzymamy odpowiednik klasy:
struct bryla {
float pole;
float obj;
float licz_pole();
float licz_obj();
};
W ten sposób zadeklarowaliśmy klasę w języku C++. Funkcje wchodzące w skład klasy nazywa się metodami. Zmienną typu bryla nazywa się obiektem. Można ją zadeklarować w następujący sposób:
bryla kula1;
Obiekt jest fizyczną realizacją klasy, podobnie jak zmienna była realizacją typu.
Opcje kompilatora GCC
W tym podrozdziale opiszemy najczęściej używane opcje kompilatora GCC. Najpierw przyjrzymy się opcjom wspólnym dla języków C i C++, a następnie przejdziemy do omówienia opcji specyficznych tylko dla języka C++. Każda opcja dostępna dla języka C może być użyta dla języka C++ (choć nie zawsze ma to sens - w takim przypadku zostanie ona zignorowana).
|
Do kompilowania programów napisanych w języku C++ najłatwiej użyć skryptu g++, ponieważ ustawia on automatycznie wszystkie wymagane opcje. |
Do kompilatora GCC przekazać można bardzo dużo różnego rodzaju opcji. Większość z nich uzależniona jest od konkretnej platformy sprzętowej albo służy do dokładnego „dostrajania” wygenerowanego kodu i prawdopodobnie nigdy nie będziesz musiał ich użyć. Poniżej omówimy opcje, które przydadzą Ci się w codziennej pracy.
Wiele opcji programu GCC wymaga podania więcej niż jednej litery. Z tego powodu nie jest możliwe grupowanie ich po wspólnym myślniku, jak ma to miejsce w przypadku większości programów systemu Linux. Przed każdą opcją znaleźć się musi osobny myślnik.
Skompilowanie programu bez podania żadnych opcji powoduje utworzenie pliku wykonywalnego o nazwie a.out. Jeśli chcesz, aby program wynikowy miał inną nazwę, powinieneś użyć opcji -o, na przykład jeśli chcesz, aby program wygenerowany na podstawie kodu źródłowego zapisanego w pliku o nazwie licznik.C (rozszerzenie .C oznacza, że plik zawiera program w języku C++, w przeciwieństwie do rozszerzenia .c, oznaczającego kod w języku C) miał nazwę licznik, powinieneś wydać polecenie:
gcc -olicznik licznik.C
|
Po opcji -o w wierszu poleceń nie powinno być spacji! Nazwa pliku wykonywalnego musi pojawić się bezpośrednio po opcji -o. |
Inne opcje pozwalają decydować, na jakim etapie proces kompilacji ma zostać zakończony. Opcja -c powoduje pominięcie konsolidacji (konwersji plików pośrednich na plik wykonywalny), tworząc tylko skompilowane pliki pośrednie (z rozszerzeniem .o). Jest ona szczególnie przydatna przy tworzeniu większych programów, ponieważ pozwala uniknąć wielokrotnego kompilowania plików, które nie zostały zmodyfikowane.
Opcja -S powoduje zatrzymanie kompilacji po wygenerowaniu plików asemblera (z rozszerzeniem .s). Opcja -E powoduje tylko wstępne przetworzenie plików źródłowych i wykonanie dyrektyw preprocesora, wyniki przesyłając do standardowego urządzenia wyjściowego.
Opcje współpracy z debugerem i programem profilującym
Program GCC posiada również kilka opcji umożliwiających współpracę z debugerem i programem profilującym. Najczęściej używane z nich to -gstabs+ oraz -pg.
Opcja -gstabs+ powoduje dołączenie do kodu programu wykonywalnego dodatkowych informacji dla debugera gdb, co znacznie ułatwia wyszukiwanie usterek w programach. Proces wyszukiwania błędów w programach napisanych w języku C++ omówiony jest dokładniej w podrozdziale „Wyszukiwanie błędów w programach C++” w dalszej części tego rozdziału.
Opcja -pg powoduje dołączenie kodu generującego informacje o ilości czasu poświęconego na wywołanie każdej z funkcji programu, które mogą być następnie przetworzone przez program gprof. Jeśli chcesz dowiedzieć się czegoś więcej o tym programie, zajrzyj do podrozdziału „gprof” w rozdziale 26. „Programowanie w języku C”.
Opcje specyficzne dla języka C++
Opcje pozwalające na modyfikację sposobu kompilacji programów w języku C++ zebrane zostały w tabeli 27.1.
Tabela 27.1. Opcje kompilacji specyficzne dla C++
Opcja |
Znaczenie |
-fall-virtual |
Traktowanie wszystkich możliwych funkcji składowych jako wirtualnych (wszystkich prócz konstruktorów oraz przeciążonych operatorów new i delete) |
-fdollars-in-identifiers |
Zezwolenie na używanie znaku $ w identyfikatorach; opcja zabraniająca jego użycia: -fnodollars-in-identifiers |
-felide-constructors |
Pomijanie konstruktorów tam, gdzie to możliwe |
-fenum-int-equiv |
Zezwolenie na niejawną konwersję z typu wyliczeniowego do int |
-fexternal-templates |
Generowanie mniejszego kodu dla szablonów - kompilator generuje tylko jedną kopię żądanej funkcji tam, gdzie jest ona zdefiniowana |
cd. na następnej stronie
Tabela 27.1. cd. Opcje kompilacji specyficzne dla C++
Opcja |
Znaczenie |
-fmemorize-lookups |
Użycie technik heurystycznych dla przyspieszenia kompilacji; opcja ta jest domyślnie wyłączona, ale różnice są widoczne tylko przy określonym typie danych wejściowych |
-fno-script-prototype |
Traktowanie deklaracji funkcji bez argumentów w ten sam sposób, jak w języku C (czyli akceptowanie dowolnej liczby argumentów) |
-fno-null-objects |
Założenie, że obiekty, do których odnoszą się referencje, są zainicjalizowane |
-fsave-memorized |
To samo co -fmemorize-lookups |
-fthis-is-variable |
Zezwolenie na przypisanie do wskaźnika this |
-nostdinc++ |
Pominięcie wyszukiwania plików nagłówkowych |
-traditional |
To samo co -fthis-is-variable |
-fno-default-inline |
Funkcje zdefiniowane w ciele klasy nie są traktowane jako inline |
-wenum-clash |
Generowanie ostrzeżeń przy konwersji pomiędzy typami wyliczeniowymi |
-woverloaded-virtual |
Generowanie ostrzeżeń, gdy klasa pochodna może być zdefiniowana błędnie przez zadeklarowanie funkcji |
-wtemplate-debugging |
Generowanie ostrzeżeń w przypadku, gdy niemożliwe będzie użycie debugera z powodu stosowania szablonów |
+eN |
Kontrola sposobu, w jaki używane będą funkcje wirtualne |
-gstabs+ |
Dodanie do kodu programu dodatkowych informacji, |
Wyszukiwanie błędów w aplikacjach C++
Możliwość wyszukiwania błędów powstających podczas pisania programu to bardzo ważna część procesu tworzenia aplikacji. Debuger opisany w rozdziale 26. może również zostać użyty dla programów napisanych w języku C++. W tym podrozdziale opisane są najważniejsze różnice pomiędzy wyszukiwaniem błędów w programach napisanych w językach C i C++.
Podstawowe polecenia debugera gdb, przedstawione już raz w rozdziale 26., dla wygody podane są również w tabeli 27.2.
Tabela 27.2. Podstawowe polecenia gdb
Polecenie |
Opis |
file |
Ładuje plik wykonywalny, w którym można będzie szukać błędów. |
kill |
Kończy działanie aktualnie wykonywanego programu. |
list |
Wyświetla listę sekcji kodu źródłowego użytego do utworzenia |
next |
Przechodzi do kolejnego wiersza kodu w bieżącej funkcji, bez wchodzenia do funkcji podrzędnych. |
step |
Przechodzi do kolejnego wiersza kodu w bieżącej funkcji, wchodząc również do funkcji podrzędnych. |
run |
Powoduje wykonanie załadowanego programu. |
quit |
Kończy pracę debugera gdb. |
watch |
Pozwala na sprawdzenie wartości zmiennej po każdej jej zmianie. |
break |
Ustawia pułapkę w kodzie źródłowym. Pułapka powoduje wstrzymanie wykonywania programu po dojściu do zadanego punktu. |
make |
Pozwala na przekompilowanie programu bez konieczności wychodzenia z gdb. |
shell |
Umożliwia wykonanie polecenia powłoki. |
Z punktu widzenia programisty, proces wyszukiwania usterek w programach napisanych w języku C++ jest bardziej złożony, niż w przypadku programów w języku C. Główną przyczyną takiego stanu rzeczy jest obsługa funkcji wirtualnych oraz wyjątków. Debuger gdb potrafi poradzić sobie z tymi mechanizmami.
Wyszukiwanie błędów
w funkcjach wirtualnych
Język C+ implementuje polimorfizm za pomocą funkcji wirtualnych. Dzięki nim istnieć może więcej niż jedna funkcja o określonej nazwie. Jedynym sposobem na ich rozróżnienie jest określenie typów argumentów, na których funkcje te operują. Określenie typów stanowi sygnaturę funkcji. Przykładowo, funkcja o prototypie
void func(int x, real f)
posiada sygnaturę int, real. Mechanizm funkcji wirtualnych stwarza pewien problem: jeśli na przykład zdefiniowano kilka funkcji o nazwie licz, jak ustawić pułapkę wywoływaną przy odwołaniu do jednej z tych funkcji? Przy wyszukiwaniu błędów w programach napisanych w języku C można było to zrobić przez podanie po poleceniu break nazwy funkcji, np.:
(gdb) break licz
Ta metoda nie działa w przypadku funkcji wirtualnych, ponieważ nie wiadomo, o którą funkcję licz chodzi. Debuger gdb potrafi poradzić sobie z takim problemem na kilka sposobów. Pierwszym z nich jest podanie całego nagłówka funkcji, na przykład tak:
(gdb) break 'licz (float)'
Teraz gdb ma już dość informacji, by wiedzieć, którą funkcję miałeś na myśli. Drugie rozwiązanie to wybranie interesującej nas funkcji z menu wyświetlanego przez gdb, jeśli ma on wątpliwości co do tego, o którą funkcję chodzi. Wybranie pierwszej pozycji menu powoduje anulowanie polecenia. Druga pozycja umożliwia ustawienie pułapki we wszystkich funkcjach o danej nazwie. Pozostałe pozycje odnoszą się kolejno do wszystkich funkcji o zadanej nazwie. Spójrzmy na przykład:
(gdb) break bryla::licz
[0] cancel
[1] all
[2] file: bryla.C line number: 153
[3] file: bryla.C line number: 207
[4] file: bryla.C line number: 247
>2 3
Breakpoint 1 at 0xb234: file: bryla.C line 153
Breakpoint 2 at 0xa435: file: bryla.C line 207
Multiple breakpoints were set
Use the "delete" command to delete unwanted breakpoints
(gdb)
Wyszukiwanie błędów
w funkcjach obsługi wyjątków
Wyjątki (ang. exceptions) to błędy występujące podczas działania programu. W C++ można tworzyć własne funkcje służące do ich obsługi. Przykładowo, jeśli pisałeś program w C i używałeś funkcji malloc do przydzielenia bloku pamięci, za każdym razem musiałeś sprawdzić zwracaną przez nią wartość, aby upewnić się, że wszystko przebiegło pomyślnie. Gdyby język C posiadał mechanizm obsługi wyjątków, wystarczyłoby napisać jedną funkcję obsługi wyjątku zgłaszanego przez funkcję malloc.
W gdb dodano dwa polecenia pozwalające na wyszukiwania błędów w funkcjach obsługi wyjątków: polecenie catch, które ustawia pułapkę w aktywnej funkcji obsługi wyjątku, oraz catch info, które wyświetla informacje o wszystkich funkcjach obsługi wyjątków. Składnia polecenia catch jest następująca:
catch wyjatki
gdzie wyjatki to lista wyjątków, które należy przechwycić.
Polecenia gdb specyficzne dla C++
Oprócz poleceń catch i catch info, do obsługi programów napisanych w języku C++ dodano również nowe opcje poleceń set i show. Zostały one zebrane w tabeli 27.3.
Tabela 27.3. Opcje poleceń set i show specyficzne dla języka C++
Polecenie |
Opis |
set print demangle |
Wyświetlanie nazw funkcji tak, jak pojawiają się one w kodzie źródłowym, a nie tak, jak przekazywane są do asemblera |
show print demangle |
Wyświetlanie informacji o tym, czy opcja print demangle |
set demangle-style |
Definicja trybu wyświetlania nazw; dostępne wartości: auto, gnu, lucid i arm |
show demangle-style |
Wyświetlanie informacji o aktualnym trybie wyświetlania nazw |
set print object |
Wyświetlanie aktualnego typu obiektu podczas wyświetlania wskaźnika do niego |
show print object |
Wyświetlanie informacji o tym, czy opcja print object jest aktywna |
set print vtbl |
Wyświetlanie tablicy funkcji wirtualnych |
show print vtbl |
Wyświetlanie informacji o tym, czy opcja print vtbl jest aktywna |
Biblioteki klas GNU C++
Pakiet GNU C++ zawiera również całkiem pokaźną bibliotekę klas. Jest to zestaw dość uniwersalnych klas, które mogą zostać użyte w różnych programach. Przykładami mogą być klasy do obsługi baz danych, graficznego interfejsu użytkownika czy implementujące abstrakcyjne struktury danych.
Na rynku istnieją również inne biblioteki klas obsługujących graficzny interfejs użytkownika, np. Microsoft Foundation Classes czy Borland's Object Windows Library, ale przeznaczone są one tylko dla platformy Windows.
W tym podrozdziale opiszemy niektóre możliwości oferowane przez biblioteki klas GNU C++.
Strumienie
Biblioteka wejścia / wyjścia libio jest implementacją strumieni obsługujących urządzenia standardowego wejścia i wyjścia dla programów opartych na interfejsie tekstowym. Jest ona funkcjonalnie niemal identyczna z bibliotekami dołączanymi do innych kompilatorów. Jej główną częścią jest obsługa strumieni wejścia, wyjścia oraz błędów. Odpowiadają one strumieniom znanym z języka C i nazywają się odpowiednio cin, cout i cerr. Dane można do nich wysyłać za pomocą operatora <<, a odczytywać operatorem >>.
Poniższy program pokazuje, jak można wykorzystać bibliotekę wejścia / wyjścia.
#include <iostream.h>
int main ()
{
char name[10];
cout << "Podaj imie.\n";
cin >> name;
cout << "Witaj " << name << ", jak leci?\n";
}
Łańcuchy znaków
Klasa String zastępuje znane z języka C tablice znaków zakończone zerem. Daje programiście nowe możliwości, na przykład pozwala na używanie operatorów i funkcji. W tabeli 27.4 zebrano operatory zdefiniowane dla obiektów klasy String.
Tabela 27.4. Operatory zdefiniowane dla obiektów klasy String
Operator |
Znaczenie |
str1==str2 |
Zwraca logiczną prawdę, jeśli tekst reprezentowany przez obiekt str1 jest taki sam, jak reprezentowany przez str2 |
str1!=str2 |
Zwraca logiczną prawdę, jeśli tekst str1 jest różny od str2 |
str1<str2 |
Zwraca logiczną prawdę, jeśli tekst str1 jest mniejszy (alfabetycznie) od str2 |
str1<=str2 |
Zwraca logiczną prawdę, jeśli tekst str1 jest mniejszy lub równy str2 |
str1>str2 |
Zwraca logiczną prawdę, jeśli tekst str1 jest większy od str2 |
str1>=str2 |
Zwraca logiczną prawdę jeśli tekst str1 jest większy lub równy str2 |
compare(str1,str2) |
Porównuje teksty str1 i str2 bez zwracania uwagi na wielkość liter |
str3=str1+str2 |
Zapisuje rezultat połączenia tekstów str1 i str2 do str3 |
Dostępne są również inne funkcje, pozwalające na różnego typu porównania, sklejenia czy wyszukiwanie i wycinanie fragmentów tekstów.
Liczby przypadkowe
W skład biblioteki klas GNU C++ wchodzą również klasy pozwalające na generowanie różnego typu liczb przypadkowych. Są to klasy RNG oraz Random.
Analiza danych statystycznych
Biblioteka klas zawiera też dwie klasy służące do zbierania i przetwarzania danych statystycznych. Są to klasy SampleStatistic oraz SampleHistogram. Klasa SampleStatistic umożliwia zbieranie danych oraz obliczanie podstawowych parametrów takiego zbioru, np. wartości średniej, maksimum i minimum, wariancji, odchylenia standardowego itd.
Klasa SampleHistogram jest pochodną klasy SampleStatistic i służy do wyświetlania histogramów.
Listy
Biblioteka klas GNU C++ pozwala na użycie dwóch rodzajów list: jednokierunkowych (klasa SLList) i dwukierunkowych (DLList). Dla obu zaimplementowane są wszystkie podstawowe metody. Najważniejsze z nich zebrane są w tabeli 27.5.
Tabela 27.5. Metody klas obsługujących listy
Metoda |
Opis |
lista.empty() |
Zwraca logiczną prawdę, jeśli lista nie zawiera żadnych elementów |
lista.length() |
Zwraca liczbę elementów listy |
lista.prepend(a) |
Umieszcza element a na początku listy |
lista.append(a) |
Umieszcza element a na końcu listy |
lista.join(lista2) |
Dołącza listę list2 do listy lista, usuwając listę lista2 |
a=lista.front() |
Zwraca wskaźnik do pierwszego elementu listy |
a=lista.rear() |
Zwraca wskaźnik do ostatniego elementu listy |
a=lista.remove_front() |
Usuwa z listy pierwszy element, zwracając wskaźnik do niego |
lista.del_front() |
Usuwa i niszczy pierwszy element listy |
lista.clear() |
Usuwa całą zawartość listy |
lista.ins_after(i,a) |
Wstawia element a do listy po pozycji i |
lista.del_after(i) |
Usuwa element następny po pozycji i |
Dodatkowe metody obsługiwane przez listę dwukierunkową zebrane są w tabeli 27.6.
Tabela 27.6. Dodatkowe metody klasy DLList
Metoda |
Opis |
a=list.remove_rear() |
Usuwa z listy ostatni element, zwracając wskaźnik do niego |
list.del_rear() |
Usuwa i niszczy ostatni element listy |
list.ins_before(i,a) |
Wstawia element a przed i |
list.del(i,dir) |
Usuwa element na bieżącej pozycji, a następnie przesuwa się do przodu o jedną pozycję, jeśli dir jest wartością dodatnią, w przeciwnym przypadku - o jedną pozycję do tyłu |
Klasy Plex
Obiekty klasy Plex zachowują się podobne jak tablice, ale mają znacznie większe możliwości. Oto niektóre z ich własności:
posiadają ustaloną górną i dolną granicę indeksowania;
mogą dynamicznie rozrastać się w lewo i prawo;
ich elementy mogą być indeksowane; inaczej niż w przypadku tablic, indeksy są sprawdzane podczas wykonania programu;
dodawane do tablicy mogą być tylko elementy wcześniej zainicjalizowane, podobnie tylko elementy zainicjalizowane można z niej odczytać.
Zdefiniowane zostały cztery typy struktur Flex: FPlex, która może rozrastać się i kurczyć w zdefiniowanych wcześniej granicach, XPlex, mogąca zmieniać rozmiary bez żadnych ograniczeń, RPlex, podobna do XPlex, ale posiadająca nowe możliwości indeksowania oraz MPlex, która jest pochodną RPlex, pozwalającą na logiczne usuwanie i odzyskiwanie elementów.
Tabela 27.7 zawiera niektóre metody i operatory tych klas.
Tabela 27.7. Metody i operatory klas Plex
Metoda |
Opis |
Plex b(a) |
Przypisuje kopię struktury a do b |
b=a |
Kopiuje Plex a do b |
a.length() |
Zwraca liczbę elementów struktury a |
a.empty() |
Zwraca logiczną prawdę, jeśli a posiada 0 elementów |
a.full() |
Zwraca logiczną prawdę, jeśli struktura a jest pełna |
a.clear() |
Usuwa wszystkie elementy struktury a |
a.append(b) |
Dołącza Plex b do górnej części a |
a.prepend(b) |
Dołącza Plex b do dolnej części a |
a.fill(z) |
Wstawia do struktury elementy równe z na wszystkich pozycjach |
a.valid(i) |
Zwraca logiczną prawdę, jeśli indeks i jest prawidłowy |
a.low_element() |
Zwraca wskaźnik do elementu o najniższej pozycji |
a.high_element() |
Zwraca wskaźnik do elementu o najwyższej pozycji |
Klasy Plex są bardzo przydatne. W oparciu o nie opracowano wiele innych klas z biblioteki GNU C++, na przykład stosy, kolejki, listy itp.
Stosy
Klasa Stacks jest implementacją stosu (ang. LIFO, last in, first out). Dostępne są trzy odmiany tej struktury: VStack - stos o ustalonym rozmiarze, podawanym podczas inicjalizacji, oraz XPStack i SLStack - stosy o dynamicznie zmienianych rozmiarach, różniące się nieco szczegółami implementacji.
Tabela 27.8 zawiera metody wspólne dla tych klas.
Tabela 27.8. Metody i operatory klas Stacks
Metoda |
Opis |
Stack st |
Konstruktor |
Stack st(rozm) |
Konstruktor stosu o rozmiarze rozm |
st.empty() |
Zwraca logiczną prawdę, jeśli stos jest pusty |
st.full() |
Zwraca logiczną prawdę, jeśli stos jest pełny |
st.length() |
Zwraca liczbę elementów przechowywanych na stosie |
st.push(x) |
Odkłada element x na stos |
x=st.pop() |
Zdejmuje element ze stosu i przypisuje go zmiennej x |
st.top() |
Zwraca wskaźnik do najwyżej położonego elementu na stosie |
st.del_top(0 |
Usuwa ze stosu najwyżej położony element |
st.clear() |
Usuwa ze stosu wszystkie elementy |
Kolejki
Kolejki (ang. FIFO, first in, first out) implementowane są za pomocą klas Queue. Dostępne są trzy odmiany tych struktur: VQueue - kolejka o ustalonym rozmiarze (podawanym podczas inicjalizacji), oraz XPQueue i SLQueue - kolejki o rozmiarze zmienianym dynamicznie (podanie rozmiaru nie jest wymagane). W tabeli 27.9 zebrano metody zaimplementowane w tych klasach.
Tabela 27.9. Metody i operatory klas Queue
Metoda |
Opis |
Queue q |
Konstruktor |
Queue q(rozm) |
Konstruktor kolejki o rozmiarze rozm |
q.empty() |
Zwraca logiczną prawdę, jeśli kolejka jest pusta |
q.full() |
Zwraca logiczną prawdę, jeśli kolejka jest pełna |
q.length() |
Zwraca liczbę elementów przechowywanych w kolejce |
q.enq(x) |
Dopisuje element x do kolejki |
cd. na następnej stronie
Tabela 27.9. cd. Metody i operatory klas Queue
Metoda |
Opis |
x=q.deq() |
Pobiera element z kolejki i przypisuje go do zmiennej x |
q.front() |
Zwraca wskaźnik do pierwszego elementu w kolejce |
q.del_front(0 |
Usuwa z kolejki pierwszy element |
q.clear() |
Usuwa z kolejki wszystkie elementy |
Oprócz wymienionych wyżej typów kolejek, w skład biblioteki klas GNU C++ wchodzą również klasy obsługujące kolejki dwukierunkowe i priorytetowe. Kolejka dwukierunkowa zawiera dodatkowe operatory pozwalające na dostęp do elementu przechowywanego na jej końcu. Kolejka priorytetowa pozwala na szybki dostęp do najmniejszego elementu i posiada dodatkowe operatory służące do wyszukiwania elementów.
Zbiory
Klasy Set używane są do przechowywania zbiorów elementów. Jedynym ograniczeniem nałożonym na elementy jest fakt, że nie mogą się one powtarzać. Dostępnych jest kilka implementacji tej klasy, ale wszystkie one mają taki sam interfejs. Metody tych klas zebrano w tabeli 27.10.
Tabela 27.10. Metody i operatory klas Set
Metoda |
Opis |
Set s |
Konstruktor |
Set s(rozm) |
Konstruktor zbioru o maksymalnym rozmiarze rozm |
s.empty() |
Zwraca logiczną prawdę, jeśli zbiór jest pusty |
s.length() |
Zwraca liczbę elementów zbioru |
i=s.add(z) |
Dodaje element z do zbioru s, zwracając numer indeksu |
s.del(z) |
Usuwa ze zbioru s element z |
s.clear() |
Usuwa wszystkie elementy ze zbioru s |
s.contains(z) |
Zwraca logiczną prawdę, jeśli element z należy do zbioru s |
s.(i) |
Zwraca wskaźnik do elementu o indeksie i |
i=s.first() |
Zwraca indeks pierwszego elementu zbioru |
s.next(i) |
Przypisuje zmiennej i indeks następnego elementu zbioru s |
i=s.seek(z) |
Przypisuje zmiennej i indeks elementu z, jeśli należy on do zbioru s, |
set1==set2 |
Zwraca logiczną prawdę, jeśli set1 zawiera te same elementy, co set2 |
set1!=set2 |
Zwraca logiczną prawdę, jeśli zbiór set1 nie zawiera tych samych elementów, co set2 |
Tabela 27.10. cd. Metody i operatory klas Set
Metoda |
Opis |
set1<=set2 |
Zwraca TRUE, jeśli zbiór set1 jest podzbiorem set2 |
set1|=set2 |
Dodaje elementy zbioru set2 do zbioru set1 |
set1-=set2 |
Usuwa ze zbioru set1 wszystkie elementy, które występują w zbiorze set2 |
set1&=set2 |
Usuwa ze zbioru set1 elementy, które nie występują w set2 |
Oprócz zbiorów dostępne są również inne klasy o podobnej funkcji - są to klasy bag (ang. torba). Struktury tego typu mogą, podobnie jak zbiory, zawierać elementy w dowolnym porządku, ale różnią się od zbiorów tym, że mogą również zawierać elementy powtarzające się. Nie obsługują w związku z tym operatorów ==, !=, |=, <=, -= i &=. Nowe metody implementowane w klasie bag zebrano w tabeli 27.11.
Tabela 27.11. Metody klasy bag nie występujące w klasie Set
Metoda |
Opis |
b.remove(z) |
Usuwa wszystkie wystąpienia elementu z |
b.nof(z) |
Zwraca liczbę wystąpień elementu z |
Poza opisanymi wyżej, biblioteka klas GNU C++ zawiera jeszcze wiele innych użytecznych klas. Oprócz biblioteki dostarczanej wraz z kompilatorem, dostępne są także inne, darmowe biblioteki, mogące również okazać się bardzo przydatne.
Podsumowanie
C++ oferuje programiście o wiele więcej niż język C. Najbardziej oczywistą przewagą jest możliwość programowania obiektowego oraz dostępność elastycznych bibliotek klas. W tym rozdziale przyjrzeliśmy się kompilatorowi i debugerowi, a także omówiliśmy pokrótce najczęściej używane klasy wchodzące w skład biblioteki GNU C++.
Jednym z problemów, na które narzekali programiści używający języka C++, był brak darmowych programów narzędziowych. Jak zauważyłeś, o wiele więcej jest programów użytkowych wspomagających programowanie w języku C. Z upływem czasu sytuacja jednak się odwraca. Rosnąca ilość użytkowników języka C++ powoduje powstawanie nowych programów narzędziowych oraz ewolucję już istniejących. Nie należy więc obawiać się „przesiadki” na C++.
Kompilator i programy narzędziowe dla języka C omówione są w rozdziale 26. „Programowanie w języku C”.
Perl, język nadający się świetnie do pisania krótkich i wydajnych programów, przedstawiony jest w rozdziale 28. „Perl”.
Kompilatory innych języków dla systemu Linux opisane są w rozdziale 30. „Inne kompilatory”.
460 Część V ♦ Linux dla programistów
460 E:\Moje dokumenty\HELION\Linux Unleashed\Indeks\27.DOC
E:\Moje dokumenty\HELION\Linux Unleashed\Indeks\27.DOC 459
Rozdzia³ 27. ♦ Programowanie w C++ 459