LEKCJA 31: PRZEKAZANIE OBIEKT�W JAKO ARGUMENT�W DO FUNKCJI. ________________________________________________________________ W trakcie tej lekcji poznasz sposoby manipulowania obiektami przy pomocy funkcji. Poznasz tak�e troch� dok�adniej referencje. ________________________________________________________________ Typowy spos�b przekazywania argument�w do funkcji w C++ to przekazanie przez warto�� (ang. by value). W przypadku obiekt�w oznacza to w praktyce przekazanie do funkcji kopii obiektu. Jako przyk�ad zastosujemy program zliczaj�cy wyst�pienia znak�w w strumieniu wej�ciowym. Zmienimy w tym programie spos�b wyprowadzenia wynik�w. Funkcji Pokazuj() przeka�emy jako argument obiekt. Obiekt-licznik zawiera w �rodku t� informacj�, kt�rej potrzebuje funkcja - ilo�� zliczonych znak�w. Zacznijmy od zdefiniowania klasy. class Licznik { public: char moja_litera; int ile; Licznik(char litera); void Skok_licznika(); }; W programie g��wnym mo�emy zastosowa� konstruktor do zainicjowania obiektu np. tak: main() { Licznik licznik_a('a'); ... Zdefiniujmy funkcj�. Obiekt licznik_a b�dzie argumentem funkcji Pokazuj(). Funkcja powinna wyprowadzi� na ekran zawarto�� pola licznik_a.ile. Deklaracja - prototyp takiej pobieraj�cej obiekt funkcji b�dzie wygl�da� tak: wart_zwracana Nazwa_funkcji(nazwa_klasy nazwa_obiektu); Nazwa klasy spe�nia dok�adnie tak� sam� rol� jak ka�dy inny typ danych. W naszym przypadku b�dzie to wygl�da� tak: void Pokazuj(Licznik obiekt); Poniewa� "obiekt" jest parametrem formalnym i jego nazwa nie jest tu istotna, mo�emy pomin�� j� w prototypie funkcji (w definicji ju� nie!) i skr�ci� zapis do postaci: void Pokazuj(Licznik); Funkcja Pokazuj() otrzyma w momencie wywo�ania jako sw�j argument kopi� obiektu, kt�r� jako argument formalny funkcji nazwali�my "obiekt". W naszym programie wywo�anie tej funkcji b�dzie wygl�da� tak: Pokazuj(licznik_a); Obiekt "licznik_a" jest tu BIE��CYM ARGUMENTEM FAKTYCZNYM. Typ (tzn. tu: klasa) argumentu faktycznego musi by� oczywi�cie zgodny z zadeklarowanym wcze�niej typem argumentu formalnego funkcji. Je�li funkcja dosta�a w�asn� kopi� obiektu, mo�e odwo�a� si� do element�w tego obiektu w taki spos�b: void Pokazuj(Licznik obiekt) { cout << obiekt.ile; } albo np. tak: int Pokazuj(Licznik obiekt) { return (obiekt.ile); } Nale�y podkre�li�, �e funkcja Pokazuj() NIE MA DOST�PU do oryginalnego obiektu i jego danych. Podobnie jak by�o to w przypadku przekazania zmiennej do funkcji i tu funkcja ma do dyspozycji WY��CZNIE SWOJ� "PRYWATN�" KOPI� obiektu. Funkcja nie mo�e zmieni� zawarto�ci p�l oryginalnego obiektu. Podobnie, jak w przypadku "zwyk�ych" zmiennych, je�li chcemy by funkcja dzia�a�a na polach oryginalnego obiektu, musimy funkcji przekaza� nie kopi� obiektu a wska�nik (pointer) do tego obiektu. Oto program przyk�adowy w ca�o�ci: [P110.CPP] //UWAGA: Program moze wymagac modelu wiekszego niz SMALL ! # include "ctype.h" # include "iostream.h" class Licznik { public: char moja_litera; int ile; Licznik(char); void Skok_licznika(); }; /* Prototypy funkcji (dwie wersje): ---------------- */ void Pokazuj1(Licznik); int Pokazuj2(Licznik); void main() { /* inicjujemy licznik: -------------------------------*/ Licznik licznik_a('a'); /* pracujemy - zliczamy: -------------------------------*/ cout << "Wpisz ciag zankow zakonczony kropka [.]" << '\n'; for(;;) { char znak; cin >> znak; if(znak == '.') break; if (znak == licznik_a.moja_litera) licznik_a.Skok_licznika(); } /* sprawdzamy: ----------------------------------------*/ cout << "Wyswietlam wyniki zliczania litery a: \n"; Pokazuj1(licznik_a); cout << '\n' << Pokazuj2(licznik_a); } Licznik::Licznik(char z) { moja_litera = z; ile = 0; } void Licznik::Skok_licznika(void) { ile++; } /* ------------ Definicje funkcji: ---------------- */ void Pokazuj1(Licznik Obiekt) { cout << Obiekt.ile; } int Pokazuj2(Licznik Obiekt) { return (Obiekt.ile); } [!!!]UWAGA: ________________________________________________________________ Programy manipuluj�ce obiektami w taki spos�b mog� wymaga� modelu pami�ci wi�kszego ni� przyjmowany domy�lnie model SMALL. Typowy komunikat pojawiaj�cy si� przy zbyt ma�ym modelu pami�ci to: Error 43: Type mismatch in parameter to call to Pokazuj1(Licznik)... (��y typ argumentu przy wywo�aniu funkcji Pokazuj(...)...) Programy obiektowe s� z regu�y szybke, ale niestety do�� "pami�cioch�onne". W IDE BORLAND C++ masz do dyspozycji opcj�: Options | Compiler | Code generation | Model Dok�adniejsze informacje o modelach pami�ci znajdziesz w dalszej cz�ci ksi��ki. ________________________________________________________________ O PROBLEMIE REFERENCJI. Typowy (domy�lny) spos�b przekazywania argument�w do funkcji w C++ polega na tzw. "przekazaniu przez warto��" i jest inny ni� Pascalu, czy Basicu. Poniewa� w polskich warunkach do C/C++ wi�kszo�� adept�w "dojrzewa" po przebrni�ciu przez Basic i/lub Pascal, programi�ci ci obci��eni s� ju� pewnymi nawykami i pewnym schematyzmem my�lenia, kt�ry do C++ niestety nie da si� zastosowa� i jest powodem wielu pomy�ek. To, co w Basicu wygl�da zrozumiale (uwaga, tu w�a�nie pojawia si� automatyzm my�lenia): PRINT X REM Wyprowad� bie��c� warto�� zmiennej X INPUT X REM Pobierz warto�� zmiennej X a w Pascalu: writeln(X); { Wyprowad� bie�ac� warto�� zmiennej X } readln(X); { Pobierz warto�� zmiennej X } przyjmuje w C/C++ form� zapisu wyra�nie dualnego: printf("%d", X); //Wyprowad� warto�� zmiennej X scanf("%d", &X); //Pobierz warto�� zmiennej X Na czym polega r�nica? Je�li odrzucimy na chwil� automatyzm i zastanowimy si� nad t� sytuacj�, zauwa�ymy, �e w pierwszym przypadku (wyprowadzanie istniej�cych ju� danych - PRINT, wrilteln, printf()) w celu poprawnego dzia�ania funkcji powinni�my przekaza� jej BIE��C� WARTO�� ARGUMENTU X (adres zmiennej w pami�ci nie jest funkcji potrzebny). Dla Basica, Pascala i C++ bie��ca warto�� zmiennej kojarzoana jest z jej identyfikatorem - tu: "X". W drugim jednak�e przypadku (pobranie danych i umieszczenie ich pod w�a�ciwym adresem pami�ci) jest inaczej. Funkcji zupe�nie nie interesuje bie��ca wart�� zmiennej X, jest jej natomiast do poprawnego dzia�ania potrzebny adres zarezerwowany dla zmiennej X w pami�ci. Ale tu okazuje si�, �e Basic i Pascal post�puj� dok�adnie tak samo, jak poprzednio: INPUT X i read(X); Oznacza to, �e X nie oznacza dla Pascala i Basica bie��cej warto�ci zmiennej, lecz oznacza (DOMY�LNIE) przekazanie do funkcji adresu zmiennej X w pami�ci. Funkcje oczywi�cie "wiedz�", co dosta�y i dalej ju� one same manipuluj� danymi we w�a�ciwy spos�b. W C++ jest inaczej. Zapis: Funkcja(X); oznacza w praktyce, �e zostan� wykonane nast�puj�ce operacje: * spod adresu pami�ci przeznaczonego dla zmiennej X zostanie (zgodnie z zadeklarowanym formatem) odczytana bie��ca warto�� zmiennej X; * warto�� X zostanie zapisana na stos (PUSH X); * zostanie wywo�ana funkcja Funkcja(); * Funkcja() pobierze sobie warto�� argumentu ze stosu (zgodnie z formatem zadeklarowanym w prototypie Funkcji()). * Funkcja() zadzia�a zgodnie ze swoj� definicj� i je�li ma co� do pozostawienia (np. return (wynik); ) pozostawi wynik. Jak wida�: * funkcja "nie wie", gdzie w pami�ci umieszczony by� przekazany jej argument; * funkcja komunikuje si� "ze �wiatem zewn�trznym" (czyli w�asnym programem, b�d� funkcj� wy�szego rz�du - wywo�uj�c�) tylko za po�rednictwem stosu; * funkcja dostaje swoj� "kopi�" argumentu z kt�rym dzia�a; * funkcja nie ma wp�ywu na "orygina�" argumentu, kt�ry pozostaje bez zmian. REFERENCJA - CO TO TAKIEGO ? Zastan�wmy si�, czym w�a�ciwie jest referencja zmiennej w C++. Pewne jest, �e jest to alternatywny spos�b odwo�ania si� do zmiennej. Zacznijmy od trywialnego przyk�adu odwo�ania si� do tej samej zmiennej maj�cej swoj� w�a�ciw� nazw� "zmienna" i referencj� "ksywa". # include "iostream.h" main() { int zmienna; int& ksywa; ... Aby "ksywa" oznacza�a t� sam� zmienn�, referencj� nale�y zainicjowa�: int& ksywa = zmienna; Zainicjujemy nasz� zmienn� "zmienna" i b�dziemy robi� z ni� cokolwiek (np. inkrementowa�). R�wnocze�nie b�dziemy sprawdza�, czy odwo�ania do zmiennej przy pomocy nazwy i referencji b�d� pozostawa� r�wnowa�ne. [P111.CPP] /* UWAGA: Program moze potrzebowac modelu wiekszego niz domyslnie ustawiany MODEL SMALL */ # include "iostream.h" main() { int zmienna = 6666; int& ksywa = zmienna; cout << '\n' << "Zmienna" << " Ksywa"; cout << '\n' << zmienna << '\t' << ksywa; for (register int i = 0; i < 5; i++, zmienna += 100) cout << '\n' << zmienna << '\t' << ksywa; return 0; } Dialog (a w�a�ciwie monolog) powinien wygl�da� tak: C:\>program Zmienna Ksywa 6666 6666 6666 6666 6766 6766 6866 6866 6966 6966 7066 7066 ...
PabloPCK