r16 dziedziczenie zaawansowane GI2EQ75VWCMDQI4M4TFNNO3E74FKNBDUBJ2EJIA


Rozdział 16.
Dziedziczenie z[Author ID1: at Tue Nov 27 09:39:00 2001 ]aawansowane [Author ID1: at Tue Nov 27 09:39:00 2001 ]

Jak dotąd[Author ID1: at Tue Nov 27 09:39:00 2001 ]Do tej pory używaliśmy[Author ID1: at Tue Nov 27 09:39:00 2001 ] korzystaliśmy z[Author ID1: at Tue Nov 27 09:39:00 2001 ] pojedynczego i wielokrotnego [Author ID1: at Tue Nov 27 09:39:00 2001 ]dziedziczenia pojedynczego i wielokrotnego [Author ID1: at Tue Nov 27 09:39:00 2001 ]w celu stworzenia relacji typu jest-czymś.

W [Author ID1: at Tue Nov 27 09:39:00 2001 ]Z tego[Author ID1: at Tue Nov 27 09:39:00 2001 ]tym[Author ID1: at Tue Nov 27 09:39:00 2001 ] rozdziale[Author ID1: at Tue Nov 27 09:39:00 2001 ]łu[Author ID1: at Tue Nov 27 09:39:00 2001 ] dowiesz się:[Author ID1: at Tue Nov 27 09:39:00 2001 ]

Zawieranie

Jak widzieliśmy [Author ID1: at Tue Nov 27 09:40:00 2001 ]pokazaliśmy [Author ID1: at Tue Nov 27 09:40:00 2001 ]w poprzednich przykładach, możliwe jest,[Author ID1: at Tue Nov 27 09:40:00 2001 ] by dane składowe jednej klasy obejmowały obiekty innych klas. Programiści C++ mówią wtedy,[Author ID1: at Tue Nov 27 09:40:00 2001 ] że klasa zewnętrzna zawiera klasę wewnętrzną. Tak więc klasa Employee (pracownik) może zawierać na przykład obiekt typu [Author ID2: at Thu Nov 8 09:19:00 2001 ]łańcucha (przechowujący nazwisko pracownika) oraz składowe całkowite (zawierające jego pensję i inne dane).

Listing 16.1 opisuje niekompletną, choć wciąż[Author ID2: at Thu Nov 8 09:20:00 2001 ]jednak[Author ID2: at Thu Nov 8 09:20:00 2001 ]owoż[Author ID2: at Thu Nov 8 09:20:00 2001 ][Author ID1: at Tue Nov 27 09:40:00 2001 ] użyteczną klasę String, dość podobną do klasy String zadeklarowanej w rozdziale 13. Ten listing nie daje[Author ID2: at Thu Nov 8 09:20:00 2001 ]generuje[Author ID2: at Thu Nov 8 09:20:00 2001 ] żadnego wyniku[Author ID2: at Thu Nov 8 09:20:00 2001 ]wydruku[Author ID2: at Thu Nov 8 09:20:00 2001 ]. Za[Author ID2: at Thu Nov 8 09:21:00 2001 ]miast tego zostanie[Author ID2: at Thu Nov 8 09:21:00 2001 ]Będzie on jednak[Author ID2: at Thu Nov 8 09:21:00 2001 ] wykorzystan[Author ID2: at Thu Nov 8 09:21:00 2001 ]ywany razem[Author ID2: at Thu Nov 8 09:21:00 2001 ] z dalszymi listingami.

Listing 16.1. Klasa String

0: // Listing 16.1 Klasa String

1:

2: #include <iostream>

3: #include[Author ID2: at Thu Nov 8 09:22:00 2001 ] <string.h>

4: using namespace std;

5:

6: class String

7: {

8: public:

9: // konstruktory

10: String();

11: String(const char *const);

12: String(const String &);

13: ~String();

14:

15: // przeciążone operatory

16: char & operator[](int offset);

17: char operator[](int offset) const;

18: String operator+(const String&);

19: void operator+=(const String&);

20: String & operator= (const String &),

21:

22: // ogólne akcesory

23: int GetLen()const { return itsLen; }

24: const char * GetString() const { return itsString; }

25: static int ConstructorCount;

26:

27: private:

28: String (int); // prywatny konstruktor

29: char * itsString;

30: unsigned short itsLen;

31:

32: };

33:

34: // domyślny konstruktor tworzący ciąg pusty ([Author ID2: at Thu Nov 8 09:23:00 2001 ]zera[Author ID2: at Thu Nov 8 09:23:00 2001 ]0[Author ID2: at Thu Nov 8 09:23:00 2001 ] bajtów)[Author ID2: at Thu Nov 8 09:23:00 2001 ]

35: String::String()

36: {

37: itsString = new char[1];

38: itsString[0] = '\0';

39: itsLen=0;

40: // cout << "\tDomyslny konstruktor lancucha\n";

41: // ConstructorCount++;

42: }

43:

44: // prywatny (pomocniczy) konstruktor, używany tylko przez

45: // metody klasy przy tworzeniu nowego, wypełnionego zerowymi

46: // bajtami, łańcucha o zadanej długości

47: String::String(int len)

48: {

49: itsString = new char[len+1];

50: for (int i = 0; i<=len; i++)

51: itsString[i] = '\0';

52: itsLen=len;

53: // cout << "\tKonstruktor String(int)\n";

54: // ConstructorCount++;

55: }

56:

57: // Zamienia tablice znaków w typ String

58: String::String(const char * const cString)

59: {

60: itsLen = strlen(cString);

61: itsString = new char[itsLen+1];

62: for (int i = 0; i<itsLen; i++)

63: itsString[i] = cString[i];

64: itsString[itsLen]='\0';

65: // cout << "\tKonstruktor String(char*)\n";

66: // ConstructorCount++;

67: }

68:

69: // konstruktor kopiujący[Author ID2: at Thu Nov 8 09:23:00 2001 ]i[Author ID2: at Thu Nov 8 09:23:00 2001 ]

70: String::String (const String & rhs)

71: {

72: itsLen=rhs.GetLen();

73: itsString = new char[itsLen+1];

74: for (int i = 0; i<itsLen;i++)

75: itsString[i] = rhs[i];

76: itsString[itsLen] = '\0';

77: // cout << "\tKonstruktor String(String&)\n";

78: // ConstructorCount++;

79: }

80:

81: // destruktor, zwalnia zaalokowaną pamięć

82: String::~String ()

83: {

84: delete [] itsString;

85: itsLen = 0;

86: // cout << "\tDestruktor klasy String\n";

87: }

88:

89: // operator równości, zwalnia istniejącą pamięć,[Author ID1: at Tue Nov 27 09:41:00 2001 ]

90: // po czym kopiuje łańcuch i rozmiar

91: String& String::operator=(const String & rhs)

92: {

93: if (this == &rhs)

94: return *this;

95: delete [] itsString;

96: itsLen=rhs.GetLen();

97: itsString = new char[itsLen+1];

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

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

100: itsString[itsLen] = '\0';

101: return *this;

102: // cout << "\toperator= klasy String\n";

103: }

104:

105: //nie const operator indeksu, zwraca

106: // referencję do znaku, więc można go

107: // zmienić!

108: char & String::operator[](int offset)

109: {

110: if (offset > itsLen)

111: return itsString[itsLen-1];

112: else

113: return itsString[offset];

114: }

115:

116: // const operator indeksu do używania z obiektami

117: // const (patrz konstruktor kopiujący[Author ID2: at Thu Nov 8 09:24:00 2001 ]i[Author ID2: at Thu Nov 8 09:24:00 2001 ]!)

118: char String::operator[](int offset) const

119: {

120: if (offset > itsLen)

121: return itsString[itsLen-1];

122: else

123: return itsString[offset];

124: }

125:

126: // tworzy nowy łańcuch przez dodanie do rhs

127: // bieżącego łańcucha

128: String String::operator+(const String& rhs)

129: {

130: int totalLen = itsLen + rhs.GetLen();

131: String temp(totalLen);

132: int i, j;

133: for (i = 0; i<itsLen; i++)

134: temp[i] = itsString[i];

135: for (j = 0; j<rhs.GetLen(); j++, i++)

136: temp[i] = rhs[j];

137: temp[totalLen]='\0';

138: return temp;

139: }

140:

141: // zmienia bieżący łańcuch, nie zwraca nic

142: void String::operator+=(const String& rhs)

143: {

144: unsigned short rhsLen = rhs.GetLen();

145: unsigned short totalLen = itsLen + rhsLen;

146: String temp(totalLen);

147: int i, j;

148: for (i = 0; i<itsLen; i++)

149: temp[i] = itsString[i];

150: for (j = 0; j<rhs.GetLen(); j++, i++)

151: temp[i] = rhs[i-itsLen];

152: temp[totalLen]='\0';

153: *this = temp;

154: }

155:

156: // int String::ConstructorCount = 0;

UWAGA Umieść[Author ID1: at Tue Nov 27 09:41:00 2001 ] [Author ID1: at Tue Nov 27 09:41:00 2001 ]K[Author ID1: at Tue Nov 27 09:41:00 2001 ]k[Author ID1: at Tue Nov 27 09:41:00 2001 ]od z listingu 16.1 umieść [Author ID1: at Tue Nov 27 09:41:00 2001 ]w pliku o nazwie String.hpp. Wtedy z[Author ID1: at Tue Nov 27 09:41:00 2001 ]Z[Author ID1: at Tue Nov 27 09:41:00 2001 ]a każdym razem,[Author ID1: at Tue Nov 27 09:41:00 2001 ] gdy będziesz potrzebował klasy String, będziesz mógł dołączyć listing 16.1 ([Author ID1: at Tue Nov 27 09:42:00 2001 ]używając instrukcji #include "String.hpp";, tak jak to robimy w dalszych listingach przedstawionyc[Author ID1: at Tue Nov 27 09:42:00 2001 ]h [Author ID1: at Tue Nov 27 09:42:00 2001 ]w tym rozdziale)[Author ID1: at Tue Nov 27 09:42:00 2001 ].

Wynik:[Author ID1: at Tue Nov 27 09:42:00 2001 ]

Brak

Analiza:[Author ID1: at Tue Nov 27 09:42:00 2001 ]

Listing 16.1 zawiera klasę String, bardzo podobną do klasy String z listingu 13.12 przedstawionego [Author ID1: at Tue Nov 27 09:42:00 2001 ]w rozdziale trzynastym, „Tablice i listy połączone”. Najważniejszą różnicą jest to, że[Author ID1: at Tue Nov 27 09:42:00 2001 ]Jednakże[Author ID1: at Tue Nov 27 09:42:00 2001 ] konstruktory i inne funkcje z listingu 13.12 zawierały instrukcje wypisujące na ekranie [Author ID1: at Tue Nov 27 09:43:00 2001 ]komunikaty na ekranie[Author ID1: at Tue Nov 27 09:43:00 2001 ]; w listingu 16.1 instrukcje te zostały wykomentowane. Z funkcji tych skorzystamy w następnych przykładach.

W linii 25.[Author ID1: at Tue Nov 27 09:43:00 2001 ] została zadeklarowana statyczna zmienna składowa ConstructorCount (licznik konstruktorów), która jest inicjalizowana w linii 156. Ta zmienna jest[Author ID2: at Thu Nov 8 09:25:00 2001 ]podlega[Author ID2: at Thu Nov 8 09:25:00 2001 ] inkrementacji [Author ID2: at Thu Nov 8 09:25:00 2001 ]inicjalizowana[Author ID2: at Thu Nov 8 09:26:00 2001 ] w każdym konstruktorze klasy. Wszystko to zostało na razie wykomentowane; skorzystamy z tego dopiero w następnych listingach.

Listing 16.2 przedstawia klasę Employee (pracownik), zawierającą trzy obiekty typu String.

Listing 16.2. Klasa Employee i program sterujący

0: // Listing 16.2 Klasa Employee i program sterujący

1: #include "String.hpp"

2:

3: class Employee

4: {

5: public:

6: Employee();

7: Employee(char *, char *, char *, long);

8: ~Employee();

9: Employee(const Employee&);

10: Employee & operator= (const Employee &);

11:

12: const String & GetFirstName() const

13: { return itsFirstName; }

14: const String & GetLastName() const { return itsLastName; }

15: const String & GetAddress() const { return itsAddress; }

16: long GetSalary() const { return itsSalary; }

17:

18: void SetFirstName(const String & fName)

19: { itsFirstName = fName; }

20: void SetLastName(const String & lName)

21: { itsLastName = lName; }

22: void SetAddress(const String & address)

23: { itsAddress = address; }

24: void SetSalary(long salary) { itsSalary = salary; }

25: private:

26: String itsFirstName;

27: String itsLastName;

28: String itsAddress;

29: long itsSalary;

30: };

31:

32: Employee::Employee():

33: itsFirstName(""),

34: itsLastName(""),

35: itsAddress(""),

36: itsSalary(0)

37: {}

38:

39: Employee::Employee(char * firstName, char * lastName,

40: char * address, long salary):

41: itsFirstName(firstName),

42: itsLastName(lastName),

43: itsAddress(address),

44: itsSalary(salary)

45: {}

46:

47: Employee::Employee(const Employee & rhs):

48: itsFirstName(rhs.GetFirstName()),

49: itsLastName(rhs.GetLastName()),

50: itsAddress(rhs.GetAddress()),

51: itsSalary(rhs.GetSalary())

52: {}

53:

54: Employee::~Employee() {}

55:

56: Employee & Employee::operator= (const Employee & rhs)

57: {

58: if (this == &rhs)

59: return *this;

60:

61: itsFirstName = rhs.GetFirstName();

62: itsLastName = rhs.GetLastName();

63: itsAddress = rhs.GetAddress();

64: itsSalary = rhs.GetSalary();

65:

66: return *this;

67: }

68:

69: int main()

70: {

71: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000);

72: Edie.SetSalary(50000);

73: String LastName("Levine");

74: Edie.SetLastName(LastName);

75: Edie.SetFirstName("Edythe");

76:

77: cout << "Imie i nazwisko: ";

78: cout << Edie.GetFirstName().GetString();

79: cout << " " << Edie.GetLastName().GetString();

80: cout << ".\nAdres: ";

81: cout << Edie.GetAddress().GetString();

82: cout << ".\nPensja: " ;

83: cout << Edie.GetSalary();

84: return 0;

85: }

Dla wygody użytkownika, [Author ID1: at Tue Nov 27 09:43:00 2001 ]implementacja klasy String została umieszczona w pliku wraz z deklaracją. W rzeczywistym programie, deklaracja tej klasy zostałaby umieszczona w pliku String.hpp, zaś jej implementacja w pliku String.cpp. Wtedy moglibyśmy dodać plik [Author ID1: at Tue Nov 27 09:43:00 2001 ]String.cpp[Author ID1: at Tue Nov 27 09:43:00 2001 ] [Author ID1: at Tue Nov 27 09:43:00 2001 ]do projektu plik [Author ID1: at Tue Nov 27 09:43:00 2001 ]String.cpp[Author ID1: at Tue Nov 27 09:43:00 2001 ] [Author ID1: at Tue Nov 27 09:43:00 2001 ](przy [Author ID1: at Tue Nov 27 09:44:00 2001 ]za [Author ID1: at Tue Nov 27 09:44:00 2001 ]pomocy[Author ID1: at Tue Nov 27 09:44:00 2001 ]ą[Author ID1: at Tue Nov 27 09:44:00 2001 ] poleceń w menu kompilatora lub przy [Author ID1: at Tue Nov 27 09:44:00 2001 ]za [Author ID1: at Tue Nov 27 09:44:00 2001 ]pomocy[Author ID1: at Tue Nov 27 09:44:00 2001 ]ą[Author ID1: at Tue Nov 27 09:44:00 2001 ] pliku makefile). Na początku pliku String.cpp musiałaby się znaleźć instrukcja #include "String.hpp".

Jednak przede wszystkim[Author ID2: at Thu Nov 8 09:27:00 2001 ]Ale, oczywiście,[Author ID2: at Thu Nov 8 09:27:00 2001 ][Author ID1: at Tue Nov 27 09:44:00 2001 ] w[Author ID1: at Tue Nov 27 09:44:00 2001 ]W[Author ID1: at Tue Nov 27 09:44:00 2001 ] rzeczywistym programie użylibyśmy łańcucha[Author ID2: at Thu Nov 8 09:28:00 2001 ] klasy[Author ID2: at Thu Nov 8 09:28:00 2001 ] String[Author ID2: at Thu Nov 8 09:28:00 2001 ] [Author ID2: at Thu Nov 8 09:28:00 2001 ]ze standardowej biblioteki C++, a nie klasy stworzonej przez siebie[Author ID1: at Tue Nov 27 09:44:00 2001 ]nas samych[Author ID1: at Tue Nov 27 09:44:00 2001 ].

Wynik:[Author ID1: at Tue Nov 27 09:44:00 2001 ]

Imie i nazwisko: Edythe Levine.

Adres: 1461 Shore Parkway.

Pensja: 50000

Analiza:[Author ID1: at Tue Nov 27 09:44:00 2001 ]

Listing 16.2 przedstawia klasę Employee zawierającą trzy obiekty łańcuchowe[Author ID2: at Thu Nov 8 09:28:00 2001 ]ów[Author ID2: at Thu Nov 8 09:28:00 2001 ]: itsFirstName (imię), itsLastName (nazwisko) oraz itsAddress (adres).

W linii 71.[Author ID1: at Tue Nov 27 09:44:00 2001 ] zostaje s[Author ID2: at Thu Nov 8 10:44:00 2001 ]u[Author ID2: at Thu Nov 8 10:44:00 2001 ]tworzony obiekt klasy Employee, przy czym [Author ID2: at Thu Nov 8 09:29:00 2001 ][Author ID1: at Tue Nov 27 09:44:00 2001 ]a[Author ID1: at Tue Nov 27 09:44:00 2001 ]zaś[Author ID2: at Thu Nov 8 10:44:00 2001 ] do jego inicjalizacji zo[Author ID2: at Thu Nov 8 10:44:00 2001 ]stają[Author ID2: at Thu Nov 8 09:29:00 2001 ][Author ID2: at Thu Nov 8 09:29:00 2001 ] wykorzystywane[Author ID2: at Thu Nov 8 09:29:00 2001 ]ane[Author ID2: at Thu Nov 8 09:29:00 2001 ] cztery wartości. W linii 72.[Author ID1: at Tue Nov 27 09:44:00 2001 ] zostaje wywołany akcesor SetSalary() (ustaw pensję), ze stałą wartością 50000. Zwróć uwagę że,[Author ID1: at Tue Nov 27 09:44:00 2001 ] w rzeczywistym programie byłaby to albo wartość dynamiczna (ustawiana podczas działania programu),[Author ID1: at Tue Nov 27 09:44:00 2001 ] albo zdefiniowana stała.

W linii 73.[Author ID1: at Tue Nov 27 09:44:00 2001 ] zostaje utworzony łańcuch, inicjalizowany przy [Author ID1: at Tue Nov 27 09:45:00 2001 ]za [Author ID1: at Tue Nov 27 09:45:00 2001 ]pomocy[Author ID1: at Tue Nov 27 09:45:00 2001 ]ą[Author ID1: at Tue Nov 27 09:45:00 2001 ] stałej łańcuchowej w stylu C++. Otrzymany obiekt łańcuchowy[Author ID2: at Thu Nov 8 09:29:00 2001 ]a[Author ID2: at Thu Nov 8 09:29:00 2001 ] jest nast[Author ID2: at Thu Nov 8 09:30:00 2001 ]ę[Author ID2: at Thu Nov 8 10:45:00 2001 ]pnie [Author ID2: at Thu Nov 8 09:30:00 2001 ]używany jako argument funkcji SetLastName() (ustaw nazwisko) w linii 74.

W linii 75.[Author ID1: at Tue Nov 27 09:45:00 2001 ] wywołana zostaje funkcja SetFirstName() (ustaw imię) klasy Employee, której przekazywana jest inna stała łańcuchowa. Jeśli się [Author ID1: at Tue Nov 27 09:45:00 2001 ]jednak bliżej się jej [Author ID1: at Tue Nov 27 09:45:00 2001 ]przyjrzysz, zauważysz,[Author ID1: at Tue Nov 27 09:45:00 2001 ] że klasa Employee nie posiada funkcji SetFirstName() [Author ID1: at Tue Nov 27 09:45:00 2001 ], [Author ID1: at Tue Nov 27 09:45:00 2001 ]przyjmującej jako[Author ID2: at Thu Nov 8 09:30:00 2001 ] argument stałą[Author ID2: at Thu Nov 8 09:30:00 2001 ]ej[Author ID2: at Thu Nov 8 09:30:00 2001 ] łańcuchową[Author ID2: at Thu Nov 8 09:30:00 2001 ]ej[Author ID2: at Thu Nov 8 09:30:00 2001 ] w stylu C; zamiast tego funkcja SetFirstName() wymaga referencji do stałego łańcucha.

Kompilator potrafi rozwikłać to wywołanie,[Author ID1: at Tue Nov 27 09:45:00 2001 ] gdyż wie,[Author ID1: at Tue Nov 27 09:45:00 2001 ] w jaki sposób może stworzyć obiekt łańcuchowy[Author ID2: at Thu Nov 8 09:30:00 2001 ]a[Author ID2: at Thu Nov 8 09:30:00 2001 ] ze stałej łańcuchowej.[Author ID2: at Thu Nov 8 09:30:00 2001 ] w stylu C[Author ID2: at Thu Nov 8 09:30:00 2001 ]. Wie, gdyż poinformowaliśmy go o tym w linii 11.[Author ID1: at Tue Nov 27 09:46:00 2001 ] listingu 16.1.

Często zadawane pytanie

Dlaczego kłopoczemy[Author ID2: at Thu Nov 8 09:31:00 2001 ]uciekamy[Author ID2: at Thu Nov 8 09:31:00 2001 ] się do [Author ID2: at Thu Nov 8 09:31:00 2001 ]wywołania[Author ID2: at Thu Nov 8 09:31:00 2001 ]em[Author ID2: at Thu Nov 8 09:31:00 2001 ] funkcji GetString() w liniach 78.[Author ID1: at Tue Nov 27 09:46:00 2001 ], 79.[Author ID1: at Tue Nov 27 09:46:00 2001 ] i 81?

78: cout << Edie.GetFirstName().GetString();

Odpowiedź: Metoda GetFirstName() obiektu Edie zwraca obiekt klasy String. Niestety, nasza klasa String jeszcze nie obsługuje operatora << dla cout. Aby więc usatysfakcjonować cout, musimy zwrócić łańcuch znaków w stylu C. Taki łańcuch zwraca metoda GetString() naszej klasy String. Problem ten rozwiążemy nieco później.

Dostęp do składowych klasy [Author ID2: at Thu Nov 8 09:32:00 2001 ]zawieranej klasy[Author ID2: at Thu Nov 8 09:32:00 2001 ]

Obiekty Employee nie posiadają specjalnych uprawnień dostępu do zmiennych składowych klasy String. Jeśli [Author ID1: at Tue Nov 27 09:46:00 2001 ]Gdyby [Author ID1: at Tue Nov 27 09:46:00 2001 ]obiekt klasy Employee, Edie (Edyta) próbowałby[Author ID1: at Tue Nov 27 09:46:00 2001 ] odwołać się do składowej itsLen w swojej własnej zmiennej itsFirstName, wystąpiłby błąd kompilacji. Nie je[Author ID1: at Tue Nov 27 09:46:00 2001 ]st [Author ID1: at Tue Nov 27 09:46:00 2001 ]stanowi [Author ID1: at Tue Nov 27 09:46:00 2001 ]to jednak większym [Author ID1: at Tue Nov 27 09:46:00 2001 ]większego [Author ID1: at Tue Nov 27 09:46:00 2001 ]problemem[Author ID1: at Tue Nov 27 09:46:00 2001 ]problemu[Author ID1: at Tue Nov 27 09:46:00 2001 ].[Author ID1: at Tue Nov 27 09:46:00 2001 ],[Author ID1: at Tue Nov 27 09:46:00 2001 ] b[Author ID1: at Tue Nov 27 09:46:00 2001 ]Interfejs dla klasy [Author ID2: at Thu Nov 8 09:32:00 2001 ]String[Author ID2: at Thu Nov 8 09:32:00 2001 ] tworzą [Author ID2: at Thu Nov 8 09:32:00 2001 ]B[Author ID2: at Thu Nov 8 09:32:00 2001 ][Author ID1: at Tue Nov 27 09:47:00 2001 ]owiem [Author ID2: at Thu Nov 8 09:32:00 2001 ]akcesory tworzą [Author ID2: at Thu Nov 8 09:32:00 2001 ]i[Author ID2: at Thu Nov 8 09:33:00 2001 ]nterfejs dla klasy [Author ID2: at Thu Nov 8 09:32:00 2001 ]String[Author ID2: at Thu Nov 8 09:32:00 2001 ],[Author ID2: at Thu Nov 8 09:33:00 2001 ] i[Author ID2: at Thu Nov 8 09:33:00 2001 ]więc[Author ID2: at Thu Nov 8 09:33:00 2001 ] klasa Employee nie musi się martwić o szczegóły implementacji obiektów łańcuchowych[Author ID2: at Thu Nov 8 09:34:00 2001 ]ów[Author ID2: at Thu Nov 8 09:34:00 2001 ] bardziej niż o szczegóły implementacji swojej składowej całkowitej,[Author ID2: at Thu Nov 8 10:46:00 2001 ] itsSalary.

Filtrowany[Author ID2: at Thu Nov 8 09:34:00 2001 ]ie[Author ID2: at Thu Nov 8 09:34:00 2001 ] dostępu[Author ID2: at Thu Nov 8 09:34:00 2001 ] do składowych [Author ID2: at Thu Nov 8 09:34:00 2001 ]zawieranych składowych[Author ID2: at Thu Nov 8 09:34:00 2001 ]

Zwróć uwagę,[Author ID1: at Tue Nov 27 09:47:00 2001 ] że klasa String posiada operator+. Projektant klasy Employee zablokował dostęp do operatora+ wywoływanego dla obiektów Employee. Uczynił to,[Author ID1: at Tue Nov 27 09:47:00 2001 ] deklarując,[Author ID1: at Tue Nov 27 09:47:00 2001 ] że wszystkie akcesory zwracające łańcuch, takie jak GetFirstName(), zwracają stałą referencję. Ponieważ operator+ nie jest (i nie może być) funkcją const (gdyż zmienia obiekt, dla którego jest wywoływany), próba napisania poniższej linii zakończy się błędem kompilacji:

String buffer = Edie.GetFirstName() + Edie.GetLastName();

GetFirstName() zwraca stały obiekt String, a dla stałych obiektów [Author ID1: at Tue Nov 27 09:47:00 2001 ]nie można używać operatora+ dla stałych obiektów.[Author ID1: at Tue Nov 27 09:47:00 2001 ].[Author ID1: at Tue Nov 27 09:47:00 2001 ]

Aby temu zaradzić, przeciążamy metodę GetFirstName() jako nie const:

const String & GetFirstName() const { return itsFirstName; }

String & GetFirstName() { return itsFirstName; }

Zwróć uwagę,[Author ID1: at Tue Nov 27 09:47:00 2001 ] z[Author ID2: at Thu Nov 8 09:46:00 2001 ]ż[Author ID2: at Thu Nov 8 09:46:00 2001 ]e zarówno [Author ID2: at Thu Nov 8 09:46:00 2001 ][Author ID1: at Tue Nov 27 09:47:00 2001 ]wartość [Author ID2: at Thu Nov 8 09:46:00 2001 ]zwracana wartość [Author ID2: at Thu Nov 8 09:46:00 2001 ]nie jest const jak i [Author ID2: at Thu Nov 8 09:46:00 2001 ][Author ID1: at Tue Nov 27 09:48:00 2001 ]oraz, że[Author ID1: at Tue Nov 27 09:48:00 2001 ]oraz że[Author ID2: at Thu Nov 8 09:46:00 2001 ] sama funkcja nie jest już const. Zmiana samej wartości [Author ID2: at Thu Nov 8 09:47:00 2001 ]zwracanej wartości [Author ID2: at Thu Nov 8 09:47:00 2001 ]nie wystarcza do przeciążenia nazwy funkcji; musimy także zmienić „stałość” samej funkcji.

Koszt zawierania

Ważne jest by[Author ID1: at Tue Nov 27 09:48:00 2001 ]Należy[Author ID1: at Tue Nov 27 09:48:00 2001 ] zdawać sobie sprawę,[Author ID1: at Tue Nov 27 09:48:00 2001 ] że użytkownik klasy Employee płaci cenę[Author ID1: at Tue Nov 27 09:48:00 2001 ]ponosi koszty[Author ID1: at Tue Nov 27 09:48:00 2001 ] tworzenia i przechowywania obiektów String za każdym razem,[Author ID1: at Tue Nov 27 09:48:00 2001 ] gdy tworzony lub kopiowany jest [Author ID1: at Tue Nov 27 09:48:00 2001 ]obiekt klasy Employee jest tworzony lub kopiowany[Author ID1: at Tue Nov 27 09:48:00 2001 ].

Odkomentowanie kilku [Author ID2: at Thu Nov 8 09:47:00 2001 ]instrukcji cout w listingu 16.1 pokazuje[Author ID2: at Thu Nov 8 10:48:00 2001 ]ujawni[Author ID2: at Thu Nov 8 10:48:00 2001 ],[Author ID1: at Tue Nov 27 09:49:00 2001 ] jak często wywoływane są konstruktory klasy String. Listing 16.3 zawiera przepisaną[Author ID2: at Thu Nov 8 09:47:00 2001 ]na [Author ID2: at Thu Nov 8 09:47:00 2001 ][Author ID1: at Tue Nov 27 09:49:00 2001 ]now[Author ID2: at Thu Nov 8 09:47:00 2001 ]o[Author ID2: at Thu Nov 8 09:47:00 2001 ][Author ID1: at Tue Nov 27 09:49:00 2001 ]ą[Author ID1: at Tue Nov 27 09:49:00 2001 ] [Author ID2: at Thu Nov 8 09:47:00 2001 ]napisaną[Author ID2: at Thu Nov 8 09:47:00 2001 ][Author ID1: at Tue Nov 27 09:49:00 2001 ] [Author ID1: at Tue Nov 27 09:49:00 2001 ]wersję programu sterującego, zawierającą komunikaty wskazujące momenty tworzenia obiektów[Author ID2: at Thu Nov 8 09:48:00 2001 ]u[Author ID2: at Thu Nov 8 09:48:00 2001 ] i wywoływania jego[Author ID2: at Thu Nov 8 09:48:00 2001 ] dla nich[Author ID2: at Thu Nov 8 10:49:00 2001 ] metod.

UWAGA Aby skompilować ten listing, odkomentuj linie 40.[Author ID1: at Tue Nov 27 09:49:00 2001 ], 53.[Author ID1: at Tue Nov 27 09:49:00 2001 ], 65.[Author ID1: at Tue Nov 27 09:49:00 2001 ], 77.[Author ID1: at Tue Nov 27 09:49:00 2001 ], 86.[Author ID1: at Tue Nov 27 09:49:00 2001 ] oraz 102.[Author ID1: at Tue Nov 27 09:49:00 2001 ] na listingu 16.1.

Listing 16.3. Konstruktory klasy [Author ID2: at Thu Nov 8 09:48:00 2001 ]zawieranej klasy[Author ID2: at Thu Nov 8 09:48:00 2001 ]

0: //Listing 16.3 Konstruktory klasy [Author ID2: at Thu Nov 8 10:49:00 2001 ]zawieranej klasy[Author ID2: at Thu Nov 8 10:49:00 2001 ]

1: #include "String.hpp"

2:

3: class Employee

4: {

5: public:

6: Employee();

7: Employee(char *, char *, char *, long),

8: ~Employee();

9: Employee(const Employee&);

10: Employee & operator= (const Employee &);

11:

12: const String & GetFirstName() const

13: { return itsFirstName; }

14: const String & GetLastName() const { return itsLastName; }

15: const String & GetAddress() const { return itsAddress; }

16: long GetSalary() const { return itsSalary; }

17:

18: void SetFirstName(const String & fName)

19: { itsFirstName = fName; }

20: void SetLastName(const String & lName)

21: { itsLastName = lName; }

22: void SetAddress(const String & address)

23: { itsAddress = address; }

24: void SetSalary(long salary) { itsSalary = salary; }

25: private:

26: String itsFirstName;

27: String itsLastName;

28: String itsAddress;

29: long itsSalary;

30: };

31:

32: Employee::Employee():

33: itsFirstName(""),

34: itsLastName(""),

35: itsAddress(""),

36: itsSalary(0)

37: {}

38:

39: Employee::Employee(char * firstName, char * lastName,

40: char * address, long salary):

41: itsFirstName(firstName),

42: itsLastName(lastName),

43: itsAddress(address),

44: itsSalary(salary)

45: {}

46:

47: Employee::Employee(const Employee & rhs):

48: itsFirstName(rhs.GetFirstName()),

49: itsLastName(rhs.GetLastName()),

50: itsAddress(rhs.GetAddress()),

51: itsSalary(rhs.GetSalary())

52: {}

53:

54: Employee::~Employee() {}

55:

56: Employee & Employee::operator= (const Employee & rhs)

57: {

58: if (this == &rhs)

59: return *this;

60:

61: itsFirstName = rhs.GetFirstName();

62: itsLastName = rhs.GetLastName();

63: itsAddress = rhs.GetAddress();

64: itsSalary = rhs.GetSalary();

65:

66: return *this;

67: }

68:

69: int main()

70: {

71: cout << "Tworzenie obiektu Edie...\n";

72: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000);

73: Edie.SetSalary(20000);

74: cout << "Wywolanie SetFirstName z parametrem char *...\n";

75: Edie.SetFirstName("Edythe");

76: cout << "Tworzenie tymczasowego lancucha LastName...\n";

77: String LastName("Levine");

78: Edie.SetLastName(LastName);

79:

80: cout << "Imie i nazwisko: ";

81: cout << Edie.GetFirstName().GetString();

82: cout << " " << Edie.GetLastName().GetString();

83: cout << "\nAdres: ";

84: cout << Edie.GetAddress().GetString();

85: cout << "\nPensja: " ;

86: cout << Edie.GetSalary();

87: cout << endl;

88: return 0;

89: }

Wynik:[Author ID1: at Tue Nov 27 09:49:00 2001 ]

1: Tworzenie obiektu Edie...

2: Konstruktor String(char*)

3: Konstruktor String(char*)

4: Konstruktor String(char*)

5: Wywolanie SetFirstName z parametrem char *...

6: Konstruktor String(char*)

7: Destruktor klasy String

8: Tworzenie tymczasowego lancucha LastName...

9: Konstruktor String(char*)

10: Imie i nazwisko: Edythe Levine

11: Adres: 1461 Shore Parkway

12: Pensja: 20000

13: Destruktor klasy String

14: Destruktor klasy String

15: Destruktor klasy String

16: Destruktor klasy String

Analiza:[Author ID1: at Tue Nov 27 09:49:00 2001 ]

Listing 16.3 wykorzystuje tę samą deklarację klasy String [Author ID1: at Tue Nov 27 09:50:00 2001 ], [Author ID1: at Tue Nov 27 09:50:00 2001 ]co listingi 16.1 i 16.2. Jednak tym razem instrukcje cout w implementacji klasy String zostały odkomentowane. Oprócz tego[Author ID1: at Tue Nov 27 09:50:00 2001 ]Poza tym[Author ID1: at Tue Nov 27 09:50:00 2001 ], dla ułatwienia analizy działania, linie wyników programu zostały ponumerowane.

W linii 71.[Author ID1: at Tue Nov 27 09:50:00 2001 ] listingu 16.3 zostaje wypisany komunikat Tworzenie obiektu Edie..., co odzwierciedla [Author ID1: at Tue Nov 27 09:50:00 2001 ]pokazuje [Author ID1: at Tue Nov 27 09:50:00 2001 ]pierwsza linia wyników. W linii 72.[Author ID1: at Tue Nov 27 09:50:00 2001 ] zostaje utworzony obiekt klasy Employee o nazwie Edie; konstruktorowi zostają przekazane cztery parametry. Wyniki pokazują że, t[Author ID1: at Tue Nov 27 09:50:00 2001 ]T[Author ID1: at Tue Nov 27 09:50:00 2001 ]ak jak można było oczekiwać, konstruktor klasy String został wywołany trzykrotnie.

Linia 74.[Author ID1: at Tue Nov 27 09:50:00 2001 ] wypisuje informacyjny komunikat, po czym w linii 75.[Author ID1: at Tue Nov 27 09:50:00 2001 ] zostaje wykonana instrukcja Edie.SetFirstName("Edythe");. Ta instrukcja powoduje utworzenie z łańcucha znaków "Edythe" tymczasowego obiektu klasy String, co odzwierciedlają linie 5.[Author ID1: at Tue Nov 27 09:50:00 2001 ] i 6.[Author ID1: at Tue Nov 27 09:50:00 2001 ] wyników. Zwróć uwagę,[Author ID1: at Tue Nov 27 09:50:00 2001 ] że tymczasowy obiekt jest niszczony natychmiast po użyciu go w instrukcji przypisania.

W linii 77.[Author ID1: at Tue Nov 27 09:50:00 2001 ] w ciele programu zostaje utworzony obiekt klasy String. W tym przypadku programista jawnie [Author ID1: at Tue Nov 27 09:50:00 2001 ]wykonuje jawnie [Author ID1: at Tue Nov 27 09:51:00 2001 ]to, co kompilator zrobił niejawnie [Author ID1: at Tue Nov 27 09:51:00 2001 ]w poprzedniej instrukcji niejawnie[Author ID1: at Tue Nov 27 09:51:00 2001 ]. Tym razem w ósmej linii wyników widzimy wywołanie konstruktora, nie ma jednak destruktora. Ten obiekt nie jest niszczony [Author ID1: at Tue Nov 27 09:51:00 2001 ]do chwili, kiedy wyjdzie poza zakres (kończący się wraz[Author ID1: at Tue Nov 27 09:51:00 2001 ]czyli[Author ID1: at Tue Nov 27 09:51:00 2001 ] za[Author ID1: at Tue Nov 27 09:51:00 2001 ] końcem [Author ID1: at Tue Nov 27 09:51:00 2001 ]koniec [Author ID1: at Tue Nov 27 09:51:00 2001 ]funkcji).

W liniach od 81.[Author ID1: at Tue Nov 27 09:51:00 2001 ] do 87.[Author ID1: at Tue Nov 27 09:51:00 2001 ] niszczone są obiekty łańcuchowe[Author ID2: at Thu Nov 8 09:49:00 2001 ]ów[Author ID2: at Thu Nov 8 09:49:00 2001 ] zawarte[Author ID2: at Thu Nov 8 10:50:00 2001 ]ych[Author ID2: at Thu Nov 8 10:50:00 2001 ] w klasie Employee, gdyż obiekt Edie wychodzi poza zakres funkcji main(). Z tego też [Author ID2: at Thu Nov 8 09:49:00 2001 ]powodu niszczony jest także obiekt łańcuchowy[Author ID2: at Thu Nov 8 09:49:00 2001 ]a[Author ID2: at Thu Nov 8 09:49:00 2001 ] LastName, stworzony wcześniej w linii 77.

Kopiowanie przez wartość

Listing 16.3 pokazuje,[Author ID1: at Tue Nov 27 09:51:00 2001 ] że stworzenie jednego obiektu Employee powoduje pięć wywołań konstruktora klasy String. Listing 16.4 zawiera kolejną wersję programu sterującego. Tym razem nie są wypisywane komunikaty o tworzeniu obiektu, lecz zostaje użyta (odkomentowana w linii 156[Author ID2: at Thu Nov 8 09:49:00 2001 ].[Author ID1: at Tue Nov 27 09:51:00 2001 ]) statyczna zmienna składowa ConstructorCount klasy String.

Gdy przyjrzysz się listingowi 16.1, zauważysz,[Author ID1: at Tue Nov 27 09:51:00 2001 ] że zmienna ConstructorCount jest (po odkomentowaniu w konstruktorach) [Author ID2: at Thu Nov 8 09:50:00 2001 ]inkrementowana za każdym razem,[Author ID1: at Tue Nov 27 09:51:00 2001 ] gdy zostaje wywołany konstruktor. Program sterujący z listingu 16.4 wywołuje funkcje wypisujące, przekazując im obiekt Employee najpierw poprzez referencję, a następnie poprzez wartość. Zmienna ConstructorCount przechowuje aktualną liczbę obiektów String [Author ID1: at Tue Nov 27 09:52:00 2001 ], [Author ID1: at Tue Nov 27 09:52:00 2001 ]s[Author ID2: at Thu Nov 8 09:51:00 2001 ]tworzonych podczas przekazywania obiektu Employee jako parametru.

UWAGA Aby skompilować ten listing, pozostaw bez zmian linie, które odkomentowałeś w celu skompilowania listingu 16.3. Oprócz tego[Author ID1: at Tue Nov 27 09:52:00 2001 ]Natomiast[Author ID1: at Tue Nov 27 09:52:00 2001 ] w listingu 16.1 odkomentuj linie 41.[Author ID1: at Tue Nov 27 09:52:00 2001 ], 54.[Author ID1: at Tue Nov 27 09:52:00 2001 ], 66.[Author ID1: at Tue Nov 27 09:52:00 2001 ], 78.[Author ID1: at Tue Nov 27 09:52:00 2001 ] oraz 156.

Listing 16.4. Przekazywanie przez wartość

0: // Listing 16.4 Przekazywanie przez wartość

1: #include "String.hpp"

2:

3: class Employee

4: {

5: public:

6: Employee();

7: Employee(char *, char *, char *, long);

8: ~Employee();

9: Employee(const Employee&);

10: Employee & operator= (const Employee &);

11:

12: const String & GetFirstName() const

13: { return itsFirstName; }

14: const String & GetLastName() const { return itsLastName; }

15: const String & GetAddress() const { return itsAddress; }

16: long GetSalary() const { return itsSalary; }

17:

18: void SetFirstName(const String & fName)

19: { itsFirstName = fName; }

20: void SetLastName(const String & lName)

21: { itsLastName = lName; }

22: void SetAddress(const String & address)

23: { itsAddress = address; }

24: void SetSalary(long salary) { itsSalary = salary; }

25: private:

26: String itsFirstName;

27: String itsLastName;

28: String itsAddress;

29: long itsSalary;

30: };

31:

32: Employee::Employee():

33: itsFirstName(""),

34: itsLastName(""),

35: itsAddress(""),

36: itsSalary(0)

37: {}

38:

39: Employee::Employee(char * firstName, char * lastName,

40: char * address, long salary):

41: itsFirstName(firstName),

42: itsLastName(lastName),

43: itsAddress(address),

44: itsSalary(salary)

45: {}

46:

47: Employee::Employee(const Employee & rhs):

48: itsFirstName(rhs.GetFirstName()),

49: itsLastName(rhs.GetLastName()),

50: itsAddress(rhs.GetAddress()),

51: itsSalary(rhs.GetSalary())

52: {}

53:

54: Employee::~Employee() {}

55:

56: Employee & Employee::operator= (const Employee & rhs)

57: {

58: if (this == &rhs)

59: return *this;

60:

61: itsFirstName = rhs.GetFirstName();

62: itsLastName = rhs.GetLastName();

63: itsAddress = rhs.GetAddress();

64: itsSalary = rhs.GetSalary();

65:

66: return *this;

67: }

68:

69: void PrintFunc(Employee);

70: void rPrintFunc(const Employee&);

71:

72: int main()

73: {

74: Employee Edie("Jane","Doe","1461 Shore Parkway", 20000);

75: Edie.SetSalary(20000);

76: Edie.SetFirstName("Edythe");

77: String LastName("Levine");

78: Edie.SetLastName(LastName);

79:

80: cout << "Ilosc konstruktorow: " ;

81: cout << String::ConstructorCount << endl;

82: rPrintFunc(Edie);

83: cout << "Ilosc konstruktorow: ";

84: cout << String::ConstructorCount << endl;

85: PrintFunc(Edie);

86: cout << "Ilosc konstruktorow: ";

87: cout << String::ConstructorCount << endl;

88: return 0;

89: }

90: void PrintFunc (Employee Edie)

91: {

92: cout << "Imie i nazwisko: ";

93: cout << Edie.GetFirstName().GetString();

94: cout << " " << Edie.GetLastName().GetString();

95: cout << ".\nAdres: ";

96: cout << Edie.GetAddress().GetString();

97: cout << ".\nPensja: " ;

98: cout << Edie.GetSalary();

99: cout << endl;

100: }

101:

102: void rPrintFunc (const Employee& Edie)

103: {

104: cout << "Imie i nazwisko: ";

105: cout << Edie.GetFirstName().GetString();

106: cout << " " << Edie.GetLastName().GetString();

107: cout << "\nAdres: ";

108: cout << Edie.GetAddress().GetString();

109: cout << "\nPensja: " ;

110: cout << Edie.GetSalary();

111: cout << endl;

112: }

Wynik:[Author ID1: at Tue Nov 27 09:52:00 2001 ]

Konstruktor String(char*)

Konstruktor String(char*)

Konstruktor String(char*)

Konstruktor String(char*)

Destruktor klasy String

Konstruktor String(char*)

Ilosc konstruktorow: 5

Imie i nazwisko: Edythe Levine

Adres: 1461 Shore Parkway

Pensja: 20000

Ilosc konstruktorow: 5

Konstruktor String(String&)

Konstruktor String(String&)

Konstruktor String(String&)

Imie i nazwisko: Edythe Levine.

Adres: 1461 Shore Parkway.

Pensja: 20000

Destruktor klasy String

Destruktor klasy String

Destruktor klasy String

Ilosc konstruktorow: 8

Destruktor klasy String

Destruktor klasy String

Destruktor klasy String

Destruktor klasy String

Analiza:[Author ID1: at Tue Nov 27 09:52:00 2001 ]

Wynik pokazuje,[Author ID1: at Tue Nov 27 09:52:00 2001 ] że przy okazji [Author ID1: at Tue Nov 27 09:52:00 2001 ]tworzeniu[Author ID1: at Tue Nov 27 09:52:00 2001 ]a[Author ID1: at Tue Nov 27 09:52:00 2001 ] jednego obiektu klasy Employee jest tworzonych pięć obiektów klasy String. Gdy obiekt klasy Employee jest przekazywany funkcji rPrintFunc() przez referencję, nie są tworzone żadne dodatkowe obiekty tej klasy, więc nie są tworzone także żadne dodatkowe obiekty klasy String (one [Author ID1: at Tue Nov 27 09:53:00 2001 ]także [Author ID1: at Tue Nov 27 09:53:00 2001 ]przekazywane poprzez referencję).

Jednak g[Author ID1: at Tue Nov 27 09:53:00 2001 ]G[Author ID1: at Tue Nov 27 09:53:00 2001 ]dy w linii 85.[Author ID1: at Tue Nov 27 09:53:00 2001 ] obiekt klasy Employee jest przekazywany do funkcji PrintFunc() poprzez wartość, tworzona jest kopia obiektu, więc [Author ID1: at Tue Nov 27 09:53:00 2001 ]oznacza to, że [Author ID1: at Tue Nov 27 09:53:00 2001 ]powstają także trzy kolejne obiekty klasy String (w wyniku wywołania konstruktora kopiującego[Author ID2: at Thu Nov 8 09:51:00 2001 ]i[Author ID2: at Thu Nov 8 09:51:00 2001 ]).

Implementowanie poprzez dziedziczenie i zawieranie oraz poprzez delegację

Czasem zdarza się,[Author ID1: at Tue Nov 27 09:53:00 2001 ] że jedna klasa chce użyć jakichś atrybutów innej klasy. Na przykład, przypuśćmy,[Author ID1: at Tue Nov 27 09:53:00 2001 ] że musimy stworzyć klasę PartsCatalog (katalog części). Z otrzymanej specyfikacji wynika,[Author ID1: at Tue Nov 27 09:53:00 2001 ] że klasa PartsCatalog ma być kolekcją [Author ID1: at Tue Nov 27 09:53:00 2001 ]zbiorem [Author ID1: at Tue Nov 27 09:53:00 2001 ]części; każda część posiada unikalny numer. Klasa PartsCatalog nie pozwala,[Author ID1: at Tue Nov 27 09:53:00 2001 ] by w kolekcji [Author ID1: at Tue Nov 27 09:53:00 2001 ]zbiorze [Author ID1: at Tue Nov 27 09:53:00 2001 ]występowały takie same pozycje,[Author ID2: at Thu Nov 8 09:52:00 2001 ] oraz[Author ID2: at Thu Nov 8 09:51:00 2001 ]natomiast[Author ID2: at Thu Nov 8 09:51:00 2001 ] umożliwia dostęp do części na podstawie jej numeru.

Listing p[Author ID1: at Tue Nov 27 09:53:00 2001 ]P[Author ID1: at Tue Nov 27 09:53:00 2001 ]odsumowujący wiadomości listing [Author ID1: at Tue Nov 27 09:53:00 2001 ]w rozdziale 14. zawierał klasę PartsList. Klasa [Author ID1: at Tue Nov 27 09:54:00 2001 ]PartsList[Author ID1: at Tue Nov 27 09:54:00 2001 ] z[Author ID1: at Tue Nov 27 09:54:00 2001 ]Z[Author ID1: at Tue Nov 27 09:54:00 2001 ]ostała dobrze przetestowana i [Author ID1: at Tue Nov 27 09:54:00 2001 ]zrozumiana[Author ID1: at Tue Nov 27 09:54:00 2001 ], więc możemy z niej skorzystać [Author ID1: at Tue Nov 27 09:54:00 2001 ]tworząc klasę PartsCatalog chcemy z niej skorzystać,[Author ID1: at Tue Nov 27 09:54:00 2001 ] ([Author ID1: at Tue Nov 27 09:54:00 2001 ] [Author ID1: at Tue Nov 27 09:54:00 2001 ]bez konieczności wymyślania wszystkiego od początku)[Author ID1: at Tue Nov 27 09:54:00 2001 ].

Moglibyśmy stworzyć nową klasę PartsCatalog i zawrzeć [Author ID1: at Tue Nov 27 09:54:00 2001 ]umieścić [Author ID1: at Tue Nov 27 09:54:00 2001 ]w niej klasę PartsList. Klasa PartsCatalog mogłaby delegować (przekazać) [Author ID2: at Thu Nov 8 09:52:00 2001 ]zarządzanie połączoną listą na[Author ID2: at Thu Nov 8 09:53:00 2001 ]do[Author ID2: at Thu Nov 8 09:53:00 2001 ] zawarty[Author ID2: at Thu Nov 8 09:53:00 2001 ]ego[Author ID2: at Thu Nov 8 09:53:00 2001 ] w niej obiektu[Author ID2: at Thu Nov 8 09:53:00 2001 ] klasy PartsList.

Alternatywą[Author ID1: at Tue Nov 27 09:55:00 2001 ]nym[Author ID1: at Tue Nov 27 09:55:00 2001 ] rozwiązaniem [Author ID1: at Tue Nov 27 09:55:00 2001 ]mogłoby być wyprowadzenie klasy PartsCatalog z klasy PartsList i przejęcie [Author ID1: at Tue Nov 27 09:55:00 2001 ]w ten sposób przejęcie [Author ID1: at Tue Nov 27 09:55:00 2001 ]właściwości klasy PartsList. Pamiętajmy jednak,[Author ID1: at Tue Nov 27 09:55:00 2001 ] że publiczne [Author ID1: at Tue Nov 27 09:55:00 2001 ]dziedziczenie publiczne [Author ID1: at Tue Nov 27 09:55:00 2001 ]przedstawia relację typu jest-czymś, więc powinniśmy sobie zadać pytanie,[Author ID1: at Tue Nov 27 09:55:00 2001 ] czy obiekt PartsCatalog rzeczywiście jest obiektem PartsList.

Jednym ze sposobów odpowiedzi na pytanie,[Author ID1: at Tue Nov 27 09:55:00 2001 ] czy obiekt PartsCatalog [Author ID1: at Tue Nov 27 09:55:00 2001 ], [Author ID1: at Tue Nov 27 09:55:00 2001 ]jest obiektem PartsList jest założenie,[Author ID1: at Tue Nov 27 09:56:00 2001 ] że PartsList jest klasą bazową,[Author ID1: at Tue Nov 27 09:56:00 2001 ] a PartsCatalog jest klasą wyprowadzoną, a [Author ID1: at Tue Nov 27 09:56:00 2001 ]i [Author ID1: at Tue Nov 27 09:56:00 2001 ]następnie [Author ID1: at Tue Nov 27 09:56:00 2001 ]zadanie poniższych pytań:

  1. Czy w klasie bazowej jest cokolwiek,[Author ID1: at Tue Nov 27 09:56:00 2001 ] co nie powinno być w klasie [Author ID2: at Thu Nov 8 09:53:00 2001 ]wyprowadzo[Author ID2: at Thu Nov 8 09:54:00 2001 ]a[Author ID2: at Thu Nov 8 09:54:00 2001 ]nej[Author ID2: at Thu Nov 8 09:53:00 2001 ]? Na przykład, czy klasa bazowa PartsList posiada funkcje nieodpowiednie dla klasy PartsCatalog? Jeśli tak, prawdopodobnie nie powinniśmy stosować dziedziczenia [Author ID1: at Tue Nov 27 09:56:00 2001 ]publicznego dziedziczenia[Author ID1: at Tue Nov 27 09:56:00 2001 ].

  2. Czy klasa, którą tworzymy,[Author ID1: at Tue Nov 27 09:56:00 2001 ] ma więcej niż jedną bazę? Na przykład, czy klasa PartsCatalog wymaga dwóch obiektów [Author ID1: at Tue Nov 27 09:56:00 2001 ]PartsList[Author ID1: at Tue Nov 27 09:56:00 2001 ] [Author ID1: at Tue Nov 27 09:56:00 2001 ]dla każdego swojego obiektu dwóch obiektów [Author ID1: at Tue Nov 27 09:56:00 2001 ]PartsList[Author ID1: at Tue Nov 27 09:56:00 2001 ]? Jeśli tak, prawie z całą pewnością powinniśmy użyć zawierania.

  3. Czy musimy dziedziczyć po klasie bazowej tak,[Author ID1: at Tue Nov 27 09:56:00 2001 ] aby móc skorzystać z funkcji wirtualnych lub z [Author ID2: at Thu Nov 8 11:03:00 2001 ]dostępu do składowych [Author ID2: at Thu Nov 8 09:54:00 2001 ]chronionych składowych[Author ID2: at Thu Nov 8 09:54:00 2001 ]? Jeśli tak, musimy użyć dziedziczenia, publicznego lub prywatnego.

Na podstawie[Author ID1: at Tue Nov 27 09:56:00 2001 ]Uwzględniając[Author ID1: at Tue Nov 27 09:56:00 2001 ] odpowiedzi na te pytania,[Author ID1: at Tue Nov 27 09:57:00 2001 ] musimy dokonać wyboru pomiędzy dziedziczeniem publicznym (relacją jest-czymś) a albo[Author ID2: at Thu Nov 8 09:55:00 2001 ] dziedziczeniem prywatnym (co [Author ID1: at Tue Nov 27 09:57:00 2001 ]którego zasady [Author ID1: at Tue Nov 27 09:57:00 2001 ]wyjaśnimy w dalszej części rozdziału) albo[Author ID2: at Thu Nov 8 09:55:00 2001 ]lub[Author ID2: at Thu Nov 8 09:55:00 2001 ] zawieraniem.

Delegacja

Dlaczego nie powinniśmy wyprowadzać klasy PartsCatalog z klasy PartsList? Obiekt PartsCatlog nie jest obiektem PartsList ponieważ,[Author ID1: at Tue Nov 27 10:04:00 2001 ] obiekty PartsList są uporządkowanymi kolekcjami[Author ID1: at Tue Nov 27 10:04:00 2001 ]zbiorami[Author ID1: at Tue Nov 27 10:04:00 2001 ], których elementy mogą się powtarzać. Klasa PartsCatalog ma zawierać unikalne pozycje, które nie muszą być uporządkowane. Piąty element obiektu PartsCatalog nie musi być częścią o numerze pięć.

Oczywiście, istnieje możliwości publicznego dziedziczenia po klasie PartsList [Author ID1: at Tue Nov 27 10:04:00 2001 ], [Author ID1: at Tue Nov 27 10:04:00 2001 ]a następnie przesłonięcia metody Insert() i operatora indeksu ([]) tak,[Author ID1: at Tue Nov 27 10:04:00 2001 ] aby działały zgodnie z naszą specyfikacją, ale w ten sposób wpłynęlibyśmy na samą esencję[Author ID1: at Tue Nov 27 10:04:00 2001 ]istotę[Author ID1: at Tue Nov 27 10:04:00 2001 ] działania tej klasy. Zamiast tego zbudujemy klasę PartsCatalog [Author ID1: at Tue Nov 27 10:04:00 2001 ], która [Author ID1: at Tue Nov 27 10:04:00 2001 ]nie posiadającą[Author ID1: at Tue Nov 27 10:04:00 2001 ] operatora indeksu, [Author ID1: at Tue Nov 27 10:05:00 2001 ] i [Author ID1: at Tue Nov 27 10:05:00 2001 ]nie pozwalającą[Author ID1: at Tue Nov 27 10:05:00 2001 ] na powtarzanie[Author ID2: at Thu Nov 8 09:58:00 2001 ]órzone[Author ID2: at Thu Nov 8 09:58:00 2001 ] elementów[Author ID2: at Thu Nov 8 09:59:00 2001 ]y[Author ID2: at Thu Nov 8 09:59:00 2001 ], zaś do [Author ID1: at Tue Nov 27 10:05:00 2001 ]w celu [Author ID1: at Tue Nov 27 10:05:00 2001 ]łączenia dwóch zestawów zdefiniujemy operator+.

W pierwszym podejściu [Author ID1: at Tue Nov 27 10:05:00 2001 ]przypadku [Author ID1: at Tue Nov 27 10:05:00 2001 ]wykorzystamy zawieranie. Klasa PartsCatalog będzie delegować zarządzanie listą na zawarty w niej obiekt klasy PartsList. To rozwiązanie ilustruje listing 16.5.

Listing 16.5. Delegowanie na zawierany obiekt klasy PartsList

0: // Listing 16.5 Delegowanie na zawierany obiekt klasy PartsList

1:

2: #include <iostream>

3: using namespace std;

4:

5: // **************** Część ************

6:

7: // Abstrakcyjna klasa bazowa części

8: class Part

9: {

10: public:

11: Part():itsPartNumber(1) {}

12: Part(int PartNumber):

13: itsPartNumber(PartNumber){}

14: virtual ~Part(){}

15: int GetPartNumber() const

16: { return itsPartNumber; }

17: virtual void Display() const =0;

18: private:

19: int itsPartNumber;

20: };

21:

22: // implementacja czystej funkcji wirtualnej, dzięki czemu[Author ID1: at Tue Nov 27 10:05:00 2001 ]temu[Author ID1: at Tue Nov 27 10:05:00 2001 ]

23: // mogą z niej korzystać [Author ID1: at Tue Nov 27 10:05:00 2001 ]klasy pochodne mogą z niej korzystać[Author ID1: at Tue Nov 27 10:05:00 2001 ]

[Author ID1: at Tue Nov 27 10:05:00 2001 ]

24: void Part::Display() const

25: {

26: cout << "\nNumer czesci: " << itsPartNumber << endl;

27: }

28:

29: // **************** Część samochodu ************

30:

31: class CarPart : public Part

32: {

33: public:

34: CarPart():itsModelYear(94){}

35: CarPart(int year, int partNumber);

36: virtual void Display() const

37: {

38: Part::Display();

39: cout << "Rok modelu: ";

40: cout << itsModelYear << endl;

41: }

42: private:

43: int itsModelYear;

44: };

45:

46: CarPart::CarPart(int year, int partNumber):

47: itsModelYear(year),

48: Part(partNumber)

49: {}

50:

51:

52: // **************** Część samolotu ************

53:

54: class AirPlanePart : public Part

55: {

56: public:

57: AirPlanePart():itsEngineNumber(1){};

58: AirPlanePart

59: (int EngineNumber, int PartNumber);

60: virtual void Display() const

61: {

62: Part::Display();

63: cout << "Nr silnika: ";

64: cout << itsEngineNumber << endl;

65: }

66: private:

67: int itsEngineNumber;

68: };

69:

70: AirPlanePart::AirPlanePart

71: (int EngineNumber, int PartNumber):

72: itsEngineNumber(EngineNumber),

73: Part(PartNumber)

74: {}

75:

76: // **************** Węzeł części ************

77: class PartNode

78: {

79: public:

80: PartNode (Part*);

81: ~PartNode();

82: void SetNext(PartNode * node)

83: { itsNext = node; }

84: PartNode * GetNext() const;

85: Part * GetPart() const;

86: private:

87: Part *itsPart;

88: PartNode * itsNext;

89: };

90: // Implementacje klasy PartNode...

91:

92: PartNode::PartNode(Part* pPart):

93: itsPart(pPart),

94: itsNext(0)

95: {}

96:

97: PartNode::~PartNode()

98: {

99: delete itsPart;

100: itsPart = 0;

101: delete itsNext;

102: itsNext = 0;

103: }

104:

105: // Gdy nie ma następnego węzła części, zwraca NULL

106: PartNode * PartNode::GetNext() const

107: {

108: return itsNext;

109: }

110:

111: Part * PartNode::GetPart() const

112: {

113: if (itsPart)

114: return itsPart;

115: else

116: return NULL; //błąd

117: }

118:

119:

120:

121: // **************** Klasa PartList ************

122: class PartsList

123: {

124: public:

125: PartsList();

126: ~PartsList();

127: // wymaga konstruktora kopiującego[Author ID2: at Thu Nov 8 09:59:00 2001 ]i[Author ID2: at Thu Nov 8 09:59:00 2001 ] i operatora przy[Author ID2: at Thu Nov 8 09:59:00 2001 ]rzy[Author ID2: at Thu Nov 8 10:22:00 2001 ][Author ID1: at Tue Nov 27 10:06:00 2001 ]o[Author ID1: at Tue Nov 27 10:06:00 2001 ]równania!

128: void Iterate(void (Part::*f)()const) const;

129: Part* Find(int & position, int PartNumber) const;

130: Part* GetFirst() const;

131: void Insert(Part *);

132: Part* operator[](int) const;

133: int GetCount() const { return itsCount; }

134: static PartsList& GetGlobalPartsList()

135: {

136: return GlobalPartsList;

137: }

138: private:

139: PartNode * pHead;

140: int itsCount;

141: static PartsList GlobalPartsList;

142: };

143:

144: PartsList PartsList::GlobalPartsList;

145:

146:

147: PartsList::PartsList():

148: pHead(0),

149: itsCount(0)

150: {}

151:

152: PartsList::~PartsList()

153: {

154: delete pHead;

155: }

156:

157: Part* PartsList::GetFirst() const

158: {

159: if (pHead)

160: return pHead->GetPart();

161: else

162: return NULL; // w celu wykrycia błędu

163: }

164:

165: Part * PartsList::operator[](int offSet) const

166: {

167: PartNode* pNode = pHead;

168:

169: if (!pHead)

170: return NULL; // w celu wykrycia błędu

171:

172: if (offSet > itsCount)

173: return NULL; // błąd

174:

175: for (int i=0;i<offSet; i++)

176: pNode = pNode->GetNext();

177:

178: return pNode->GetPart();

179: }

180:

181: Part* PartsList::Find(

182: int & position,

183: int PartNumber) const

184: {

185: PartNode * pNode = 0;

186: for (pNode = pHead, position = 0;

187: pNode!=NULL;

188: pNode = pNode->GetNext(), position++)

189: {

190: if (pNode->GetPart()->GetPartNumber() == PartNumber)

191: break;

192: }

193: if (pNode == NULL)

194: return NULL;

195: else

196: return pNode->GetPart();

197: }

198:

199: void PartsList::Iterate(void (Part::*func)()const) const

200: {

201: if (!pHead)

202: return;

203: PartNode* pNode = pHead;

204: do

205: (pNode->GetPart()->*func)();

206: while (pNode = pNode->GetNext());

207: }

208:

209: void PartsList::Insert(Part* pPart)

210: {

211: PartNode * pNode = new PartNode(pPart);

212: PartNode * pCurrent = pHead;

213: PartNode * pNext = 0;

214:

215: int New = pPart->GetPartNumber();

216: int Next = 0;

217: itsCount++;

218:

219: if (!pHead)

220: {

221: pHead = pNode;

222: return;

223: }

224:

225: // jeśli ten węzeł jest mniejszy niż głowa,

226: // staje się nową głową

227: if (pHead->GetPart()->GetPartNumber() > New)

228: {

229: pNode->SetNext(pHead);

230: pHead = pNode;

231: return;

232: }

233:

234: for (;;)

235: {

236: // jeśli nie ma następnego, dołączamy nowy

237: if (!pCurrent->GetNext())

238: {

239: pCurrent->SetNext(pNode);

240: return;

241: }

242:

243: // jeśli trafia pomiędzy bieżący a nastepny,

244: // wstawiamy go tu[Author ID2: at Thu Nov 8 10:00:00 2001 ]o[Author ID2: at Thu Nov 8 10:00:00 2001 ]; w przeciwnym razie bierzemy następny

245: pNext = pCurrent->GetNext();

246: Next = pNext->GetPart()->GetPartNumber();

247: if (Next > New)

248: {

249: pCurrent->SetNext(pNode);

250: pNode->SetNext(pNext);

251: return;

252: }

253: pCurrent = pNext;

254: }

255: }

256:

257:

258:

259: class PartsCatalog

260: {

261: public:

262: void Insert(Part *);

263: int Exists(int PartNumber);

264: Part * Get(int PartNumber);

265: operator+(const PartsCatalog &);

266: void ShowAll() { thePartsList.Iterate(Part::Display); }

267: private:

268: PartsList thePartsList;

269: };

270:

271: void PartsCatalog::Insert(Part * newPart)

272: {

273: int partNumber = newPart->GetPartNumber();

274: int offset;

275:

276: if (!thePartsList.Find(offset, partNumber))

277: thePartsList.Insert(newPart);

278: else

279: {

280: cout << partNumber << " byl ";

281: switch (offset)

282: {

283: case 0: cout << "pierwsza "; break;

284: case 1: cout << "druga "; break;

285: case 2: cout << "trzecia "; break;

286: default: cout << offset+1 << "th ";

287: }

288: cout << "pozycja. Odrzucony!\n";

289: }

290: }

291:

292: int PartsCatalog::Exists(int PartNumber)

293: {

294: int offset;

295: thePartsList.Find(offset,PartNumber);

296: return offset;

297: }

298:

299: Part * PartsCatalog::Get(int PartNumber)

300: {

301: int offset;

302: Part * thePart = thePartsList.Find(offset, PartNumber);

303: return thePart;

304: }

305:

306:

307: int main()

308: {

309: PartsCatalog pc;

310: Part * pPart = 0;

311: int PartNumber;

312: int value;

313: int choice;

314:

315: while (1)

316: {

317: cout << "(0)Wyjscie (1)Samochod (2)Samolot: ";

318: cin >> choice;

319:

320: if (!choice)

321: break;

322:

323: cout << "Nowy numer czesci?: ";

324: cin >> PartNumber;

325:

326: if (choice == 1)

327: {

328: cout << "Model?: ";

329: cin >> value;

330: pPart = new CarPart(value,PartNumber);

331: }

332: else

333: {

334: cout << "Numer silnika?: ";

335: cin >> value;

336: pPart = new AirPlanePart(value,PartNumber);

337: }

338: pc.Insert(pPart);

339: }

340: pc.ShowAll();

341: return 0;

342: }

Wynik

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 1234

Model?: 94

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 4434

Model?: 93

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 1234

Model?: 94

1234 byl pierwsza pozycja. Odrzucony!

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 2345

Model?: 93

(0)Wyjscie (1)Samochod (2)Samolot: 0

Numer czesci: 1234

Rok modelu: 94

Numer czesci: 2345

Rok modelu: 93

Numer czesci: 4434

Rok modelu: 93

UWAGA Niektóre kompilatory nie potrafią skompilować linii 266., mimo iż w C++ jest ona [Author ID2: at Thu Nov 8 10:00:00 2001 ]poprawna. Jeśli zdarzy się to w przypadku twojego kompilatora, zmień tę linię na:

266: void ShowAll() { thePartsList.Iterate(&Part::Display); }

(Chodzi o dopisanie[Author ID2: at Thu Nov 8 10:01:00 2001 ] znaku ampersand (&) przed Part::Display.) Jeśli rozwiąże to problem, natychmiast zadzwoń do twórcy swojego kompilatora i poskarż się.

Analiza

Listing 16.5 zawiera klasy Part, PartNode oraz PartsList z listingu podsumowującego wiadomości (na końcu rozdziału czternastego).

W liniach od 259. do 269. została zadeklarowana nowa klasa, PartsCatalog (katalog części). Jedną ze składowych tej klasy jest obiekt klasy PartsList; właśnie do tej klasy jest delegowane zarządzanie listą. Można także powiedzieć, że klasa PartsCatalog jest zaimplementowana poprzez klasę PartsList.

Zwróć uwagę, że klienci klasy PartsCatalog nie mają bezpośredniego dostępu do klasy PartsList. Interfejsem dla niej jest klasa PartsCatalog i w związku z tym działanie klasy PartsList[Author ID2: at Thu Nov 8 10:01:00 2001 ]Catalog[Author ID2: at Thu Nov 8 10:01:00 2001 ] uległo dużej zmianie. Na przykład, metoda PartsCatalog::Insert() nie pozwala, by w liście PartsList pojawiły się elementy powielone.

Implementacja metody PartsCatalog::Insert() rozpoczyna się w linii 271. Obiekt Part, który jest przekazywany jako parametr, jest pytany o wartość swojej zmiennej składowej itsPartNumber. Ta wartość jest przekazywana do metody PartsList::Find() (znajdź) i jeśli nie zostanie znaleziona pasująca część, element jest wstawiany do listy; w przeciwnym razie wypisywany jest komunikat informacyjny.

Zwróć uwagę, że klasa PartsCatalog wstawia elementy, wywołując metodę Insert() dla swojej zmiennej składowej pl[Author ID2: at Thu Nov 8 10:02:00 2001 ]thePartsList[Author ID2: at Thu Nov 8 10:02:00 2001 ], która jest obiektem klasy PartsList. Mechanizm wstawiania i zarządzania listą połączoną, a także wyszukiwanie i pobieranie jej elementów, należy wyłącznie do ob[Author ID2: at Thu Nov 8 10:02:00 2001 ]iektu [Author ID2: at Thu Nov 8 10:02:00 2001 ]klasy PartsList, zawartego[Author ID2: at Thu Nov 8 10:02:00 2001 ]j[Author ID2: at Thu Nov 8 10:02:00 2001 ] w klasie PartsCatalog. Nie ma powodu, by klasa PartsCatalog powielała ten kod, gdyż może skorzystać z dobrze zdefiniowanego interfejsu.

Na tym właśnie polega idea ponownego wykorzystania klas w C++: klasa PartsCatalog może ponownie skorzystać z kodu klasy PartsList, a projektant klasy PartsCatalog może po prostu zignorować szczegóły implementacji klasy PartsList. Interfejs klasy PartsList (to jest deklaracja tej klasy) dostarcza wszystkich informacji potrzebnych projektantowi klasy PartsCatalog.

Dziedziczenie prywatne

Gdyby klasa PartsCatalog musiała mieć dostęp do chronionych składowych klasy PartsList (w tym przypadku składowe te nie występują) lub musiała przesłaniać którąś z metod tej klasy, wtedy klasa PartsCatalog musiałaby zostać wyprowadzona z klasy PartsList.

Ponieważ klasa PartsCatalog nie jest obiektem PartsList i ponieważ nie chcemy udostępniać klientom klasy PartsCatalog całego zestawu funkcji klasy PartsList, musimy użyć prywatnego [Author ID2: at Thu Nov 8 10:03:00 2001 ]dziedziczenia prywatnego[Author ID2: at Thu Nov 8 10:03:00 2001 ].

Należy wiedzieć, iż wszystkie zmienne i funkcje składowe klasy bazowej są traktowane tak, jakby były zadeklarowane jako prywatne, bez względu na ich rzeczywiste deklaracje w klasie bazowej. Tak więc żadna funkcja, która nie jest funkcją składową klasy PartsCatalog, nie ma dostępu do żadnej składowej klasy PartsList. Obowiązuje tu następująca zasada: dziedziczenie prywatne nie[Author ID2: at Thu Nov 8 10:03:00 2001 ] nie [Author ID2: at Thu Nov 8 10:03:00 2001 ]wiąże się z dziedziczeniem interfejsu, a jedynie z [Author ID2: at Thu Nov 8 10:03:00 2001 ]implementacją[Author ID2: at Thu Nov 8 10:03:00 2001 ]i[Author ID2: at Thu Nov 8 10:03:00 2001 ].

Klasa PartsList jest niewidoczna dla klientów klasy PartsCatalog. Nie jest dla nich dostępny żaden z jej interfejsów: nie mogą wywoływać żadnych z jej metod. Mogą jednak wywoływać metody klasy PartsCatalog; metody tej klasy mogą z kolei odwoływać się do składowych klasy PartsList (gdyż klasa PartsCatalog jest z niej wyprowadzona). Ważny jest tu fakt, że klasa PartsCatalog nie jest klasą PartsList, tak jak w przypadku dziedziczenia publicznego. Klasa PartsCatalog jest zaimplementowana poprzez klasę PartsList, tak jak miało to miejsce w przypadku zawierania. Dziedziczenie prywatne stanowi jedynie ułatwienie.

Listing 16.6 demonstruje użycie dziedziczenia prywatnego; w tym przykładzie klasa PartsCatalog została przepisana jako dziedzicząca prywatnie po klasie PartsList.

Listing 16.6. Dziedziczenie prywatne

0: //Listing 16.6 demonstruje dziedziczenie prywatne

1: #include <iostream>

2: using namespace std;

3:

4: // **************** Część ************

5:

6: // Abstrakcyjna klasa bazowa części

7: class Part

8: {

9: public:

10: Part():itsPartNumber(1) {}

11: Part(int PartNumber):

12: itsPartNumber(PartNumber){}

13: virtual ~Part(){}

14: int GetPartNumber() const

15: { return itsPartNumber; }

16: virtual void Display() const =0;

17: private:

18: int itsPartNumber;

19: };

20:

21: // implementacja czystej funkcji wirtualnej, dzięki temu

22: // mogą z niej korzystać klasy pochodne

23: void Part::Display() const

24: {

25: cout << "\nNumer czesci: " << itsPartNumber << endl;

26: }

27:

28: // **************** Część samochodu ************

29:

30: class CarPart : public Part

31: {

32: public:

33: CarPart():itsModelYear(94){}

34: CarPart(int year, int partNumber);

35: virtual void Display() const

36: {

37: Part::Display();

38: cout << "Rok modelu: ";

39: cout << itsModelYear << endl;

40: }

41: private:

42: int itsModelYear;

43: };

44:

45: CarPart::CarPart(int year, int partNumber):

46: itsModelYear(year),

47: Part(partNumber)

48: {}

49:

50:

51: // **************** Część samolotu ************

52:

53: class AirPlanePart : public Part

54: {

55: public:

56: AirPlanePart():itsEngineNumber(1){};

57: AirPlanePart(int EngineNumber, int PartNumber);

58: virtual void Display() const

59: {

60: Part::Display();

61: cout << "Nr silnika: ";

62: cout << itsEngineNumber << endl;

63: }

64: private:

65: int itsEngineNumber;

66: };

67:

68: AirPlanePart::AirPlanePart

69: (int EngineNumber, int PartNumber):

70: itsEngineNumber(EngineNumber),

71: Part(PartNumber)

72: {}

73:

74: // **************** Węzeł części ************

75: class PartNode

76: {

77: public:

78: PartNode (Part*);

79: ~PartNode();

80: void SetNext(PartNode * node)

81: { itsNext = node; }

82: PartNode * GetNext() const;

83: Part * GetPart() const;

84: private:

85: Part *itsPart;

86: PartNode * itsNext;

87: };

88: // Implementacje klasy PartNode...

89:

90: PartNode::PartNode(Part* pPart):

91: itsPart(pPart),

92: itsNext(0)

93: {}

94:

95: PartNode::~PartNode()

96: {

97: delete itsPart;

98: itsPart = 0;

99: delete itsNext;

100: itsNext = 0;

101: }

102:

103: // Gdy nie ma następnego węzła części, zwraca NULL

104: PartNode * PartNode::GetNext() const

105: {

106: return itsNext;

107: }

108:

109: Part * PartNode::GetPart() const

110: {

111: if (itsPart)

112: return itsPart;

113: else

114: return NULL; //błąd

115: }

116:

117:

118:

119: // **************** Klasa PartList ************

120: class PartsList

121: {

122: public:

123: PartsList();

124: ~PartsList();

125: // wymaga konstruktora kopiującego[Author ID2: at Thu Nov 8 10:04:00 2001 ]i[Author ID2: at Thu Nov 8 10:04:00 2001 ] i operatora przy[Author ID2: at Thu Nov 8 10:04:00 2001 ]orównania!

126: void Iterate(void (Part::*f)()const) const;

127: Part* Find(int & position, int PartNumber) const;

128: Part* GetFirst() const;

129: void Insert(Part *);

130: Part* operator[](int) const;

131: int GetCount() const { return itsCount; }

132: static PartsList& GetGlobalPartsList()

133: {

134: return GlobalPartsList;

135: }

136: private:

137: PartNode * pHead;

138: int itsCount;

139: static PartsList GlobalPartsList;

140: };

141:

142: PartsList PartsList::GlobalPartsList;

143:

144:

145: PartsList::PartsList():

146: pHead(0),

147: itsCount(0)

148: {}

149:

150: PartsList::~PartsList()

151: {

152: delete pHead;

153: }

154:

155: Part* PartsList::GetFirst() const

156: {

157: if (pHead)

158: return pHead->GetPart();

159: else

160: return NULL; // w celu wykrycia błędu

161: }

162:

163: Part * PartsList::operator[](int offSet) const

164: {

165: PartNode* pNode = pHead;

166:

167: if (!pHead)

168: return NULL; // w celu wykrycia błędu

169:

170: if (offSet > itsCount)

171: return NULL; // błąd

172:

173: for (int i=0;i<offSet; i++)

174: pNode = pNode->GetNext();

175:

176: return pNode->GetPart();

177: }

178:

179: Part* PartsList::Find(int & position, int PartNumber) const

180: {

181: PartNode * pNode = 0;

182: for (pNode = pHead, position = 0;

183: pNode!=NULL;

184: pNode = pNode->GetNext(), position++)

185: {

186: if (pNode->GetPart()->GetPartNumber() == PartNumber)

187: break;

188: }

189: if (pNode == NULL)

190: return NULL;

191: else

192: return pNode->GetPart();

193: }

194:

195: void PartsList::Iterate(void (Part::*func)()const) const

196: {

197: if (!pHead)

198: return;

199: PartNode* pNode = pHead;

200: do

201: (pNode->GetPart()->*func)();

202: while (pNode = pNode->GetNext());

203: }

204:

205: void PartsList::Insert(Part* pPart)

206: {

207: PartNode * pNode = new PartNode(pPart);

208: PartNode * pCurrent = pHead;

209: PartNode * pNext = 0;

210:

211: int New = pPart->GetPartNumber();

212: int Next = 0;

213: itsCount++;

214:

215: if (!pHead)

216: {

217: pHead = pNode;

218: return;

219: }

220:

221: // jeśli ten węzeł jest mniejszy niż głowa,

222: // staje się nową głową

223: if (pHead->GetPart()->GetPartNumber() > New)

224: {

225: pNode->SetNext(pHead);

226: pHead = pNode;

227: return;

228: }

229:

230: for (;;)

231: {

232: // jeśli nie ma następnego, dołączamy nowy

233: if (!pCurrent->GetNext())

234: {

235: pCurrent->SetNext(pNode);

236: return;

237: }

238:

239: // jeśli trafia pomiędzy bieżący a nastepny,

240: // wstawiamy go tu; w przeciwnym razie bierzemy następny

241: pNext = pCurrent->GetNext();

242: Next = pNext->GetPart()->GetPartNumber();

243: if (Next > New)

244: {

245: pCurrent->SetNext(pNode);

246: pNode->SetNext(pNext);

247: return;

248: }

249: pCurrent = pNext;

250: }

251: }

252:

253:

254:

255: class PartsCatalog : private PartsList

256: {

257: public:

258: void Insert(Part *);

259: int Exists(int PartNumber);

260: Part * Get(int PartNumber);

261: operator+(const PartsCatalog &);

262: void ShowAll() { Iterate(Part::Display); }

263: private:

264: };

265:

266: void PartsCatalog::Insert(Part * newPart)

267: {

268: int partNumber = newPart->GetPartNumber();

269: int offset;

270:

271: if (!Find(offset, partNumber))

272: PartsList::Insert(newPart);

273: else

274: {

275: cout << partNumber << " byl ";

276: switch (offset)

277: {

278: case 0: cout << "pierwsza "; break;

279: case 1: cout << "druga "; break;

280: case 2: cout << "trzecia "; break;

281: default: cout << offset+1 << "th ";

282: }

283: cout << "pozycja. Odrzucony!\n";

284: }

285: }

286:

287: int PartsCatalog::Exists(int PartNumber)

288: {

289: int offset;

290: Find(offset,PartNumber);

291: return offset;

292: }

293:

294: Part * PartsCatalog::Get(int PartNumber)

295: {

296: int offset;

297: return (Find(offset, PartNumber));

298:

299: }

300:

301: int main()

302: {

303: PartsCatalog pc;

304: Part * pPart = 0;

305: int PartNumber;

306: int value;

307: int choice;

308:

309: while (1)

310: {

311: cout << "(0)Wyjscie (1)Samochod (2)Samolot: ";

312: cin >> choice;

313:

314: if (!choice)

315: break;

316:

317: cout << "Nowy numer czesci?: ";

318: cin >> PartNumber;

319:

320: if (choice == 1)

321: {

322: cout << "Model?: ";

323: cin >> value;

324: pPart = new CarPart(value,PartNumber);

325: }

326: else

327: {

328: cout << "Numer silnika?: ";

329: cin >> value;

330: pPart = new AirPlanePart(value,PartNumber);

331: }

332: pc.Insert(pPart);

333: }

334: pc.ShowAll();

335: return 0;

336: }

Wynik

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 1234

Model?: 94

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 4434

Model?: 93

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 1234

Model?: 94

1234 byl pierwsza pozycja. Odrzucony!

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 2345

Model?: 93

(0)Wyjscie (1)Samochod (2)Samolot: 0

Numer czesci: 1234

Rok modelu: 94

Numer czesci: 2345

Rok modelu: 93

Numer czesci: 4434

Rok modelu: 93

Analiza

Listing 16.6 pokazuje zmieniony interfejs klasy PartsCatalog oraz przepisany program sterujący. Interfejsy innych klas nie zmieniły się w stosunku do listingu 16.5.

W linii 255. listingu 16.6 klasa PartsCatalog została zadeklarowana jako dziedzicząca prywatnie po klasie PartsList. Interfejs klasy PartsCatalog pozostał taki sam jak na listingu 16.5, jednak obecnie [Author ID2: at Thu Nov 8 10:06:00 2001 ]w tej klasie nie korzystamy już oczywiście[Author ID2: at Thu Nov 8 10:06:00 2001 ] z obiektu klasy PartsList jako zmiennej składowej.

Funkcja PartsCatalog::ShowAll() wywołuje metodę PartsList::Iterate(), przekazując jej odpowiedni wskaźnik do funkcji składowej klasy Part. Metoda ShowAll() pełni rolę publicznego interfejsu do metody Iterate(), dostarczając poprawnych informacji, ale nie pozwala [Author ID2: at Thu Nov 8 10:06:00 2001 ]zabezpieczając[Author ID2: at Thu Nov 8 10:06:00 2001 ] klasom[Author ID2: at Thu Nov 8 10:06:00 2001 ]y[Author ID2: at Thu Nov 8 10:06:00 2001 ] klientów przed[Author ID2: at Thu Nov 8 10:06:00 2001 ]na[Author ID2: at Thu Nov 8 10:07:00 2001 ] bezpośrednie[Author ID2: at Thu Nov 8 10:07:00 2001 ]m[Author ID2: at Thu Nov 8 10:07:00 2001 ] wywoływaniem[Author ID2: at Thu Nov 8 10:07:00 2001 ] tej metody. Choć klasa PartsList mogłaby pozwolić na przekazywanie innych funkcji do metody Iterate(), jednak nie pozwala na to klasa PartsCatalog.

Zmianie uległa także sama funkcja Insert(). Zauważ, że w linii 271[Author ID2: at Thu Nov 8 10:07:00 2001 ].3[Author ID2: at Thu Nov 8 10:07:00 2001 ] metoda Find() jest teraz wywoływana bezpośrednio, gdyż została odziedziczona po klasie bazowej. Wywołanie metody Insert() w linii 272. musi oczywiście korzystać z pełnej nazwy kwalifikowanej, gdyż w przeciwnym razie mielibyśmy do czynienia z rekurencją.

Podsumowując, gdy metody klasy PartsCatalog chcą wywołać metodę klasy PartsList, mogą uczynić to bezpośrednio. Jedyny wyjątek stanowi sytuacja, w której klasa PartsCatalog przesłania metodę, a potrzebn[Author ID2: at Thu Nov 8 10:07:00 2001 ]a jest jej wersja z klasy PartsList. W takim przypadku konieczne jest użycie pełnej nazwy kwalifikowanej.

Dziedziczenie prywatne pozwala, by klasa PartsCatalog dziedziczyła to, czego może użyć i wciąż zapewniała klasom klientów pośredni dostęp do metody Insert() i innych metod, do których te klasy nie powinny mieć bezpośredniego dostępu.

TAK

NIE

Używaj dziedziczenia publicznego, gdy wyprowadzany obiekt jest rodzajem obiektu bazowego.

Używaj zawierania wtedy, gdy chcesz delegować funkcjonalność na inną klasę i nie potrzebujesz dostępu do jej chronionych [Author ID2: at Thu Nov 8 10:08:00 2001 ]składowych chronionych[Author ID2: at Thu Nov 8 10:08:00 2001 ].

Używaj dziedziczenia prywatnego wtedy, gdy musisz zaimplementować jedną klasę poprzez drugą i chcesz mieć dostęp do jej chronionych składowych[Author ID2: at Thu Nov 8 10:08:00 2001 ]skła[Author ID2: at Thu Nov 8 10:08:00 2001 ]dowych[Author ID2: at Thu Nov 8 10:08:00 2001 ].

Nie używaj dziedziczenia prywatnego, gdy musisz użyć więcej niż jednej klasy bazowej. W takiej sytuacji musisz użyć zawierania. Na przykład, gdyby klasa PartsCatalog potrzebowała dwóch obiektów PartsList, nie mógłbyś użyć dziedziczenia prywatnego.

Nie używaj dziedziczenia publicznego, gdy składowe klasy bazowej nie powinny być dostępne dla klientów klasy pochodnej.

Funkcje[Author ID2: at Thu Nov 8 10:09:00 2001 ]Klasy[Author ID2: at Thu Nov 8 10:09:00 2001 ] zaprzyjaźnione

Czasem klasy tworzy się łącznie, jako zestaw. Na przykład, klasy PartNode i PartsList są ze sobą ściśle powiązane i byłoby wygodnie, gdyby klasa PartsList mogła bezpośrednio odczytywać wskaźnik do klasy Part z klasy PartNode, czyli mieć bezpośredni dostęp do jej zmiennej składowej itsPart.

Nie chcielibyśmy, by składowa itsPart była składową publiczną, ani nawet składową chronioną, gdyż jest ona szczegółem implementacji klasy PartNode, który powinien pozostać prywatny. Chcemy jednak udostępnić ją klasie PartsList.

Jeśli chcesz udostępnić swoje prywatne dane lub funkcje składowe innej klasie, musisz zadeklarować tę klasę jako zaprzyjaźnioną. To rozszerza interfejs twojej klasy o interfejs klasy zaprzyjaźnionej.

Gdy klasa PartNode zadeklaruje klasę PartsList jako zaprzyjaźnioną, wszystkie dane i funkcje składowe klasy PartNode są dla klasy PartsList dostępne jako składowe publiczne.

Należy pamiętać, że takie „zaprzyjaźnienie” nie może być przekazywane dalej. Choć ty jesteś moim przyjacielem, a Joe jest twoim przyjacielem, nie oznacza to, że Joe jest moim przyjacielem. Przyjaźń nie jest także dziedziczona. Choć jesteś moim przyjacielem i mam zamiar wyjawić ci swoją tajemnicę, nie oznacza to, że mam zamiar wyjawić ją twoim dzieciom.

„Zaprzyjaźnienie” nie działa zwrotnie. Zadeklarowanie klasy ClassOne jako klasy zaprzyjaźnionej klasy ClassTwo nie sprawia, że klasa ClassTwo jest klasą zaprzyjaźnioną klasy ClassOne. Być może chcesz wyjawić mi swoje sekrety, ale to nie oznacza, że ja chcę wyjawić ci moje.

Listing 16.7 ilustruje zastosowanie klasy zaprzyjaźnionej. Ten przykład to zmodyfikowana wersja listingu 16.6, w której klasa PartsList jest klasą zaprzyjaźnioną klasy PartNode. Zwróć uwagę, że to nie czyni z klasy PartNode klasy zaprzyjaźnionej klasy PartsList.

Listing 16.7. Przykład klasy zaprzyjaźnionej

0: //Listing 16.7 Przykład klasy zaprzyjaźnionej

1:

2: #include <iostream>

3: using namespace std;

4:

5: // **************** Część ************

6:

7: // Abstrakcyjna klasa bazowa części

8: class Part

9: {

10: public:

11: Part():itsPartNumber(1) {}

12: Part(int PartNumber):

13: itsPartNumber(PartNumber){}

14: virtual ~Part(){}

15: int GetPartNumber() const

16: { return itsPartNumber; }

17: virtual void Display() const =0;

18: private:

19: int itsPartNumber;

20: };

21:

22: // implementacja czystej funkcji wirtualnej, dzięki temu

23: // mogą z niej korzystać klasy pochodne

24: void Part::Display() const

25: {

26: cout << "\nNumer czesci: ";

27: cout << itsPartNumber << endl;

28: }

29:

30: // **************** Część samochodu ************

31:

32: class CarPart : public Part

33: {

34: public:

35: CarPart():itsModelYear(94){}

36: CarPart(int year, int partNumber);

37: virtual void Display() const

38: {

39: Part::Display();

40: cout << "Rok modelu: ";

41: cout << itsModelYear << endl;

42: }

43: private:

44: int itsModelYear;

45: };

46:

47: CarPart::CarPart(int year, int partNumber):

48: itsModelYear(year),

49: Part(partNumber)

50: {}

51:

52:

53: // **************** Część samolotu ************

54:

55: class AirPlanePart : public Part

56: {

57: public:

58: AirPlanePart():itsEngineNumber(1){};

59: AirPlanePart(int EngineNumber, int PartNumber);

60: virtual void Display() const

61: {

62: Part::Display();

63: cout << "Nr silnika: ";

64: cout << itsEngineNumber << endl;

65: }

66: private:

67: int itsEngineNumber;

68: };

69:

70: AirPlanePart::AirPlanePart(int EngineNumber, int PartNumber):

71: itsEngineNumber(EngineNumber),

72: Part(PartNumber)

73: {}

74:

75: // **************** Węzeł części ************

76: class PartNode

77: {

78: public:

79: friend class PartsList;

80: PartNode (Part*);

81: ~PartNode();

82: void SetNext(PartNode * node)

83: { itsNext = node; }

84: PartNode * GetNext() const;

85: Part * GetPart() const;

86: private:

87: Part *itsPart;

88: PartNode * itsNext;

89: };

90:

91:

92: PartNode::PartNode(Part* pPart):

93: itsPart(pPart),

94: itsNext(0)

95: {}

96:

97: PartNode::~PartNode()

98: {

99: delete itsPart;

100: itsPart = 0;

101: delete itsNext;

102: itsNext = 0;

103: }

104:

105: // Gdy nie ma następnego węzła części, zwraca NULL

106: PartNode * PartNode::GetNext() const

107: {

108: return itsNext;

109: }

110:

111: Part * PartNode::GetPart() const

112: {

113: if (itsPart)

114: return itsPart;

115: else

116: return NULL; //błąd

117: }

118:

119:

120: // **************** Klasa PartList ************

121: class PartsList

122: {

123: public:

124: PartsList();

125: ~PartsList();

126: // wymaga konstruktora kopiującego[Author ID2: at Thu Nov 8 10:09:00 2001 ]i[Author ID2: at Thu Nov 8 10:09:00 2001 ] i operatora porównania!

127: void Iterate(void (Part::*f)()const) const;

128: Part* Find(int & position, int PartNumber) const;

129: Part* GetFirst() const;

130: void Insert(Part *);

131: Part* operator[](int) const;

132: int GetCount() const { return itsCount; }

133: static PartsList& GetGlobalPartsList()

134: {

135: return GlobalPartsList;

136: }

137: private:

138: PartNode * pHead;

139: int itsCount;

140: static PartsList GlobalPartsList;

141: };

142:

143: PartsList PartsList::GlobalPartsList;

144:

145: // Implementacje list...

146:

147: PartsList::PartsList():

148: pHead(0),

149: itsCount(0)

150: {}

151:

152: PartsList::~PartsList()

153: {

154: delete pHead;

155: }

156:

157: Part* PartsList::GetFirst() const

158: {

159: if (pHead)

160: return pHead->itsPart;

161: else

162: return NULL; // w celu wykrycia błędu

163: }

164:

165: Part * PartsList::operator[](int offSet) const

166: {

167: PartNode* pNode = pHead;

168:

169: if (!pHead)

170: return NULL; // w celu wykrycia błędu

171:

172: if (offSet > itsCount)

173: return NULL; // błąd

174:

175: for (int i=0;i<offSet; i++)

176: pNode = pNode->itsNext;

177:

178: return pNode->itsPart;

179: }

180:

181: Part* PartsList::Find(int & position, int PartNumber) const

182: {

183: PartNode * pNode = 0;

184: for (pNode = pHead, position = 0;

185: pNode!=NULL;

186: pNode = pNode->itsNext, position++)

187: {

188: if (pNode->itsPart->GetPartNumber() == PartNumber)

189: break;

190: }

191: if (pNode == NULL)

192: return NULL;

193: else

194: return pNode->itsPart;

195: }

196:

197: void PartsList::Iterate(void (Part::*func)()const) const

198: {

199: if (!pHead)

200: return;

201: PartNode* pNode = pHead;

202: do

203: (pNode->itsPart->*func)();

204: while (pNode = pNode->itsNext);

205: }

206:

207: void PartsList::Insert(Part* pPart)

208: {

209: PartNode * pNode = new PartNode(pPart);

210: PartNode * pCurrent = pHead;

211: PartNode * pNext = 0;

212:

213: int New = pPart->GetPartNumber();

214: int Next = 0;

215: itsCount++;

216:

217: if (!pHead)

218: {

219: pHead = pNode;

220: return;

221: }

222:

223: // jeśli ten węzeł jest mniejszy niż głowa,

224: // staje się nową głową

225: if (pHead->itsPart->GetPartNumber() > New)

226: {

227: pNode->itsNext = pHead;

228: pHead = pNode;

229: return;

230: }

231:

232: for (;;)

233: {

234: // jeśli nie ma następnego, dołączamy ten nowy

235: if (!pCurrent->itsNext)

236: {

237: pCurrent->itsNext = pNode;

238: return;

239: }

240:

241: // jeśli trafia pomiędzy bieżący a nastepny,

242: // wstawiamy go tu; w przeciwnym razie bierzemy następny

243: pNext = pCurrent->itsNext;

244: Next = pNext->itsPart->GetPartNumber();

245: if (Next > New)

246: {

247: pCurrent->itsNext = pNode;

248: pNode->itsNext = pNext;

249: return;

250: }

251: pCurrent = pNext;

252: }

253: }

254:

255: class PartsCatalog : private PartsList

256: {

257: public:

258: void Insert(Part *);

259: int Exists(int PartNumber);

260: Part * Get(int PartNumber);

261: operator+(const PartsCatalog &);

262: void ShowAll() { Iterate(Part::Display); }

263: private:

264: };

265:

266: void PartsCatalog::Insert(Part * newPart)

267: {

268: int partNumber = newPart->GetPartNumber();

269: int offset;

270:

271: if (!Find(offset, partNumber))

272: PartsList::Insert(newPart);

273: else

274: {

275: cout << partNumber << " byl ";

276: switch (offset)

277: {

278: case 0: cout << "pierwsza "; break;

279: case 1: cout << "druga "; break;

280: case 2: cout << "trzecia "; break;

281: default: cout << offset+1 << "-ta ";

282: }

283: cout << "pozycja. Odrzucony!\n";

284: }

285: }

286:

287: int PartsCatalog::Exists(int PartNumber)

288: {

289: int offset;

290: Find(offset,PartNumber);

291: return offset;

292: }

293:

294: Part * PartsCatalog::Get(int PartNumber)

295: {

296: int offset;

297: return (Find(offset, PartNumber));

298: }

299:

300: int main()

301: {

302: PartsCatalog pc;

303: Part * pPart = 0;

304: int PartNumber;

305: int value;

306: int choice;

307:

308: while (1)

309: {

310: cout << "(0)Wyjscie (1)Samochod (2)Samolot: ";

311: cin >> choice;

312:

313: if (!choice)

314: break;

315:

316: cout << "Nowy numer czesci?: ";

317: cin >> PartNumber;

318:

319: if (choice == 1)

320: {

321: cout << "Model?: ";

322: cin >> value;

323: pPart = new CarPart(value,PartNumber);

324: }

325: else

326: {

327: cout << "Numer silnika?: ";

328: cin >> value;

329: pPart = new AirPlanePart(value,PartNumber);

330: }

331: pc.Insert(pPart);

332: }

333: pc.ShowAll();

334: return 0;

335: }

Wynik

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 1234

Model?: 94

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 4434

Model?: 93

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 1234

Model?: 94

1234 byl pierwsza pozycja. Odrzucony!

(0)Wyjscie (1)Samochod (2)Samolot: 1

Nowy numer czesci?: 2345

Model?: 93

(0)Wyjscie (1)Samochod (2)Samolot: 0

Numer czesci: 1234

Rok modelu: 94

Numer czesci: 2345

Rok modelu: 93

Numer czesci: 4434

Rok modelu: 93

Analiza

W linii 79. klasa PartsList została zadeklarowana jako klasa zaprzyjaźniona klasy PartNode.

Na listingu[Author ID2: at Thu Nov 8 10:28:00 2001 ] deklarację klasy zaprzyjaźnionej umieszczono w sekcji publicznej, ale nie jest to niezbędne; klasa zaprzyjaźniona może być zadeklarowana w dowolnym miejscu deklaracji klasy, bez konieczności zmiany znaczenia instrukcji zaprzyjaźnienia (friend). Dzięki tej instrukcji wszystkie prywatne funkcje i dane składowe klasy PartNode stają się dostępne dla wszystkich funkcji składowych klasy PartsList.

Zmianę tę odzwierciedla w linii 157. implementacja funkcji GetFirst(). Zamiast zwracać pHead->GetPart, obecnie funkcja ta może zwrócić (wcześniej będącą prywatną) zmienną składową pHead->itsPart. Metoda Insert() może teraz użyć pNode->itsNext (zamiast wywoływać pNode->SetNext(pHead)).

Oczywiście, zmiany te są kosmetyczne i nie było istotnego powodu, by uczynić z klasy PartsList klasę zaprzyjaźnioną klasy PartNode, ale zmiany te[Author ID2: at Thu Nov 8 10:29:00 2001 ] [Author ID2: at Thu Nov 8 10:29:00 2001 ]mogą[Author ID2: at Thu Nov 8 10:29:00 2001 ] posłużyły[Author ID2: at Thu Nov 8 10:29:00 2001 ]ć[Author ID2: at Thu Nov 8 10:29:00 2001 ] do[Author ID2: at Thu Nov 8 10:29:00 2001 ]la[Author ID2: at Thu Nov 8 10:29:00 2001 ] zilustrowania działania słowa kluczowego friend (przyjaciel).

Klasy zaprzyjaźnione powinny być deklarowane z dużą rozwagą. Deklaracja taka przydaje się, gdy dwie klasy są ze sobą ściśle powiązane i często korzystają ze swoich składowych. Jednak używaj jej rozsądnie; często równie łatwe okazuje się zastosowanie publicznych akcesorów, dzięki którym można modyfikować jedną z klas bez konieczności modyfikowania drugiej.

UWAGA Często słyszy się, że początkujący programiści C++ narzekają, że deklaracja klasy zaprzyjaźnionej pomniejsza znaczenia kapsułkowania, tak ważnego dla obiektowo zorientowanego programowania. Mówiąc szczerze, to nonsens. Deklaracja friend czyni z zadeklarowanej klasy zaprzyjaźnionej część interfejsu klasy i nie pomniejsza znaczenia [Author ID2: at Thu Nov 8 10:30:00 2001 ]kapsułkowania[Author ID2: at Thu Nov 8 10:30:00 2001 ] bardziej podkopuje e[Author ID2: at Thu Nov 8 10:30:00 2001 ]niż publiczne dziedziczenie.

Klasa zaprzyjaźniona

Aby zadeklarować klasę zaprzyjaźnioną innej klasy, przed nazwą klasy, której chcesz przydzielić dostęp, umieść słowo kluczowe friend. W ten sposób ja mogę zadeklarować, że jesteś moim przyjacielem, ale ty sam nie możesz tego zadeklarować.

Przykład:

class PartNode {

public:

friend class PartsList; // deklaruje klasę PartsList jako zaprzyjaźnioną

};

Funkcje zaprzyjaźnione

Czasem zdarza się, że chcemy nadać ten poziom dostępu nie całej klasie, ale tylko jednej czy dwóm jej funkcjom. Możemy to uczynić, deklarując jako zaprzyjaźnioną funkcję innej klasy (a nie całą tę klasę). W rzeczywistości, jako zaprzyjaźnioną możemy zadeklarować dowolną funkcję, nie tylko funkcję składową innej klasy.

Funkcje zaprzyjaźnione i przeciążanie operatorów

Listing 16.1 zawierał klasę String, w której został przeciążony operator+. Oprócz tego, klasa ta zawierała konstruktor przyjmujący stały wskaźnik do znaków, dzięki czemu można było tworzyć obiekty łańcuchów z łańcuchów znaków w stylu C. Pozwalało to na tworzenie obiektu łańcucha i dodawanie do niego łańcucha w stylu C.

UWAGA Łańcuchy w stylu C są tablicami znaków zakończonymi znakiem null, takimi jak char myString[] = "Witaj Świecie".

Nie mogliśmy jednak stworzyć łańcucha w stylu C (tablicy znaków) i dodać do niego obiektuu[Author ID2: at Thu Nov 8 11:10:00 2001 ] łańcuchow[Author ID2: at Thu Nov 8 10:30:00 2001 ]egoa[Author ID2: at Thu Nov 8 10:30:00 2001 ], tak jak w poniższym[Author ID2: at Thu Nov 8 10:31:00 2001 ]ch[Author ID2: at Thu Nov 8 10:31:00 2001 ] przykładzie[Author ID2: at Thu Nov 8 10:31:00 2001 ]liniach[Author ID2: at Thu Nov 8 10:31:00 2001 ]:

char cString[] = {"Witaj"};

String sString(" Swiecie");

String sStringTwo = cString + sString; // błąd!

Łańcuchy w stylu C nie posiadają przeciążonego operatora+. Jak wspominaliśmy w rozdziale 10., „Funkcje zaawansowane”, gdy piszemy cString + sString;, w rzeczywistości wywołujemy cString.operator+(sString). Ponieważ łańcuchy w stylu C nie posiadają operatora+(), powoduje to błąd kompilacji.

Możemy rozwiązać ten problem, deklarując w klasie String funkcję zaprzyjaźnioną, która przeciąży operator+, lecz przyjmie dwa obiekty łańcuchów. Łańcuch w stylu C zostanie zamieniony przez odpowiedni konstruktor na obiekt łańcucha, po czym zostanie wywołany operator+ używający dwóch obiektów łańcuchów.

Listing 16.8. Zaprzyjaźniony operator+

0: //Listing 16.8 - zaprzyjaźnione operatory

1:

2: #include <iostream>

3: #include <string.h>

4: using namespace std;

5:

6: // Zasadnicza klasa obiektu łańcuchowego[Author ID2: at Thu Nov 8 10:31:00 2001 ]a[Author ID2: at Thu Nov 8 10:31:00 2001 ]

7: class String

8: {

9: public:

10: // konstruktory

11: String();

12: String(const char *const);

13: String(const String &);

14: ~String();

15:

16: // przeciążone operatory

17: char & operator[](int offset);

18: char operator[](int offset) const;

19: String operator+(const String&);

20: friend String operator+(const String&, const String&);

21: void operator+=(const String&);

22: String & operator= (const String &);

23:

24: // ogólne akcesory

25: int GetLen()const { return itsLen; }

26: const char * GetString() const { return itsString; }

27:

28: private:

29: String (int); // prywatny konstruktor

30: char * itsString;

31: unsigned short itsLen;

32: };

33:

34: // domyślny konstruktor tworzący ciąg pusty (0[Author ID2: at Thu Nov 8 10:31:00 2001 ]zera[Author ID2: at Thu Nov 8 10:31:00 2001 ] bajtów)[Author ID2: at Thu Nov 8 10:31:00 2001 ]

35: String::String()

36: {

37: itsString = new char[1];

38: itsString[0] = '\0';

39: itsLen=0;

40: // cout << "\tDomyslny konstruktor lancucha\n";

41: // ConstructorCount++;

42: }

43:

44: // prywatny (pomocniczy) konstruktor, używany tylko przez

45: // metody klasy przy tworzeniu nowego, wypełnionego zerowymi

46: // bajtami, łańcucha o żądanej długości

47: String::String(int len)

48: {

49: itsString = new char[len+1];

50: for (int i = 0; i<=len; i++)

51: itsString[i] = '\0';

52: itsLen=len;

53: // cout << "\tKonstruktor String(int)\n";

54: // ConstructorCount++;

55: }

56:

57: // Zamienia tablice znakow w typ String

58: String::String(const char * const cString)

59: {

60: itsLen = strlen(cString);

61: itsString = new char[itsLen+1];

62: for (int i = 0; i<itsLen; i++)

63: itsString[i] = cString[i];

64: itsString[itsLen]='\0';

65: // cout << "\tKonstruktor String(char*)\n";

66: // ConstructorCount++;

67: }

68:

69: // konstruktor kopiujący[Author ID2: at Thu Nov 8 10:32:00 2001 ]i[Author ID2: at Thu Nov 8 10:32:00 2001 ]

70: String::String (const String & rhs)

71: {

72: itsLen=rhs.GetLen();

73: itsString = new char[itsLen+1];

74: for (int i = 0; i<itsLen;i++)

75: itsString[i] = rhs[i];

76: itsString[itsLen] = '\0';

77: // cout << "\tKonstruktor String(String&)\n";

78: // ConstructorCount++;

79: }

80:

81: // destruktor, zwalnia zaalokowaną pamięć

82: String::~String ()

83: {

84: delete [] itsString;

85: itsLen = 0;

86: // cout << "\tDestruktor klasy String\n";

87: }

88:

89: // operator równości, zwalnia istniejącą pamięć,

90: // po czym kopiuje łańcuch i rozmiar

91: String& String::operator=(const String & rhs)

92: {

93: if (this == &rhs)

94: return *this;

95: delete [] itsString;

96: itsLen=rhs.GetLen();

97: itsString = new char[itsLen+1];

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

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

100: itsString[itsLen] = '\0';

101: return *this;

102: // cout << "\toperator= klasy String\n";

103: }

104:

105: //nie const operator indeksu, zwraca

106: // referencję do znaku, więc można go

107: // zmienić!

108: char & String::operator[](int offset)

109: {

110: if (offset > itsLen)

111: return itsString[itsLen-1];

112: else

113: return itsString[offset];

114: }

115:

116: // const operator indeksu do używania z obiektami

117: // const (patrz konstruktor kopiujący[Author ID2: at Thu Nov 8 10:32:00 2001 ]i[Author ID2: at Thu Nov 8 10:32:00 2001 ]!)

118: char String::operator[](int offset) const

119: {

120: if (offset > itsLen)

121: return itsString[itsLen-1];

122: else

123: return itsString[offset];

124: }

125:

126: // tworzy nowy łańcuch przez dodanie

127: // bieżącego łańcucha do rhs

128: String String::operator+(const String& rhs)

129: {

130: int totalLen = itsLen + rhs.GetLen();

131: String temp(totalLen);

132: int i, j;

133: for (i = 0; i<itsLen; i++)

134: temp[i] = itsString[i];

135: for (j = 0, i = itsLen; j<rhs.GetLen(); j++, i++)

136: temp[i] = rhs[j];

137: temp[totalLen]='\0';

138: return temp;

139: }

140:

141: // tworzy nowy łańcuch przez[Author ID2: at Thu Nov 8 10:32:00 2001 ] dodanie

142: // jednego łańcucha do drugiego

143: String operator+(const String& lhs, const String& rhs)

144: {

145: int totalLen = lhs.GetLen() + rhs.GetLen();

146: String temp(totalLen);

147: int i, j;

148: for (i = 0; i<lhs.GetLen(); i++)

149: temp[i] = lhs[i];

150: for (j = 0, i = lhs.GetLen(); j<rhs.GetLen(); j++, i++)

151: temp[i] = rhs[j];

152: temp[totalLen]='\0';

153: return temp;

154: }

155:

156: int main()

157: {

158: String s1("Lancuch Jeden ");

159: String s2("Lancuch Dwa ");

160: char *c1 = { "Lancuch-C Jeden " } ;

161: String s3;

162: String s4;

163: String s5;

164:

165: cout << "s1: " << s1.GetString() << endl;

166: cout << "s2: " << s2.GetString() << endl;

167: cout << "c1: " << c1 << endl;

168: s3 = s1 + s2;

169: cout << "s3: " << s3.GetString() << endl;

170: s4 = s1 + c1;

171: cout << "s4: " << s4.GetString() << endl;

172: s5 = c1 + s2;

173: cout << "s5: " << s5.GetString() << endl;

174: return 0;

175: }

Wynik

s1: Lancuch Jeden

s2: Lancuch Dwa

c1: Lancuch-C Jeden

s3: Lancuch Jeden Lancuch Dwa

s4: Lancuch Jeden Lancuch-C Jeden

s5: Lancuch-C Jeden Lancuch Dwa

Analiza

Z wyjątkiem metody operator+, wszystkie inne metody pozostały takie same jak na listingu 16.1. W linii 21. nowy operator, operator+, został przeciążony tak[Author ID2: at Thu Nov 8 10:32:00 2001 ], [Author ID2: at Thu Nov 8 10:32:00 2001 ]aby [Author ID2: at Thu Nov 8 10:33:00 2001 ]przyjmował[Author ID2: at Thu Nov 8 10:33:00 2001 ]ujący[Author ID2: at Thu Nov 8 10:33:00 2001 ] dwie stałe referencje do łańcuchów i zwracał[Author ID2: at Thu Nov 8 10:33:00 2001 ]jący[Author ID2: at Thu Nov 8 10:33:00 2001 ] łańcuch; metoda ta została zadeklarowana jako zaprzyjaźniona.

Zwróć uwagę, że operator+ nie jest funkcją składową tej, ani żadnej innej klasy. W klasie String jest ona deklarowana tylko po to, aby uczynić ją zaprzyjaźnioną dla tej klasy, ale ponieważ jest zadeklarowana, nie potrzebujemy już innego prototypu.

Implementacja funkcji operator+ jest zawarta w liniach od 143. do 154. Zwróć uwagę, że jest ona podobna do wcześniejszej wersji operatora+, ale przyjmuje dwa łańcuchy i odwołuje się do nich poprzez publiczne akcesory.

Program sterujący demonstruje użycie tej funkcji w linii 172., w której operator+ może być teraz wywołany dla łańcucha w stylu C.

Funkcje zaprzyjaźnione

Funkcję zaprzyjaźnioną deklaruje się, używając słowa kluczowego friend oraz pełnej specyfikacji funkcji. Zadeklarowanie funkcji jako zaprzyjaźnionej nie umożliwia jej dostępu do wskaźnika this klasy, ale udostępnia jej wszystkie prywatne i chronione funkcje i dane składowe.

Przykład:

class PartNode

{ // ...

// deklarujemy jako zaprzyjaźnioną funkcję innej klasy

friend void PartsList::Insert(Part *);

// deklarujemy jako zaprzyjaźnioną funkcję globalną

friend int SomeFunction();

// ...

};

Przeciążanie operatora wstawiania

Jesteśmy już gotowi do nadania naszej klasie String możliwości korzystania z cout w [Author ID2: at Thu Nov 8 10:37:00 2001 ]taki[Author ID2: at Thu Nov 8 10:37:00 2001 ] samo[Author ID2: at Thu Nov 8 10:37:00 2001 ] sposób[Author ID2: at Thu Nov 8 10:38:00 2001 ], w [Author ID2: at Thu Nov 8 10:38:00 2001 ]jaki czyni to [Author ID2: at Thu Nov 8 10:38:00 2001 ]każdy [Author ID2: at Thu Nov 8 10:36:00 2001 ]inny[Author ID2: at Thu Nov 8 10:36:00 2001 ]e[Author ID2: at Thu Nov 8 10:36:00 2001 ] typy[Author ID2: at Thu Nov 8 10:36:00 2001 ]. Do tej pory, gdy chcieliśmy wypisać łańcuch, musieliśmy robić to następująco:

cout << theString.GetString();

My natomiast chcemy mieć możliwość pisania:

cout << theString;

Aby to osiągnąć, musimy przeciążyć operator<<(). W rozdziale 17. zajmiemy się plusami i minusami pracy z obiektem iostream; na razie jedynie zobaczmy, jak na listingu 16.9 został przeciążony operator<< z wykorzystaniem funkcji zaprzyjaźnionej.

Listing 16.9. Przeciążanie operatora<<()

0: // Listing 16.9 Przeciążanie operatora<<()

1:

2: #include <iostream>

3: #include <string.h>

4: using namespace std;

5:

6: class String

7: {

8: public:

9: // konstruktory

10: String();

11: String(const char *const);

12: String(const String &);

13: ~String();

14:

15: // przeciążone operatory

16: char & operator[](int offset);

17: char operator[](int offset) const;

18: String operator+(const String&);

19: void operator+=(const String&);

20: String & operator= (const String &);

21: friend ostream& operator<<

22: ( ostream& theStream,String& theString);

23: // ogólne akcesory

24: int GetLen()const { return itsLen; }

25: const char * GetString() const { return itsString; }

26:

27: private:

28: String (int); // prywatny konstruktor

29: char * itsString;

30: unsigned short itsLen;

31: };

32:

33:

34: // domyślny konstruktor tworzący ciąg zera bajtów

35: String::String()

36: {

37: itsString = new char[1];

38: itsString[0] = '\0';

39: itsLen=0;

40: // cout << "\tDomyslny konstruktor lancucha\n";

41: // ConstructorCount++;

42: }

43:

44: // prywatny (pomocniczy) konstruktor, używany tylko przez

45: // metody klasy przy tworzeniu nowego, wypełnionego zerowymi

46: // bajtami, łańcucha o żądanej długości

47: String::String(int len)

48: {

49: itsString = new char[len+1];

50: for (int i = 0; i<=len; i++)

51: itsString[i] = '\0';

52: itsLen=len;

53: // cout << "\tKonstruktor String(int)\n";

54: // ConstructorCount++;

55: }

56:

57: // Zamienia tablice znaków w typ String

58: String::String(const char * const cString)

59: {

60: itsLen = strlen(cString);

61: itsString = new char[itsLen+1];

62: for (int i = 0; i<itsLen; i++)

63: itsString[i] = cString[i];

64: itsString[itsLen]='\0';

65: // cout << "\tKonstruktor String(char*)\n";

66: // ConstructorCount++;

67: }

68:

69: // konstruktor kopiujący[Author ID2: at Thu Nov 8 10:40:00 2001 ]i[Author ID2: at Thu Nov 8 10:40:00 2001 ]

70: String::String (const String & rhs)

71: {

72: itsLen=rhs.GetLen();

73: itsString = new char[itsLen+1];

74: for (int i = 0; i<itsLen;i++)

75: itsString[i] = rhs[i];

76: itsString[itsLen] = '\0';

77: // cout << "\tKonstruktor String(String&)\n";

78: // ConstructorCount++;

79: }

80:

81: // destruktor, zwalnia zaalokowaną pamięć

82: String::~String ()

83: {

84: delete [] itsString;

85: itsLen = 0;

86: // cout << "\tDestruktor klasy String\n";

87: }

88:

89: // operator równości, zwalnia istniejącą pamięć,

90: // po czym kopiuje łańcuch i rozmiar

91: String& String::operator=(const String & rhs)

92: {

93: if (this == &rhs)

94: return *this;

95: delete [] itsString;

96: itsLen=rhs.GetLen();

97: itsString = new char[itsLen+1];

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

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

100: itsString[itsLen] = '\0';

101: return *this;

102: // cout << "\toperator= klasy String\n";

103: }

104:

105: // nie const operator indeksu, zwraca

106: // referencję do znaku, więc można go

107: // zmienić!

108: char & String::operator[](int offset)

109: {

110: if (offset > itsLen)

111: return itsString[itsLen-1];

112: else

113: return itsString[offset];

114: }

115:

116: // const operator indeksu do używania z obiektami

117: // const (patrz konstruktor kopiujący[Author ID2: at Thu Nov 8 10:40:00 2001 ]i[Author ID2: at Thu Nov 8 10:40:00 2001 ]!)

118: char String::operator[](int offset) const

119: {

120: if (offset > itsLen)

121: return itsString[itsLen-1];

122: else

123: return itsString[offset];

124: }

125:

126: // tworzy nowy łańcuch przez dodanie

127: // bieżącego łańcucha do rhs

128: String String::operator+(const String& rhs)

129: {

130: int totalLen = itsLen + rhs.GetLen();

131: String temp(totalLen);

132: int i, j;

133: for (i = 0; i<itsLen; i++)

134: temp[i] = itsString[i];

135: for (j = 0; j<rhs.GetLen(); j++, i++)

136: temp[i] = rhs[j];

137: temp[totalLen]='\0';

138: return temp;

139: }

140:

141: // zmienia bieżący łańcuch, nie zwraca nic

142: void String::operator+=(const String& rhs)

143: {

144: unsigned short rhsLen = rhs.GetLen();

145: unsigned short totalLen = itsLen + rhsLen;

146: String temp(totalLen);

147: int i, j;

148: for (i = 0; i<itsLen; i++)

149: temp[i] = itsString[i];

150: for (j = 0, i = 0; j<rhs.GetLen(); j++, i++)

151: temp[i] = rhs[i-itsLen];

152: temp[totalLen]='\0';

153: *this = temp;

154: }

155:

156: // int String::ConstructorCount =

157: ostream& operator<< ( ostream& theStream,String& theString)

158: {

159: theStream << theString.itsString;

160: return theStream;

161: }

162:

163: int main()

164: {

165: String theString("Witaj swiecie.");

166: cout << theString;

167: return 0;

168: }

Wynik

Witaj swiecie.

Analiza

W linii 21. operator<< został zadeklarowany jako funkcja zaprzyjaźniona, przyjmująca referencję do ostream oraz referencję do obiektu klasy String i zwracająca referencję do ostream. Zauważ, że nie jest to funkcja składowa klasy String. Zwraca ona referencję do ostream, więc możemy łączyć wywołania operatora<<, na przykład tak jak w poniższej linii:

cout << "Mam: " << itsAge << " lat.";

Implementacja samej funkcji zaprzyjaźnionej jest zawarta w liniach od 157. do 161. W rzeczywistości ukrywa ona tylko szczegóły przekazywania łańcucha do ostream (to właśnie powinna robić). Więcej informacji na temat przeciążania tego operatora oraz operatora>> znajdziesz w rozdziale 17.

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

2 E:\- pshem -\plany\warsztat c++\_mat\Helion - C++ dla kazdego\r16-06.doc



Wyszukiwarka

Podobne podstrony:
Energetyka jądrowa jest jedną z bardziej zaawansowanych dziedzin w dzisiejszym świecie
Zaawansowane metody udrażniania dród oddechowych
Rodowody, dziedziczenie, imprinting
dziedziczenie chorob jednogenowych
Zaawansowane zabiegi ratujące życie
dziedziny wychowania(1)
Kolonialne dziedzictwo
16 Dziedziczenie przeciwtestamentowe i obliczanie zachowkuid 16754 ppt
Stopnie zaawansowania w ozt
Matematyka zaawansowana rroznic Nieznany
078c rozp zm rozp min gosp w spr szkolenia w dziedzinie bhp
Decyzja Rady 90 424 EWG z dnia 26 czerwca 1990 r w sprawie wydatków w dziedzinie weterynarii

więcej podobnych podstron