LEKCJA 25: PRZYKŁAD OBIEKTU. ________________________________________________________________ W trakcie tej lekcji dowiesz się, jak praktycznie projektuje się klasy i obiekty. Twój pierwszy obiekt zacznie działać. ________________________________________________________________ Nasz pierwszy, doświadczalny obiekt będzie zliczać ile razy użytkownik nacisnął określony klawisz - np. literę "A". Najpierw podejdziemy do problemu "klasycznie". Utworzymy strukturę Licznik, którą można wykorzystać do przechowywania istotnych dla nas informacji: char znak - znak do zliczania int ile - ile razy wystąpił dany znak. Zwróć uwagę, że Licznik oznacza tu typ struktur (nowy formalny typ danych) a licznik oznacza naszą roboczą zmienną danego typu. struct Licznik //Licznik - nowy typ struktur { public: //Status public jest domyślny dla struktur char znak; int ile; ... } licznik; //Zmienna typu "Licznik" Do pól struktury licznik.znak i licznik.ile możemy odwoływać się w programie w następujący sposób: //Przypisanie (zainicjowanie pola struktury) licznik.znak = 'A'; cin >> licznik.znak; //Odczyt (wyprowadzenie) bież. zawartości pola struktury. cout << licznik.znak; Potrzebna nam będzie funkcja, przy pomocy której przekażemy do struktury informację, jaki znak powinien być zliczany. Nazwijmy tę funkcję Inicjuj(). Funkcja Inicjuj() powinna nam zainicjować pole struktury tzn. po przekazaniu jej jako argumentu tego znaku, który ma podlegać zliczaniu, funkcja powinna "przenieść" znak i zapisać go w polu licznik.znak naszej roboczej struktury. Wywołanie funkcji w programie powinno wyglądać tak: main() { .... Inicjuj('A'); .... //UWAGA: Nie tak: //licznik.Inicjuj() - funkcja jest zewnętrzna ! Aby funkcja inicjująca pole struktury zadziałała prawidłowo, jej definicja powinna wyglądać tak: void Inicjuj(char x) //Deklaracja zmiennej znak. { licznik.znak = x; //x - wewnętrzna zmienna funkcji licznik.ile = 0; } Inicjując strukturę licznik funkcja zeruje pole "ile" struktury. Przyda nam się jeszcze jedna funkcja PlusJeden(). Ta funkcja powinna zwiększyć zmienną służącą do zliczania ile razy wystąpił interesujący nas znak po każdym pojawieniu się odpowiedniego znaku (w tym przypadku "A"). void PlusJeden(void) //Definicja funkcji { //incrementującej licznik licznik.ile++; } Zbudowaliśmy licznik, który składa się z danych rozmieszczonych na polach struktury oraz dwu stowarzyszonych ze strukturą funkcji. Jeśli spróbujemy zastosować to w programie, gdzie: char znak_we - znak wczytany z klawiatury; program będzie wyglądać tak: void main() { char znak_we; Inicjuj('A'); cout << "\nWpisz tekst zawierajacy litery A" cout << "\nK - oznacza Koniec zliczania: "; for(;;) //Wczytujemy znaki { cin >> znak_we; if (znak_we == 'k' || znak_we == 'K') break; if(znak_we == licznik.znak) PlusJeden(); } .... W tekście mogą wystąpić zarówno duże jak i małe litery. Jeśli zechcemy zliczać i jedne i drugie, możemy posłużyć się funkcją biblioteczną C zamieniającą małe litery na duże - toupper(). Najpierw poddamy wczytany zank konwersji a następnie porównamy z "zadanym" na polu licznik.znak: if(licznik.znak == toupper(znak_we)) PlusJeden(); Po przerwaniu pętli przez użytkownika wystarczy sprawdzić jaka wartość jest wpisana w polu licznik.ile i możemy wydrukować wynik zliczania wystąpień litery 'A' we wprowadzonym tekście. cout << "\nLitera " << licznik.znak << " wystąpila " << licznik.ile << " razy."; Program w całości będzie wyglądał tak: [P092.CPP] # include # include //Prototyp f. toupper() struct Licznik { char znak; int ile; } licznik; void Inicjuj(char x) { licznik.znak = x; licznik.ile = 0; } void PlusJeden(void) { licznik.ile++; } void main() { char znak_we; Inicjuj('A'); cout << "\nWpisz tekst zawierajacy litery A"; cout << "\nPierwzse wytapienie litery k lub K"; cout << "\n - oznacza Koniec zliczania: "; for(;;) { cin >> znak_we; if (znak_we == 'k' || znak_we == 'K') break; if(licznik.znak == toupper(znak_we)) PlusJeden(); } cout << "\nLitera " << licznik.znak << " wystapila " << licznik.ile << " razy."; } Jeśli dane i funkcje połączymy w jedną całość - powstanie obiekt. Zawartość naszego obiektu powinna wyglądać tak: Dane: char znak; int ile; Funkcje: void Inicjuj(char); void PlusJeden(void); Łączymy w całość funkcje operujące pewnymi danymi i te właśnnie dane. Co więcej, jeśli zaistnieją takie funkcje, które nie będą wykorzystywane przez nikogo więcej poza własnym obiektem i poza jego składnikami: funkcją Inicjuj() i funkcją PlusJeden(), funkcje te nie muszą być widoczne, ani dostępne dla reszty programu. Takie funkcje mogą wraz z danymi zostać uznane za prywatną część obiektu. Takie praktyki, szczególnie w programach przeznaczonych dla środowiska Windows są uzasadnione i pożyteczne. Rozważmy obiekt, modularyzację i hermetyzację obiektu na konkretnych przykładach. Zacznijmy od zdefiniowania klasy. class Licznik { char znak; int ile; public: void Inicjuj(char); void PlusJeden(void); }; Następny krok, to zdefiniowanie obu funkcji. Zwróć uwagę, że funkcje nie są już definiowane "niezależnie", lecz w stosunku do własnej klasy: void Licznik::Inicjuj(char x) { znak = x; ile = 0; } void Licznik::PlusJeden(void) { ile++; } Skoro funkcje widzą już wyłącznie własną klasę, zapis licznik.znak może zostać uproszczony do --> znak i licznik.ile do --> ile Aby wskazać, że funkcje są członkami klasy Licznik stosujemy operator :: (oper. widoczności/przesłaniania - ang. scope resolution operator). Taki sposób zapisu definicji funkcji oznacza dla C++, że funkcja jest członkiem klasy (ang. member function). Logika C++ w tym przypadku wygląda tak: * Prototypy funkcji należy umieścić w definicji klasy. * Definicje funkcji mogą znajdować się w dowolnym miejscu programu, ponieważ operator przesłaniania :: pozwala rozpatrywać klasę podobnie jak zmienne globalne. * Wstawiając operator :: pomiędzy nazwę klasy i prototyp funkcji informujemy C++ że dana funkcja jest członkiem określonej klasy. Funkcje - członkowie klas nazywane są często METODAMI. Definicje klas i definicje funkcji - METOD są często umieszczane razem - w plikach nagłówkowych. Jeśli posługujemy się taką metodą, wystarczy dołączyć odpowiedni plik dyrektywą # include. Kompilator C++ skompiluje wtedy automatycznie wszystkie funkcje, które znajdzie w dołączonych plikach nagłówkowych. Możemy przystąpić do utworzenia programu. main() { char znak_we; //Dekl. zwyklej zmiennej Licznik licznik; //Deklarujemy obiekt klasy Licznik licznik.Inicjuj('A'); //Inicjujemy licznik ... Możemy teraz określić ilość wprowadzonych z klawiatury liter 'A' oraz 'a' i wyprowadzić ją na ekran monitora. Pojawia się jednak pewien problem. Nie uda się sięgnąć z zewnątrz do prywatnych danych obiektu tak, jak poprzednio: if(licznik.znak == toupper(znak_we)) .... Potrzebna nam będzuie jeszcze jedna metoda autoryzowana do dostępu do danych obiektu: char Licznik::Pokaz(void); która nie będzie w momencie wywołania pobierać od programu żadnych argumentów (void), natomiast pobierze znak z pola char Licznik.znak i przekaże tę informację w postaci zmiennej typu char do programu. Definicja takiej metody powinna być następująca: char Licznik::Pokaz(void) { return znak; } Ten sam problem wystąpi przy próbie pobrania od obiektu efektów jego pracy - stanu pola licznik.ile. Do tego też niezbędna jest autoryzowana do dostępu metoda. Nazwiemy ją Efekt(): int Licznik::Efekt(void) { return ile; } Program w wersji obiektowej będzie wyglądać tak: [P093.CPP] # include # include class Licznik { char znak; int ile; public: void Inicjuj(char); void PlusJeden(void); char Pokaz(void); int Efekt(void); }; void main() { char znak_we; Licznik licznik; licznik.Inicjuj('A'); cout << "\nWpisz tekst zawierajacy litery A"; cout << "\nPierwsze wytapienie litery k lub K"; cout << "\n - oznacza Koniec zliczania: "; for(;;) { cin >> znak_we; if (znak_we == 'k' || znak_we == 'K') break; if(licznik.Pokaz() == toupper(znak_we)) licznik.PlusJeden(); } cout << "\nLitera " << licznik.Pokaz() << " wystapila " << licznik.Efekt() << " razy."; } /* Definicje wszystkich funkcji: */ void Licznik::Inicjuj(char x) { znak = x; ile = 0; } void Licznik::PlusJeden(void) { ile++; } char Licznik::Pokaz(void) { return znak; } int Licznik::Efekt(void) { return ile; } Przejdziemy teraz do bardziej szczegółowego omówienia zasygnalizowanego wcześniej problemu inicjowania struktur w pamięci przy pomocy funkcji o specjalnym przeznaczeniu - tzw. KONSTRUKTORÓW.