6135


Rozdział 19.
--> Wzorce[Author ID1: at Fri Nov 9 09:25:00 2001 ]Szablony[Author ID1: at Fri Nov 9 09:25:00 2001 ][Author ID2: at Thu Nov 29 10:26:00 2001 ]Wzorce[Author ID2: at Thu Nov 29 10:26:00 2001 ][Author:MP] [Author ID2: at Thu Nov 29 10:26:00 2001 ]

Przydatnym dla programistów C++ nowym narzędziem są „typy sparametryzowane”, czyli wzorce[Author ID1: at Fri Nov 9 09:25:00 2001 ]wzorce. Wzorce[Author ID1: at Fri Nov 9 09:25:00 2001 ]Są one tak użytecznym narzędziem, że do definicji języka C++ została wprowadzona standardowa biblioteka wzorców (STL, Standard Template Library).

Z tego rozdziału dowiesz się:

Czym są wzorce[Author ID1: at Fri Nov 9 09:26:00 2001 ]wzorce?

Pod koniec czternastego rozdziału stworzyliśmy obiekt PartsList i wykorzystaliśmy go do stworzenia katalogu części, klasy PartsCatalog. Jeśli jednak chcielibyśmy na obiekcie PartsList oprzeć listę kotów, pojawiłby się problem: klasa PartsList wie tylko o częściach.

Aby rozwiązać ten problem, moglibyśmy stworzyć klasę bazową List. Wtedy moglibyśmy wyciąć i wkleić większość kodu z klasy PartsList do nowej deklaracji klasy CatsList. Tydzień później, gdybyśmy zechcieli stworzyć listę obiektów Car, musielibyśmy stworzyć nową klasę i znów kopiować i wklejać kod.

Nie jest to dobre rozwiązanie. Z czasem mogłoby się okazać, że klasa List oraz klasy z niej wyprowadzone muszą być rozszerzone. Rozprowadzenie wszystkich zmian po wszystkich powiązanych z nią klasach byłoby koszmarem.

Problem ten rozwiązują wzorce, które po zaadoptowaniu standardu ANSI stały się integralną częścią języka. Podobnie jak wszystkie elementy w C++, są one bardzo elastyczne, a przy tym bezpieczne (pod względem typów).

Typy parametryzowane

Wzorce pozwalają na nauczenie kompilatora, w jaki sposób ma tworzyć listę rzeczy dowolnego typu (zamiast tworzenia zestawu list specyficznego typu) — PartsList jest listą części, CatsList jest listą kotów. Listy te różnią się tylko rodzajem przechowywanych w nich rzeczy. W trakcie korzystania z wzorców typ rzeczy zawartej w liście staje się parametrem definicji klasy.

Standardowym komponentem w prawie wszystkich bibliotekach C++ jest klasa tablicy. Jak widzieliśmy w przypadku klasy List, tworzenie osobnych tablic dla liczb całkowitych, liczb [Author ID1: at Mon Nov 12 10:48:00 2001 ]zmiennopozycyjnych[Author ID1: at Fri Nov 9 09:27:00 2001 ]rzecinkowych[Author ID1: at Fri Nov 9 09:27:00 2001 ] czy dla obiektów jest żmudne i nieefektywne. Wzorce[Author ID1: at Fri Nov 9 09:27:00 2001 ]Wzorce umożliwiają zadeklarowanie sparametryzowanej klasy tablicy, a następnie określenie, jaki typ obiektu będzie zawierał każdy jej egzemplarz. Zauważ, że standardowa biblioteka wzorców[Author ID1: at Fri Nov 9 09:28:00 2001 ]wzorców zawiera standardowy zestaw klas kontenerów, obejmujący tablice, listy i tak dalej. W tym rozdziale sami stworzymy klasę kontenera, aby przekonać się, jak działają wzorce[Author ID1: at Mon Nov 12 09:30:00 2001 ]wzorce, ale w komercyjnych aplikacjach prawie z pewnością użyjesz klas standardowych (zamiast tworzyć własne).

Tworzenie egzemplarza wzorca

Tworzenie egzemplarza wzorca oznacza tworzenie określonego typu na podstawie tego wzorca. Poszczególne, tworzone aktualnie klasy są nazywane egzemplarzami wzorca[Author ID1: at Mon Nov 12 09:30:00 2001 ]wzorca.

Parametryzowane wzorce[Author ID1: at Mon Nov 12 09:31:00 2001 ]wzorce pozwalają na tworzenie klasy ogólnej i przekazywanie jej typów jako parametrów, w celu stworzenia konkretnego egzemplarza wzorca[Author ID1: at Mon Nov 12 10:50:00 2001 ]wzorca.

Definicja wzorca[Author ID1: at Mon Nov 12 09:31:00 2001 ]wzorca

Parametryzowany obiekt Array (wzorzec[Author ID1: at Mon Nov 12 09:31:00 2001 ]wzorzec dla tablic) deklarujemy, pisząc:

1: template <class T> // deklaruje wzorzec[Author ID1: at Mon Nov 12 09:31:00 2001 ]wzorzec i parametr

2: class Array // parametryzowana klasa

3: {

4: public:

5: Array();

6: // w tym miejscu pełna deklaracja klasy

7: };

Słowo kluczowe template (wzorzec[Author ID1: at Mon Nov 12 09:31:00 2001 ]wzorzec) jest używane na początku każdej deklaracji i definicji klasy wzorca[Author ID1: at Mon Nov 12 09:32:00 2001 ]wzorcowej[Author ID1: at Mon Nov 12 09:32:00 2001 ]. Parametry wzorca[Author ID1: at Mon Nov 12 09:32:00 2001 ]wzorca występują po słowie kluczowym template. Parametry są rzeczami[Author ID1: at Mon Nov 12 09:32:00 2001 ]zmieniane [Author ID1: at Mon Nov 12 09:32:00 2001 ](uaktualnia[Author ID1: at Mon Nov 12 09:33:00 2001 ]ne) [Author ID1: at Mon Nov 12 09:33:00 2001 ]w każdym egzemplarzu. Na przykład, w pokazanym tu wzorcu[Author ID1: at Mon Nov 12 09:34:00 2001 ]wzorcu tablicy zmienia się typ przechowywanych w niej obiektów. Jeden egzemplarz może być tablicą liczb całkowitych, a inny może być tablicą obiektów typu Animal.

W tym przykładzie zostało użyte słowo kluczowe class, po którym następuje identyfikator T. Słowo kluczowe class wskazuje że ten parametr (identyfikator) [Author ID1: at Mon Nov 12 09:34:00 2001 ]jest typem. Identyfikator T jest używany w pozostałej części definicji wzorca[Author ID1: at Mon Nov 12 09:34:00 2001 ]wzorca i odnosi się do typu parametryzowanego. W jednym z egzemplarzy tej klasy w miejscu wszystkich identyfikatorów T zostanie podstawiony na przykład typ int, a w innym egzemplarzu zostanie podstawiony typ Cat.

Aby zadeklarować egzemplarze wzorca[Author ID1: at Mon Nov 12 09:35:00 2001 ]wzorca typu int i Cat parametryzowanej klasy Array, moglibyśmy napisać:

Array<int> anIntArray;

Array<Cat> aCatArray;

Obiekt anIntArray to obiekt typu tablica liczb całkowitych; obiekt aCatArray to obiekt typu tablica kotów. Od tego momentu możemy używać typu Array<int> wszędzie tam, gdzie normalnie używa[Author ID1: at Mon Nov 12 09:35:00 2001 ]libyś[Author ID1: at Mon Nov 12 09:36:00 2001 ]my jakiegoś typu[Author ID1: at Mon Nov 12 09:35:00 2001 ] (np.[Author ID1: at Mon Nov 12 09:38:00 2001 ] [Author ID1: at Mon Nov 12 09:35:00 2001 ]moglibyśmy użyć typu [Author ID1: at Mon Nov 12 09:35:00 2001 ][Author ID1: at Mon Nov 12 09:38:00 2001 ]jako[Author ID1: at Mon Nov 12 10:53:00 2001 ]dla[Author ID1: at Mon Nov 12 10:53:00 2001 ] wartości zwracanej przez funkcję, jako parametru funkcji i tak dalej). Pełną deklarację tej okrojonej klasy Array przedstawia listing 19.1.

UWAGA Listing 19.1 nie jest kompletnym programem!

Listing 19.1. Wzorzec[Author ID1: at Mon Nov 12 09:38:00 2001 ]Wzorzec klasy tablicy

0: //Listing 19.1 Wzorzec klasy tablicy

1: #include <iostream>

2: using namespace std;

3: const int DefaultSize = 10;

4:

5: template <class T> // deklaruje wzorzec i parametr

6: class Array // parametryzowana klasa

7: {

8: public:

9: // konstruktory

10: Array(int itsSize = DefaultSize);

11: Array(const Array &rhs);

12: ~Array() { delete [] pType; }

13:

14: // operatory

15: Array& operator=(const Array&);

16: T& operator[](int offSet) { return pType[offSet]; }

17:

18: // akcesory

19: int getSize() { return itsSize; }

20:

21: private:

22: T *pType;

23: int itsSize;

24: };

Wynik

Brak. Program nie jest kompletny.

Analiza

Definicja wzorca[Author ID1: at Mon Nov 12 09:38:00 2001 ]wzorca rozpoczyna się w linii 5. od słowa kluczowego template, po którym występuje parametr. W tym przypadku parametr jest identyfikowany przez słowo kluczowe class, zaś do reprezentowania parametryzowanego typu został użyty identyfikator T.

Od linii 6. aż do końca wzorca[Author ID1: at Mon Nov 12 09:39:00 2001 ]wzorca w linii 24., pozostała część deklaracji wygląda tak samo jak deklaracja każdej innej klasy. Jedyną różnicą jest to, że tam, gdzie normalnie występowałby typ obiektu, występuje identyfikator T. Na przykład, można oczekiwać, że operator[] będzie zwracał referencję do obiektu zawartego w tablicy — rzeczywiście, jest zadeklarowany jako zwracający referencję do T.

Gdy deklarowany jest egzemplarz tablicy liczb całkowitych, dostarczony[Author ID1: at Mon Nov 12 09:39:00 2001 ]to zdefiniowany[Author ID1: at Mon Nov 12 09:39:00 2001 ] dla tej tablicy operator= zwróci referencję do liczby całkowitej. Gdy jest deklarowany egzemplarz tablicy obiektów klasy Animal, dostarczony[Author ID1: at Mon Nov 12 09:40:00 2001 ]to zdefini[Author ID1: at Mon Nov 12 09:40:00 2001 ]owany[Author ID1: at Mon Nov 12 09:40:00 2001 ] dla tej tablicy operator= zwróci referencję do obiektu Animal.

Użycie nazwy

Słowo Array może być użyte bez kwalifikowania wewnątrz deklaracji klasy. We wszystkich innych miejscach programu do klasy tej trzeba się odwoływać jako do Array<T>. Na przykład, jeśli nie za[Author ID1: at Mon Nov 12 09:41:00 2001 ]w[Author ID1: at Mon Nov 12 09:41:00 2001 ]piszemy konstruktora wewnątrz deklaracji klasy, to [Author ID1: at Mon Nov 12 09:41:00 2001 ]musimy napisać:

template <class T>

Array<T>::Array(int size):

itsSize = size

{

pType = new T[size];

for (int i = 0; i < size; i++)

pType[i] = 0;

}

Deklaracja w pierwszej linii tego fragmentu kodu jest wymagana do zidentyfikowania typu (class T). Nazwą wzorca[Author ID1: at Mon Nov 12 09:42:00 2001 ]wzorca jest Array<T>, a nazwą funkcji jest Array(int size). Pozostała część funkcji jest taka sama, jak w przypadku zwykłej funkcji. Powszechną i zalecaną praktyką jest przetestowanie działania klasy i jej funkcji jako zwykłych deklaracji przed zamienieniem ich wee[Author ID1: at Mon Nov 12 09:42:00 2001 ] wzorzec[Author ID1: at Mon Nov 12 09:42:00 2001 ]wzorzec. To rozwiązanie upraszcza tworzenie wzorca[Author ID1: at Mon Nov 12 09:43:00 2001 ]wzorca, umożliwiając skoncentrowanie się na celu programowania; rozwiązania[Author ID1: at Mon Nov 12 09:44:00 2001 ]e[Author ID1: at Mon Nov 12 09:44:00 2001 ], poprzez stworzenie wzorca[Author ID1: at Mon Nov 12 09:44:00 2001 ]wzorca, uogólnienie[Author ID1: at Mon Nov 12 09:44:00 2001 ]amy[Author ID1: at Mon Nov 12 09:44:00 2001 ] dopiero później.

Implementowanie wzorca[Author ID1: at Mon Nov 12 09:44:00 2001 ]wzorca

Pełna implementacja klasy wzorca Array wymaga zaimplementowania konstruktora kopiującego[Author ID1: at Mon Nov 12 09:44:00 2001 ]i[Author ID1: at Mon Nov 12 09:44:00 2001 ], operatora= i tak dalej. Prosty program sterujący, przenaczony do testowania tej klasy wzorca[Author ID1: at Mon Nov 12 09:44:00 2001 ]wzorcowej[Author ID1: at Mon Nov 12 09:45:00 2001 ] przedstawia listing 19.2.

UWAGA Niektóre starsze kompilatory nie obsługują wzorców[Author ID1: at Mon Nov 12 09:45:00 2001 ]wzorców. Wzorce[Author ID1: at Mon Nov 12 09:45:00 2001 ]Wzorce są jednak częścią standardu ANSI C++ i są obsługiwane w obecnych wersjach kompilatorów wiodących producentów. Jeśli masz bardzo stary kompilator, nie będziesz mógł skompilować i uruchomić przykładowych programów przedstawionych w tym rozdziale. Jednak mimo to, powinieneś go w całości i wrócić do niego później, gdy zdobędziesz nowszy kompilator.

Listing 19.2. Implementacja wzorca[Author ID1: at Mon Nov 12 09:45:00 2001 ]wzorca klasy tablicy

0: //Listing 19.2 Implementacja wzorca[Author ID1: at Mon Nov 12 09:46:00 2001 ]wzorca klasy tablicy

1: #include <iostream>

2:

3: const int DefaultSize = 10;

4:

5: // deklaruje prostą klasę Animal tak, abyśmy

6: // mogli tworzyć tablice obiektów typu Animal

7:

8: class Animal

9: {

10: public:

11: Animal(int);

12: Animal();

13: ~Animal() {}

14: int GetWeight() const { return itsWeight; }

15: void Display() const { std::cout << itsWeight; }

16: private:

17: int itsWeight;

18: };

19:

20: Animal::Animal(int weight):

21: itsWeight(weight)

22: {}

23:

24: Animal::Animal():

25: itsWeight(0)

26: {}

27:

28:

29: template <class T> // deklaruje wzorzec[Author ID1: at Mon Nov 12 09:46:00 2001 ]wzorzec i parametr

30: class Array // parametryzowana klasa

31: {

32: public:

33: // konstruktory

34: Array(int itsSize = DefaultSize);

35: Array(const Array &rhs);

36: ~Array() { delete [] pType; }

37:

38: // operatory

39: Array& operator=(const Array&);

40: T& operator[](int offSet) { return pType[offSet]; }

41: const T& operator[](int offSet) const

42: { return pType[offSet]; }

43: // akcesory

44: int GetSize() const { return itsSize; }

45:

46: private:

47: T *pType;

48: int itsSize;

49: };

50:

51: // oraz implementacje...

52:

53: // implementacja konstruktora

54: template <class T>

55: Array<T>::Array(int size):

56: itsSize(size)

57: {

58: pType = new T[size];

59: for (int i = 0; i<size; i++)

60: pType[i] = 0;

61: }

62:

63: // konstruktor kopiujący[Author ID1: at Mon Nov 12 09:46:00 2001 ]i[Author ID1: at Mon Nov 12 09:46:00 2001 ]

64: template <class T>

65: Array<T>::Array(const Array &rhs)

66: {

67: itsSize = rhs.GetSize();

68: pType = new T[itsSize];

69: for (int i = 0; i<itsSize; i++)

70: pType[i] = rhs[i];

71: }

72:

73: // operator=

74: template <class T>

75: Array<T>& Array<T>::operator=(const Array &rhs)

76: {

77: if (this == &rhs)

78: return *this;

79: delete [] pType;

80: itsSize = rhs.GetSize();

81: pType = new T[itsSize];

82: for (int i = 0; i<itsSize; i++)

83: pType[i] = rhs[i];

84: return *this;

85: }

86:

87: // program sterujący

88: int main()

89: {

90: Array<int> theArray; // tablica liczb całkowitych

91: Array<Animal> theZoo; // tablica obiektów typu Animal

92: Animal *pAnimal;

93:

94: // wypełniamy tablice

95: for (int i = 0; i < theArray.GetSize(); i++)

96: {

97: theArray[i] = i*2;

98: pAnimal = new Animal(i*3);

99: theZoo[i] = *pAnimal;

100: delete pAnimal;

101: }

102: // wypisujemy zawartość tablic

103: for (int j = 0; j < theArray.GetSize(); j++)

104: {

105: std::cout << "theArray[" << j << "]:\t";

106: std::cout << theArray[j] << "\t\t";

107: std::cout << "theZoo[" << j << "]:\t";

108: theZoo[j].Display();

109: std::cout << std::endl;

110: }

111:

112: return 0;

113: }

Wynik

theArray[0]: 0 theZoo[0]: 0

theArray[1]: 2 theZoo[1]: 3

theArray[2]: 4 theZoo[2]: 6

theArray[3]: 6 theZoo[3]: 9

theArray[4]: 8 theZoo[4]: 12

theArray[5]: 10 theZoo[5]: 15

theArray[6]: 12 theZoo[6]: 18

theArray[7]: 14 theZoo[7]: 21

theArray[8]: 16 theZoo[8]: 24

theArray[9]: 18 theZoo[9]: 27

Analiza

Linie od 8. do 26. zawierają okrojoną klasę Animal, stworzoną tu po to, by w tablicy mógł zostać umieszczony także typ definiowany przez użytkownika.

Linia 29. deklaruje,[Author ID1: at Mon Nov 12 11:00:00 2001 ] że to, co następuje po niej, jest wzorcem[Author ID1: at Mon Nov 12 09:46:00 2001 ]wzorcem, i że parametrem tego wzorca[Author ID1: at Mon Nov 12 09:47:00 2001 ]wzorca jest typ oznaczony jako T. Klasa Array posiada dwa konstruktory; pierwszy z nich otrzymuje rozmiar, który domyślnie ustawiany jest zgodnie ze stałą DefaultSize (rozmiar domyślny).

Deklarowane są operatory przypisania i indeksu, przy czym ten drugi deklarowany jest zarówno w wariancie const jak i zwykłym. Jedynym dostarczonym[Author ID1: at Mon Nov 12 09:47:00 2001 ]zdefiniowanym[Author ID1: at Mon Nov 12 09:47:00 2001 ] akcesorem jest funkcja GetSize(), zwracająca rozmiar tablicy.

Z pewnością można sobie wyobrazić lepszy interfejs; zdefiniowany[Author ID1: at Mon Nov 12 09:47:00 2001 ] tu interfejs byłby nieodpowiedni w każdym poważnym programie korzystającym z klasy Arraydostarczony[Author ID1: at Mon Nov 12 09:47:00 2001 ]. Jako minimum wymagane są operatory do usuwania elementów, zwiększania i pakowania tablicy i tak dalej. Wszystko to jest dostarczane przez klasy kontenerów w STL, które opiszemy w dalszej części rozdziału.

Prywatne dane to: rozmiar tablicy oraz wskaźnik do rzeczywistej, zawartej w pamięci tablicy obiektów.

Funkcje wzorca[Author ID1: at Mon Nov 12 09:48:00 2001 ]wzorcowe[Author ID1: at Mon Nov 12 09:48:00 2001 ]

Gdy chcemy przekazać obiekt tablicy do funkcji, musimy przekazać jej określony egzemplarz tej tablicy, a nie wzorzec[Author ID1: at Mon Nov 12 09:48:00 2001 ]wzorzec. Tak więc, jeśli funkcja SomeFunction()przyjmuje jako parametr tablicę liczb całkowitych, możemy napisać:

void SomeFunction(Array<int>&); // ok

nie możemy jednak napisać:

void SomeFunction(Array<T>&); // błąd!

gdyż kompilator nie wie, jakim typem jest T&. Nie możemy także napisać:

void SomeFunction(Array &); // błąd

gdyż klasa Array nie istnieje — jest[Author ID1: at Mon Nov 12 11:02:00 2001 ]istnieje[Author ID1: at Mon Nov 12 11:02:00 2001 ] jedynie wzorzec[Author ID1: at Mon Nov 12 09:48:00 2001 ]wzorzec i jego egzemplarze.

Aby uzyskać bardziej ogólny pogląd, musimy zadeklarować funkcję wzorca[Author ID1: at Mon Nov 12 09:48:00 2001 ]wzorco[Author ID1: at Mon Nov 12 11:02:00 2001 ].

template <class T>

void MyTemplateFunction(Array<T>&); // ok

Funkcja MyTemplateFunction() jest zadeklarowana jako funkcja wzorca[Author ID1: at Mon Nov 12 09:48:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 11:02:00 2001 ] (poprzez deklarację w poprzedzającej jej[Author ID1: at Mon Nov 12 09:49:00 2001 ]ą[Author ID1: at Mon Nov 12 09:49:00 2001 ] linii). Zauważ, że funkcja wzorca[Author ID1: at Mon Nov 12 09:49:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 09:49:00 2001 ] może mieć dowolną nazwę, podobnie jak każda inna funkcja.

Funkcje wzorców[Author ID1: at Mon Nov 12 09:49:00 2001 ]wzorcowe[Author ID1: at Mon Nov 12 09:49:00 2001 ] mogą także przyjmować egzemplarze wzorca[Author ID1: at Mon Nov 12 09:49:00 2001 ]wzorca, a nie tylko jego s[Author ID1: at Mon Nov 12 09:50:00 2001 ]parametryzowane formy. Na przykład:

template <class T>

void MyOtherFunction(Array<T>&, Array<int>&); // ok

Zwróć uwagę, że ta funkcja otrzymuje dwie tablice: tablicę parametryzowaną oraz tablicę liczb całkowitych. Pierwsza może być tablicą dowolnych obiektów, lecz druga musi być zawsze tablicą liczb całkowitych.

Wzorce[Author ID1: at Mon Nov 12 09:51:00 2001 ]Wzorce i przyjaciele

Klasy wzorców[Author ID1: at Mon Nov 12 09:51:00 2001 ]wzorcowe[Author ID1: at Mon Nov 12 09:51:00 2001 ] mogą deklarować trzy rodzaje przyjaciół:

Niewzorcowe[Author ID1: at Mon Nov 12 09:54:00 2001 ] wzorcowe [Author ID1: at Mon Nov 12 09:54:00 2001 ]zaprzyjaźnione klasy i funkcje.

Istnieje możliwość zadeklarowania dowolnej klasy lub funkcji jako zaprzyjaźnionej z klasą wzorco[Author ID1: at Mon Nov 12 09:55:00 2001 ]wzorca[Author ID1: at Mon Nov 12 09:55:00 2001 ]. Każdy egzemplarz klasy będzie traktował klasę lub funkcję zaprzyjaźnioną tak, jakby deklaracja przyjaźni została zawarta z tym konkretnym egzemplarzem[Author ID1: at Mon Nov 12 09:55:00 2001 ]. Listing 19.3 dodaje definicję funkcji zaprzyjaźnionej, Intrude(), do definicji wzorca[Author ID1: at Mon Nov 12 09:55:00 2001 ]wzorca klasy Array. Funkcja ta zostaje wywołana przez program sterujący. Ponieważ jest zaprzyjaźniona, funkcja Intrude() może odwoływać się do prywatnych danych klasy Array. Jednak ponieważ nie jest funkcją wzorca[Author ID1: at Mon Nov 12 09:56:00 2001 ]wzorco[Author ID1: at Mon Nov 12 09:56:00 2001 ], może być wywoływana jedynie dla klas Array zawierających zmienne typu int.

Listing 19.3. Niewzorcowa[Author ID1: at Mon Nov 12 09:56:00 2001 ] wzorcowa[Author ID1: at Mon Nov 12 09:56:00 2001 ] funkcja zaprzyjaźniona

0: // Listing 19.3 - Specyficzne dla typu funkcje zaprzyjaźnione wee[Author ID1: at Mon Nov 12 09:57:00 2001 ] wzorcu[Author ID1: at Mon Nov 12 09:57:00 2001 ]wzorcu

1:

2: #include <iostream>

3: using namespace std;

4:

5: const int DefaultSize = 10;

6:

7: // deklaruje prostą klasę Animal tak abyśmy

8: // mogli tworzyć tablice obiektów typu Animal

9:

10: class Animal

11: {

12: public:

13: Animal(int);

14: Animal();

15: ~Animal() {}

16: int GetWeight() const { return itsWeight; }

17: void Display() const { cout << itsWeight; }

18: private:

19: int itsWeight;

20: };

21:

22: Animal::Animal(int weight):

23: itsWeight(weight)

24: {}

25:

26: Animal::Animal():

27: itsWeight(0)

28: {}

29:

30: template <class T> // deklaruje wzorzec[Author ID1: at Mon Nov 12 09:57:00 2001 ]wzorzec i parametr

31: class Array // klasa parametryzowana

32: {

33: public:

34: // konstruktory

35: Array(int itsSize = DefaultSize);

36: Array(const Array &rhs);

37: ~Array() { delete [] pType; }

38:

39: // operatory

40: Array& operator=(const Array&);

41: T& operator[](int offSet) { return pType[offSet]; }

42: const T& operator[](int offSet) const

43: { return pType[offSet]; }

44: // akcesory

45: int GetSize() const { return itsSize; }

46:

47: // funkcja zaprzyjaźniona

48: friend void Intrude(Array<int>);

49:

50: private:

51: T *pType;

52: int itsSize;

53: };

54:

55: // funkcja zaprzyjaźniona. Nie jest wzorcowa[Author ID1: at Mon Nov 12 09:57:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 09:57:00 2001 ], więc może być

56: // użyta tylko z tablicami wartości int! Wnika w prywatne dane.

57: void Intrude(Array<int> theArray)

58: {

59: cout << "\n*** Funkcja Intrude() ***\n";

60: for (int i = 0; i < theArray.itsSize; i++)

61: cout << "i: " << theArray.pType[i] << endl;

62: cout << "\n";

63: }

64:

65: // oraz implementacje...

66:

67: // implementacja konstruktora

68: template <class T>

69: Array<T>::Array(int size):

70: itsSize(size)

71: {

72: pType = new T[size];

73: for (int i = 0; i<size; i++)

74: pType[i] = 0;

75: }

76:

77: // konstruktor kopii[Author ID1: at Mon Nov 12 09:58:00 2001 ]ujący[Author ID1: at Mon Nov 12 09:58:00 2001 ]

78: template <class T>

79: Array<T>::Array(const Array &rhs)

80: {

81: itsSize = rhs.GetSize();

82: pType = new T[itsSize];

83: for (int i = 0; i<itsSize; i++)

84: pType[i] = rhs[i];

85: }

86:

87: // operator=

88: template <class T>

89: Array<T>& Array<T>::operator=(const Array &rhs)

90: {

91: if (this == &rhs)

92: return *this;

93: delete [] pType;

94: itsSize = rhs.GetSize();

95: pType = new T[itsSize];

96: for (int i = 0; i<itsSize; i++)

97: pType[i] = rhs[i];

98: return *this;

99: }

100:

101: // program sterujący

102: int main()

103: {

104: Array<int> theArray; // tablica liczb całkowitych

105: Array<Animal> theZoo; // tablica obiektów typu Animal

106: Animal *pAnimal;

107:

108: // wypełniamy tablice

109: for (int i = 0; i < theArray.GetSize(); i++)

110: {

111: theArray[i] = i*2;

112: pAnimal = new Animal(i*3);

113: theZoo[i] = *pAnimal;

114: }

115:

116: int j;

117: for (j = 0; j < theArray.GetSize(); j++)

118: {

119: cout << "theZoo[" << j << "]:\t";

120: theZoo[j].Display();

121: cout << endl;

122: }

123: cout << "Teraz uzywamy funkcji zaprzyjaznionej do \n";

124: cout << "wypisania elementow tablicy Array<int>";

125: Intrude(theArray);

126:

127: cout << "\n\nGotowe.\n";

128: return 0;

129: }

Wynik

theZoo[2]: 6

theZoo[3]: 9

theZoo[4]: 12

theZoo[5]: 15

theZoo[6]: 18

theZoo[7]: 21

theZoo[8]: 24

theZoo[9]: 27

Teraz uzywamy funkcji zaprzyjaznionej do

wypisania elementow tablicy Array<int>

*** Funkcja Intrude() ***

i: 0

i: 2

i: 4

i: 6

i: 8

i: 10

i: 12

i: 14

i: 16

i: 18

Gotowe.

Analiza

Deklaracja wzorca[Author ID1: at Mon Nov 12 09:58:00 2001 ]wzorca Array została rozszerzona o zaprzyjaźnioną funkcję Intrude(). Deklarujemy, że każdy egzemplarz tablicy typów int będzie traktował funkcję Intrude() jak funkcję zaprzyjaźnioną, więc będzie ona miała dostęp do prywatnych danych i funkcji składowych egzemplarza tablicy.

W linii 60. funkcja Intrude() bezpośrednio odwołuje się do składowej itsSize, a w linii 61. do wskaźnika pType. Standardowe użycie tych składowych nie było konieczne, gdyż klasa Array zapewnia akcesory publiczne, służy ono jednak zilustrowaniu, w jaki sposób można deklarować funkcje zaprzyjaźnione dla wzorców.

Ogólne wzorcowe[Author ID1: at Mon Nov 12 09:59:00 2001 ]wzorcowe[Author ID1: at Mon Nov 12 09:59:00 2001 ] zaprzyjaźnione klasy i funkcje

Dodanie operatora wyświetlania do klasy Array byłoby pomocne. Operator wyświetlania można zadeklarować dla każdego możliwego typu wzorca[Author ID1: at Mon Nov 12 09:59:00 2001 ]wzorca Array, ale w ten sposób cały wysiłek włożony w zmianę wzorca[Author ID1: at Mon Nov 12 09:59:00 2001 ]klasy Array na wzorzec stałby się bezcelowy.

My potrzebujemy operatora wstawiania działającego w przypadku zastosowania każdego typu wzorca Array:

ostream& operator<< (ostream&, Array<T>&);

Aby uzyskać zamierzony efekt, musimy zadeklarować operator<< jako funkcję wzorca:

template <class T> ostream& operator<< (ostream&, Array<T>&)

Teraz, gdy operator<< jest funkcją wzorca[Author ID1: at Mon Nov 12 10:00:00 2001 ]wzorco[Author ID1: at Mon Nov 12 10:00:00 2001 ], musimy jedynie dostarczyć[Author ID1: at Mon Nov 12 10:00:00 2001 ]podać[Author ID1: at Mon Nov 12 10:00:00 2001 ] jego implementację. Listing 19.4 przedstawia wzorzec[Author ID1: at Mon Nov 12 10:00:00 2001 ]wzorzec Array rozszerzony o tę deklarację oraz implementację operatora<<.

Listing 19.4. Użycie operatora ostream

0: //Listing 19.4 Użycie operatora ostream

1: #include <iostream>

2: using namespace std;

3:

4: const int DefaultSize = 10;

5:

6: class Animal

7: {

8: public:

9: Animal(int);

10: Animal();

11: ~Animal() {}

12: int GetWeight() const { return itsWeight; }

13: void Display() const { cout << itsWeight; }

14: private:

15: int itsWeight;

16: };

17:

18: Animal::Animal(int weight):

19: itsWeight(weight)

20: {}

21:

22: Animal::Animal():

23: itsWeight(0)

24: {}

25:

26: template <class T> // deklaruje wzorzec i parametr

27: class Array // parametryzowana klasa

28: {

29: public:

30: // konstruktory

31: Array(int itsSize = DefaultSize);

32: Array(const Array &rhs);

33: ~Array() { delete [] pType; }

34:

35: // operatory

36: Array& operator=(const Array&);

37: T& operator[](int offSet) { return pType[offSet]; }

38: const T& operator[](int offSet) const

39: { return pType[offSet]; }

40: // akcesory

41: int GetSize() const { return itsSize; }

42:

43: friend ostream& operator<< (ostream&, Array<T>&);

44:

45: private:

46: T *pType;

47: int itsSize;

48: };

49:

50: template <class T>

51: ostream& operator<< (ostream& output, Array<T>& theArray)

52: {

53: for (int i = 0; i<theArray.GetSize(); i++)

54: output << "[" << i << "] " << theArray[i] << endl;

55: return output;

56: }

57:

58: // oraz implementacje...

59:

60: // implementacja konstruktora

61: template <class T>

62: Array<T>::Array(int size):

63: itsSize(size)

64: {

65: pType = new T[size];

66: for (int i = 0; i<size; i++)

67: pType[i] = 0;

68: }

69:

70: // konstruktor kopii[Author ID1: at Mon Nov 12 10:01:00 2001 ]ujący[Author ID1: at Mon Nov 12 10:01:00 2001 ]

71: template <class T>

72: Array<T>::Array(const Array &rhs)

73: {

74: itsSize = rhs.GetSize();

75: pType = new T[itsSize];

76: for (int i = 0; i<itsSize; i++)

77: pType[i] = rhs[i];

78: }

79:

80: // operator=

81: template <class T>

82: Array<T>& Array<T>::operator=(const Array &rhs)

83: {

84: if (this == &rhs)

85: return *this;

86: delete [] pType;

87: itsSize = rhs.GetSize();

88: pType = new T[itsSize];

89: for (int i = 0; i<itsSize; i++)

90: pType[i] = rhs[i];

91: return *this;

92: }

93:

94: int main()

95: {

96: bool Stop = false; // znacznik dla pętli

97: int offset, value;

98: Array<int> theArray;

99:

100: while (!Stop)

101: {

102: cout << "Podaj indeks (0-9) ";

103: cout << "oraz wartosc. (-1 aby skonczyc): " ;

104: cin >> offset >> value;

105:

106: if (offset < 0)

107: break;

108:

109: if (offset > 9)

110: {

111: cout << "***Prosze uzywac wartosci pomiedzy 0 i 9.***\n";

112: continue;

113: }

114:

115: theArray[offset] = value;

116: }

117:

118: cout << "\nOto cala tablica:\n";

119: cout << theArray << endl;

120: return 0;

121: }

Wynik

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 1 10

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 2 20

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 3 30

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 4 40

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 5 50

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 6 60

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 7 70

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 8 80

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 9 90

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 10 10

***Prosze uzywac wartosci pomiedzy 0 i 9.***

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): -1 -1

Oto cala tablica:

[0] 0

[1] 10

[2] 20

[3] 30

[4] 40

[5] 50

[6] 60

[7] 70

[8] 80

[9] 90

Analiza

W linii 43. funkcja wzorca[Author ID1: at Mon Nov 12 10:02:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 10:02:00 2001 ] operator<<() została zadeklarowana jako funkcja zaprzyjaźniona klasy wzorca[Author ID1: at Mon Nov 12 10:02:00 2001 ]wzorcowej[Author ID1: at Mon Nov 12 10:02:00 2001 ] Array. Ponieważ operator<<() jest implementowany jako funkcja wzorca[Author ID1: at Mon Nov 12 10:02:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 10:02:00 2001 ], każdy egzemplarz tej parametryzowanej klasy tablicy automatycznie będzie posiadał operator<<(). Implementacja tego operatora rozpoczyna się w linii 50. Wywoływany jest kolejno każdy element tablicy. Możemy uzyskać zamierzony efekt tylko wtedy, gdy dla każdego typu obiektów przechowywanych w tablicy będzie zdefiniowany operator<<().

Użycie elementów wzorca[Author ID1: at Mon Nov 12 10:03:00 2001 ]wzorca

Elementy wzorca[Author ID1: at Mon Nov 12 10:03:00 2001 ]wzorca mogą być traktowane tak jak każdy inny typ. Można przekazywać je jako parametry, poprzez wartość lub poprzez referencję, można je także zwracać jako wartości zwrotne funkcji, również poprzez wartość lub poprzez referencję. Listing 19.5 przedstawia sposób przekazywania obiektów wzorca[Author ID1: at Mon Nov 12 10:03:00 2001 ]wzorca.

Listing 19.5. Przekazywanie obiektów wzorca[Author ID1: at Mon Nov 12 10:03:00 2001 ]wzorca do i z funkcji

0: //Listing 19.5 Przekazywanie obiektów wzorca do i z funkcji

1: #include <iostream>

2: using namespace std;

3:

4: const int DefaultSize = 10;

5:

6: // Standardowa klasa do umieszczania w tablicach

7: class Animal

8: {

9: public:

10: // konstruktory

11: Animal(int);

12: Animal();

13: ~Animal();

14:

15: // akcesory

16: int GetWeight() const { return itsWeight; }

17: void SetWeight(int theWeight) { itsWeight = theWeight; }

18:

19: // zaprzyjaźnione operatory

20: friend ostream& operator<< (ostream&, const Animal&);

21:

22: private:

23: int itsWeight;

24: };

25:

26: // operator ekstrakcji dla wypisywania wartości obiektu klasy Animals

27: ostream& operator<<

28: (ostream& theStream, const Animal& theAnimal)

29: {

30: theStream << theAnimal.GetWeight();

31: return theStream;

32: }

33:

34: Animal::Animal(int weight):

35: itsWeight(weight)

36: {

37: // cout << "Animal(int)\n";

38: }

39:

40: Animal::Animal():

41: itsWeight(0)

42: {

43: // cout << "Animal()\n";

44: }

45:

46: Animal::~Animal()

47: {

48: // cout << "Destruktor klasy Animal...\n";

49: }

50:

51: template <class T> // deklaruje wzorzec[Author ID1: at Mon Nov 12 10:04:00 2001 ]wzorzec i parametr

52: class Array // klasa parametryzowana

53: {

54: public:

55: Array(int itsSize = DefaultSize);

56: Array(const Array &rhs);

57: ~Array() { delete [] pType; }

58:

59: Array& operator=(const Array&);

60: T& operator[](int offSet) { return pType[offSet]; }

61: const T& operator[](int offSet) const

62: { return pType[offSet]; }

63: int GetSize() const { return itsSize; }

64:

65: // funkcja zaprzyjaźniona

66: friend ostream& operator<< (ostream&, const Array<T>&);

67:

68: private:

69: T *pType;

70: int itsSize;

71: };

72:

73: template <class T>

74: ostream& operator<< (ostream& output, const Array<T>& theArray)

75: {

76: for (int i = 0; i<theArray.GetSize(); i++)

77: output << "[" << i << "] " << theArray[i] << endl;

78: return output;

79: }

80:

81: // oraz implementacje...

82:

83: // implementacja konstruktora

84: template <class T>

85: Array<T>::Array(int size):

86: itsSize(size)

87: {

88: pType = new T[size];

89: for (int i = 0; i<size; i++)

90: pType[i] = 0;

91: }

92:

93: // konstruktor kopii[Author ID1: at Mon Nov 12 10:04:00 2001 ]ujący[Author ID1: at Mon Nov 12 10:04:00 2001 ]

94: template <class T>

95: Array<T>::Array(const Array &rhs)

96: {

97: itsSize = rhs.GetSize();

98: pType = new T[itsSize];

99: for (int i = 0; i<itsSize; i++)

100: pType[i] = rhs[i];

101: }

102:

103: void IntFillFunction(Array<int>& theArray);

104: void AnimalFillFunction(Array<Animal>& theArray);

105:

106: int main()

107: {

108: Array<int> intArray;

109: Array<Animal> animalArray;

110: IntFillFunction(intArray);

111: AnimalFillFunction(animalArray);

112: cout << "intArray...\n" << intArray;

113: cout << "\nanimalArray...\n" << animalArray << endl;

114: return 0;

115: }

116:

117: void IntFillFunction(Array<int>& theArray)

118: {

119: bool Stop = false;

120: int offset, value;

121: while (!Stop)

122: {

123: cout << "Podaj indeks (0-9) ";

124: cout << "oraz wartosc. (-1 aby skonczyc): " ;

125: cin >> offset >> value;

126: if (offset < 0)

127: break;

128: if (offset > 9)

129: {

130: cout << "***Prosze uzywac wartosci pomiedzy 0 i 9.***\n";

131: continue;

132: }

133: theArray[offset] = value;

134: }

135: }

136:

137:

138: void AnimalFillFunction(Array<Animal>& theArray)

139: {

140: Animal * pAnimal;

141: for (int i = 0; i<theArray.GetSize(); i++)

142: {

143: pAnimal = new Animal;

144: pAnimal->SetWeight(i*100);

145: theArray[i] = *pAnimal;

146: delete pAnimal; // kopia została umieszczona w tablicy

147: }

148: }

Wynik

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 1 10

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 2 20

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 3 30

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 4 40

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 5 50

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 6 60

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 7 70

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 8 80

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 9 90

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): 10 10

***Prosze uzywac wartosci pomiedzy 0 i 9.***

Podaj indeks (0-9) oraz wartosc. (-1 aby skonczyc): -1 -1

intArray...

[0] 0

[1] 10

[2] 20

[3] 30

[4] 40

[5] 50

[6] 60

[7] 70

[8] 80

[9] 90

animalArray...

[0] 0

[1] 100

[2] 200

[3] 300

[4] 400

[5] 500

[6] 600

[7] 700

[8] 800

[9] 900

Analiza

W celu zaoszczędzenia miejsca, w programie zrezygnowano z większości implementacji klasy Array. Klasa Animal została zadeklarowana w liniach od 7. do 24. Choć jest ona okrojona i uproszczona, posiada własny operator wstawiania (<<), umożliwiający wypisywanie wartości obiektów typu Animal. W tym przypadku wypisywanie wartości polega po prostu na wypisaniu wagi zwierzęcia.

Zauważ, że klasa Animal posiada konstruktor domyślny. Jest to konieczne, ponieważ gdy dodajemy do tablicy obiekt, w celu jego stworzenia używany jest domyślny konstruktor jego klasy. Jak wkrótce zobaczymy, wynikają z tego pewne trudności.

W linii 103. została zadeklarowana funkcja IntFillFunction(). Jej prototyp wskazuje, że ta funkcja przyjmuje tablicę liczb całkowitych. Zwróć też uwagę, że nie jest to funkcja wzorc[Author ID1: at Mon Nov 12 10:04:00 2001 ]a[Author ID1: at Mon Nov 12 10:05:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 10:05:00 2001 ]. IntFillFunction() oczekuje tylko jednego typu tablicy — tablicy wartości całkowitych. W linii 104. funkcja AnimalFillFunction() przyjmuje tablicę Array obiektów Animal.

Implementacje tych funkcji różnią się od siebie, gdyż wypełnianie tablicy wartości całkowitych nie musi odbywać się w ten sam sposób, co wypełnianie tablicy obiektów Animal.

Funkcje specjalizowane

Gdy w listingu 19.5 usuniesz znaki komentarza z instrukcji wypisujących komunikaty w konstruktorach i destruktorach klasy Animal, odkryjesz nowe, dodatkowe wywołania tych konstruktorów i destruktorów.

Gdy obiekt jest dodawany do tablicy, wywoływany jest jego konstruktor domyślny. Jednak konstruktor klasy Array przypisuje wartość 0 każdemu elementowi tablicy (co widzieliśmy w liniach 59. i 60. listingu 19.2).

Gdy piszemy someAnimal = (Animal) 0;, wywołujemy dla obiektu Animal domyślny operator=. To powoduje utworzenie tymczasowego obiektu Animal z użyciem konstruktora przyjmującego wartość całkowitą (zero). Ten obiekt tymczasowy jest używany po prawej stronie operatora=, a następnie jest niszczony.

Jest to niepotrzebne marnotrawstwo czasu, gdyż obiekt Animal jest już odpowiednio zainicjalizowany. Nie możemy jednak usunąć tej linii, gdyż zmienne całkowite nie są automatycznie inicjalizowane wartością zero. W tej sytuacji należy nakzać wzorca[Author ID1: at Mon Nov 12 10:05:00 2001 ]wzorcowi, by nie używał tego konstruktora dla klasy Animal, a zamiast niego użył specjalnego konstruktora tej klasy.

Na listingu 19.6 pokazano, jak możemy dostarczyć jawnej implementacji[Author ID1: at Mon Nov 12 10:05:00 2001 ]o[Author ID1: at Mon Nov 12 10:05:00 2001 ] dla klasy Animal.

Listing 19.6. Specjalne implementacje wzorca

0: #include <iostream>

1: using namespace std;

2:

3: const int DefaultSize = 3;

4:

5: // Standardowa klasa do umieszczania w tablicach

6: class Animal

7: {

8: public:

9: // konstruktory

10: Animal(int);

11: Animal();

12: ~Animal();

13:

14: // akcesory

15: int GetWeight() const { return itsWeight; }

16: void SetWeight(int theWeight) { itsWeight = theWeight; }

17:

18: // zaprzyjaźnione operatory

19: friend ostream& operator<< (ostream&, const Animal&);

20:

21: private:

22: int itsWeight;

23: };

24:

25: // operator ekstrakcji dla wypisywania wartości obiektu klasy Animals

26: ostream& operator<<

27: (ostream& theStream, const Animal& theAnimal)

28: {

29: theStream << theAnimal.GetWeight();

30: return theStream;

31: }

32:

33: Animal::Animal(int weight):

34: itsWeight(weight)

35: {

36: cout << "Animal(int) ";

37: }

38:

39: Animal::Animal():

40: itsWeight(0)

41: {

42: cout << "Animal() ";

43: }

44:

45: Animal::~Animal()

46: {

47: cout << "Destruktor klasy Animal...";

48: }

49:

50: template <class T> // deklaruje wzorzec[Author ID1: at Mon Nov 12 10:06:00 2001 ]wzorzec i parametr

51: class Array // klasa parametryzowana

52: {

53: public:

54: Array(int itsSize = DefaultSize);

55: Array(const Array &rhs);

56: ~Array() { delete [] pType; }

57:

58: // operatory

59: Array& operator=(const Array&);

60: T& operator[](int offSet) { return pType[offSet]; }

61: const T& operator[](int offSet) const

62: { return pType[offSet]; }

63:

64: // akcesory

65: int GetSize() const { return itsSize; }

66:

67: // funkcja zaprzyjaźniona

68: friend ostream& operator<< (ostream&, const Array<T>&);

69:

70: private:

71: T *pType;

72: int itsSize;

73: };

74:

75: template <class T>

76: Array<T>::Array(int size = DefaultSize):

77: itsSize(size)

78: {

79: pType = new T[size];

80: for (int i = 0; i<size; i++)

81: pType[i] = (T)0;

82: }

83:

84: template <class T>

85: Array<T>& Array<T>::operator=(const Array &rhs)

86: {

87: if (this == &rhs)

88: return *this;

89: delete [] pType;

90: itsSize = rhs.GetSize();

91: pType = new T[itsSize];

92: for (int i = 0; i<itsSize; i++)

93: pType[i] = rhs[i];

94: return *this;

95: }

96:

97: template <class T>

98: Array<T>::Array(const Array &rhs)

99: {

100: itsSize = rhs.GetSize();

101: pType = new T[itsSize];

102: for (int i = 0; i<itsSize; i++)

103: pType[i] = rhs[i];

104: }

105:

106:

107: template <class T>

108: ostream& operator<< (ostream& output, const Array<T>& theArray)

109: {

110: for (int i = 0; i<theArray.GetSize(); i++)

111: output << "[" << i << "] " << theArray[i] << endl;

112: return output;

113: }

114:

115:

116: Array<Animal>::Array(int AnimalArraySize):

117: itsSize(AnimalArraySize)

118: {

119: pType = new Animal[AnimalArraySize];

120: }

121:

122:

123: void IntFillFunction(Array<int>& theArray);

124: void AnimalFillFunction(Array<Animal>& theArray);

125:

126: int main()

127: {

128: Array<int> intArray;

129: Array<Animal> animalArray;

130: IntFillFunction(intArray);

131: AnimalFillFunction(animalArray);

132: cout << "intArray...\n" << intArray;

133: cout << "\nanimalArray...\n" << animalArray << endl;

134: return 0;

135: }

136:

137: void IntFillFunction(Array<int>& theArray)

138: {

139: bool Stop = false;

140: int offset, value;

141: while (!Stop)

142: {

143: cout << "Podaj indeks (0-2) ";

144: cout << "oraz wartosc. (-1 aby skonczyc): " ;

145: cin >> offset >> value;

146: if (offset < 0)

147: break;

148: if (offset > 2)

149: {

150: cout << "***Prosze uzywac wartosci pomiedzy 0 i 2.***\n";

151: continue;

152: }

153: theArray[offset] = value;

154: }

155: }

156:

157:

158: void AnimalFillFunction(Array<Animal>& theArray)

159: {

160: Animal * pAnimal;

161: for (int i = 0; i<theArray.GetSize(); i++)

162: {

163: pAnimal = new Animal(i*10);

164: theArray[i] = *pAnimal;

165: delete pAnimal;

166: }

167: }

UWAGA W celu ułatwienia analizy, do wyniku zostały dodane numery linii. Numery te nie są wypisywane przez program.

Wynik

1: Animal() Animal() Animal() Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 0 0

2: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 1 1

3: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 2 2

4: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): -1 -1

5: Animal(int) Destruktor klasy Animal...Animal(int) Destruktor klasy Animal...Animal(int) Destruktor klasy Animal...intArray...

6: [0] 0

7: [1] 1

8: [2] 2

9:

10: animalArray...

11: [0] 0

12: [1] 10

13: [2] 20

14:

15: Destruktor klasy Animal...Destruktor klasy Animal...Destruktor klasy Animal...

16: <<< Drugie uruchomienie >>>

17: Animal() Destruktor klasy Animal...

18: Animal() Destruktor klasy Animal...

19: Animal() Destruktor klasy Animal...

20: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 0 0

21: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 1 1

22: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): 2 2

23: Podaj indeks (0-2) oraz wartosc. (-1 aby skonczyc): -1 -1

24: Animal(int)

25: Destruktor klasy Animal...

26: Animal(int)

27: Destruktor klasy Animal...

28: Animal(int)

29: Destruktor klasy Animal...

30: intArray...

31: [0] 0

32: [1] 1

33: [2] 2

34:

35: animalArray...

36: [0] 0

37: [1] 10

38: [2] 20

39:

40: Destruktor klasy Animal...

41: Destruktor klasy Animal...

42: Destruktor klasy Animal...

Analiza

Na listingu 19.6, z wywołań wypisujących komunikaty, zostały usunięte znaki komentarza, dzięki czemu widać, kiedy są tworzone tymczasowe obiekty Animal. W celu skrócenia zapisu wyników, wartość DefaultSize została zmniejszona do trzech.

Konstruktory i destruktor klasy Animal, zawarte w liniach od 33. do 48., wypisują komunikaty wskazujące moment, kiedy zostały wywołane.

W liniach od 75. do 82. zostaje zadeklarowane działanie konstruktora klasy Array. W liniach od 116. do 120. został zademonstrowany wyspecjalizowany konstruktor dla tablicy Array obiektów typu Animal. Zwróć uwagę, że w tym wyspecjalizowanym konstruktorze do ustawiania domyślnej wartości każdego obiektu Animal jest używany jego konstruktor domyślny i nie jest dokonywane żadne jawne przypisanie.

Przy pierwszym uruchomieniu programu zostaje wypisany pierwszy zestaw wyników. Pierwsza linia wyniku pokazuje,[Author ID1: at Mon Nov 12 10:07:00 2001 ] że w [Author ID1: at Mon Nov 12 10:07:00 2001 ]wyniku tworzenia tablicy są wywoływane trzy konstruktory domyślne. Użytkownik wpisuje cztery liczby, z których trzy są umieszczane w tablicy wartości całkowitych.

Wykonanie programu przechodzi do funkcji AniamFillFunction(). W linii 163. na stercie jest tworzony nowy obiekt tymczasowy, a jego wartość jest w linii 164. używana do zmodyfikowania obiektu Animal w tablicy. W linii 165. ten obiekt tymczasowy jest niszczony. Czynność tę powtarza się dla każdego elementu tablicy, co odzwierciedla piąta linia wyniku.

Pod koniec programu tablice są niszczone i gdy są wywoływane ich destruktory, zostają zniszczone także zawarte w nich obiekty. Odzwierciedla to piętnasta linia wyniku.

Przed drugim uruchomieniem programu (linie wyniku od 17. do 42.) została wykomentowana specjalna implementacja konstruktora tablicy, zawarta w liniach od 116. do 120. Gdy program został uruchomiony ponownie, podczas konstruowania tablicy obiektów Animal nastąpiło więc wywołanie konstruktora wzorca[Author ID1: at Mon Nov 12 10:07:00 2001 ]wzorca, zawartego w liniach od 74. do 81.

To spowodowało, że w liniach 79. i 80. programu,[Author ID1: at Mon Nov 12 10:07:00 2001 ] dla każdego elementu tablicy był tworzony obiekt tymczasowy Animal. Pokazują to linie od 18. do 20. wyniku.

Jak można było oczekiwać, poza tym wyniki obu uruchomień programu nie uległy zmianie.

Wzorce[Author ID1: at Mon Nov 12 10:07:00 2001 ]Wzorce i składowe statyczne

Wzorzec może deklarować statyczne dane składowe. Każdy egzemplarz wzorca[Author ID1: at Mon Nov 12 10:07:00 2001 ]wzorca ma wtedy własny zestaw danych statycznych, po jednym dla każdego typu klasy. Tak więc, gdy dodamy składową statyczną do klasy Array (na przykład licznik określający, ile tablic zostało utworzonych), będziemy mieli jedną taką składową dla każdego typu: jedną dla wszystkich tablic Animal i inną dla wszystkich tablic wartości całkowitych. Na listingu 19.7 do klasy Array dodano daną statyczną i statyczną funkcję składową.

Listing 19.7. Użycie danych statycznych i funkcji składowych wee[Author ID1: at Mon Nov 12 10:08:00 2001 ] wzorcach[Author ID1: at Mon Nov 12 10:08:00 2001 ]wzorcach

0: #include <iostream>

1: using namespace std;

2:

3: const int DefaultSize = 3;

4:

5: // Standardowa klasa do umieszczania w tablicach

6: class Animal

7: {

8: public:

9: // konstruktory

10: Animal(int);

11: Animal();

12: ~Animal();

13:

14: // akcesory

15: int GetWeight() const { return itsWeight; }

16: void SetWeight(int theWeight) { itsWeight = theWeight; }

17:

18: // zaprzyjaźnione operatory

19: friend ostream& operator<< (ostream&, const Animal&);

20:

21: private:

22: int itsWeight;

23: };

24:

25: // operator ekstrakcji dla wypisywania wartości obiektu klasy Animals

26: ostream& operator<<

27: (ostream& theStream, const Animal& theAnimal)

28: {

29: theStream << theAnimal.GetWeight();

30: return theStream;

31: }

32:

33: Animal::Animal(int weight):

34: itsWeight(weight)

35: {

36: //cout << "Animal(int) ";

37: }

38:

39: Animal::Animal():

40: itsWeight(0)

41: {

42: //cout << "Animal() ";

43: }

44:

45: Animal::~Animal()

46: {

47: //cout << "Destruktor klasy Animal...";

48: }

49:

50: template <class T> // deklaruje wzorzec i parametr

51: class Array // parametryzowana klasa

52: {

53: public:

54: // konstruktory

55: Array(int itsSize = DefaultSize);

56: Array(const Array &rhs);

57: ~Array() { delete [] pType; itsNumberArrays--; }

58:

59: // operatory

60: Array& operator=(const Array&);

61: T& operator[](int offSet) { return pType[offSet]; }

62: const T& operator[](int offSet) const

63: { return pType[offSet]; }

64: // akcesory

65: int GetSize() const { return itsSize; }

66: static int GetNumberArrays() { return itsNumberArrays; }

67:

68: // funkcja zaprzyjaźniona

69: friend ostream& operator<< (ostream&, const Array<T>&);

70:

71: private:

72: T *pType;

73: int itsSize;

74: static int itsNumberArrays;

75: };

76:

77: template <class T>

78: int Array<T>::itsNumberArrays = 0;

79:

80: template <class T>

81: Array<T>::Array(int size = DefaultSize):

82: itsSize(size)

83: {

84: pType = new T[size];

85: for (int i = 0; i<size; i++)

86: pType[i] = (T)0;

87: itsNumberArrays++;

88: }

89:

90: template <class T>

91: Array<T>& Array<T>::operator=(const Array &rhs)

92: {

93: if (this == &rhs)

94: return *this;

95: delete [] pType;

96: itsSize = rhs.GetSize();

97: pType = new T[itsSize];

98: for (int i = 0; i<itsSize; i++)

99: pType[i] = rhs[i];

100: }

101:

102: template <class T>

103: Array<T>::Array(const Array &rhs)

104: {

105: itsSize = rhs.GetSize();

106: pType = new T[itsSize];

107: for (int i = 0; i<itsSize; i++)

108: pType[i] = rhs[i];

109: itsNumberArrays++;

110: }

111:

112: template <class T>

113: ostream& operator<< (ostream& output, const Array<T>& theArray)

114: {

115: for (int i = 0; i<theArray.GetSize(); i++)

116: output << "[" << i << "] " << theArray[i] << endl;

117: return output;

118: }

119:

120: int main()

121: {

122: cout << Array<int>::GetNumberArrays() << " tablic typu int\n";

123: cout << Array<Animal>::GetNumberArrays();

124: cout << " tablic typu Animal\n\n";

125: Array<int> intArray;

126: Array<Animal> animalArray;

127:

128: cout << intArray.GetNumberArrays() << " tablic typu int\n";

129: cout << animalArray.GetNumberArrays();

130: cout << " tablic typu Animal\n\n";

131:

132: Array<int> *pIntArray = new Array<int>;

133:

134: cout << Array<int>::GetNumberArrays() << " tablic typu int\n";

135: cout << Array<Animal>::GetNumberArrays();

136: cout << " tablic typu Animal\n\n";

137:

138: delete pIntArray;

139:

140: cout << Array<int>::GetNumberArrays() << " tablic typu int\n";

141: cout << Array<Animal>::GetNumberArrays();

142: cout << " tablic typu Animal\n\n";

143: return 0;

144: }

Wynik

0 tablic typu int

0 tablic typu Animal

1 tablic typu int

1 tablic typu Animal

2 tablic typu int

1 tablic typu Animal

1 tablic typu int

1 tablic typu Animal

Analiza

Do klasy Array dodano w linii 74. statyczną zmienną tsNumberArrays, a ponieważ ta dana jest prywatna, w linii 66. dodano także publiczny akcesor GetNumberArrays().

Inicjalizacja tej danej statycznej odbywa się z użyciem pełnej kwalifikacji wzorca[Author ID1: at Mon Nov 12 10:08:00 2001 ]wzorca, co pokazują linie 77. i 78. Konstruktory oraz destruktor klasy Array modyfikują wartość tej zmiennej tak aby zawsze zawierała poprawną ilość istniejących tablic.

Dostęp do składowych statycznych wee[Author ID1: at Mon Nov 12 10:09:00 2001 ] wzorcu[Author ID1: at Mon Nov 12 10:09:00 2001 ]wzorcu odbywa się tak samo, jak dostęp do składowych statycznych we wszystkich innych klasach: poprzez istniejący obiekt (jak pokazano w liniach 134. i 135.) lub z użyciem pełnej specyfikacji klasy (co pokazano w liniach 128. i 129.). Zwróć uwagę, że odwołując się do statycznej danej musisz użyć tablicy właściwego typu. Dla każdego typu tablicy istnieje jedna zmienna statyczna.

TAK

NIE

W razie potrzeby możesz użyć składowych statycznych wee wzorcach[Author ID1: at Mon Nov 12 10:09:00 2001 ] wzorcach.

Używaj parametrów w funkcjach wz[Author ID1: at Mon Nov 12 10:10:00 2001 ]orca[Author ID1: at Mon Nov 12 10:11:00 2001 ]wzorcowych[Author ID1: at Mon Nov 12 10:11:00 2001 ], aby zawęzić ich egzemplarze tak, aby były bezpieczne (ze względu na typy).

Standardowa biblioteka wzorcówtypów[Author ID1: at Mon Nov 12 10:15:00 2001 ]

Nowością w języku C++ jest zaadoptowanie Standardowej Biblioteki Typów (STL, Standard Type[Author ID1: at Mon Nov 12 10:16:00 2001 ]Template[Author ID1: at Mon Nov 12 10:16:00 2001 ] Library). Obecnie biblioteka ta jest obsługiwana we wszystkich ważniejszych kompilatorach. STL jest biblioteką klas kontenerów opartych na wzorcach[Author ID1: at Mon Nov 12 10:17:00 2001 ]wzorcach, obejmuje ona wektory, listy, kolejki i stosy. STL zawiera także kilka standardowych algorytmów, takich jak sortowanie i wyszukiwanie.

STL jest alternatywą dla „ponownego wymyślania koła”, przynajmniej w przypadku wymienionych tu standardowych zastosowań. Biblioteka STL jest dokładnie przetestowana, zapewnia wysoką wydajność i jest darmowa. Ponadto, STL nadaje się do ponownego wykorzystania.[Author ID1: at Mon Nov 12 10:18:00 2001 ];[Author ID1: at Mon Nov 12 10:18:00 2001 ] [Author ID1: at Mon Nov 12 11:10:00 2001 ]g[Author ID1: at Mon Nov 12 10:18:00 2001 ] [Author ID1: at Mon Nov 12 11:10:00 2001 ]G[Author ID1: at Mon Nov 12 10:18:00 2001 ]dy zrozumiesz już, jak działa kontener STL, możesz go wykorzystyw[Author ID1: at Mon Nov 12 10:19:00 2001 ]ać we wszystkich swoich programach, nie zastanawiając[Author ID1: at Mon Nov 12 10:18:00 2001 ]trudząc[Author ID1: at Mon Nov 12 10:18:00 2001 ] się za każdym razem jak go użyć[Author ID1: at Mon Nov 12 10:18:00 2001 ]nad tworzeniem go od nowa[Author ID1: at Mon Nov 12 10:18:00 2001 ].

Kontenery

Kontener jest obiektem przechowującym inne obiekty. Biblioteka standardowa C++ zawiera serię klas kontenerów stanowiących wydajne narzędzie, pomagające programistom C++ w obsłudze standardowych zadań programu. Klasy kontenerów zawarte w STL dzielą się na sekwencyjne i asocjatyw[Author ID1: at Mon Nov 12 10:20:00 2001 ]cyj[Author ID1: at Mon Nov 12 10:20:00 2001 ]ne. Kontenery sekwencyjne zostały zaprojektowane w celu zapewnienia sekwencyjnego oraz swobodnego dostępu do swoich elementów. Kontenery asocjacyjne są zoptymalizowane do dostępu do swoich elementów poprzez tak zwane klucze. Tak jak w przypadku wszystkich innych komponentów biblioteki standardowej C++, bibliotekę STL można przenosić pomiędzy różnymi systemami operacyjnymi. Wszystkie klasy kontenerów STL są zdefiniowane w przestrzeni nazw std.

Kontenery sekwencyjne

Kontenery sekwencyjne, znajdujące się w standardowej bibliotece ty[Author ID1: at Mon Nov 12 10:20:00 2001 ]pów[Author ID1: at Mon Nov 12 10:20:00 2001 ]wzorców, zapewniają efektywny sekwencyjny dostęp do listy obiektów. Biblioteka standardowa C++ zawiera trzy kontenery sekwencyjne: vector, list oraz deque.

Kontener vector

Do przechowywania elementów często wykorzystuje się tablice. Wszystkie elementy w tablicy mają ten sam typ i są dostępne poprzez swoje indeksy. STL zawiera klasę kontenera vector (wektor), która zachowuje się jak tablica, lecz jest bardziej elastyczna i bezpieczniejsza w użyciu niż standardowa tablica C++.

vector jest kontenerem zoptymalizowanym w celu zapewnienia szybkiego dostępu do elementu na podstawie jego indeksu. Klasa kontenera vector jest zdefiniowana w pliku nagłówkowym <vector> w przestrzeni nazw <std> (więcej informacji na temat przestrzeni nazw znajdziesz w rozdziale 18., „Przestrzenie nazw”). Wektor może, w miarę potrzeby, zwiększać objętość. Przypuśćmy, że stworzyliśmy wektor mogący zmieścić dziesięć elementów. Gdy wypełnimy go dziesięcioma obiektami, będzie pełny. Gdy następnie dodamy do niego kolejny obiekt, wektor automatycznie zwiększy swoją objętość tak, aby zmieścić jedenasty element. Klasa vector jest zdefiniowana następująco:

template <class T, class A = allocator<T>> class vector

{

// składowe klasy

};

Pierwszy argument (class T) jest typem elementów w wektorze. Drugi argument (class A) jest klasą alokatora. Alokatory są menedżerami pamięci odpowiedzialnymi za alokację i dealokację pamięci dla elementów przechowywanych w kontenerze. Koncepcja alokatorów i ich implementacja są bardziej skomplikowanymi zagadnieniami i wykraczają poza zakres tej książki.

Domyślnie, elementy są tworzone za pomocą operatora new() i są zwalniane za pomocą operatora delete(). Oznacza to, że do stworzenia nowego elementu jest wywoływany domyślny konstruktor klasy T. To kolejny argument przemawiający za tym, by jawnie definiować domyślne konstruktory dla własnych klas. Gdy tego nie uczynimy, nie będziemy mogli przechowywać egzemplarzy obiektów swojej klasy w standardowych kontenerach.

Wektory zawierające wartości całkowite i wartości zmiennopozyc[Author ID1: at Mon Nov 12 10:20:00 2001 ]yjne[Author ID1: at Mon Nov 12 10:21:00 2001 ]rzecinkowe[Author ID1: at Mon Nov 12 10:21:00 2001 ] można zdefiniować następująco:

vector<int> vInts; // wektor zawierający elementy typu int

vector<float> vFloat; // wektor zawierający elementy typu float

Zwykle mamy pewne pojęcie o tym, ile elementów może zawierać wektor. Na przykład, przypuśćmy, że w naszej szkole maksymalna ilość uczniów w klasie nie przekracza pięćdziesięciu. Aby stworzyć wektor uczniów w klasie, chcemy, aby był on na tyle duży, by zmieścił pięćdziesiąt elementów. Klasa standardowego wektora posiada konstruktor pozwalający na określenie początkowej ilości elementów. Tak więc wektor dla pięćdziesięciu uczniów w klasie możemy zdefiniować następująco:

vector<Student> MathClass(50);

Kompilator zaalokuje pamięć wystarczającą dla zmieszczenia pięćdziesięciu uczniów; każdy element zostanie stworzony za pomocą domyślnego konstruktora Student:: Student().

Aktualna ilość przechowywanych w wektorze elementów może zostać odczytana funkcją size() (rozmiar). W tym przykładzie vStudent.size() zwróci wartość 50.

Inna funkcja składowa, capacity() (pojemność), informuje nas dokładnie, ile elementów może zostać zmieszczonych w tablicy, zanim konieczne będzie zwiększenie jej rozmiaru. Zajmiemy się tym później.

Mówimy, że wektor jest pusty, gdy nie zawiera on żadnego elementu, tj. gdy jego rozmiar wynosi zero. Aby ułatwić sprawdzenie, czy wektor jest pusty, klasa vector posiada funkcję składową empty(), która zwraca wartość true wtedy, gdy wektor jest pusty.

Aby przypisać obiekt typu [Author ID1: at Mon Nov 12 10:21:00 2001 ]Student o nazwie Harry do elementu wektora MathClass, możemy użyć operatora indeksu []:

MathClass[5] = Harry;

Indeksy są liczone od zera. Jak być może zauważyłeś, do przypisania obiektu Harry do szóstego elementu wektora został użyty przeciążony operator= klasy Student. Aby sprawdzić wiek Harryego, możemy odwołać się do jego danych, używając:

MathClass[5].GetAge();

Jak już wspominałem, gdy do wektora zostanie dodanych więcej elementów niż może zmieścić, wektor automatycznie zwiększa swoją pojemność. Przypuśćmy na przykład, że jedna z klas stała się tak popularna, że ilość jej uczniów przekroczyła pięćdziesiąt. Cóż, może w przypadku klasy o profilu matematycznym jest to mało prawdopodobne, ale kto wie, wszystko może się zdarzyć. Gdy do klasy MathClass zostanie dodana pięćdziesiąta pierwsza osoba, Sally, kompilator rozszerzy tablicę.

Element może zostać dodany do wektora na kilka sposobów; jednym z nich jest użycie funkcji push_back():

MathClass.push_back(Sally);

Ta funkcja składowa dołącza nowy obiekt Student::Sally do końca wektora MathClass. W tym momencie w wektorze MathClass mamy pięćdziesiąt jeden elementów, a Sally znajduje się w elemencie MathClass[50].

Aby ta funkcja mogła działać, nasza klasa Student musi posiadać zdefiniowany konstruktor kopii[Author ID1: at Mon Nov 12 10:22:00 2001 ]ujący[Author ID1: at Mon Nov 12 10:22:00 2001 ]. W przeciwnym razie funkcja push_back() nie będzie mogła stworzyć kopii obiektu Sally.

STL nie określa maksymalnej ilość elementów w wektorze; decyzję tę może podjąć producent kompilatora. Klasa vector posiada jednak specjalną funkcję, zwracającą wartość tej „magicznej” dla twojego kompilatora liczby; jest nią funkcja[Author ID1: at Mon Nov 12 10:22:00 2001 ]ą[Author ID1: at Mon Nov 12 10:22:00 2001 ] max_size().

Listing 19.8 demonstruje omówione dotąd składowe klasy vector. W listingu tym została użyta standardowa klasa string, ułatwiająca posługiwanie się łańcuchami znaków. Więcej informacji na temat tej klasy znajdziesz w dokumentacji swojego kompilatora.

Listing 19.8. Tworzenie wektora i dostęp do jego elementów

0: #include <iostream>

1: #include <string>

2: #include <vector>

3: using namespace std;

4:

5: class Student

6: {

7: public:

8: Student();

9: Student(const string& name, const int age);

10: Student(const Student& rhs);

11: ~Student();

12:

13: void SetName(const string& name);

14: string GetName() const;

15: void SetAge(const int age);

16: int GetAge() const;

17:

18: Student& operator=(const Student& rhs);

19:

20: private:

21: string itsName;

22: int itsAge;

23: };

24:

25: Student::Student()

26: : itsName("Nowy uczen"), itsAge(16)

27: {}

28:

29: Student::Student(const string& name, const int age)

30: : itsName(name), itsAge(age)

31: {}

32:

33: Student::Student(const Student& rhs)

34: : itsName(rhs.GetName()), itsAge(rhs.GetAge())

35: {}

36:

37: Student::~Student()

38: {}

39:

40: void Student::SetName(const string& name)

41: {

42: itsName = name;

43: }

44:

45: string Student::GetName() const

46: {

47: return itsName;

48: }

49:

50: void Student::SetAge(const int age)

51: {

52: itsAge = age;

53: }

54:

55: int Student::GetAge() const

56: {

57: return itsAge;

58: }

59:

60: Student& Student::operator=(const Student& rhs)

61: {

62: itsName = rhs.GetName();

63: itsAge = rhs.GetAge();

64: return *this;

65: }

66:

67: ostream& operator<<(ostream& os, const Student& rhs)

68: {

69: os << rhs.GetName() << " ma " << rhs.GetAge() << " lat(a)";

70: return os;

71: }

72:

73: template<class T>

74: // wyświetla właściwości wektora

75: void ShowVector(const vector<T>& v);

76:

77: typedef vector<Student> SchoolClass;

78:

79: int main()

80: {

81: Student Harry;

82: Student Sally("Sally", 15);

83: Student Bill("Bill", 17);

84: Student Peter("Peter", 16);

85:

86: SchoolClass EmptyClass;

87: cout << "EmptyClass:\n";

88: ShowVector(EmptyClass);

89:

90: SchoolClass GrowingClass(3);

91: cout << "GrowingClass(3):\n";

92: ShowVector(GrowingClass);

93:

94: GrowingClass[0] = Harry;

95: GrowingClass[1] = Sally;

96: GrowingClass[2] = Bill;

97: cout << "GrowingClass(3) po przypisaniu uczniow:\n";

98: ShowVector(GrowingClass);

99:

100: GrowingClass.push_back(Peter);

101: cout << "GrowingClass() po dodaniu czwartego ucznia:\n";

102: ShowVector(GrowingClass);

103:

104: GrowingClass[0].SetName("Harry");

105: GrowingClass[0].SetAge(18);

106: cout << "GrowingClass() po wywolaniu funkcji Set\n:";

107: ShowVector(GrowingClass);

108:

109: return 0;

110: }

111:

112: //

113: // Wyświetla właściwości wektora

114: //

115: template<class T>

116: void ShowVector(const vector<T>& v)

117: {

118: cout << "max_size() = " << v.max_size();

119: cout << "\tsize() = " << v.size();

120: cout << "\tcapacity() = " << v.capacity();

121: cout << "\t" << (v.empty()? "pusta": "nie pusta");

122: cout << "\n";

123:

124: for (int i = 0; i < v.size(); ++i)

125: cout << v[i] << "\n";

126:

127: cout << endl;

128: }

Wynik

EmptyClass:

max_size() = 214748364 size() = 0 capacity() = 0 pusta

GrowingClass(3):

max_size() = 214748364 size() = 3 capacity() = 3 nie pusta

Nowy uczen ma 16 lat

Nowy uczen ma 16 lat

Nowy uczen ma 16 lat

GrowingClass(3) po przypisaniu uczniow:

max_size() = 214748364 size() = 3 capacity() = 3 nie pusta

Nowy uczen ma 16 lat

Sally ma 15 lat

Bill ma 17 lat

GrowingClass() po dodaniu czwartego ucznia:

max_size() = 214748364 size() = 4 capacity() = 6 nie pusta

Nowy uczen ma 16 lat

Sally ma 15 lat

Bill ma 17 lat

Peter ma 16 lat

GrowingClass() po wywolaniu funkcji Set

max_size() = 214748364 size() = 4 capacity() = 6 nie pusta

Harry ma 18 lat

Sally ma 15 lat

Bill ma 17 lat

Peter ma 16 lat

Analiza

Klasa Student jest zdefiniowana w liniach od 5. do 23. Implementacje jej funkcji składowych są zawarte w liniach od 25. do 65. Klasa ta jest prosta i przyjazna dla kontenerów. Z powodów omawianych wcześniej, zdefiniowaliśmy w niej domyślny konstruktor, konstruktor kopii[Author ID1: at Mon Nov 12 10:29:00 2001 ]ujący[Author ID1: at Mon Nov 12 10:29:00 2001 ] oraz przeciążony operator przypisania. Zwróć uwagę, że jej zmienna składowa itsName została zdefiniowana jako egzemplarz klasy string języka C++. Jak widać, z łańcuchami C++ pracuje się dużo łatwiej niż z łańcuchami char* w stylu języka C.

W liniach 73. i 75. jest zadeklarowana funkcja wzorca[Author ID1: at Mon Nov 12 10:29:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 10:29:00 2001 ] ShowVector(); jej definicja znajduje się w liniach od 115. do 128. Demonstruje ona użycie niektórych funkcji składowych wektora: max_size(), size(), capacity() oraz empty(). Jak widać w wynikach, maksymalna ilość obiektów typu [Author ID1: at Mon Nov 12 10:30:00 2001 ]Student, jaką może pomieścić wektor w Visual C++, wynosi 214 748 364. Dla elementów innego typu wartość ta może być inna. Na przykład, wektor wartości typu int może zawierać do 1 073 741 823 elementów. Jeśli używasz innych kompilatorów, wartości te mogą być inne.

W liniach 124. i 125. „przechodzimy” przez każdy element w wektorze i wyświetlan[Author ID1: at Mon Nov 12 10:30:00 2001 ]m[Author ID1: at Mon Nov 12 10:30:00 2001 ]y jego wartość, używając przeciążonego operatora wstawiania <<, zdefiniowanego w liniach od 67. do 71.

W liniach od 81. do 84. tworzonych jest czterech uczniów. W linii 86. zostaje zdefiniowany pusty wektor o nazwie EmptyClass (pusta klasa). Jest on tworzony za pomocą domyślnego konstruktora klasy vector. Gdy wektor jest tworzony w ten sposób, kompilator nie alokuje dla niego żadnej pamięci. Jak widać w wynikach wypisywanych przez funkcję ShowVector(EmptyClass), jego rozmiar i pojemność wynoszą zero.

W linii 90. zostaje zdefiniowany wektor mogący zmieścić trzech uczniów. Jak można było oczekiwać, zarówno jego rozmiar, jak i pojemność mają wartość 3. Elementy tego wektora (GrowingClass) są wypełniane obiektami w liniach od 94. do 96.; wykorzystujemy do tego operator [].

W linii 100. do wektora jest dodawany czwarty uczeń, Peter. To powoduje zwiększenie rozmiaru wektora do czterech. Co ciekawe, jego objętość faktycznie zwiększa się do sześciu. To oznacza, że kompilator zaalokował miejsce wystarczające do zmieszczenia sześciu obiektów typu Student. Ponieważ wektory muszą być alokowane w ciągłym bloku pamięci, rozszerzanie ich wymaga wykonania całego zestawu operacji. Najpierw alokowany jest nowy blok pamięci, wystarczający do zmieszczenia wszystkich czterech obiektów Student. Następnie do nowo zaalokowanej pamięci są kopiowane trzy elementy; po trzecim elemencie zostaje dołączony czwarty element. Na koniec zostaje zwolniony blok pamięci zajmowany pierwotnie. Gdy w wektorze znajduje się wiele elementów, ten proces alokacji i dealokacji może zajmować dużo czasu. W związku z tym kompilator przyjmuje strategię redukującą częstotliwość przeprowadzania tak kosztownych operacji. W tym przykładzie, gdy dodamy do wektora jeden czy dwa nowe obiekty, nie będzie potrzeby dealokowania i ponownego alokowania pamięci.

W liniach 104. i 105. ponownie używamy operatora indeksu [] w celu zmiany zmiennych składowych pierwszego elementu w wektorze GrowingClass.

TAK

NIE

Zdefiniuj też konstruktor kopii[Author ID1: at Mon Nov 12 10:32:00 2001 ]ujący[Author ID1: at Mon Nov 12 10:32:00 2001 ] dla takiej klasy.

Zdefiniuj dla niej także przeciążony operator przypisania.

Klasa kontenera vector posiada jeszcze inne funkcje składowe. Funkcja front() zwraca referencję do pierwszego elementu wektora. Funkcja back() zwraca referencję do ostatniego elementu. Funkcja at() działa jak operator indeksu []. Jest jednak bardziej bezpieczna, gdyż sprawdza, czy przekazany jej indeks pasuje do zakresu dostępnych elementów. Jeśli indeks jest poza zakresem, funkcja zgłasza wyjątek out_of_range (poza zakresem; wyjątki zostaną omówione w następnym rozdziale).

Funkcja insert() wstawia jeden lub więcej elementów we wskazane miejsce w wektorze. Funkcja pop_back() usuwa z wektora ostatni element. Na koniec, funkcja remove() usuwa z wektora jeden lub więcej elementów.

Kontener list

Lista (list) jest kontenerem przeznaczonym do zoptymalizowania częstych operacji wstawiania i usuwania elementów.

Klasa kontenera list w STL jest zdefiniowana w pliku nagłówkowym <list> w przestrzeni nazw std. Klasa list jest zaimplementowana jako lista połączona podwójnie, w której każdy węzeł posiada dowiązania do poprzedniego i następnego węzła listy.

Klasa list posiada wszystkie funkcje składowe oferowane przez klasę vector. Jak widzieliśmy w programie podsumowującym wiadomości w rozdziale 14., po liście możemy poruszać się podążając za łączami zawartymi w każdym z węzłów. Zwykle takie łącza są zaimplementowane za pomocą wskaźników. Standardowy kontener list wykorzystuje w tym celu mechanizm zwany iteratorem.

Iterator jest uogólnieniem wskaźnika. Aby uzyskać węzeł wskazywany przez iterator, możemy wyłuskać go z tego iteratora. Listing 19.9 przedstawia użycie iteratorów w celu uzyskania dostępu do węzłów listy.

Listing 19.9. „Przechodzenie[Author ID1: at Mon Nov 12 10:33:00 2001 ]jście[Author ID1: at Mon Nov 12 10:33:00 2001 ]przez [Author ID1: at Mon Nov 12 10:33:00 2001 ]listę[Author ID1: at Mon Nov 12 10:33:00 2001 ]y[Author ID1: at Mon Nov 12 10:33:00 2001 ] za pomocą iteratora

0: #include <iostream>

1: #include <list>

2: using namespace std;

3:

4: typedef list<int> IntegerList;

5:

6: int main()

7: {

8: IntegerList intList;

9:

10: for (int i = 1; i <= 10; ++i)

11: intList.push_back(i * 2);

12:

13: for (IntegerList::const_iterator ci = intList.begin();

14: ci != intList.end(); ++ci)

15: cout << *ci << " ";

16:

17: return 0;

18: }

Wynik

2 4 6 8 10 12 14 16 18 20

Analiza

W linii 8. zmienna intList zostaje zdefiniowana jako lista elementów typu int. W liniach 10. i 11., za pomocą funkcji push_back(), do listy zostaje dodanych dziesięć dodatnich, parzystych wartości.

W liniach od 13. do 15. odwołujemy się do każdego węzła listy, używając iteratora const_iterator[Author ID1: at Mon Nov 12 10:33:00 2001 ]. Oznacza to, że poprzez ten iterator nie chcemy zmieniać węzłów. Gdybyśmy chcieli zmienić węzeł wskazywany przez iterator, musielibyśmy zamiast tego użyć operatora nie const:

intList::iterator

Funkcja składowa begin() zwraca iterator wskazujący na pierwszy węzeł listy. Jak widać, do przeniesienia iteratora na następny węzeł możemy użyć operatora ++. Funkcja składowa end() jest dość niezwykła — zwraca iterator wskazujący o jeden węzeł za koniec listy. Musimy więc dopilnować, by nasz iterator nie osiągał węzła wskazywanego prze end().

Iterator jest wyłuskiwany tak samo jak wskaźnik i zwraca wskazywany przez siebie węzeł, co widzimy w linii 15.

Choć iteratory zostały tu przedstawione wraz z klasą list, są one jednak dostarczane także przez klasę vector. Oprócz funkcji przedstawionych dla klasy vector, klasa list posiada także funkcje push_front() oraz pop_front(), które działają podobnie jak funkcje push_back() i pop_back(), z tym że nie dodają i nie usuwają elementu na końcu listy, ale na jej początku.

Kontener deque

Kontener deque przypomina podwójnie zakończony wektor — podobnie jak klasa vector, zapewnia on efektywność operacji sekwencyjnego zapisu i odczytu. Jednak oprócz tego, klasa kontenera deque optymalizuje operacje na końcach wektora. Te operacje są zaimplementowane podobnie jak w klasie kontenera list, w której alokacje pamięci odbywają się tylko w przypadku nowych elementów. Ta właściwość klasy deque eliminuje potrzebę realokowania całego kontenera do nowego bloku pamięci, co jest jedną z wad klasy vector. W związku z tym kontenery tego typu doskonale nadają się do aplikacji, w których wstawianie i usuwanie elementów dotyczy któregoś z końców wektora i w których ważny jest sekwencyjny dostęp do elementów. Przykładem takiej aplikacji może być symulator zestawiania składów pociągów, w którym wagony mogą być dołączane na początku lub na końcu składu.

Stosy

Stos jest jedną ze struktur najczęściej wykorzystywanych przy programowaniu komputerów. Nie został on jednak zaimplementowany jako osobna klasa kontenera, ale jako klasa pośrednia dla klasy kontenera. Klasa wzorca[Author ID1: at Mon Nov 12 10:34:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 10:34:00 2001 ] stack (stos) jest zdefiniowana w pliku nagłówkowym <stack> w przestrzeni nazw std.

Stos jest ciągłym, zaalokowanym blokiem, który może się rozszerzać i kurczyć na jednym końcu. Elementy znajdujące się na stosie są dostępne tylko poprzez jego koniec. Podobne właściwości zauważyliśmy w kontenerach sekwencyjnych, a mianowicie w kontenerach vector i deque. W rzeczywistości, do zaimplementowania stosu może zostać użyty każdy kontener sekwencyjny, obsługujący operacje back(), push_back() oraz pop_back(). W przypadku stosu większość innych metod kontenerów nie jest potrzebna i w związku z tym klasa stack ich nie udostępnia.

Klasa wzorca stack w STL jest zaprojektowana tak, by mogła przechowywać dowolny typ obiektów, jednak by wszystkie elementy na stosie muszą być tego samego typu.

Stos jest strukturą LIFO (last in — first out, pierwszy wchodzi — ostatni wychodzi). Przypomina nieco zatłoczoną windę: pierwsza osoba wchodząca do windy przesuwa się w stronę ściany, zaś ostatnia osoba stoi tuż przy drzwiach. Gdy winda dotrze na dane piętro, wtedy ostatnią wychodzącą osobą jest ta, która weszła do windy pierwsza. Jeśli któraś z osób chce wysiąść na którymś z wcześniejszych pięter, muszą wyjść wszystkie te osoby, które stoją pomiędzy nią a drzwiami windy i dopiero potem mogą wejść z powrotem.

Zgodnie z konwencją, otwarty koniec nazywa się szczytem stosu, zaś operacje wykonywane na stosie nazywane są odkładaniem (push) i zdejmowaniem (pop). Te konwencjonalne określenia przejęła klasa stack.

UWAGA Klasa stack w STL nie przypomina mechanizmu stosu używanego przez kompilatory i systemy operacyjne, w których stosy mogą zawierać obiekty różnych rodzajów. Jednak sama zasada działania stosu jest bardzo podobna.

Kolejki

Kolejka jest kolejną, powszechnie wykorzystywaną w programowaniu strukturą. Elementy są dodawane do jednego końca kolejki, a pobierane są z drugiego. Możemy zastosować prostą analogię: stos przypomina stos talerzy w restauracji. Talerze dodaje się do stosu, kładąc je zawsze na górze; zabiera się je także z góry (tzn. pierwszy zostaje zabrany talerz odłożony na stos ostatnio).

Kolejka przypomina kolejkę w sklepie. Stajesz na końcu kolejki i wychodzisz na jej początku. Nazywa się to strukturą FIFO (first in — first out, pierwszy wchodzi — pierwszy wychodzi); stos jest strukturą LIFO.

Podobnie jak stos, kolejka jest zaimplementowana jako klasa pośrednia dla kontenera. Kontener musi obsługiwać operacje front(), back(), push_back() oraz pop_front().

Kontenery asocjacyjne

O ile kontenery sekwencyjne są zaprojektowane do sekwencyjnego i swobodnego dostępu do elementów z użyciem indeksu lub operatora, o tyle kontenery asocjacyjne są zaprojektowane dla szybkiego swobodnego dostępu z wykorzystaniem kluczy. Biblioteka standardowa C++ zawiera cztery kontenery asocjacyjne: map, multimap, set oraz multiset.

Kontener map

Widzieliśmy że wektor jest jakby rozszerzoną wersją tablicy. Posiada wszystkie charakterystyki tablicy oraz kilka dodatkowych właściwości. Niestety, wektory przejęły także jedną z poważniejszych wad tablic, a mianowicie brak mechanizmu swobodnego dostępu z użyciem kluczy innych niż indeksy lub iteratory. Z drugiej strony, kontenery asocjacyjne zapewniają właśnie szybki swobodny dostęp z wykorzystaniem kluczy.

Biblioteka standardowa C++ zawiera cztery kontenery asocjacyjne: map (mapa), multimap (multimapa), set (zestaw) oraz multiset (multizestaw). Następny listing pokazuje sposób zaimplementowania przy pomocy mapy naszego szkolnego przykładu z listingu 19.8.

Listing 19.8. Klasa kontenera mapy

0: #include <iostream>

1: #include <string>

2: #include <map>

3: using namespace std;

4:

5: class Student

6: {

7: public:

8: Student();

9: Student(const string& name, const int age);

10: Student(const Student& rhs);

11: ~Student();

12:

13: void SetName(const string& name);

14: string GetName() const;

15: void SetAge(const int age);

16: int GetAge() const;

17:

18: Student& operator=(const Student& rhs);

19:

20: private:

21: string itsName;

22: int itsAge;

23: };

24:

25: Student::Student()

26: : itsName("Nowy uczen"), itsAge(16)

27: {}

28:

29: Student::Student(const string& name, const int age)

30: : itsName(name), itsAge(age)

31: {}

32:

33: Student::Student(const Student& rhs)

34: : itsName(rhs.GetName()), itsAge(rhs.GetAge())

35: {}

36:

37: Student::~Student()

38: {}

39:

40: void Student::SetName(const string& name)

41: {

42: itsName = name;

43: }

44:

45: string Student::GetName() const

46: {

47: return itsName;

48: }

49:

50: void Student::SetAge(const int age)

51: {

52: itsAge = age;

53: }

54:

55: int Student::GetAge() const

56: {

57: return itsAge;

58: }

59:

60: Student& Student::operator=(const Student& rhs)

61: {

62: itsName = rhs.GetName();

63: itsAge = rhs.GetAge();

64: return *this;

65: }

66:

67: ostream& operator<<(ostream& os, const Student& rhs)

68: {

69: os << rhs.GetName() << " ma " << rhs.GetAge() << " lat";

70: return os;

71: }

72:

73: template<class T, class A>

74: void ShowMap(const map<T, A>& v); // wyświetla właściwości mapy

75:

76: typedef map<string, Student> SchoolClass;

77:

78: int main()

79: {

80: Student Harry("Harry", 18);

81: Student Sally("Sally", 15);

82: Student Bill("Bill", 17);

83: Student Peter("Peter", 16);

84:

85: SchoolClass MathClass;

86: MathClass[Harry.GetName()] = Harry;

87: MathClass[Sally.GetName()] = Sally;

88: MathClass[Bill.GetName()] = Bill;

89: MathClass[Peter.GetName()] = Peter;

90:

91: cout << "MathClass:\n";

92: ShowMap(MathClass);

93:

94: cout << "Wiemy ze " << MathClass["Bill"].GetName()

95: << " ma " << MathClass["Bill"].GetAge() << " lat\n";

96:

97: return 0;

98: }

99:

100: //

101: // wyświetla właściwości mapy

102: //

103: template<class T, class A>

104: void ShowMap(const map<T, A>& v)

105: {

106: for (map<T, A>::const_iterator ci = v.begin();

107: ci != v.end(); ++ci)

108: cout << ci->first << ": " << ci->second << "\n";

109:

110: cout << endl;

111: }

Wynik

MathClass:

Bill: Bill ma 17 lat

Harry: Harry ma 18 lat

Peter: Peter ma 16 lat

Sally: Sally ma 15 lat

Wiemy ze Bill ma 17 lat

Analiza

W linii 2. dołączyliśmy plik nagłówkowy <map>, gdyż będziemy używać standardowej klasy kontenera map. W związku z tym definiujemy funkcję wz[Author ID1: at Mon Nov 12 10:38:00 2001 ]orca[Author ID1: at Mon Nov 12 10:39:00 2001 ]wzorco[Author ID1: at Mon Nov 12 10:39:00 2001 ] ShowMap, wyświetlającą elementy w mapie. W linii 76. typ SchoolClass jest definiowany jako mapa elementów; każdy z nich stanowi parę składającą się z klucza i wartości. Pierwsza wartość w parze jest kluczem. W naszej klasie SchoolClass jako nazw kluczy użyliśmy imion uczniów, które są łańcuchami znaków. Klucze elementów w kontenerze mapy muszą być unikalne, tzn. dwa elementy nie mogą mieć tego samego klucza. Drugą wartością w parze jest samo[Author ID1: at Mon Nov 12 10:41:00 2001 ] obiekt; w tym przykładzie jest to obiekt klasy Student. Typ pary danych jest w STL zaimplementowany jako struktura zawierająca dwie składowe, mianowicie first (pierwsza) oraz second (druga). Możemy używać tych składowych w celu uzyskania dostępu do klucza i wartości zawartych w węźle.

Pominiemy funkcję main() i najpierw przyjrzymy się funkcji ShowMap(). Ta funkcja, odwołując się do obiektu mapy, używa iteratora const. W linii 108. ci->first wskazuje klucz (nazwę ucznia), a ci->second wskazuje wartość (obiekt klasy Student).

Wróćmy do linii od 80. do 83. Są w nich tworzone cztery obiekty klasy Student. W linii 85. zostaje zdefiniowany obiekt MathClass, będący egzemplarzem naszego kontenera SchoolClass. W liniach od 86. do 89. dodajemy do kontenera MathClass czterech uczniów, korzystając przy tym z następującej składni:

obiekt_mapy[wartość_klucza] = wartość_obiektu;

W celu dodania pary (klucz, wartość) do mapy moglibyśmy także użyć funkcji push_back() lub insert(); więcej szczegółów na ten temat znajdziesz w dokumentacji swojego kompilatora.

Po dodaniu do mapy wszystkich uczniów, możemy odwoływać się do nich poprzez ich wartości kluczy. W liniach 94. i 95. używamy MathClass["Bill"], aby odwołać się do danych Billa.

Inne kontenery asocjacyjne

Klasa kontenera multimap jest klasą map, która nie wymaga, by klucze były unikalne. Tę samą wartość klucza może mieć więcej niż jeden element.

Klasa kontenera set także przypomina klasę mapy; jednak jej elementami nie są pary (klucz, wartość), lecz same klucze.

Llasa kontenera multiset jest klasą set pozwalającą na występowanie powielonych kluczy.

Klasy algorytmów

Kontener to dobre miejsce do przechowywania sekwencji elementów. Wszystkie kontenery standardowe definiują operacje manipulowania kontenerami i ich elementami. Implementowanie wszystkich tych operacji we własnych sekwencjach może być pracochłonne i podatne na błędy. Ponieważ w przypadku większości sekwencji wykonywane operacje są takie same, zastosowanie zestawu ogólnych algorytmów może zredukować potrzebę tworzenia własnych operacji dla każdego nowego kontenera. Biblioteka standardowa zawiera około sześćdziesięciu standardowych algorytmów wykonujących większość podstawowych i powszechnie używanych operacji na kontenerach.

Te standardowe algorytmy są zdefiniowane w pliku nagłówkowym <algorithm> w przestrzeni nazw std.

Aby zrozumieć, jak działają standardowe algorytmy, musimy poznać koncept[Author ID1: at Mon Nov 12 10:42:00 2001 ] pojęcie [Author ID1: at Mon Nov 12 10:42:00 2001 ]obiektów funkcyjnych[Author ID1: at Mon Nov 12 10:42:00 2001 ]ji[Author ID1: at Mon Nov 12 10:42:00 2001 ]. Obiekt funkcji[Author ID1: at Mon Nov 12 10:42:00 2001 ]yjny[Author ID1: at Mon Nov 12 10:42:00 2001 ] jest egzemplarzem klasy definiującej przeciążony operator (). W związku z tym może on być wywołany jako funkcja. Listing 19.11 demonstruje obiekt funkcji[Author ID1: at Mon Nov 12 10:43:00 2001 ]yjny[Author ID1: at Mon Nov 12 10:43:00 2001 ].

Listing 19.11. Obiekt funkcji

0: #include <iostream>

1: using namespace std;

2:

3: template<class T>

4: class Print {

5: public:

6: void operator()(const T& t)

7: {

8: cout << t << " ";

9: }

10: };

11:

12: int main()

13: {

14: Print<int> DoPrint;

15: for (int i = 0; i < 5; ++i)

16: DoPrint(i);

17: return 0;

18: }

Wynik

0 1 2 3 4

Analiza

W liniach od 3. do 10. została zdefiniowana klasa wzorca[Author ID1: at Mon Nov 12 10:43:00 2001 ]wzorcowa[Author ID1: at Mon Nov 12 10:43:00 2001 ] Print. Przeciążony operator () zdefiniowany w liniach od 6. do 9. przyjmuje obiekt i wypisuje go na standardowym wyjściu. W linii 14. obiekt DoPrint jest definiowany jako egzemplarz klasy Print. Możemy więc użyć obiektu DoPrint w celu wypisania dowolnej wartości całkowitej tak, jak użylibyśmy funkcji, co pokazano w linii 16.

Algorytmy nie zmieniające[Author ID1: at Mon Nov 12 10:44:00 2001 ]Bezmutacyjne operacje[Author ID1: at Mon Nov 12 10:44:00 2001 ] sekwencyjne[Author ID1: at Mon Nov 12 10:44:00 2001 ]ji[Author ID1: at Mon Nov 12 10:44:00 2001 ]

Algorytmy nie zmieniające sekwencji to operacje, które nie zmieniają elementów w sekwencji. Obejmują one operatory takie, jak for_each() (dla każdego) oraz find() (znajdź), search() (szukaj), count() (policz) itd. Listing 19.12 przedstawia sposób użycia obiektu funkcyjn[Author ID1: at Mon Nov 12 10:44:00 2001 ]ego[Author ID1: at Mon Nov 12 10:45:00 2001 ]ji[Author ID1: at Mon Nov 12 10:44:00 2001 ] oraz algorytmu for_each() w celu wypisania elementów wektora.

Listing 19.12. Użycie algorytmu for_each()

0: #include <iostream>

1: #include <vector>

2: #include <algorithm>

3: using namespace std;

4:

5: template<class T>

6: class Print

7: {

8: public:

9: void operator()(const T& t)

10: {

11: cout << t << " ";

12: }

13: };

14:

15: int main()

16: {

17: Print<int> DoPrint;

18: vector<int> vInt(5);

19:

20: for (int i = 0; i < 5; ++i)

21: vInt[i] = i * 3;

22:

23: cout << "for_each()\n";

24: for_each(vInt.begin(), vInt.end(), DoPrint);

25: cout << "\n";

26:

27: return 0;

28: }

Wynik

for_each()

0 3 6 9 12

Analiza

Zwróć uwagę, że algorytmy standardowe w C++ są zdefiniowane w pliku nagłówkowym <algorithm>, więc musimy dołączyć go do kodu programu. Większość programu powinna być łatwo zrozumiała. W linii 24. zostaje wywołana funkcja for_each() „przechodząca” przez wszystkie elementy w wektorze vInt. Dla każdego elementu wywołuje ona obiekt funkcji DoPrint i przekazuje element do funkcji DoPrint.operator(). To powoduje, że wartość elementu zostaje wypisana na ekranie.

A[Author ID1: at Mon Nov 12 10:45:00 2001 ]Mutacyjne a[Author ID1: at Mon Nov 12 10:45:00 2001 ]lgorytmy zmieniające[Author ID1: at Mon Nov 12 10:45:00 2001 ] sekwency[Author ID1: at Mon Nov 12 10:45:00 2001 ]jn[Author ID1: at Mon Nov 12 10:45:00 2001 ]e

A[Author ID1: at Mon Nov 12 10:46:00 2001 ]Mutac[Author ID1: at Mon Nov 12 10:46:00 2001 ]yjne a[Author ID1: at Mon Nov 12 10:46:00 2001 ]lgorytmy zmieniające[Author ID1: at Mon Nov 12 10:46:00 2001 ] sekwency[Author ID1: at Mon Nov 12 10:46:00 2001 ]jn[Author ID1: at Mon Nov 12 10:46:00 2001 ]e wykonują operacje zmieniające elementy w sekwencji. Należą do nich operacje wypełniające sekwencje lub zmieniające kolejność zawartych w nich elementów. Listing 19.13 przedstawia algorytm fill() (wypełnij).

Listing 19.13. Algorytm zmieniający sekwencję

0: #include <iostream>

1: #include <vector>

2: #include <algorithm>

3: using namespace std;

4:

5: template<class T>

6: class Print

7: {

8: public:

9: void operator()(const T& t)

10: {

11: cout << t << " ";

12: }

13: };

14:

15: int main()

16: {

17: Print<int> DoPrint;

18: vector<int> vInt(10);

19:

20: fill(vInt.begin(), vInt.begin() + 5, 1);

21: fill(vInt.begin() + 5, vInt.end(), 2);

22:

23: for_each(vInt.begin(), vInt.end(), DoPrint);

24: cout << "\n\n";

25:

26: return 0;

27: }

Wynik

1 1 1 1 1 2 2 2 2 2

Analiza

Jedyną nową zawartością tego listingu są linie 20. i 21., w których jest używany algrytm fill(). Algorytm ten wypełnia elementy sekwencji żądaną wartością. W linii 20. przypisuje wartość całkowitą 1 do pięciu pierwszych elementów wektora vInt. W linii 21. pięciu ostatnim elementom wektora jest przypisywana wartość 2.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 F:\korekta\r19-06.doc[Author ID2: at Thu Nov 29 10:26:00 2001 ]C:\Moje dokumenty\jr\doc\Korekt_rzeczo\3\Kopia r19-05.doc[Author ID2: at Thu Nov 29 10:26:00 2001 ]

[Author ID2: at Thu Nov 29 10:26:00 2001 ][Author ID2: at Thu Nov 29 10:26:00 2001 ][Author ID2: at Thu Nov 29 10:26:00 2001 ][Author ID2: at Thu Nov 29 10:26:00 2001 ][Author ID2: at Thu Nov 29 10:26:00 2001 ][Author ID2: at Thu Nov 29 10:27:00 2001 ]Template[Author ID2: at Thu Nov 29 10:26:00 2001 ]” w odniesieniu do języka C++ to „wzorzec”, a nie „szablon”. (Za: Bjorne Stroustrup, Język C++, PWN).[Author ID2: at Thu Nov 29 10:27:00 2001 ]



Wyszukiwarka

Podobne podstrony:
6135
6135
6135
6135, konspekty z w-f
6135
6135
06 Kancelarie odszkodowawczeid 6135 pptx

więcej podobnych podstron