C++ - lekcje

142
1 SPIS LEKCJA 1. Co o C i C++ kady wiedzie powinien. LEKCJA 2. Jak korzysta z kompilatora BORLAND C++? LEKCJA 3. Glówne menu i inne elementy IDE. LEKCJA 4. Jeszcze o IDE C++ . LEKCJA 5 - DZIALANIA PRZY POMOCY MYSZKI I BLDY W PROGRAMIE LEKCJA 6 - NASTPNY PROGRAM - KOMPUTEROWA ARYTMETYKA LEKCJA 7. Z czego sklada si program LEKCJA 8. Jakich slów kluczowych uywa C++. LEKCJA 9: O SPOSOBACH ODWOLYWANIA SI DO DANYCH LEKCJA 10 Jakie operatory stosuje C++. LEKCJA 11. Jak deklarowa zmienne. Co to jest wskanik LEKCJA 12. Wskaniki i tablice w C i C++. LEKCJA 13. Jak tworzy w programie ptle i rozgalzienia LEKCJA 14. Jak tworzy i stosowa struktury LEKCJA 15. Jak poslugiwa si funkcjami. LEKCJA 16 - ASEMBLER TASM i BASM. LEKCJA 17: TROCH SZCZEGÓLÓW TECHNICZNYCH LEKCJA 18 - O LACUCHACH TEKSTOWYCH LEKCJA 19: KILKA INNYCH PRZYDATNYCH FUNKCJI LEKCJA 20 - JELI PROGRAM POWINIEN URUCHOMI INNY PROGRAM... LEKCJA 21: KILKA PROCESÓW JEDNOCZENIE. LEKCJA 22. NA ZDROWY CHLOPSKI ROZUM PROGRAMISTY. LEKCJA 23 - Co nowego w C++? LEKCJA 24 : SKD WZILY SI KLASY I OBIEKTY W C++. LEKCJA 25: PRZYKLAD OBIEKTU. LEKCJA 26: CO TO JEST KONSTRUKTOR. LEKCJA 27: O DZIEDZICZENIU. LEKCJA 28: DZIEDZICZENIE ZLOONE. LEKCJA 29: FUNKCJE I OVERLOADING. LEKCJA 30: WYMIANA DANYCH MIDZY OBIEKTAMI. LEKCJA 31: PRZEKAZANIE OBIEKTÓW JAKO ARGUMENTÓW DO FUNKCJI LEKCJA 33: WSKANIKI DO OBIEKTÓW. LEKCJA 34 OVERLOADING OPERATORÓW. LEKCJA 35: O ZASTOSOWANIU DZIEDZICZENIA LEKCJA 36: FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE. LEKCJA 37: KADY DYSK JEST ZA MALY, A KADY PROCESOR ZBYT WOLNY... LEKCJA 38: O C++, Windows i malym Chiczyku. czyli: KTO POWIEDZIAL, E PROGRAMOWANIE DLA WINDOWS JEST TRUDNE?!!! LEKCJA 39: KORZYSTAMY ZE STANDARDOWYCH ZASOBÓW Windows. LEKCJA 40: STRUKTURA PROGRAMU PROCEDURALNO - ZDARZENIOWEGO PRZEZNACZONEGO DLA WINDOWS. LEKCJA 41: JAK TWORZY SI APLIKACJ DLA Windows? LEKCJA 42: KOMPILATORY "SPECJALNIE DLA Windows". LEKCJA 43: Elementy sterujce i zarzdzanie programem. LEKCJA 44: O Okienkach dialogowych. LEKCJA 45: Dolczanie zasobów - menu i okienka dialogowe. LEKCJA 46: O PROGRAMACH OBIEKTOWO - ZDARZENIOWYCH. LEKCJA 47: APLIKACJA OBIEKTOWA - RYSOWANIE W OKNIE. LEKCJA 48: O PAKIETACH BORLAND C++ 4/4.5.

Transcript of C++ - lekcje

Page 1: C++ - lekcje

1

SPIS LEKCJA 1. Co o C i C++ ka�dy wiedzie� powinien. LEKCJA 2. Jak korzysta� z kompilatora BORLAND C++? LEKCJA 3. Główne menu i inne elementy IDE. LEKCJA 4. Jeszcze o IDE C++ . LEKCJA 5 - DZIAŁANIA PRZY POMOCY MYSZKI I BŁ�DY W PROGRAMIE LEKCJA 6 - NAST�PNY PROGRAM - KOMPUTEROWA ARYTMETYKA LEKCJA 7. Z czego składa si� program LEKCJA 8. Jakich słów kluczowych u�ywa C++. LEKCJA 9: O SPOSOBACH ODWOŁYWANIA SI� DO DANYCH LEKCJA 10 Jakie operatory stosuje C++. LEKCJA 11. Jak deklarowa� zmienne. Co to jest wska�nik LEKCJA 12. Wska�niki i tablice w C i C++. LEKCJA 13. Jak tworzy� w programie p�tle i rozgał�zienia LEKCJA 14. Jak tworzy� i stosowa� struktury LEKCJA 15. Jak posługiwa� si� funkcjami. LEKCJA 16 - ASEMBLER TASM i BASM. LEKCJA 17: TROCH� SZCZEGÓLÓW TECHNICZNYCH LEKCJA 18 - O ŁA�CUCHACH TEKSTOWYCH LEKCJA 19: KILKA INNYCH PRZYDATNYCH FUNKCJI LEKCJA 20 - JE�LI PROGRAM POWINIEN URUCHOMI� INNY PROGRAM... LEKCJA 21: KILKA PROCESÓW JEDNOCZE�NIE. LEKCJA 22. NA ZDROWY CHŁOPSKI ROZUM PROGRAMISTY. LEKCJA 23 - Co nowego w C++? LEKCJA 24 : SKD WZI�ŁY SI� KLASY I OBIEKTY W C++. LEKCJA 25: PRZYKŁAD OBIEKTU. LEKCJA 26: CO TO JEST KONSTRUKTOR. LEKCJA 27: O DZIEDZICZENIU. LEKCJA 28: DZIEDZICZENIE ZŁOONE. LEKCJA 29: FUNKCJE I OVERLOADING. LEKCJA 30: WYMIANA DANYCH MI�DZY OBIEKTAMI. LEKCJA 31: PRZEKAZANIE OBIEKTÓW JAKO ARGUMENTÓW DO FUNKCJI LEKCJA 33: WSKA�NIKI DO OBIEKTÓW. LEKCJA 34 OVERLOADING OPERATORÓW. LEKCJA 35: O ZASTOSOWANIU DZIEDZICZENIA LEKCJA 36: FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE. LEKCJA 37: KA�DY DYSK JEST ZA MAŁY, A KA�DY PROCESOR ZBYT WOLNY... LEKCJA 38: O C++, Windows i małym Chi�czyku. czyli: KTO POWIEDZIAŁ, E PROGRAMOWANIE DLA WINDOWS JEST TRUDNE?!!! LEKCJA 39: KORZYSTAMY ZE STANDARDOWYCH ZASOBÓW Windows. LEKCJA 40: STRUKTURA PROGRAMU PROCEDURALNO - ZDARZENIOWEGO PRZEZNACZONEGO DLA WINDOWS. LEKCJA 41: JAK TWORZY SI� APLIKACJ� DLA Windows? LEKCJA 42: KOMPILATORY "SPECJALNIE DLA Windows". LEKCJA 43: Elementy steruj ce i zarz dzanie programem. LEKCJA 44: O Okienkach dialogowych. LEKCJA 45: Doł czanie zasobów - menu i okienka dialogowe. LEKCJA 46: O PROGRAMACH OBIEKTOWO - ZDARZENIOWYCH. LEKCJA 47: APLIKACJA OBIEKTOWA - RYSOWANIE W OKNIE. LEKCJA 48: O PAKIETACH BORLAND C++ 4/4.5.

Page 2: C++ - lekcje

2

LEKCJA 1. Co o C i C++ ka�dy wiedzie� powinien. W trakcie tej lekcji dowiesz si�, dlaczego pora na C++. ____________________________________________________ J�zyk C++ jest uniwersalnym, nowoczesnym j�zykiem programowania. Stosowane przez USA i inne kraje wobec Polski wieloletnie embargo COCOM'u (przeszkody w dost�pie do nowoczesnej technologii) sprawiły m. in., �e popularno�� OS2, UNIXa i C/C++ jest w Polsce do dzi� nieproporcjonalnie mała, a Basica, Pascala i DOSa nieproporcjonalnie du�a. W USA C++ ju� od kilku lat stanowi podstawowe narz�dzie programistów. Ju� słysz� oburzenie (A co mnie obchodzi historia "komputerologii" i koligacyjki!). Otó� obchodzi, bo wynikaj z niej pewne "grzechy pierworodne" j�zyka C/C++, a dla Ciebie, szanowny Czytelniku - pewne wnioski praktyczne. Grzech Pierwszy: * Kompilator j�zyka C/C++ jest standardowym wyposa�eniem systemu operacyjnego UNIX. Skutki praktyczne: Ka�dy PC jest w momencie zakupu (co cz�sto wchodzi w cen� zakupu komputera) wyposa�any w system operacyjny DOS - np. DR DOS, PC DOS, PTS DOS lub MS DOS. Standardowo w zestaw systemu MS DOS wchodzi interpreter j�zyka BASIC (w MS-DOS - QBasic.EXE). Mo�esz wi�c by� pewien, �e je�li jest DOS, to musi by� i BASIC. Podobnie rzecz ma si� z C/C++. Je�li jest na komputerze system UNIX (za wyj tkiem najubo�szych wersji systemu XENIX), masz tam do dyspozycji kompilator C/C++, za to BASICA ani Pascala prawie na pewno tam nie ma. Podobnie coraz popularniejszy OS/2 wyposa�ony jest w kompilator (całkiem niezły) C++ i dodatkowo jeszcze w pewne gotowe-firmowe biblioteki. Grzech drugi: * J�zyk C/C++ powstał jeszcze zanim wymy�lono PC, DOS, GUI (Graficzny Interfejs U�ytkownika), Windows i inne tym podobne. Dwa najwa�niejsze skutki praktyczne: I. W zało�eniach twórców j�zyk C++ miał by� szybki (i jest) i zajmowa� mało miejsca w pami�ci (bo ówczesne komputery miały jej bardzo mało!). Zawiera wi�c ró�ne, niezrozumiałe dla nas z dzisiejszego punktu widzenia skróty. Np. to co w Pascalu czy Basicu wygl da zrozumiale: i:=i+1; (Pascal) 10 I=I+1 lub inaczej NEXT I (Basic) to w j�zyku C++ wygl da dziwacznie: i++; albo jeszcze dziwniej ++i; Tym niemniej zwró� uwag�, �e w Pascalu zajmuje to 7 znaków, w Basicu - 8 znaków (spacja to te� znak!), a w C++ tylko 4. Inny przykład: X=X+5 (Basic, 5 znaków), X:=X+5 (Pascal, 6 znaków), X+=5 (C++, tylko 4 znaki). Z takiej wła�nie filozofii wynika i sama nazwa - najkrótsza z mo�liwych. Je�li bowiem i++ miało znaczy� mniej wi�cej tyle samo co NEXT I (nast�pne I) to C++ znaczy mniej wi�cej tyle samo co "NAST�PNA WERSJA C". II. Nie ma nic za darmo. W j�zyku C/C++, podobnie jak w samochodzie wy�cigowym formuły I, za szybko�� i skuteczno�� płaci si� komfortem. Konstrukcje stosowane w j�zyku C/C++ s maksymalnie dostosowane do "wygody" komputera. Pozwala to na uzyskiwanie ˙niezwykle szybkich "maszynowo-zorientowanych" kodów wykonywalnych programu, ale od programisty wymaga przyzwyczajenia si� do "komputerowo-zorientowanego sposobu my�lenia". Grzech Trzeci (i chyba najci��szy):

* Jest najlepszy. Ostro�niej - jest najch�tniej stosowanym narz�dziem profesjonalnych programistów. Ma oczywi�cie konkurentów. Visual Basic (do małych aplikacji okienkowych), Turbo Pascal (do nauki podstaw - elementów programowania sekwencyjnego, proceduralno-strukturalnego), QuickBasic (programowanie strukturalne w �rodowisku DOS), Fortran 90, ADA, SmallTalk, itp, itd. Sam wielki Peter Norton przyznaje, �e pocz tkowe wersje swojego słynnego pakietu Norton Utilities pisał w Pascalu, ale dopiero przesiadka na C/C++ pozwoliła mu doprowadzi� NU do dzisiejszej doskonało�ci. Jakie s programy Petera Nortona - ka�dy widzi... Zapewne masz na swoim komputerze kilka ró�nych aplikacji (np. TAG, QR-Tekst, Word, itp.) - je�li zajrzysz do nich do �rodka (View), mo�esz sam przekona� si�, �e wi�kszo�� z nich została napisana wła�nie w C++ (Kompilatory C++ pozostawiaj w kodzie wynikowym .EXE swoj wizytówk� zwykle czyteln przy pomocy przegl darki; przekonasz si� o tym tak�e zagl daj c przez [View] do własnych programów); stosowane narz�dzia mo�esz rozpozna� tak�e po obecno�ci dynamicznych bibliotek - np. BWCC.DLL - biblioteka elementów steruj cych - klawiszy, itp - Borland Custom Controls for Windows). Skutki praktyczne: Nauczywszy si� j�zyka C/C++ mo�esz nie ba� si� ani systemu UNIX/XENIX a ich �rodowiska okienkowego - X Windows, ani OS2, ani Windows 95 (dotychczasowe testy starych 16-bitowych aplikacji wykazały b. wysoki stopie� kompatibilno�ci), ani stacji roboczych, ani du�ych komputerów klasy mainframe. J�zyk C/C++ dosłu�ył si� bowiem ogromnej ilo�ci tzw. implementacji czyli swoich odmian, przeznaczonych dla ró�nych komputerów i dla ró�nych systemów operacyjnych. Windows NT i Windows 95 równie� zostały napisane w C++. Czytaj c pras� (np. Computer World, PC-Kurier i in.) zwró� uwag�, �e najwi�cej ofert pracy jest wła�nie dla programistów posługuj cych si� C++ (i tak zapewne b�dzie jeszcze przez kilka lat, póki nie wymy�l czego� lepszego - np. jakiego� C+++). Z Grzechu Trzeciego (cho� nie tylko) wynika tak�e po�rednio Grzech Czwarty. J�zyka C++ Grzech Czwarty - ANSI C, C++, czy Turbo C++, Visual C++, czyli mała wie�a BABEL. Nie jestem pewien, czy "wie�a BABEL" jest okre�leniem trafniejszym ni� "kamie� filozoficzny", b d� "perpetuum mobile". To co w ci gu ostatnich lat stało si� z j�zykiem C++ ma co� wspólnego z ka�dym z tych utopijnych symboli. A w du�ym uproszczeniu było to tak. Podobnie, jak mechanikom od zarania dziejów marzyło si� perpetuum mobile, tak informatykom zawsze marzyło si� stworzenie jednego SUPER-UNIWERSALNEGO j�zyka programowania. Takiego, który byłby zupełnie niezale�ny od sprz�tu tzn., aby program napisany w takim j�zyku mógł by� przeniesiony BEZ ADNYCH ZMIAN na dowolny komputer I DZIAŁAŁ. Do takiej roli pretendowały kolejno FORTRAN, Algol a potem przyszła pora na C/C++. Gdyby informatycy nie okazali si� zbyt zachłanni, mo�e co� by z tego wyszło. Ale, jak to w �yciu, programi�ci (podobnie jak �ona rybaka z bajki "O rybaku i złotej rybce") chcieli wszystkiego naraz: * �eby program dał si� przenie�� na komputer innego typu i działał, * �eby działał szybko i optymalnie wykorzystywał sprz�t, * �eby umiał wszystko, co w informatyce tylko wymy�lono (tj. i grafika, i obiekty, i obsługa peryferii i...). I stało si�. W pomy�lanym jako uniwersalny j�zyku zacz�ły powstawa� odmiany, dialekty, mutacje, wersje itp. itd. Je�li C++ nie jest Twoim pierwszym j�zykiem, z pewno�ci zauwa�yłe� Czytelniku, �e pomi�dzy GW Basic a Quick Basic s pewne drobne ró�nice. Podobnie Turbo Pascal 7.0 troch� ró�ni si� od Turbo Pascala 5.0. Mimo to przykład poni�ej pewnie Ci� troch� zaskoczy. Dla zilustrowania skali problemu przedstawiam poni�ej dwie wersje TEGO SAMEGO PROGRAMU napisanego w dwu ró�nych wersjach TEGO SAMEGO J�ZYKA C++. . Obydwa programy robi dokładnie to samo. Maj za zadanie wypisa� na ekranie napis "Hello World" (czyli "Cze�� �wiecie!").

Page 3: C++ - lekcje

3

Program (1) main() { printf("Hello World\n"); } Program (2) #include <windows.h> #include <iostream.h> LPSTR p = "Hello World\n"; main(void) { cout << "Hello World" << endl; MessageBox(0, p, "Aplikacja dla Windows", MB_OK); return (0); } Có� za uderzaj ce podobie�stwo, prawda? Ale �arty na bok. Je�li zaistnieje jaki� problem, zawsze mamy co najmniej trzy wyj�cia. Mo�emy: 1. Udawa�, �e go nie ma. Tak post�puje wielu autorów podr�czników na temat C/C++. 2. Krzycze�, �e nam si� to nie podoba. Mamy pełne prawo obrazi� si� i wróci� do Basica lub Pascala. 3. Spróbowa� porusza� si� w tym g szczu. Wyj�cie trzecie ma jedn wad� - jest najtrudniejsze, ale i efekty takiego wyboru s najbardziej obiecuj ce. Je�li chcesz zaryzykowa� i wybra� wyj�cie trzecie, spróbujmy zrobi� pierwszy krok w tej "d�ungli". Wyja�nijmy kilka nazw, poj�� i zasad gry obowi zuj cych w tym obszarze. J�zyki programowania posługuj si� pewnymi specyficznymi grupami słów i symboli. S to m. in.: * słowa kluczowe (tu pomi�dzy wersjami C++ rozbie�no�ci s niewielkie), * operatory (po prostu znaki operacji - np. +), (tu zgodno�� jest niemal 100 %-owa) * dyrektywy (polecenia dla kompilatora JAK tworzy� program wynikowy; tu ju� jest gorzej, szczególnie dyrektywa #pragma w ka�dej wersji kompilatora C++ jest inna) * nazwy funkcji (z tym gorzej, bo ka�dy producent ma własne funkcje i własne upodobania) * nazwy stałych (gdyby chodziło tylko o PI i e - wszystko byłoby proste) * nazy zasobów (FILE, PRN, CONSOLE, SCREEN itp. itd) (tu jest lepiej, ale te� rozbie�no�ci s zauwa�alne) Autor programu mo�e jeszcze nadawa� zmiennym (liczbom, zmiennym napisom, obiektom, itp.) własne nazwy, wi�c czasem nawet wytrawny programista ma kłopoty ze zrozumieniem tekstu �ródłowego programu... W j�zyku C a nast�pnie C++ przyj�to pewne maniery nadawania nazw - identyfikatorów ułatwiaj ce rozpoznawanie tych grup słów: * nazwa() - funkcja * słowa kluczowe i nazwy zmiennych - małymi literami * STAŁE - nazwy stałych najcz��ciej du�ymi literami * long/LONG - typy danych podstawowe/predefiniowane dla Windows _NAZWA - nazwy stałych predefiniowanych przez producenta __nazwa lub __nazwa__ - identyfikatory charakterystyczne dla danej wersji kompilatora itp., których to zwyczajów i ja postaram si� przestrzega� w tek�cie ksi �ki.

Ameryka�ski Instytut Standardów ANSI od lat prowadzi walk� z wiatrakami. Stoi na stra�y jednolitego standardu j�zyka, który nazywa si� standardem ANSI C i ANSI C++. Wielcy producenci od czasu do czasu organizuj konferencje i spotkania gdzie� w ciepłych krajach i uzgadniaj niektóre standardy - czyli wspólne dla nich i zalecane dla innych normy, ale niektórzy bywaj zazdro�ni o własne tajemnice i nie publikuj wszystkich informacji o swoich produktach. Dlatego wszelkie "słuszne i uniwersalne" standardy typu ODBC, Latin 2, Mazovia, LIM, OLE, DDE, BGI, itp., itd. maj niestety do dzi� ograniczony zakres stosowalno�ci a wszelkie zapewnienia producentów o całkowitej zgodno�ci ich produktu z... (tu wpisa� odpowiednie) nale�y niestety nadal traktowa� z pewn rezerw . W niniejszej ksi �ce zajmiemy si� kompilatorem Borland C++ w jego wersjach 3.0 do 4.5, jest to bowiem najpopularniejszy w Polsce kompilator j�zyka C/C++ przeznaczony dla komputerów IBM PC. Nie bez znaczenia dla tej decyzji był tak�e fakt, �e Borland C++ i Turbo C++ bez konfliktów współpracuje z: * Turbo Pascal i Borland Pascal; * Assemblerami: TASM, BASM i MASM; * Turbo Debuggerem i Turbo Profilerem; * bibliotekami Turbo Vision, ObjectVision, Object Windows Library, Database Tools, itp. * pakietami innych producentów - np. Win/Sys Library, Object Professional, CA-Visual Objects, Clipper, itp. i in. produktami "ze stajni" Borlanda popularnymi w�ród programistów. Programy TASM/BASM, Debugger, Profiler a tak�e niektóre biblioteki (np. Object Windows Library, Turbo Vision Library, itp. wchodz w skład pakietów instalacyjnych BORLANDA, ale UWAGA - niestety nie wszystkich). Borland C++ 4+ pozwala, dzi�ki obecno�ci specjalnych klas VBX w bibliotece klas i obiektów Object Windows Library na wykorzystanie programów i zasobów tworzonych w �rodowisku Visual Basic'a. Podobnie kompilatory C++ firmy Microsoft (szczególnie Visual C++) bezkonfliktowo współpracuj z zasobami innych aplikacji - np. Access, Excel, itp.. Warto tu zwróci� uwag� na jeszcze jeden czynnik, który mo�e sta� si� Twoim, Czytelniku atutem. Je�li znasz ju� kompilatory Turbo Pascal, b d� Borland Pascal, zwró� uwag�, �e wiele funkcji zaimplementowanych w Turbo Pascal 6.0. czy 7.0 ma swoje odpowiedniki w BORLAND C++ i Turbo C++. Odpowiedniki te zwykle działaj dokładnie tak samo, a ró�ni si� najcz��ciej nieznacznie pisowni nazwy funkcji. Wynika to z błogosławie�stwa "lenistwa" (pono� homo sapiens najwi�cej wynalazków popełniał wła�nie ze strachu, b d� z lenistwa...). Firmie Borland "nie chciało si�" wymy�la� od nowa tego, co ju� sprawdziło si� wcze�niej i do czego przyzwyczaili si� klienci! I odwrotnie. Poznawszy Borland/Turbo C++ z łatwo�ci zauwa�ysz te same funkcje w Borland/Turbo Pascalu. [!!!]UWAGA! ________________________________________________________________ O Kompilatorach BORLAND C++ 4 i 4.5 napisz� nieco pó�niej, poniewa� s bardziej skomplikowane i wymagaj troch� wi�kszej znajomo�ci zasad tworzenia i uruchamiania programów (projekty). To prawda, �e zawieraj narz�dzia klasy CASE do automatycznego generowania aplikacji i jeszcze kilka innych ułatwie�, ale miej troch� cierpliwo�ci... ________________________________________________________________ [???] C.A.S.E. ________________________________________________________________ CASE - Computer Aided Software Engineering - in�ynieria programowa wspomagana komputerowo. Najnowsze kompilatory C++ wyposa�one s w narz�dzia nowej generacji. W ró�nych wersjach nazywaj si� one AppExpert, ClassExpert, AppWizard, VBX Generator, itp. itd, które pozwalaj w du�ym stopniu zautomatyzowa� proces tworzenia aplikacji. Nie mo�na jednak zaczyna� kursu pilota�u od programowania autopilota - a kursu programowania od automatycznych generatorów aplikacji dla Windows... ________________________________________________________________ Zaczynamy zatem od rzeczy najprostszych, maj c jedynie t�

Page 4: C++ - lekcje

4

krzepi c �wiadomo��, �e gdy ju� przyst pimy do pisania aplikacji konkurencyjnej wobec Worda, QR-Tekst'a, czy Power Point'a - mo�e nas wspomaga� system wspomaganina CASE doł czony do najnowszych wersji BORLAND C++ 4 i 4.5. Je�li mamy ju� gotowe aplikacje w Visual Basic'u - Borland C++ 4+ pozwoli nam skorzysta� z elementów tych programów (ale pracowa� te aplikacje po przetransponowaniu do C++ b�d od kilku do kilkuset razy szybciej). _______________________________________________________________ LEKCJA 2. Jak korzysta� z kompilatora BORLAND C++? ________________________________________________________________ W trakcie tej lekcji poznasz sposoby rozwi zania typowych problemów wyst�puj cych przy uruchomieniu kompilatora Borland C++. ________________________________________________________________ UWAGA: Z A N I M rozpoczniesz prac� z dyskietk doł czon do niniejszej ksi �ki radzimy Ci SPORZDZI� ZAPASOW KOPI� DYSKIETKI przy pomocy rozkazu DISKCOPY, np. DISKCOPY A: A: lub DISKCOPY B: B: Unikniesz dzi�ki temu by� mo�e wielu kłopotów, których mo�e Ci narobi� np. przypadkowy wirus lub kropelka kawy. INSTALACJA DYSKIETKI. Na dyskietce doł czonej do niniejszej ksi �ki, któr najlepiej zainstalowa� na dysku stałym (z dyskiem pracuje si� znacznie szybciej, a prócz tego jest tam znacznie wi�cej miejsca), w jej katalogu głównym znajduje si� programik instalacyjny o nazwie: INSTALUJ.BAT napisany jako krótki plik wsadowy w j�zyku BPL (Batch Programming Language - j�zyk programowania wsadowego). Aby zainstalowa� programy z dyskietki na własnym dysku powiniene�: * sprawdzi�, czy na dysku (C:, D:, H: lub innym) jest co najmniej 2 MB wolnego miejsca, * wło�y� dyskietk� do nap�du i wyda� rozkaz: <-- patrz tekst ksiazki * po naci�ni�ciu [Entera] rozpocznie si� nstalacja. O zako�czeniu instalacji zostaniesz poinformowany napisem na ekranie. UWAGI: * Je�li korzystasz z nap�du dyskietek B:, lub chcesz zainstalowa� programy z dyskietki na innym dysku ni� C: - wystarczy napisa� rozkaz - np. B:\INSTALUJ AMC48 D: i nacisn � [Enter]. * Program instalacyjny zadziała poprawnie tylko wtedy, gdy masz system operacyjny DOS 6+ (6.0 lub nowszy) na dysku C: w katalogu C:\DOS. * Mo�esz zainstalowa� programy z dyskietki z poziomu �rodowiska Windows. W oknie Mened�era Programów: - rozwi� menu Plik - wybierz rozkaz Uruchom... - do okienka wpisz <-- patrz tekst ksi �ki Program instalacyjny utworzy na wskazanym dysku katalog \C-BELFER i tam skopiuje cał zawarto�� dyskietki oraz dokona dekompresji (rozpakowania) plików. Je�li chcesz skopiwa� zawarto�� dyskietki do własnego katalogu roboczego, wystarczy "wskaza�" programowi instalacyjnemu wła�ciwy adres: <-- patrz tekst ksi �ki Zostanie utworzony katalog: F:\USERS\ADAM\TEKSTY\C-BELFER UWAGA: Prócz przykładów opisanych w ksi �ce dyskietka zawiera dodatkowo kilka przykładowych aplikacji, na które zabrakło miejsca, mi�dzy innymi:

WYBORY95 - prosta gra zr�czno�ciowa (dla Windows) FOR*.CPP - przykłady zastosowania p�tli BGI*.CPP - przykłady grafiki DOS/BGI oraz programik ułatwiaj cy kurs - MEDYT.EXE wyposa�ony w dodatkowe pliki tekstowe. I. URUCHOMIENIE KOMPILATORA. Aby uruchomi� kompilator, powiniene� w linii rozkazu po DOS'owskim znaku zach�ty (zwykle C> lub C:\>) wyda� polecenie: BC i nacisn � [Enter]. (UWAGA: w ró�nych wersjach kompilatorów mo�e to by� np.: BC, TC, a dla Windows np. BCW - sprawd� swoj wersj�) Je�li Twój komputer odpowiedział na to: Bad command or file name * na Twoim komputerze nie ma kompilatora BORLAND C++: ROZWIZANIE: Zainstaluj C++. * w pliku AUTOEXEC.BAT nie ma �cie�ki dost�pu do katalogu, w którym zainstalowany jest kompilator C++. ROZWIZANIE: 1. Zmieni� bie� cy katalog (i ewentualnie dysk) na odpowiedni, np.: D:[Enter] CD D:\BORLANDC\BIN[Enter]. //UWAGA: Podkatalog \BIN Albo 2. Ustawi� �cie�k� dost�pu przy pomocy rozkazu np: PATH C:\BORLANDC\BIN (lub D:\TURBOC\BIN stosownie do rozmieszczenia plików na Twoim komputerze; najlepiej zasi�gnij rady lokalnego eksperta). [???] NIE CHCE USTAWI� �CIEKI ? ________________________________________________________________ Tak czasem si� zdarza - zwykle wtedy, gdy pracujesz w DOS-ie z programem Norton Commander. Musisz pozby� si� "na chwil�" programu NC. Naci�nij [F10] - Quit i potwierd� przez [Y] lub [Enter]. Po ustawieniu �cie�ek mo�esz powtórnie uruchomi� NC. ________________________________________________________________ Albo 3. Doda� do pliku AUTOEXEC.BAT dodatkow �cie�k�. Jest to wyj�cie najlepsze. Na ko�cu linii ustawiaj cej �cie�ki - np.: PATH C:\; C:\DOS; C:\NC; C:\WINDOWS dodaj �cie�k� do kompilatora C++, np.: PATH C:\; C:\DOS; C:\NC; D:\BORLANDC\BIN; Załatwi to problem "raz na zawsze". Po uruchomieniu komputera �cie�ka b�dzie odt d zawsze ustawiana automatycznie. Poniewa� kompilator C++ wymaga w trakcie pracy otwierania i ł czenia wielu plików, ró�ne wersje (program instalacyjny INSTALL.EXE podaje t� informacj� w okienku pod koniec instalacji) wymagaj dodania do pliku konfiguracyjnego CONFIG.SYS wiersza: FILES = 20 (dla ró�nych wersji warto�� ta wacha si� w granicach od 20 do 50). Najbezpieczniej, je�li nie masz pewno�ci doda� 50. Je�li wybrałe� wariant trzeci i ewentualnie zmodyfikowałe� swój CONFIG.SYS, wykonaj przeładowanie systemu [Ctrl]-[Alt]-[Del]. Teraz mo�esz wyda� rozkaz BC[Enter] Mam nadziej�, �e tym razem si� udało i oto jeste�my w IDE Borland C++. Je�li nie jeste� jedynym u�ytkownikiem, na ekranie rozwinie si� cała kaskada okienek roboczych. Skonsultuj z wła�cicielem, które z nich mo�na pozamyka� a które pliki mo�na skasowa� lub przenie��. Pami�taj "primo non nocere" - przede wszystkim nie szkodzi�!

Page 5: C++ - lekcje

5

�[S!] IDE = Integrated Development Environment, IDE, czyli Zintegrowane �rodowisko Uruchomieniowe. Bardziej prozaicznie - poł czony EDYTOR i KOMPILATOR. Zapewne znasz ju� co� podobnego z Pascala lub Quick Basica. Od dzi� b�dzie to Twoje �rodowisko pracy, w którym b�dziesz pisa�, uruchamia� i modyfikowa� swoje programy. �[???] DISK FULL! ________________________________________________________________ Co robi�, je�li przy próbie uruchomienia kompilator C++ odpowiedział Ci: Disk full! Not enough swap space. Program BC.EXE (TC.EXE) jest bardzo długi. Je�li wydasz rozkaz (wariant 1: Turbo C++ 1.0, ni�ej BORLAND C++ 3.1): DIR TC.EXE uzyskasz odpowied�, jak poni�ej: C:>DIR TC.EXE Directory of D:\TC\BIN TC EXE 876480 05-04-90 1:00a 1 file(s) 876480 bytes 17658880 bytes free C:>DIR BC.EXE Directory of C:\BORLANDC\BIN BC EXE 1410992 06-10-92 3:10a 1 file(s) 1410992 bytes 18926976 bytes free Poniewa� plik kompilatora nie mie�ci si� w 640 K pami�ci musi dokonywa� tzw. SWAPOWANIA i tworzy na dysku dodatkowy plik tymczasowy (ang. swap file). Na dysku roboczym powinno pozostawa� najmniej 500 KB wolnego miejsca. Je�li mo�esz, pozostaw na tym dysku wolne nie mniej ni� 1 MB. Ułatwi to i przyspieszy prac�. ________________________________________________________________ Tworzony tymczasowo plik roboczy wygl da tak: Volume in drive D has no label Directory of D:\SIERRA TC000A SWP 262144 12-13-94 5:42p (13-XII to dzi�!) 1 file(s) 262144 bytes 11696320 bytes free �[!!!] UWAGA: Kompilator C++ b�dzie próbował tworzy� plik tymczasowy zawsze w bie� cym katalogu, tzn. tym, z którego wydałe� rozkaz TC lub BC. II. WNIOSKI PRAKTYCZNE. * Lepiej nie uruchamia� C++ "siedz c" na dyskietce, poniewa� mo�e mu tam zabrakn � miejsca na plik tymczasowy. * Dla u�ytkowników Novella: Uruchamiajcie kompilator C++ tylko we własnych katalogach - do innych mo�ecie nie mie� praw zapisu. Plik .SWP jest tworzony tylko podczas sesji z kompilatorem C++ i usuwany natychmiast po jej zako�czeniu. Mo�esz go zobaczy� tylko wychodz c "na chwil�" do systemu DOS przy pomocy rozkazu DOS Shell (menu File). �[S!] SWAP - Zamiana. ________________________________________________________________ Je�li wszystkie dane, potrzebne do pracy programu nie mieszcz si� jednocze�nie w pami�ci operacyjnej komputera, to program - "wła�ciciel", (lub system operacyjny - DOS, OS2, Windows) mo�e dokona� tzw. SWAPOWANIA. Polega to na usuni�ciu z pami�ci operacyjnej i zapisaniu na dysk zb�dnej w tym momencie cz��ci danych, a na ich miejsce wpisaniu odczytanej z dysku innej cz��ci danych, zwykle takich, które s programowi pilnie

potrzebne do pracy wła�nie teraz. ________________________________________________________________ �[Z] - Propozycje zada� do samodzielnego wykonania. ---------------------------------------------------------------- 1.1 Sprawd� ile bajtów ma plik .EXE w tej wersji kompilatora C++, której u�ywasz. 1.2. Posługuj c si� rozkazem DOS Shell z menu File sprawd� gdzie znajduje si� i jakiej jest wielko�ci plik tymczasowy .SWP. Ile masz wolnego miejsca na dysku ? ________________________________________________________________ EOF LEKCJA 3. Główne menu i inne elementy IDE. ________________________________________________________________ W trakcie tej lekcji dowiesz si� jak porusza� si� w zintegrowanym �rodowisku (IDE) Turbo C++. ________________________________________________________________ Najwa�niejsz rzecz w �rodowisku IDE jest GŁÓWNE MENU (ang. MENU BAR), czyli pasek, który widzisz w górnej cz��ci ekranu. Działa to podobnie, jak główne menu w programie Norton Commander (dost�pne tam przez klawisz [F9]). KRÓTKI PRZEGLD GŁÓWNEGO MENU. Przyci�nij klawisz [F10]. Główne menu stało si� aktywne. Teraz przy pomocy klawiszy kursora (ze strzałkami [<-], [->]) mo�esz porusza� si� po menu i wybra� t� grup� polece�, która jest Ci potrzebna. A oto nazwy poszczególnych grup: [S!]�GRUPY POLECE� - NAZWY POSZCZEGÓLNYCH "ROZWIJANYCH" MENU. = Bez nazwy (menu systemowe). File Operacje na plikach. Edit Edycja plików z tekstami �ródłowymi programów. Search Przeszukiwanie. Run Uruchomienie programu. Compile Kompilacja programu. Debug "Odpluskwianie", czyli wyszukiwanie bł�dów w programie. Project Tworzenie du�ych, wielomodułowych programów. Options Opcje, warianty IDE i kompilatora. Window Okna (te na ekranie). Help Pomoc, niestety po angielsku. UWAGA: __________________________________________________________ W niektórych wersjach kompilatora na pasku głównego menu pojawi si� jeszcze Browse - przegl danie (funkcji, struktury klas i obiektów). Zwró� uwag�, �e w okienkowych wersjach niektóre rozkazy "zmieniaj " menu i trafiaj do Browse, Debug, Project. W BC++ 4 menu Run brak (!). Tworzenie aplikacji sprowadza si� tam do nast�puj cych kroków: Project | Open project lub | AppExpert Debug | Run ROZWIJAMY MENU. Z takiego kr�cenia si� w kółko po pasku (a propos, czy zauwa�yłe�, �e pasek pod�wietlenia mo�e by� "przewijany w kółko"?) jeszcze niewiele wynika. Robimy wi�c nast�pny krok. Wska� w menu głównym nazw� "File" i naci�nij [Enter]. Rozwin�ło si� menu File zawieraj ce list� rozkazów dotycz cych operacji na plikach. Po tym menu te� mo�esz si� porusza� przy pomocy klawiszy kursora ze strzałkami gór� lub w dół. Masz do wyboru dwie grupy rozkazów rozdzielone poziom lini : �[S!] ______________________________________________________________ Open - Otwórz istniej cy ju� plik z programem (np. w celu dopisania czego� nowego).

Page 6: C++ - lekcje

6

New - Utwórz nowy plik (zaczynamy tworzy� nowy program). Save - Zapisz bie� cy program na dysk. Pami�taj: Pliki z dysku nie znikaj po wył czeniu komputera. Zawsze lepiej mie� o jedn kopi� za du�o ni� o jedn za mało. oraz Print - Wydrukuj program. Get Info�� - Wy�wietl informacje o stanie IDE. Dos Shell - Wyj�cie "na chwil�" do systemu DOS z mo�liwo�ci powrotu do IDE przez rozkaz EXIT. Quit - Wyj�cie z IDE Turbo C++ i powrót do DOSa. Inaczej - KONIEC PRACY. _______________________________________________________________ Skoro ju� wiemy jak rozpocz � prac� nad nowym programem, zacznijmy przygotowanie do uruchomienia naszego pierwszego programu. Wybierz z menu File rozkaz Open... (otwórz plik). Poniewa� rozkaz taki jest niejednoznaczny, wymaga przed wykonaniem podania dodatkowych informacji. Gdyby Twój komputer mówił, zapytałby w tym momencie "który plik mam otworzy�?". Pytanie zada� musi, b�dzie wi�c prowadził dialog z Tob przy pomocy OKIENEK DIALOGOWYCH. Je�li wybrałe� z menu rozkaz OPEN i nacisn łe� [Enter], to masz wła�nie na ekranie takie okienko dialogowe. Okienko składa si� z kilku charakterystycznych elementów: OKIENKO TEKSTOWE - (ang. Text Box lub Input Box) w którym mo�esz pisa� (klawisz BackSpace [<-] pozwoli Ci skasowa� wprowadzony tekst, je�li si� rozmy�lisz). Okienko to zawiera tekst "*.C". OKIENKO Z LIST - (ang. List Box) zawiera list� plików, z której mo�esz wybra� plik z programem. KLAWISZE OPCJI/POLECE� - (ang. Command Button) kiedy ju� dokonasz wyboru, to mo�esz wskazuj c taki klawisz np. potwierdzi� [OK], zrezygnowa� [Cancel], otworzy� plik [Open] itp.. Pomi�dzy elementami okienka dialogowego mo�esz porusza� si� przy pomocy klawiszy kursora i klawisza [Tab] lub kombinacji klawiszy [Shift]-[Tab] (spróbuj!). Mo�esz tak�e posługiwa� si� myszk . Wi�cej o okienkach i menu dowiesz si� z nast�pnych lekcji, a na razie wró�my do naszego podstawowego zadania - tworzenia pierwszego programu. Zanim zaczniemy tworzy� program włó� do kieszeni nap�du A: (lub B:) dyskietk� doł czon do niniejszej ksi �ki. Mo�e ona sta� si� Twoj dyskietk robocz i pomocnicz zarazem na okres tego kursu. Je�eli zainstalowałe� zawarto�� dyskietki na dysku - przejd� do stosownego katalogu - C:\C-BELFER (D:\C-BELFER) i odszukaj tam programy przykładowe. Je�li nie - mo�esz nadal korzysta� z dyskietki (jest na niej troch� miejsca). Wpisz do okienka tekstowego nazw� A:\PIERWSZY (lub odpowiednio np. C:\C-BELFER\PIERWSZY). Rozszerzeniem mo�esz si� nie przejmowa� - zostanie nadane automatycznie. Plik roboczy z Twoim programem zostanie utworzony na dyskietce w nap�dzie A:. Wska� klawisz [Open] w okienku dialogowym i naci�nij [Enter] na klawiaturze. UWAGA! _________________________________________________________________ Dopóki manipulujesz okienkiem tekstowym i okienkiem z list klawisz polecenia [Open] jest wyró�niony (pod�wietlony) i traktowany jako tzw. OPCJA DOMY�LNA (ang. default). W tym stadium aby wybra� [Open] WYSTARCZY NACISN� [Enter]. __________________________________________________________________ Wrócili�my do IDE. zmieniło si� tyle, �e w nagłówku okna edytora

zamiast napisu "NONAME00.CPP" (ang. no mame - bez nazwy) jest teraz nazwa Twojego programu - PIERWSZY.CPP. Kursor miga w lewym górnym rogu okna edytora. Mo�emy zaczyna�. Pierwsze podej�cie do programu zrobimy troch� "intuicyjnie". Zamiast wyja�nia� wszystkie szczegóły posłu�ymy si� analogi do konstrukcji w Pascalu i Basicu (zakładam, �e napisałe� ju� cho�by jeden program w którym� z tych j�zyków). Szczegóły te wyja�ni� dokładniej pocz wszy od nast�pnej lekcji. WPISUJEMY PROGRAM "PIERWSZY.CPP". Wpisz nast�puj cy tekst programu: /* Program przykładowy - [P-1] */ #include <stdio.h> main() { printf("Autor: ..........."); /*tu wpisz imie Twoje!*/ printf(" TO JA, TWOJ PROGRAM - PIERWSZY.CPP"); printf("...achoj !!!"); } I ju�. Jak widzisz nie jest to a� takie straszne. Gdyby nie to, �e zamiast znajomego PRINT"TO JA...", albo writeln(".."); jest printf("...");, byłoby prawie całkiem zrozumiałe. Podobny program w Pascalu mógłby wygl da� np. tak: # include <stdio.h> uses Crt; main() /* pocz tek */ program AHOJ; {pocz tek} { Begin printf("Autor"); write('Autor'); printf("TO JA"); write('TO JA'); printf("ahoj"); write('ahoj'); } end. a w BASICU: 10 PRINT "Autor" : REM Pocz tek 20 PRINT "TO JA" 30 PRINT "ahoj" 40 END �[!!!]UWAGA ______________________________________________________________ Zwró� uwag�, �e działanie funkcji: PRINT (Basic), printf() (C++), Write i Writeln (Pascal) nie jest identyczne, a TYLKO PODOBNE. ________________________________________________________________ Sprawdzimy, czy program działa. Tam, gdzie s kropki wpisz Twoje imi� - np. Ewa, Marian, Marcin. Pami�taj o postawieniu na ko�cu znaków cudzysłowu ("), zamkni�ciu nawiasu i �redniku (;) na ko�cu linii (wiersza). Naci�nij kombinacj� klawiszy [Alt]-[R]. Jest to inny, ni� opisano poprzednio sposób dost�pu do menu. Kombinacja klawiszy [Alt]-[Litera] powoduje uaktywnienie tego menu, którego nazwa zaczyna si� na podan liter�. Przy takiej konwencji litera nie musi by� zawsze pierwsz liter nazwy opcji. Mo�e to by� tak�e litera wyró�niona w nazwie przez podkre�lenie lub wy�wietlenie np. w innym kolorze. I tak: [Alt]+[F] menu File (Plik) [Alt]+[C] menu Compile (Kompilacja [Alt]+[R] menu Run (Uruchamianie) [Alt]+[W] menu Window (Okna) itd., itd.. Kombinacja [Alt]+[R] wybiera wi�c menu Run (uruchomienie programu). Menu Run daje Ci do wyboru nast�puj ce polecenia: [S!] ________________________________________________________________

Page 7: C++ - lekcje

7

Run - Uruchomienie programu (Utwórz plik .EXE i Wykonaj). Program Reset - "Wyzerowanie" zmiennych programu. Go to Cursor - Wykonanie programu do miejsca wskazanego kursorem w tek�cie. Trace Into - Uruchom �ledzenie programu. Step Over - �ledzenie programu z mo�liwo�ci pomini�cia funkcji. (dosł. tzw. "Przekraczanie" funkcji). Arguments - Uruchom program z zadanymi argumentami. ________________________________________________________________ Wybierz "Run". Je�li nie zrobiłe� �adnego bł�du, program powinien si� skompilowa� z komentarzem "Success" i wykona� (kompilacja zako�czona sukcesem; napis mignie tak szybko, �e mo�esz tego nie zauwa�y�). Je�li chcesz spokojnie obejrze� wyniki działania swojego programu powiniene� wykona� nast�puj ce czynno�ci: 1. Rozwi� menu Window naciskaj c klawisze [Alt]-[W]. 2. Wybierz z menu rozkaz User screen (ekran u�ytkownika). Mo�esz wykona� to samo bez rozwijania menu naciskaj c kombinacj� klawiszy [Alt]-[F5]. 3. Po przejrzeniu wydruku naci�nij [Enter]. Wrócisz do okna edytora. Je�li zrobiłe� bł�dy - kompilacja si� nie uda i program nie zostanie wykonany, w okienku natomiast pojawi si� napis "Errors" (czyli "Bł�dy"). Je�li tak si� stało naci�nij [Enter] dwukrotnie. Popraw ewentualne niezgodno�ci i spróbuj jeszcze raz. Bł�dów zwykle bywa nie wi�cej ni� dwa. Najcz��ciej jest to brak lub przekłamanie której� litery (w słowie main lub printf) i brak �rednika na ko�cu linii. W okienku komunikatów (Message) mog pojawi� si� napisy - np.: Error: Statement missing ; (Bł d: Zgubiony znak ;) �[S] Error Messages - Komunikaty o bł�dach. ________________________________________________________________ Najcz��ciej w komunikatach o bł�dach b�d na pocz tku pojawia� si� nast�puj ce słowa: Error - bł d Warning - ostrze�enie Syntax - składnia (składniowy) Expression - wyra�enie never used - nie u�yte (nie zastosowane) assign - przypisywa�, nadawa� warto��/znaczenie value - warto�� statement - operator, operacja, wyra�enie ________________________________________________________________ �[???] Co z tym �rednikiem? ________________________________________________________________ Zwró� uwag�, �e po pd�wietleniu komunikatu o bł�dzie (pasek wyró�nienia pod�wietlenia mo�esz przesuwa� po li�cie przy pomocy klawiszy ze strzałkami w gór� i w dół) i po naci�ni�ciu [Entera] kompilator poka�e ten wiersz programu, w którym jego zdaniem jest co� nie w porz dku. Brak �rednika zauwa�a zwykle dopiero po przej�ciu do nast�pnego wiersza (i ten�e wiersz poka�e), co bywa na pocz tku troch� myl ce. ________________________________________________________________ [???] CZEGO ON JESZCZE CHCE ? ________________________________________________________________ Nawet po usuni�ciu wszystkich bł�dów C++ nie "uspokoi si�" całkiem i b�dzie wy�wietlał ci gle komunikat ostrzegawczy: * w OKIENKU KOMPILACJI: (bardzo typowa sytuacja) Errors: 0 (Bł�dy: 0) Warnings: 1 (Ostrze�enia: 1) * W OKIENKU KOMUNIKATÓW - (Messages - tym w dolnej cz��ci ekranu):

*WARNING A:\PIERWSZY.C 4: Function should return a value in function main (Uwaga: Funkcja main powinna zwróci� warto��.) Na razie zadowolimy si� spostrze�eniem, �e: * Bł�dy UNIEMOLIWIAJ KOMPILACJ� i powoduj komunikat ERRORS. * Ostrze�enia NIE WSTRZYMUJ KOMPILACJI i powoduj komunikat WARNINGS. Jaki jest sens powy�szego ostrze�enia i jak go unikn � dowiesz si� z nast�pnych lekcji. ________________________________________________________________ Pozostaje nam w ramach tej lekcji: * Zapisa� Twój pierwszy program na dysku i * Wyj�� z IDE C++. JAK STD WYJ�� ? Aby zapisa� plik PIERWSZY.CPP z Twoim programem (ko�cow ostateczn wersj ) na dysk nale�y wykona� nast�puj ce czynno�ci: 1. Naci�nij klawisz [F10]. W głównym menu pojawi si� pasek wyró�nienia sygnalizuj c, �e menu stało si� aktywne. 2. Naci�nij klawisz [F]. Pasek wyró�nienia przesunie si� pod�wietlaj c menu File (operacje na plikach). Rozwinie si� menu File. 3. Naci�nij klawisz [S] - wybierz polecenie Save (je�li chcesz zapisa� program w bie� cym katalogu i pod bie� c nazw ) lub rozkaz Save As... (zapisz jako...), podaj nowy dysk/katalog i now nazw� pliku. Tekst Twojego programu został zapisany na dysku/dyskietce. Teraz mo�emy wyj�� z C++. Aby to zrobi�, wykonaj nast�puj ce czynno�ci: 1. Naci�nij klawisz [F10]. Uaktywni si� główne menu. 2. Rozwi� menu File naciskaj c klawisz [F]. 3. Wybierz z menu polecenie "Exit/Quit" i naci�nij [Enter]. �[!!!] SAVE szybciej. ________________________________________________________________ Zwróc uwag�, �e zamiast rozwija� kolejne menu, mo�esz korzysta� z kombinacji klawiszy, które pozwalaj Ci wyda� rozkaz bez rozwijania menu. Takie kombinacje klawiszy (ang. hot keys lub shortcut keys) znajdziesz w menu obok rozkazu, np.: [Alt]-[X] - Quit/Exit [F2] - Save [F3] - Open [Alt]-[F5] - User screen (Podgl danie działania programu) itp. ________________________________________________________________ �[Z] ________________________________________________________________ 1. Spróbuj napisa� i uruchomi� kilka własnych programów wypisuj cych ró�ne napisy. W swoich programach zastosuj funkcj� printf() według nast�puj cego wzoru: printf("....tu wpisz napis do wydrukowania..."); zastosuj znaki przej�cia do nowego wiersza według wzoru: printf("...napis...\n"); porównaj działanie. Swoim programom staraj si� nadawa� łatwe do rozpoznania nazwy typu PIERWSZY, DRUGI, ADAM1, PRZYKLAD itp. �[???] NIE CHCE DZIAŁA� ? ________________________________________________________________ Pami�taj, �e dla j�zyka C i C++ (w przeciwie�stwie np. do Basica) PRINTF i printf to nie to samo! Słowa kluczowe i nazwy standardowych funkcji MUSZ BY� PISANE MAŁYMI LITERAMI !!!

Page 8: C++ - lekcje

8

________________________________________________________________ �[???] GDZIE MOJE PROGRAMY ? ________________________________________________________________ B d� spokojny. Zapisz wersje �ródłowe programów na dyskietk� (dysk). Swoje programy skompilowane do wykonywalnej wersji *.EXE znajdziesz najprawdopodobniej w katalogu głównym tego dysku, na którym zainstalowany został C++ lub w katalogu \BORLANDC\BIN\.... Je�li ich tam nie ma, zachowaj zimn krew i przeczytaj uwa�nie kilka nast�pnych stron. ________________________________________________________________ � PAMI�TAJ: ________________________________________________________________ Je�li masz oryginalny tekst programu, nazywany WERSJ �RÓDŁOW PROGRAMU, zawsze mo�esz uzyska� ten program w wersji "roboczej", tzn. skompilowa� go na plik wykonywalny typu *.EXE (ang. EXEcutable - wykonywalny). ________________________________________________________________ �[S!] printf() - PRINTing Function - Funkcja DRUKuj ca ________________________________________________________________ na ekranie (dokładniej - na standardowym urz dzeniu wyj�cia). Odpowiednik PRINT w Basicu lub write w Pascalu. Dla ułatwienia rozpoznawania nazw funkcji w tek�cie wi�kszo�� autorów pisz ca o j�zyku C++ umieszcza zawsze po nazwie funkcji par� nawiasów (tak te� musi j stosowa� programista w programach w C++). Ja tak�e b�d� stosowa� dalej t� zasad�. ________________________________________________________________ �[???] A JE�LI NIE MA C++ ??? ________________________________________________________________ W przeciwie�stwie do INTERPRETERÓW (np. QBasic), które musz by� obecne, by program zadziałał, KOMPILATORY tworz wersje wykonywalne programów, które mog pracowa� niezale�nie. W katalogu głównym tego dysku, na którym jest zainstalowany Twój BORLAND/Turbo C++ znajdziesz swoje programy PIERWSZY.EXE, DRUGI.EXE itp. Aby te programy uruchomi� nie musisz uruchamia� kompilatora C++. Wystarczy: 1. Przej�� na odpowiedni dysk przy pomocy polecenia: D: (E: lub F:) 2. Przej�� do odpowiedniego katalogu - np. głównego: CD \ 3. Wyda� polecenie: PIERWSZY[Enter] ________________________________________________________________ �[!!!]UWAGA: ________________________________________________________________ Je�li nie jeste� jedynym u�ytkownikiem kompilatora C++ i na tym samym komputerze pracuje jeszcze kto� inny, sprawd�, czy inny u�ytkownik nie ustawił inaczej katalogu wyj�ciowego (Options | Directories | Output Directory). Katalog wyj�ciowy (ang. output directory) to ten katalog, w którym C++ zapisuje pliki *.EXE po wykonaniu kompilacji. Je�li jeste� skazany na własne siły - patrz - nast�pne lekcje. ________________________________________________________________ SPECJALNE KLAWISZE, które warto pozna�. Oto skrócona tabela z najwa�niejszymi kombinacjami klawiszy słu� cymi do "nawigacji" (czyli pro�ciej - poruszania si�) w �rodowisku IDE kompilatorów BORLAND C++ i Turbo C++. Przydatne w Borland C++ i Turbo C++ kombinacje klawiszy. ________________________________________________________________ Wybór rozkazów z menu: Alt+F Rozwini�cie menu File (operacje na plikach) Alt+E Rozwini�cie menu Edit (edycja tekstu programu) Alt+S Rozwini�cie menu Search (przeszukiwanie) Alt+R Rozwini�cie menu Run (uruchamianie programu) Alt+C Rozwini�cie menu Compile (kompilacja) Alt+D Rozwini�cie menu Debug (diagnostyka i bł�dy) Alt+P Rozwini�cie menu Project (program wielomodułowy) Alt+O Rozwini�cie menu Option (opcje, konfiguracja) Alt+W Rozwini�cie menu Window (zarz dzanie oknami)

Alt+H Rozwini�cie menu Help (pomoc) Alt+B Rozwini�cie menu przegl darki - Browse (Win) Alt+X Wyj�cie z kompilatora DOS'owskiego - Exit Alt+F4 Wyj�cie z kompilatora dla Windows ________________________________________________________________ Rozkazy w trybie edycji tekstu programu: ________________________________________________________________ Shift+Delete Wytnij wybrany blok tekstu (Cut) i umie�� w przechowalni (Clipboard) Shift+Insert Wstaw blok tekstu z przechowalni (Paste) Ctrl+Insert Skopiuj zaznaczony blok tekstu do przechowalni (Copy) Ctrl+Y Skasuj wiersz tekstu (Delete a line) Ctrl+Delete Skasuj zaznaczony blok tekstu Shift+[-->] Zaznaczanie bloku tekstu w prawo Shift+[<--] Zaznaczanie bloku tekstu w lewo Shift+[Down Arrow] Zaznaczanie bloku tekstu w dół (strzałka w dół) Shift+[Up Arrow] Zaznaczanie bloku tekstu w gór� (strzałka w gór�) Alt+Backspace Anuluj ostatni operacj� (Undo) Ctrl+L Powtórz przeszukiwanie (Repeat search) ________________________________________________________________ Rozkazy ogólnego przeznaczenia: ________________________________________________________________ F1 Wy�wietl pomoc - Help screen F2 Zapisz bie� cy stan tekstu na dysk (Save) F3 Otwórz nowy plik (Open) F4 Uruchom i wykonaj program do pozycji wskazanej kursorem F5 Powi�ksz (maximize) bie� ce aktywne okno F6 Przejd� do nast�pnego okna (next window) F7 Wykonaj program krok-po-kroku F8 Wykonaj program krok-po-kroku z pomini�ciem �ledzenia funkcji F9 Skompiluj i skonsoliduj program (Compile/Make) F10 Uaktywnij pasek głównego menu Shift+F1 Wy�wietl spis tre�ci Help - tzw. Help index Shift+F2 Wybierz rozkaz Arguments... z menu Run (uruchamianie programu z parametrami w DOS'owskim wierszu rozkazu) Ctrl+F1 Podpowiedzi kontekstowe (help topic search) Ctrl+F2 Wyzeruj bie� cy program Ctrl+F5 Zmie� pozycj� aktywnego okna Ctrl+F7 Wy�wietl okienko dialogowe "Add Watch" Ctrl+F8 Zaznacz punkt krytyczny (Toggle breakpoint) Ctrl+F9 Uruchom program (Run) Ctrl+PgUp Skocz na pocz tek pliku Ctrl+PgDn Skocz na koniec pliku Alt+F1 Poka� poprzedni ekran Help Alt+F2 Zmniejsz okno Alt+F3 Zamknij aktywne okno Alt+F4 Dokonaj inspekcji (inspect) Alt+F5 Poka� DOS'owski ekran roboczy (User screen) Alt+F7 Przejd� do poprzedniego bł�du (previous error) Alt+F8 Przejd� do nast�pnego bł�du (next error) ________________________________________________________________ ________________________________________________________________ EOF LEKCJA 4. Jeszcze o IDE C++ . _______________________________________________________________ W trakcie tej lekcji: 1. Dowiesz si� wi�cej o menu i okienkach w �rodowisku IDE. 2. Poznasz troch� technik "myszologicznych". 3. Napiszesz i uruchomisz swój drugi program. ________________________________________________________________ W dolnej cz��ci ekranu jest podobny pasek do paska menu, niemniej wa�ny, cho� o innym przeznaczeniu. Pasek ten jest to tzw. WIERSZ STATUSOWY (ang. Status Line). Jak wynika z nazwy w tym wierszu wy�wietlane s informacje dotycz ce bie� cego stanu (i bie� cych mo�liwo�ci) �rodowiska IDE. Zaryzykuj� tez�, �e cz�sto jeden prosty, własny eksperyment mo�e by� wi�cej wart ni�

Page 9: C++ - lekcje

9

wiele stron opisów. Poeksperymentujmy zatem chwil� z wierszem statusowym. [???] NIE CHCE SI� URUCHOMI� ??? ________________________________________________________________ Je�li przy starcie kompilatora C++ nast pi komunikat: System Message Disk is not ready in drive A [Retry] [Cancel] (Komunikat systemu C++: Dyskietka w nap�dzie A nie gotowa do odczytu; Jeszcze raz? Zrezygnowa�?) to znaczy, �e C++ nie mo�e odtworzy� ostatniego ekranu roboczego, poniewa� nie udost�pniłe� mu dyskietki z programami, nad którymi ostatnio pracowałe�. ________________________________________________________________ W wierszu statusowym wyja�nione jest działanie klawiszy funkcyjnych F1, F2, itd. Mog tam równie� pojawia� si� krótkie napisy-wyja�nienia dotycz ce np. rozkazu wyró�nionego wła�nie w menu. Powinien tam by� napis: F1 Help F2 Save F3 Load AltF9 Compile F9 Make F10 Menu znaczy to: [F1] - Pomoc [F2] - Zapami�tanie bie� cego pliku na dysku pod bie� c nazw (nawet je�li t nazw jest NONAME01.CPP, tzn. została nadana automatycznie i znaczy - o ironio - "BEZNAZWY01.CPP") i w bie� cym katalogu. [F3] - Załadowanie do okienka edycyjnego nowego pliku tekstowego (np. nowego programu). [Alt]-[F9] - Kompilacja w trybie "Compile". [F9] - Kompilacja w trybie "Make" (jednoczesnej kompilacji i konsolidacji). [F10] - Uaktywnienie głównego menu. JAK ZROBI� PORZDEK? W trakcie uruchamiania kompilator korzysta z plików zewn�trznych. C++ stara si� by� USER FRIENDLY (przyjazny wobec u�ytkownika) i odtworzy� taki stan ekranu, w jakim ostatnio przerwałe� prac�, co nie zawsze jednak jest korzystne. W wierszu statusowym pojawiaj si� napisy informuj ce o tym (np: Loading Desktop File . . . - ładuj� plik zawieraj cy konfiguracj� ostatniego ekranu roboczego...). Je�li chcesz by na pocz tku sesji z C++ ekran był "dziewiczo" czysty, powiniene�: * zmieni� nazw� pliku [D:]\BORLANDC\BIN\TCDEF.DSK na dowoln inn , np. STARY.DSK lub STARY1.DSK, stosuj c polecenie systemu DOS RENAME. [D:] oznacza odpowiedni dla Twojego komputera dysk. C++ wystartuje wtedy z czystym ekranem i utworzy nowy plik TCDEF.DSK. * Plików TCDEF nie nale�y usuwa�. Kiedy nabierzesz troch� wprawy pliki te znacznie przyspiesz i ułatwi Ci prac� z C++. Aby zamkn � zb�dne okna mo�esz zastosowa� równie� rozkaz Close (ang. Close - zamknij) z menu Window (okna). Zwró� uwag�, �e polecenie Close odnosi si� do bie� cego okna wyró�nionego przy pomocy podwójnej ramki. Aby zamkn � bie� ce okno, powiniene�: 1. Nacisn � klawisze [Alt]-[W] Rozwinie si� menu Windows. 2. Wybra� z menu rozkaz Close - [C]. Mo�e pojawi� si� okienko z ostrze�eniem: WARNING: A:\PIERWSZY.CPP not saved. Save? (UWAGA: plik A:\PIERWSZY.CPP nie zapisany na dysku. Zapisa� ?).

[???] ZNIKNŁ PROGRAM ??? ________________________________________________________________ C++ chce Ci� uchroni� przed utrat programu, ale uwa�aj! Je�li odpowiesz Yes - Tak ([Y] lub [Enter]), to nowa wersja programu zostanie nadpisana na star ! ________________________________________________________________ [!!!] ZAMYKANIE OKIEN. ________________________________________________________________ Mo�esz szybciej zamkn � okno naciskaj c kombinacj� klawiszy [Alt]-[F3]. ________________________________________________________________ [!!!]UWAGA ________________________________________________________________ B d� ostro�ny podejmuj c decyzj� o zapisie wersji programu na dysk. Okienko z ostrze�eniem pojawi si� za ka�dym razem przed zamkni�ciem okna edycyjnego z tekstem programu. Je�li przy zamykaniu okna nie pojawi si� ostrze�enie, to znaczy, �e program w tej wersji, któr widzisz na ekranie został ju� zapisany na dysk. ________________________________________________________________ A JE�LI NIE CHC� ZAMYKA� OKIEN? W porz dku, nie musisz. W menu Window ([Alt]-[W]) masz do dyspozycji rozkaz Next (nast�pne okno). Mo�esz go wybra� albo naciskaj c klawisz [N], albo przy pomocy klawiszy kursora. Ka�de z okien na Twoim roboczym ekranie ma nazw� - nagłówek - np. NONAME00.CPP, PIERWSZY.CPP, ale nie tylko. Pierwsze dziesi�� okien ma równie� swoje numery - podane blisko prawego - górnego rogu okna w nawiasach kwadratowych - np. [1], [2] itd. Posługuj c si� tym rozkazem mo�esz przechodzi� od okna do okna nie zamykaj c �adnego z okien. Spróbuj! Jest jeszcze inny sposób przej�cia od okna do okna. Je�li chcesz przej�� do okna o numerze np. [1], [2], [5] itp. powiniene� nacisn � kombinacj� klawiszy [Alt]-[1], [Alt]-[5] itp.. Niestety, tylko pierwsze 9 okien ma swoje numerki. Mo�esz korzysta� z listy okien (Window | List) lub klawisza funkcyjnego [F6]. [S] ACTIVE WINDOW - AKTYWNE OKNO. ________________________________________________________________ Na ekranie mo�e si� znajdowa� jednocze�nie wiele okien, ale w danym momencie tylko jedno z nich mo�e by� AKTYWNE. Aktywne okno, to to, w którym miga kursor i w którym aktualnie pracujesz. Aktywne okno jest dodatkowo wyró�nione podwójn ramk . ________________________________________________________________ [???] Robi "na szaro"? ________________________________________________________________ Zwró� uwag�, �e dopóki bie� cym aktywnym oknem jest okienko komunikatów (Message - to w dolnej cz��ci ekranu), nie mo�esz np. powtórzy� kompilacji programu. Rozkazy Compile | Compile i Run | Run b�d "zrobione na szaro" (ang. grayed out) - czyli nieaktywne. Najpierw trzeba przej�� do okna edycji tekstu programu (np. poprzez klikni�cie myszk ). ________________________________________________________________ Rozwi� menu Options (opcje). Mo�esz to zrobi� na wiele sposobów. Najszybciej chyba naciskaj c: [Alt]+[O] Rozwin�ło si� menu, udost�pniaj c nast�puj c list� polece�: FULL MENUs - Pełne Menu ("s" oznacza, �e chodzi o "te" menu w liczbie mnogiej, a nie o pojedyncze menu). COMPILER - Kompilator. MAKE... - dosł. "ZRÓB", dotyczy tworzenia "projektów" (zwró� uwag� na wielokropek [...]). DIRECTORIES... - KATALOGI (znów wielokropek !). ENVIRONMENT... - OTOCZENIE lub inaczej �RODOWISKO. SAVE - ZAPAMI�TAJ (UWAGA: To jest zupełnie inne SAVE ni� w menu File. Nie wolno myli� tych polece�. Pomyłka grozi utrat tekstu programu!).

Page 10: C++ - lekcje

10

Popatrz na lini� statusow . Je�li b�dziesz porusza� si� po menu Option, pod�wietlaj c kolejne rozkazy, w wierszu statusowym b�dzie wy�wietlany krótki opis działania wskazanego rozkazu. I tak, powiniene� zobaczy� kolejno nast�puj ce napisy: Full Menus [Off/On]- Use or don't use full set of menu commands. (Stosuj lub nie stosuj pełnego zestawu rozkazów w menu - domy�lnie przyjmowane jest Off/Nie). Compiler - Set compiler defaults for code generation, error messages and names. (Ustaw domy�lne parametry pracy kompilatora dotycz ce generowania kodu programu, komunikatów o bł�dach i nazw). Make... - Set condition for project-makes. (Ustawianie warunków do tworzenia projektu). Directories... - Set path for compile, link and executable files. (Wybierz katalogi i ustaw �cie�ki dost�pu dla kompilacji, konsolidacji i WSKA MIEJSCE - GDZIE ZAPISA� PLIK TYPU *.EXE po kompilacji). Environment... - Make environment wide settings (eg, mouse settings). (Ustawienie parametrów rozszerzonego otoczenia, np. parametrów pracy myszki). Save - Save all the settings you've made in the Options menu. (Powoduje zapami�tanie na dysku wszystkich zmian parametrów roboczych IDE, które ustawiłe�, korzystaj c z rozkazów dost�pnych za po�rednictwem menu Options.). Ten rozkaz pozwala Ci ustawi� konfiguracj� IDE "raz na zawsze". Przygotujmy si� do powtórzenia kompilacji programu PIERWSZY.CPP. Je�li masz na ekranie rozwini�te menu Options, wybierz z menu polecenie Directories... . KOMPILACJA ZE WSKAZANIEM ADERSU. 1. Wska� w menu polecenie Directories i naci�nij [Enter]. Po poleceniu umieszczony jest wielokropek. Znaczy to, �e rozkaz nie zostanie wykonany, zanim komputer nie uzyska od Ciebie pewnych dodatkowych informacji. Wiesz ju�, �e praktycznie oznacza to dla Ciebie konieczno�� "wypełnienia" okienka dialogowego. Po wybraniu polecenia Directories ukazało si� okienko dialogowe ju� "wst�pnie wypełnione". Takie "wst�pne wypełnienie" okienka daje Ci pewne dodatkowe informacje. Wynika z niego mianowicie JAKIE PARAMETRY S PRZYJMOWANE DOMY�LNIE (default). W okienku dialogowym masz trzy okienka tekstowe: * Include Directories (Katalog zawieraj cy pliki nagłówkowe, np. STDIO.H, CONIO.H, GRAPHICS.H itp. doł czane do programów). * Library Directories (Katalog zawieraj cy gotowe biblioteki, zawarte w plikach typu *.LIB,). * Output Directory (Katalog wyj�ciowy, w którym po kompilacji b�d umieszczane Twoje programy w wersji *.EXE). Pierwsze dwa zostawimy w spokoju. 2. Naci�nij dwukrotnie klawisz [Tab]. Kursor wskazuje teraz okienko tekstowe Output Directory. 3. Wpisz do okienka tekstowego Output Directory: A:\ lub C:\C-BELFER znaczy to, �e od teraz po wykonaniu kompilacji i utworzeniu pliku wykonywalnego typu *.EXE, plik taki zostanie zapisany we wskazanym katalogu i na wskazanym dysku/dyskietce. UWAGA:

________________________________________________________________ * Je�li zainstalowałe� zawarto�� dyskietki na dysku i wolisz posługiwa� si� własnym katalogiem roboczym - wpisz tam odpowiedni �cie�k� dost�pu - np. C:\C-BELFER. Je�li Twój katalog zagnie�d�ony jest gł�biej (np. w przypadku u�ytkowników sieci Novell) - podaj pełn �cie�k� dost�pu - np.: F:\USERS\ADAM\C-BELFER * Wsz�dzie, gdzie w tre�ci ksi �ki odwołuj� si� do dyskietki A: mo�esz konsekwentnie po zainstalowaniu stosowa� odpowiedni katalog na dysku stałym, b d� na dysku sieciowym. ________________________________________________________________ 4. Naci�nij [Enter]. Spróbuj teraz, znan z poprzedniej lekcji metod , wczyta� do okienka edytora Twój pierwszy program. Musisz wykona� nast�puj ce czynno�ci: 1. Włó� do nap�du A: dyskietk� z programem PIERWSZY.CPP (je�li jeszcze jej tam nie ma). 2. Rozwi� menu File, naciskaj c kombinacj� klawiszy [Alt]-[F]. 3. Wybierz z menu rozkaz Open, naciskaj c klawisz [O]. Pojawi si� znane Ci okienko dialogowe. Zwró� uwag� na wiersz statusowy. Napis: Enter directory path and file mask znaczy: Wpisz �cie�k� dost�pu do katalogu i "wzorzec" nazwy pliku. U�yte słowo "wzorzec" oznacza, �e wolno Ci wpisa� do okienka tekstowego tak�e nazwy wieloznaczne, zawieraj ce znaki "*" i "?", np.: *.C A:\???.C D:\BORLANDC\SOURCE\P*.* itp. (Spróbuj!, zawsze mo�esz si� wycofa� lub zmieni� zdanie, posługuj c si� klawiszami [BackSpace], [Shift], [Tab] i [Esc].). Klawisz [Tab] umo�liwia Ci skok od okienka do okienka "do przodu", a [Shift]-[Tab] - "do tyłu". Zgodnie z nazw (ang. ESCape - ucieka�), klawisz [Esc] pozwala Ci wycofa� si� z niewygodnych sytuacji - np. zamkn � okienko dialogowe lub zwin � rozwini�te menu bez �adnej akcji. Je�li wpiszesz wzorzec nazwy, to w okienku z list zobaczysz wszystkie pliki wybrane z podanego dysku i z podanego katalogu według zadanego wzorca. Aby wybra� plik z listy nale�y klawiszem [Tab] przej�� do okienka z list , klawiszami kursora wskaza� potrzebny plik i nacisn � [Enter]. 4. Wpisz do okienka tekstowego A:\PIERWSZY.CPP 5. Naci�nij [Enter]. [!!!]FAST START - SZYBKI START. ________________________________________________________________ Je�li chcesz by C++ automatycznie wczytał Twój program do okienka edytora, to mo�esz zada� nazw� pliku z tekstem programu jako parametr w wierszu polecenia, uruchamiaj c C++ np. tak: BC A:\PIERWSZY.CPP Je�li korzystasz z programu Norton Commander, to mo�esz doda� do pliku NC.EXT nast�puj cy wiersz: C: TC !.! cpp: bc !.! wówczas wystarczy tylko wskaza� odpowiedni plik typu *.C lub .CPP z tekstem programu i nacisn � [Enter]. ________________________________________________________________ Kompilatory Borlanda mog w ró�nych wersjach nazywa� si� ró�nie: TC.EXE, BC.EXE, BCW.EXE (dla Windows), itp.. Sprawd� swoj wersj� kompilatora i wpisz wła�ciwe nazwy dodaj c ewentualnie �cie�ki dost�pu - np.: C: D:\BORLANDC\BIN\BC !.!

Page 11: C++ - lekcje

11

CPP: WIN C:\BORLANDC\BIN\BCW !.! [!!!]UWAGA ________________________________________________________________ Rozkazy uruchamiaj ce kompilator mog by� zło�one nawet z 4 parametrów - np.: WIN /3 C:\BORLANDC\BIN\BCW C:\C-BELFER\PROGRAMY\P027.CPP spowoduje: * uruchomienie Windows w trybie rozszerzonym 386 * uruchomienie kompilatora w wersji dla Windows - BCW.EXE * załadowanie pliku z programem - P27.CPP z wskazanego katalogu ________________________________________________________________ [P002.CPP] Dokonaj w swoim programie nast�puj cych zmian: ________________________________________________________________ #include (stdio.h> #include <conio> main() { printf("\n"); printf("Autor: np. Antoni Kowalski\n"); printf("program: PIERWSZY.CPP \n - wersja II \n"); getch(); } ________________________________________________________________ ******Uwaga: Je�li pracujesz w Windows - Z TEGO MIEJSCA******** przy pomocy rozkazów Edit | Copy mo�esz przenie�� program do okna kompilatora poprzez schowek Windows (Clipboard). W oknie kompilatora nale�y: 1. Otworzy� nowe okno edytora tekstowego: File | New 2. Wstawi� plik ze schowka: Edit | Paste --- To okno (AM-Edit) i całego BELFRA mo�esz w tym czasie zredukowa� --- Do ikonki.------------------------------------------------------ ******************************************************************** Dzi�ki dodaniu do tekstu programu funkcji getch(), program nie powinien ju� tak szybko mign � na ekranie i znikn �. Zatrzyma si� teraz i zaczeka na przyci�ni�cie klawisza. Funkcja getch(), działa podobnie do: 10 IF INKEY$="" GOTO 10 w Basicu lub Readln w Pascalu. Nazwa pochodzi od GET CHaracter (POBIERZ ZNak, z klawiatury). Skompiluj program PIERWSZY.CPP. Aby to zrobi�, powiniene�: 1. Rozwin � menu Compile - [Alt]-[C]. 2. Wybra� z menu rozkaz Compile - [C]. Ostrze�enie WARNING na razie ignorujemy. Wykonaj kompilacj� programu powtórnie przy pomocy rozkazu Run z menu Run. Naci�nij kolejno klawisze: [Alt]-[R], [R] lub [Alt]-[R], [Enter] Ten sam efekt uzyskasz naciskaj c kombinacj� klawiszy [Ctrl]-[F9]. Uruchom program powtórnie naciskaj c kombinacj� klawiszy [Alt]-[R], [R]. Zwró� uwag�, �e teraz kompilacja nast pi znacznie szybciej. Tak naprawd� C++ stwierdzi tylko, �e od ostatniej kompilacji nie dokonano �adnych zmian w programie i odst pi od zb�dnej kompilacji. Takie wła�nie znaczenie ma komunikat "Checking dependences" (sprawdzam zale�no�ci, który mignie w okienku kompilacji. Po korekcie programu napisy wygl daj znacznie przyzwoiciej, prawda? Po obejrzeniu napisów

naci�nij [Enter]. Mo�emy teraz wyj�� z programu C++. Rozwi� menu File naciskaj c klawisze [Alt]-[F] i wybierz z menu rozkaz Quit. Pojawi si� okienko z ostrze�eniem: WARNING: A:\PIERWSZY.CPP not saved. Save? (UWAGA: plik A:\PIERWSZY.CPP nie zapisany na dysku. Zapisa� ?). W ten sposób C++ ZNOWU chce Ci� uchroni� przed utrat programu, ale uwa�aj! Je�li odpowiesz Tak ([Y] lub [Enter]), to nowa wersja programu zostanie nadpisana na star ! Je�li odpowiesz Nie [N] na dysku pozostanie stara wersja programu a nowa zniknie. Po wyj�ciu z C++ znajdziesz si� w jego katalogu roboczym, lub w tym katalogu bie� cym, z którego wydałe� rozkaz uruchomienia kompilatora C++. Aby uruchomi� swój program musisz zatem wyda� nast�puj cy rozkaz: A:\PIERWSZY.EXE lub krócej A:\PIERWSZY a je�li chcesz si� przekona�, czy Twój program jest tam, gdzie powinien by�, mo�esz go zobaczy�. Napisz rozkaz DIR A:\ lub DIR A:\*.EXE Aby upewni� si� całkowicie, �e to wła�nie ten program, zwró� uwag� na dat� i czas utworzenia pliku. Je�li masz prawidłowo ustawiony zegar w swoim komputerze, data powinna by� dzisiejsza a czas - kilka minut temu. Je�li co� jest nie tak, powiniene� przy pomocy rozkazów systemu DOS: DATE i TIME zrobi� porz dek w swoim systemie. O takich drobiazgach warto pami�ta�. Pozwoli Ci to w przyszło�ci odró�ni� nowsze i starsze wersje programów, unikn � pomyłek i zaoszcz�dzi� wiele pracy. [Z] 1. - Propozycja zadania - �wiczenia do samodzielnego wykonania. ------------------------------------------------------------------- Spróbuj odszuka� plik �ródłowy .CPP i plik wynikowy .EXE wychodz c "na chwil�" z IDE przy pomocy rozkazu File | DOS Shell. ------------------------------------------------------------------- A teraz zajrzyjmy do �rodka do pliku PIERWSZY.EXE. Je�li korzystasz z programu Norton Commander, to masz do dyspozycji opcje [F3] - View (przegl danie) i [F4] - Edit (edycja). Je�li nie korzystasz z NC, musisz wyda� nast�puj cy rozkaz: TYPE A:\PIERWSZY.EXE | C:\DOS\MORE lub C:\DOS\EDIT A:\PIERWSZY.EXE Jak widzisz na ekranie, napisy zawarte w programie pozostały czytelne, ale to co wida� dookoła nie wygl da najlepiej. Na podstawie tego co widzisz, mo�na (na razie ostro�nie) wysnu� wniosek, �e ani Viewer (przegl darka), ani Edytor, które doskonale spisuj si� przy obróbce plików tekstowych, nie nadaj si� do analizy i obróbki programów w wersji *.EXE. Narz�dziami, które b�dziemy musieli stosowa�, mog by� programy typu DEBUGGER, PROFILER, LINKER (konsolidator), kompilator i in.. Mam nadziej�, �e czujesz si� w �rodowisku IDE ju� troch� swobodniej, a wi�c bierzemy si� za drugi program. __________________________________________________________ EOF LEKCJA 5 - DZIAŁANIA PRZY POMOCY MYSZKI I BŁ�DY W PROGRAMIE. ________________________________________________________________ Z tej lekcji dowiesz si�, * Jak posługiwa� si� myszk w �rodowisku IDE (DOS) * O czy nale�y pami�ta�, przy tworzeniu i uruchamianiu programów. * Jak poprawia� bł�dy w programie.

Page 12: C++ - lekcje

12

________________________________________________________________ Zanim b�dzie mo�na kontynuowa� eksperymenty, trzeba co� zrobi�, by robocze okno edytora było puste. Aby otworzy� takie nowe puste okno edytora nale�y: * Rozwin � menu File; * Wybra� z menu rozkaz New (nowy). Na ekranie monitora otworzy si� nowe puste okno zatytułowane "NONAME00.CPP", "NONAME01.CPP", itp (lub "bez nazwy" i o kolejnym numerze). Ró�ne edytoro-podobne aplikacje maj zwyczaj otwierania okna dla nowego pliku tekstowego i nadawanie mu na pocz tku jednej z dwóch nazw: [S] SŁOWNICZEK: UFO w trybie Edycji ________________________________________________________________ Untitled - niezatytułowany Noname - bez nazwy (Tak na marginesie UFO to skrót od Unidentified Flying Object - Niezidentyfikowany Obiekt Lataj cy, gdy przejdziemy do programowania obiektowego, znajomo�� tego terminu te� Ci si� przyda). ________________________________________________________________ Nadanie plikowi dyskowemu z tekstem �ródłowym programu jego wła�ciwej nazwy i zapisanie go na dysku stałym komputera w okre�lonym miejscu nast�puje w tym momencie, kiedy po napisaniu programu zapisujesz go na dysk rozkazem: File | Save lub File | Save As... Zapis File | Save oznacza "Rozkaz Save z menu File". Gdy po opracowaniu programu rozwiniesz menu File i wybierzesz rozkaz Save as... (zapisz jako...), pojawi si� okienko dialogowe "Save File as" (zapis pliku jako...). Do okienka edycyjnego "Name" (nazwa) mo�esz wpisa� nazw�, któr chcesz nada� swojemu nowemu programowi. Zwró� uwag�, �e mo�esz poda� nazw� pliku i jednocze�nie wskaza� miejsce - np.: Name: F:\USERS\ADAM\PROBY\PROGRAM.CPP Po wpisaniu nazwy naci�nij klawisz [Enter] lub wybierz klawisz [OK] w okienku dialogowym myszk . Tytuł okna edytora zmieni si� na wybran nazw�. Mo�esz równie� (je�li odpowiedni katalog ju� istnieje), wskaza� wła�ciwy katalog w okienku z list "Files" i dwukrotnie "klikn �" lewym klawiszem myszki. Mo�esz wskaza� myszk okienko edycyjne i nacisn � lewy klawisz myszki, b d� naciska� klawisz [Tab] a� do momentu, gdy kursor zostanie przeniesiony do okienka edycyjnego. Okienko edycyjne to to okienko, do którego wpisujesz nazw� pliku. W okienku edycyjnym (Save File As) naciskaj klawisz [BackSpace] a� do chwili skasowania zb�dnej nazwy pliku i pozostawienia tam tylko �cie�ki dost�pu - np. A:\PROBY\. Wpisz nazw� programu - np. PROG1.CPP. Po wpisaniu nazwy mo�esz nacisn � [Enter] lub wskaza� myszk klawisz [OK] w okienku i nacisn � lewy klawisz myszki. Je�li tak zrobisz w przypadku pustego okienka NONAME00.CPP - kompilator utworzy na dysku we wskazanym katalogu plik o zadanej nazwie - np. A:\PROBY\PROGR1.CPP (na razie pusty). Zmieni si� tak�e nagłówek (nazwa) okienka edycyjnego na ekranie roboczym. [!!!]UWAGA. ________________________________________________________________ Wszystkie pliki zawieraj ce teksty programów w j�zyku C++ powinny ˙mie� charakterystyczne rozszerzenie *.CPP (CPP to skrót od C Plus Plus), lub .C. Po tym rozszerzeniu rozpoznaje te programy kompilator. Nadanie rozszerzenia .C lub .CPP mo�e dodatkowo wpływa� na sposób kompilacji programu. Zanim wyja�nimy te szczegóły, b�dziemy zawsze stosowa� rozszerzenie .CPP. Wszelkie inne rozszerzenia (.BAK, .TXT, .DEF, itp.) nie przeszkadzaj w edycji i kompilacji programu, ale mog w niejawny sposób wpłyn � na sposób kompilacji. ________________________________________________________________ Je�li masz puste robocze okno edytora - mo�esz wpisa� tam swój własny nowy program. Wpisz:

void main(void) Ka�dy program w C++ składa si� z instrukcji. Wiele takich instrukcji to wywołania funkcji. W C++ rozkaz wywołania i wykonania funkcji polega na wpisaniu nazwy funkcji (bez �adnego dodatkowego słowa typu run, execute, load, itp.). Tych funkcji mo�e by� w programie jedna, b d� wi�cej. Tworzenie programu w C++ z zastosowaniem funkcji (takich jakgdyby mini-programików) przypomina składanie wi�kszej cało�ci z klocków. Nale�y podkre�li�, �e: ka�dy program w C++ musi zawiera� funkcj� main() (ang. main - główna). Wykonanie ka�dego programu rozpoczyna si� wła�nie od pocz tku funkcji main(). Innymi słowy - miejsce zaznaczone w programie przy pomocy funkcji main() to takie miejsce, w które komputer zagl da zawsze na pocz tku wykonania programu i od tego wła�nie miejsca rozpoczyna poszukiwanie i wykonywanie rozkazów. [S] Entry Point ___________________________________________________________________ Punkt wej�cia do programu nazywa si�: Program Entry Point Taki wła�nie punkt wej�cia wskazuje słowo main(). Punk wej�cia mog mie� nie tylko programy .EXE ale tak�e biblioteki (.DLL - dynamicznie doł czanie biblioteki). ____________________________________________________________________ Ka�da funkcja powinna mie� pocz tek i koniec. Pocz tek funkcji w C/C++ zaznacza si� przez otwarcie nawiasów klamrowych { a koniec funkcji poprzez zamkni�cie } . Pocz tek głównej funkcji main() to zarazem pocz tek całego programu. Zaczynamy zwykle od umieszczenia w oknie edytora C++ znaków pocz tku i ko�ca programu. main() { <-- tu rozbudowuje si� tekst programu } Najpierw naci�nij [Enter] i przejd� do pocz tku nowej linii. Umie�� w tej nowej linii znak pocz tku programu - nawias { (lewy nawias klamrowy). Nast�pnie naci�nij [Enter] powtórnie i umie�� w nast�pnej linii prawy nawias klamrowy - }. [!!!] NAJPIERW Save !!! ________________________________________________________________ Zanim jeszcze sko�czysz redagowanie programu i si�gniesz do klawiszy [Alt]+[R], pami�taj, �e przed próbami kompilacji i uruchomienia programu zawsze NAJPIERW nale�y zapisa� program na dysk. Je�li przy próbach uruchomienia co� pójdzie nie tak - masz pewno��, �e Twoja praca nie pójdzie na marne. Czasami przy próbach uruchamiania programów zdarza si�, �e bł�dy mog spowodowa� zawieszenie komputera. Programista jest wtedy zmuszony do restartu komputera, przy wył czeniu komputera to, co było tylko na ekranie i tylko w pami�ci operacyjnej - niestety znika bezpowrotnie. ________________________________________________________________ Aby zapisa� tekst programu na dysk nale�y: * Wybra� z menu rozkaz File | Save As... albo * Nacisn � klawisz funkcyjny [F2] (działa jak File | Save) Po wydaniu rozkazu Save mo�esz by� pewien, �e Twój program jest bezpieczny i komputer mo�e si� spokojnie "ZAWIESI�" nie czyni c szkody. Aby skompilowa� i uruchomi� ten program nale�y: * Wybra� rozkaz Run | Run * Nacisn � kombinacj� klawiszy [Ctrl]+[F9] Podobnie jak wcze�niej, kompilator wy�wietli na ekranie okienko zawieraj ce komunikaty o przebiegu kompilacji. Po zako�czeniu kompilacji nast pi wykonanie programu. Na moment mignie roboczy ekran u�ytkownika. Na nieszcz��cie program nic nie robi, wi�c nic si� tam nie wydarzy.

Page 13: C++ - lekcje

13

Aby przeanalizowa�, jak kompilator C++ reaguje na bł�dy w programach, zmie� tekst w pierwszej linii programu na bł�dny: vod main(void) { } Spróbuj powtórnie skompilowa� i uruchomi� program. Kompilator wy�wietli okienko, w którym pojawi si� komunikat o bł�dach. W taki wła�nie sposób kompilator taktownie informuje programist�, �e nie jest a� taki dobry, jak mu si� czasami wydaje. Komputer jest niestety pedantem. Oczekuje (my, ludzie tego nie wymagamy) absolutnej dokładno�ci i �elaznego przestrzegania pewnych zasad. "Zjadaj c" jedn liter� naruszyłe� takie zasady, co zauwa�ył kompilator. W górnej cz��ci ekranu kompilator wyró�nił paskiem pod�wietlenia ten wiersz programu, który zawiera bł d. W dolnej cz��ci ekranu, w tzw. okienku komunikatów (ang. Message window) pojawił si� komunikat, jaki rodzaj bł�du został wykryty w Twoim programie. W danym przypadku komunikat brzmi: Declaration syntax error - Bł d w składni deklaracji Co to jest deklaracja? Pierwsza linia (wiersz) funkcji nazywa si� deklaracj funkcji. Taka pierwsza linia zawiera informacje wa�ne dla kompilatora: nazw� funkcji oraz tzw. typy warto�ci u�ywanych przez funkcj�. Komunikat o bł�dzie oznacza, �e nieprawidłowo została napisana nazwa funkcji lub nazwy typów warto�ci, którymi posługuje si� funkcja. W naszym przypadku słowo void zostało przekr�cone na "vod", a słowo to ma w C++ specjalne znaczenie. Słowo "void" jest cz��ci j�zyka C++, a dokładniej - słowem kluczowym (ang. keyword). [S] Function declaration - Deklaracja funkcji. Keyword - Słowo kluczowe. ________________________________________________________________ Function declaration - Deklaracja funkcji. Pierwszy wiersz funkcji jest nazywany deklaracj funkcji. Ten wiersz zawiera informacje dla kompilatora C++ pozwalaj ce poprawnie przetłumaczy� funkcj� na kod maszynowy. Keyword - Słowo kluczowe. to specjalne słowo wchodz ce w skład j�zyka programowania. Słowa kluczowe to słowa o zastrze�onym znaczeniu, które mo�na stosowa� w programach wył cznie w przewidzianym dla nich sensie. ________________________________________________________________ Popraw bł d w tek�cie. Aby robocze okienko edytora stało si� oknem aktywnym, wska� kursorem myszki dowolny punkt w oknie edytora i naci�nij lewy klawisz myszki, albo naci�nij klawisz [F6]. Zmie� słowo "vod" na "void". Przy pomocy klawiszy ze strzałkami umie�� migaj cy kursor po prawej stronie nawiasu { sygnalizuj cego ˙pocz tek programu i naci�nij [Enter]. Spowoduje to wstawienie pomi�dzy pocz tek a koniec programu nowej pustej linii i umieszczenie kursora na pocz tku nowego wiersza. Wpisz do nowego wiersza instrukcj� oczyszczenia ekranu (odpowiednik instrukcji CLS w Basicu): clrscr(); W C++ clrscr() oznacza wywołanie funkcji czyszcz cej roboczy ekran programu (User screen). Nazwa funkcji pochodzi od skrótu: CLeaR SCReen - czy�� ekran. e to funkcja - mo�na rozpozna� po dodanej za nazw parze nawiasów okr głych - (). W tym jednak przypadku wiersz: clrscr(); stanowi nie deklaracj� funkcji, lecz wywołanie funkcji (ang. function call). C++ znalazłszy w programie wywołanie funkcji wykona wszystkie rozkazy, które zawiera wewn trz funkcja clrscr(). Nie musisz przejmowa� si� tym, z jakich rozkazów składa si� funkcja clrscr(). Te rozkazy nie stanowi cz��ci

Twojego programu, lecz s zawarte w jednym z "fabrycznych" plików bibliotecznych zainstalowanych wraz z kompilatorem C++. [S] Function - Funkcja Fuction call - Wywołanie funkcji ________________________________________________________________ Funkcja to co� przypominaj ce mini-program. Funkcja zawiera list� rozkazów słu� cych do wykonania typowych operacji (np. czyszczenie ekranu, wy�wietlanie menu, wydruk, czy sortowanie listy imion). W programach posługujemy si� zwykle wieloma funkcjami. Poznałe� ju� najwa�niejsz funkcj� główn - main(). W C/C++ mo�esz posługiwa� si� gotowymi funkcjami (tzw. bibliotecznymi) a tak�e tworzy� nowe własne funkcje. Na razie b�dziemy posługiwa� si� gotowymi funkcjami dostarczanymi przez producenta wraz z kompilatorem C++. ________________________________________________________________ Wł cz kompilacj� i prób� uruchomienia programu. Kompilator stwierdzi, �e program zawiera bł�dy. Naci�nij dowolny klawisz, by znikn�ło okienko kompilacji. Kompilator napisał: Error: Function 'clrscr' should have a prototype (Funkcja 'clrscr' powinna mie� prototyp) [???] O co mu chodzi? ________________________________________________________________ Tzw. PROTOTYP funkcji to co� bardzo podobnego do deklaracji funkcji. Prototyp słu�y do przekazania kompilatorowi pewnych informacji o funkcji jeszcze przed u�yciem tej funkcji w programie. Dla przykładu, gdy pisałe� pierwsz lini� programu: void main(void) podałe� nie tylko nazw� funkcji - main, lecz tak�e umie�cili�my tam dwukrotnie słowo void. Dokładnie o znaczeniu tych słów napiszemy w dalszej cz��ci ksi �ki. Na razie zwró�my jedynie uwag�, �e podobnych "dodatkowych" informacji dotycz cych funkcji clrscr() w naszym programie nie ma. ________________________________________________________________ Zwró� uwag�, �e zapisy: main() int main(void) main(void) { { { } } } s całkowiecie równowa�ne. Fakt, �e słowa kluczowe void (w nawiasie) i int (przed funkcj i tylko tam!) mog zosta� pomini�te wskazuje, �e s to warto�ci domy�lne (default settings) przyjmowane automatycznie. Funkcja clrscr() została napisana przez programistów z firmy BORLAND i znajduje si� gdzie� w osobnym pliku doł czonym do kompilatora C++. Aby móc spokojnie posługiwa� si� t funkcj w swoich programach, powiniene� doł czy� do swojego programu informacj� w jakim pliku dyskowym znajduje si� opis funkcji clrscr(). Taki (do�� szczegółowy) opis funkcji nazywa si� wła�nie prototypem funkcji. Aby doda� do programu t� (niezb�dn ) informacj� * naci�nij [F6] by przej�� do okna edytora * ustaw migaj cy kursor na pocz tku tekstu programu * naci�nij [Enter] dwukrotnie, by doda� dwie nowe puste linie do tekstu programu * na samym pocz tku programu wpisz: #include <conio.h> Takie specjalne linie (zwró� uwag� na pod�wietlenie) rozpoczynaj ce si� od znaku # (ASCII 35) nie s wła�ciwie normaln cz��ci składow programu. Nie stanowi one jednej z instrukcji programu, mówi cej komputerowi CO NALEY ROBI�, lecz stanowi tzw. dyrektyw� (rozkaz) dla kompillatora C++ - W JAKI SPOSÓB KOMPILOWA� PROGRAM. Dyrektywa kompilatora (ang. compiler

Page 14: C++ - lekcje

14

directive) powoduje dokonanie okre�lonych działa� przez kompilator na etapie tłumaczenia programu na kod maszynowy. W danym przypadku dyrektywa #include .... (ang. include - wł cz, doł cz) powoduje wł czenie we wskazane miejsce zawarto�ci zewn�trznego tekstowego pliku dyskowego - np.: CONIO.H, (plik CONIO.H nazywany ˙tak�e "plikiem nagłówkowym" znajduje si� w podkatalogu \INCLUDE). Kompilator doł cza zawarto�� pliku CONIO.H jeszcze przed rozpocz�ciem procesu kompilacji programu. Naci�nij kombinacj� klawiszy [Ctrl]+[F9]. Spowoduje to kompilacj� i uruchomienie programu (Run). Przykładowy program powinien tym razem przekompilowa� si� bez bł�dów. Po dokonaniu kompilacji powinien szybko błysn � ekran u�ytkownika. Po tym błysku powinien nast pi� powrót do roboczego �rodowiska IDE kompilatora C++. Je�li nie zd �yłe� si� przyjrze� i chcesz spokojnie sprawdzi�, co zrobił Twój program - naci�nij kombinacj� klawiszy [Alt]+[F5]. Dzi�ki działaniu funkcji clrscr() ekran b�dzie całkowicie czysty. [S] Compiler directive - DYREKTYWA KOMPILATORA ________________________________________________________________ Dyrektywa kompilatora to rozkaz wyja�niaj cy kompilatorowi C++ w jaki sposób dokonywa� kompilacji programu. Dyrektywy kompilatora zawsze rozpoczynaj si� od znaku # (ang. hash). Kompilatory C++ posiadaj pewien dodatkowy program nazywany PREPROCESOREM. Preprocesor dokonuje przetwarzania tekstu programu jescze przed rozpocz�ciem wła�ciwej kompilacji. Dokładniej rzecz bior c #include jest wła�ciwie dyrektyw preprocesora (szczegóły w dalszej cz��ci ksi �ki). ________________________________________________________________ [Z] - Propozycje zada� do samodzielnego wykonania. ________________________________________________________________ 1. Spróbuj poeksperymentowa� "zjadaj c" kolejno ró�ne elementy w poprawnie działaj cym na pocz tku programie: - litera w nazwie funkcji - �rednik na ko�cu wiersza - cudzysłów obejmuj cy tekst do wydrukowania - nawias ( lub ) w funkcji printf() - nawias klamrowy { lub } - znak dyrektywy # - cał dyrektyw� #include <stdio.h> Porównaj komunikaty o bł�dach i zgłaszan przez kompilator liczb� bł�dów. Czy po przekłamaniu jednego znaku kompilator zawsze zgłasza dokładnie jeden bł d? ________________________________________________________________ ______________________________________________________________________ EOF LEKCJA 6 - NAST�PNY PROGRAM - KOMPUTEROWA ARYTMETYKA. ________________________________________________________________ W trakcie tej lekcji napiszesz i uruchomisz nast�pny program wykonuj cy proste operacje matematyczne. ________________________________________________________________ Aby przyst pi� po wyja�nieniach do pracy nad drugim programem, powiniene� wykona� nast�puj ce czynno�ci: 1. Zrób porz dek na ekranie. Zamknij rozkazem Close z menu Window zb�dne okna (mo�esz posłu�y� si� kombinacj [Alt]-[F3]). 2. Rozwi� menu File. 3. Wybierz z menu rozkaz Open... 4. Wpisz do okienka tekstowego: A:\DRUGI.CPP 5. Naci�nij [Enter].

6. Wpisz do okienka edytora tekst programu: [P003.CPP ] /* Program przykladowy: _DRUGI.CPP */ # include <conio.h> /* zwró� uwag�, �e tu NIE MA [;] ! */ # include <stdio.h> /* drugi plik nagłówkowy */ int main() /* tu tez nie ma �rednika [;] ! */ { float x, y; float wynik; clrscr(); printf("Zamieniam ulamki zwykle na dziesietne\n"); printf("\nPodaj licznik ulamka: "); scanf("%f", &x); /* pobiera liczb� z klawiatury */ printf("\nPodaj mianownik ulamka: "); scanf( "%f", &y); wynik = x / y; /* tu wykonuje sie dzielenie */ printf("\n %f : %f = %f", x, y, wynik); printf("\n nacisnij dowolny klawisz...\n"); getch(); /* program czeka na nacisniecie klawisza. */ return 0; //<-- zwrot zera do systemu } UWAGA: _________________________________________________________________ * Komentarze uj�te w [/*.....*/] mo�esz pomin �. Komentarz jest przeznaczony dla człowieka. Kompilator ignoruje całkowicie komentarze i traktuje komentarz jak puste miejsce, a dokładniej - tak samo jak pojedyncz spacj�. Komentarz w C++ mo�e mie� dwie formy: /* Tekst komentarza */ // Tekst komentarza w drugim przypadku ogranicznikiem pola komentarza jest koniec wiersza. * Spacjami i TABami mo�esz operowa� dowolnie. Kompilator ignoruje tak�e puste miejsca w tek�cie. Nie nale�y natomiast stosowa� spacji w obr�bie słów kluczowych i identyfikatorów. ________________________________________________________________ 7. Skompiluj program [Alt]-[C], [M] lub [Enter]. 8. Popraw ewentualne bł�dy. 9. Uruchom program rozkazem Run, naciskaj c [Alt]-[R], [R]. 10. Zapisz wersj� �ródłow programu DRUGI.CPP na dyskietk� A:\ stosuj c tym razem SHORTCUT KEY - klawisz [F2]. [S!] scanf() - SCANing Function - Funkcja SKANuj ca. ________________________________________________________________ Funkcja pobiera ze standardowego urz dzenia wej�cia- zwykle z klawiatury podan przez u�ytkownika liczb� lub inny ci g znaków. Działa podobnie do funkcji INPUT w Basicu, czy readln w Pascalu. * float - do Floating Point - "Pływaj cy" - zmienny przecinek. Słowo kluczowe słu� ce do tzw. DEKLARACJI TYPU ZMIENNEJ lub funkcji. Oznacza liczb� rzeczywist np.: float x = 3.14; * int - od Integer - całkowity. Słowo kluczowe słu� ce do deklaracji typu zmiennej lub funkcji. Oznacza liczb� całkowit np.: 768. * #include - Wł cz. Dyrektywa wł czaj ca cały zewn�trzny plik tekstowy. W tym przypadku wł czone zostały dwa tzw. pliki nagłówkowe: CONIO.H i STDIO.H. * CONIO.H - CONsole Input/Output.

Page 15: C++ - lekcje

15

Plik nagłówkowy zawieraj cy prototypy funkcji potrzebnych do obsługi standardowego Wej�cia/Wyj�cia na/z konsoli (CONsole). Plik zawiera mi�dzy innymi prototyp funkcji clrscr(), potrzebnej nam do czyszczenia ekranu. *STDIO.H - STanDard Input/Output Plik nagłówkowy zawieraj cy prototypy funkcji potrzebnych do obsługi standardowego Wej�cia/Wyj�cia na/z konsoli (Input - Wej�cie, Output - Wyj�cie). Plik zawiera mi�dzy innymi prototyp funkcji printf(), potrzebnej nam do drukowania wyników na ekranie. return - słowo kluczowe: Powrót, zwrot. Po wykonaniu programu liczba 0 (tak kazali�my programowi rozkazem return 0;) jest zwracana do systemu operacyjnego, w naszym przypadku do DOSa. Zwró� uwag�, �e nie pojawiło si� tym razem ostrze�enie WARNING podczas kompilacji. ________________________________________________________________ OPERATORY ARYTMETYCZNE C++. C++ potrafi oczywi�cie nie tylko dzieli� i mno�y�. Oto tabela operatorów arytmetycznych c i C++. OPERATORY ARYTMETYCZNE j�zyka C++. ________________________________________________________________ Operator Nazwa Tłumaczenie Działanie ________________________________________________________________ + ADDition Dodawanie Suma liczb - SUBstraction Odejmowanie Ró�nica liczb * MULtiplication Mno�enie Iloczyn liczb / DIVision Dzielenie Iloraz liczb % MODulus Dziel Modulo Reszta z dzielenia ________________________________________________________________ Przykładowe wyniki niektórych operacji arytmetycznych. ________________________________________________________________ Działanie (zapis w C++) Wynik działania ________________________________________________________________ 5 + 7 12 12 - 7 5 3 * 8 24 10 / 3 3.333333 10 % 3 1 ________________________________________________________________ [???] Czym ró�ni si� dzielenie / od % ? ________________________________________________________________ Operator dzielenia modulo % zamiast wyniku dzielenia - daje rzeszt� z dzielenia. Dla przykładu, dzielenie liczby 14 przez liczb� 4 daje wynik 3, reszta z dzielenia 2. Wynik operacji 14%4 b�dzie wi�c wynosi� 2. Operator ten jest niezwykle przydatny np. przy sprawdzaniu podzielno�ci, skalowaniu, okre�laniu zakresów liczb przypadkowych, itp.. Przykłady generacji liczb pseudolosowych wybiegaj nieco w przyszło��, ale postanowiłem w Samouczku umie�ci� je razem. Po przestudiowaniu tworzenia p�tli programowych mo�esz wróci� do tej lekcji i rozwa�y� przykłady po raz wtóry. Przykład 1: randomize(); int X=ramdom(); X = X % 10; Przykład 2: --------------------- #include <stdlib.h> /* Zwróc uwag� na doł czony plik */ #include <stdio.h>

main() { int i; printf("Dziesi�� liczb pseudo-losowych od 0 do 99\n\n"); for(i=0; i<10; i++) printf("%d\n", rand() % 100); return 0; } Przykad3 -------------------- #include <stdlib.h> #include <stdio.h> #include <time.h> void main() { randomize(); printf("Liczby pseudolosowe z zakresu: 0-99 --> %d\n", random (100)); } Przykład 4 ----------------- #include <stdlib.h> #include <stdio.h> #include <time.h> int main(void) { int i; randomize(); printf("Liczby pseudolosowe: 0 to 99\n\n"); for(i=0; i<10; i++) printf("%d\n", rand() % 100); return 0; } Zwró� uwag�, �e to randomize() uruchamia generator liczb pseudolosowych, czyli jakgdyby "wł cza b�ben maszyny losuj cej". ________________________________________________________________ Wykonaj z programem DRUGI.CPP kilka eksperymentów. [Z] ________________________________________________________________ 1. Zamie� operator dzielenia na operator mno�enia [*]: wynik = x * y; /* tu wykonuje sie mno�enie */ i napis w pierwszej funkcji printf() na np. taki: printf( "Wykonuje mnozenie liczb" ); Uruchom program. Sprawd� poprawno�� działania programu w szerokim zakresie liczb. Przy jakiej wielko�ci liczb pojawiaj si� bł�dy? 2. Zmie� nazwy zmiennych x, y, wynik na inne, np.: to_jest_liczba_pierwsza, to_jest_liczba_druga, itp. Czy C++ poprawnie rozpoznaje i rozró�nia takie długie nazwy? Kiedy zaczynaj si� kłopoty? Sprawd�, czy mo�na w nazwie zmiennej u�y� spacji? Jaki komunikat wy�wietli kompilator? ________________________________________________________________ [???] PRZEPADŁ PROGRAM ??? ________________________________________________________________ Nie przejmuj si�. Wersja pocz tkowa programu DRUGI.CPP jest na dyskietce doł czonej do niniejszej ksi �ki (tam nazywa si� DRUGI.CPP). Zwró� uwag�, �e kompilator C++ tworzy automatycznie kopie zapasowe plików �ródłowych z programami i nadaje im standardowe rozszerzenie *.BAK. Zanim zatem zaczniesz si� denerwowa�, sprawd�, czy kopia np. DRUGI.BAK nie jest wła�nie t wersj programu, która Ci "przepadła". ________________________________________________________________ __________________________________________________________________

Page 16: C++ - lekcje

16

EOF LEKCJA 7. Z czego składa si� program. _______________________________________________________________ W trakcie tej lekcji: * Dowiesz si� co robi�, je�li t�sknisz za Pascalem. * Zapoznasz si� wst�pnie z preprocesorem C++. * Poznasz dokładniej niektóre elementy j�zyka C++. _______________________________________________________________ Zanim zagł�bimy si� w szczegóły działania preprocesora i kompilatora, dla zilustrowania mechanizmu działania dyrektyw popełnimy �art programistyczny. Nie ma nic gorszego ni� spali� dobry �art, upewnijmy si� wi�c najpierw, czy nasza "czarodziejska kula" jest gotowa do magicznych sztuczek. Sprawd�, czy na dyskietce znajduj si� pliki A:\PASCAL.H A:\POLTEKST.H Je�li nie, to przed zabaw w magiczne sztuczki programistyczne musisz odtworzy� te pliki z zapasowej kopii dyskietki, któr sporz dziłe� przed rozpocz�ciem LEKCJI 1. Je�li masz ju� oba pliki, to wykonaj nast�puj ce czynno�ci: 1. Włó� do nap�du A: dyskietk� z plikami PASCAL.H i POLTEKST.H. 2. Uruchom kompilator C++. PROGRAMY HOKUS.EXE i POKUS.EXE - czyli sztuczki z Preprpcesorem C++ 1. Zrób porz dek na ekranie - pozamykaj zb�dne okna. 2. Naci�nij klawisz [F3]. Pojawi si� znajome okienko dialogowe "Open". 3. Wpisz do okienka tekstowego nazw� nowego programu: A:\HOKUS.C i naci�nij [Enter]. 4. Wpisz nast�puj cy tekst programu: [P004.CPP] #include <a:\pascal.h> Program Begin Write("Ten program jest podobny"); Write(" do Turbo Pascala "); Write(" tak tez mozna pisac w BORLAND C++ !"); Readln; End 5. Uruchom program [Ctrl]-[F9]. Je�li wyst pi bł�dy, skoryguj ewentualne niezgodno�ci z oryginałem. Ostrze�enie "WARNING" mo�esz zignorowa�. UWAGA: MUSI ZOSTA� ZACHOWANA IDEALNA ZGODNO�� z tekstem oryginału! 6. Uruchom program rozkazem Run [Alt]-[R], [Enter]. Zwró� uwag�, �e powtórna kompilacja przebiega szybciej, je�li w mi�dzyczasie nie dokonałe� zmian w programie. 7. Zamknij okno edytora rozkazem Close (z menu Window). Zapisz program HOKUS.CPP w wersji �ródłowej na dyskietk� A:. A teraz nast�pna sztuczka, na któr pozwala C++. Utworzymy nast�pny program POKUS.CPP. 1. Wykonaj czynno�ci z pp. 1 i 2 z poprzedniego przykładu. 2. Otwórz okienko nowego programu - File | Open (np. klawiszem [F3]) i wpisz nazw� programu. Mo�esz zastosowa� równie� File | New. A:\POKUS.CPP 3. Naci�nij [Enter]. 4. Wpisz tekst programu: [P005.CPP]

# include <a:\poltekst.h> program poczatek czysty_ekran drukuj ("Ten program - POKUS.CPP "); drukuj ("Jest napisany po polsku "); drukuj ("a mimo to Turbo C++ go rozumie!"); czekaj; koniec 5. Uruchom program [Alt]-[R], [R]. Je�li wyst pi bł�dy, skoryguj ewentualne niezgodno�ci z oryginałem. Ostrze�enie "WARNING" mo�esz zignorowa�. UWAGA: MUSI ZOSTA� ZACHOWANA IDEALNA ZGODNO��! 6. Zamknij okno edytora rozkazem Close (z menu Window). Zapisz program HOKUS.C w wersji �ródłowej na dyskietk� A:. WYJA�NIENIE SZTUCZEK - PREPROCESOR C++ CPP.EXE. A teraz wyja�nienie naszych magicznych sztuczek. Je�li jeste� niecierpliwy, na pewno ju� sam zajrzałe� do plików PASCAL.H i POLTEKST.H, bo jest chyba oczywiste od pocz tku, �e to tam wła�nie musi ukrywa� si� to wszystko, co pozwala nam robi� nasze hokus-pokus. Skorzystali�my z pewnej nie wyst�puj cej ani w Pascalu, ani w Basicu umiej�tno�ci j�zyków C i C++ - a mianowicie z PREPROCESORA. Najcz��ciej stosowanymi dyrektywami preprocesora s : # include - wł cz i # define - zdefiniuj Do rozpoznania dyrektyw preprocesora słu�y znak (#) - HASH. Zwró� uwag�, �e zapisy #include # include s całkowicie równowa�ne. Poza tym dyrektywy preprocesora nie ko�cz si� �rednikiem. Działanie preprocesora (czyli wst�pne przetwarzanie tekstu programu jeszcze przed przyst pieniem do kompilacji) polega na zast pieniu w tek�cie programu jednych ła�cuchów znaków przez inne. Takie pary mo�emy "zada�" preprocesorowi wła�nie dyrektyw #define. Nasze nagłówki wygl daj nast�puj co: PASCAL.H: _______________________________________________________________ # include <stdio.h> # define Program main() # define Begin { # define Writeln printf # define Readln getch() # define End } ________________________________________________________________ POLTEKST.H: ________________________________________________________________ # include <stdio.h> # define program main() # define poczatek { # define koniec } # define czysty_ekran clrscr(); # define drukuj printf # define czekaj getch() ________________________________________________________________ Zwró� uwag�, �e warunkiem poprawnego zadziałania preprocesora jest zrezygnowanie ze spacji wewn trz ła�cuchów znakowych, spacje bowiem w preprocesorze rozdzielaj dwa ła�cuchy znaków - np. "drukuj" - ten ZA KTÓRY CHCEMY CO� PODSTAWI� oraz np. "printf"

Page 17: C++ - lekcje

17

- ten, KTÓRY NALEY PODSTAWIA�. Cz�sto w programach zauwa�ysz ła�cuchy znaków pisane w do�� specjalny sposób: napisy_w_których_unika_si�_spacji. ELEMENTY PROGRAMU W J�ZYKU C++. Uogólniaj c, program w j�zyku C++ składa si� z nast�puj cych elementów: 1. Dyrektyw preprocesora. Przykład: #define drukuj printf Działanie: W tek�cie programu PONIEJ niniejszej dyrektywy zast p wszystkie ła�cuchy znaków "drukuj" ła�cuchami znaków "printf". #include <D:\KATALOG\nazwa.roz> Działanie: W to miejsce pliku wstaw zawarto�� pliku tekstowego NAZWA.ROZ z katalogu KATALOG na dysku D:. 2. Komentarzy. Przykład: // Tu obliczamy sum� lub /*To jest komentarz*/ 3. Deklaracji. Przykład: KADY PROGRAM musi zawiera� deklaracj� funkcji main (ang. main - główna). Funkcja ta cz�sto jest bezparametrowa, co mo�na zaakcentowa� wpisuj c w nawiasy słowo kluczowe void: main(void) lub pisz c puste nawiasy: main() 4. Instrukcji. i++; Działanie: Dokonaj inkrementacji zmiennej i, tzn. wykonaj operacj� i:=i+1 [???] Dla dociekliwych - kilka słów o funkcji main() ________________________________________________________________ Funkcja main() wyst�puje najcz��ciej w nast�puj cych (równowa�nych) postaciach: main() int main() int main(void) - program w momencie uruchomienia nie pobiera �adnych argumentów z wiersza rozkazu --> () lub (void) - program zwraca po zako�czeniu jedn liczn� (int = integer - liczba całkowita) do systemu operacyjnego informuj c go w taki sposób, czy wykonał si� do ko�ca i bezbł�dnie i czy mo�na go usun � z pami�ci (bywaj tak�e programy rezyduj ce w pami�ci - tzw. TSR, o czym system operacyjny powinien "wiedzie�"). void main() void main(void) - program nie pobiera i nie zwraca �adnych paramatrów. Główna funkcja main() mo�e w �rodowisku okienkowym przeobrazi� si� w główn funkcj� okienkow : WinMain(.....) a w �rodowisku obiektowym w OwlMain(....) OWL - biblioteka obiektów dla Windows - Object Windows Library. W nawiasach funkcji main(), WinMain() i OwlMain() mog pojawi� si� parametry (argumenty) pobierane przez program w momencie uruchomienia z wiersza rozkazu lub od �rodowiska operacyjnego (szczegóły w dalszej cz��ci ksi �ki). Programy w C++ mog składa� si� z wielu plików dyskowych. Typowy program zawiera. Nazywa si� to zwykle projektami wielomodułowymi

- a poszczególne pliki - modułami lub elementami składowymi projektu: * plik nagłówkowy - NAZWA.H * moduł główny - NAZWA.CPP (ten i tylko ten zawiera funkcj� main()) * moduły pomocnicze - NAZWA2.CPP, NAZWA3.CPP, itp * pliki z zasobami typu menu, okienka dialogowe, itp - NAZWA.RC, NAZWA.DLG * wreszcie plik instrukta�owy - jak z tego wszystkiego zrobi� ko�cow aplikacj�. W zale�no�ci od wersji kompilatora pliki instrukta�owe mog mie� nazwy: NAZWA.PRJ (Project - BORLAND), NAZWA.IDE, a dla programu MAKE - MAKEFILE, NAZWA.MAK, NAZWA.NMK, itp. W �rodowisku Windows wyst�puje jeszcze zwykle w składzie projektów aplikacji tzw. plik definicji sposobu wykorzystania zasobów - NAZWA.DEF. ________________________________________________________________ [S!] void - czyli nijaki. ________________________________________________________________ Słowa kluczowe: void - pusty, wolny, nieokre�lony, avoid - unika�. main - główny, główna. return - powrót, zwrot. Nazwa funkcji: exit() - wyj�cie. ________________________________________________________________ Po nazwie funkcji main() NIE NALEY stawia� �rednika (;). Przy pomocy tej funkcji program kontaktuje si� z systemem operacyjnym. Parametry funkcji main, to te same parametry z którymi uruchamiamy nasz program w systemie DOS. Np. rozkaz FORMAT A: oznacza, �e do programu przekazujemy parametr A:. Poniewa� w ka�dym programie oprócz nagłówka funkcji: main(void) podajemy równie� tzw. ciało funkcji, np.: { printf("wydrukuj cokolwiek"); return 0; } jest to jednocze�nie DEFINICJA FUNKCJI main(). Zwró� uwag�, �e funkcja printf() nie jest w powy�szym przykładzie w �aden sposób ani deklarowana ani definiowana. Wiersz: printf("pisz!"); stanowi WYWOŁANIE funkcji printf() z parametrem 'pisz!' - ła�cuchem znaków, który nale�y wydrukowa�. W C++ nawet je�li nawiasy przeznaczone w funkcji na przekazanie jej argumentów s puste - musz by� obecne. Poprawne wywołanie funkcji w j�zyku C++ mo�e mie� nast�puj c form�: nazwa_funkcji(); nazwa_funkcji(par1, par2, par3, .....); zmienna = nazwa_funkcji(par1, par2, ...); Funkcja w momencie jej wywołania uzyskuje przekazane jej parametry. S to tzw. ARGUMENTY FUNKCJI. Aby to wszystko bardziej przypominało to, co znasz ze szkoły popatrzmy na analogi�. W zapisie: y = sin(x) lub y = sin(90) x - oznacza argument funkcji, który mo�e by� zmienn (w szkole nazywałe� zmienne "niewiadomymi") y - oznacza warto�� zwracan "po zadziałaniu" funkcji sin() - oznacza nazw� funkcji. Zastosowanie funkcji b�dziemy w programach nazywa� "wywołaniem funkcji".

Page 18: C++ - lekcje

18

J�zyk C++ operuje wył cznie poj�ciem FUNKCJI. W C ani w C++ nie ma podziału na FUNKCJE i PROCEDURY. Ka�da funkcja mo�e by� w programie wywoływana wielokrotnie. Ka�de wywołanie funkcji mo�e nast�powa� z innymi argumentami. Funkcja mo�e w wyniku swojego działania zmienia� warto�� jakiej� zmiennej wyst�puj cej w programie. Mówimy wtedy, �e funkcja ZWRACA warto�� do programu. Funkcja main() jest funkcj szczególn , która "zwraca" warto�� do systemu operacyjnego, w którym pracuje program. Zapis: main() lub int main() { { return 5; exit(5); } } oznacza: 1. Funkcja main jest bezparametrowa (nie przyjmuje �adnych argumentów z zewn trz). 2. Funkcja main zwraca jako wynik swojego działania liczb� całkowit typu int (ang. INTeger - całkowita). Zwró� uwag�, �e jest to domy�lny sposób działania funkcji main(). Je�li nie napiszemy przed funkcj main() słowa "int" - kompilator C++ doda je sobie automatycznie. Je�li �wiadomie nie zamierzamy zwraca� do systemu operacyjnego �adnych informacji - musimy wyra�nie napisa� tam "void". 3. Funkcja zwróci do systemu DOS warto�� 5. Zwró� uwag� na istotn ró�nic� formaln , Słowo "return" jest słowem kluczowym j�zyka C, natomiast słowo "exit" jest nazw funkcji exit(). Zastosowanie tej funkcji w programie wymaga doł czenia pliku nagłówkowego z jej prototypem. Poniewa� nasz kurs j�zyka C++ rozpocz�li�my od programu z funkcj printf() i zapewne b�dzie nam ona towarzyszy� jeszcze długo, pora po�wi�ci� jej troch� uwagi. FUNKCJA printf(). Jest to funkcja FORMATOWANEGO wyj�cia na standardowe urz dzenie wyj�cia (ang. stdout - STandarD OUTput). Definicja - �ci�lej tzw. PROTOTYP tej funkcji znajduje si� w pliku nagłówkowym STDIO.H. Wniosek praktyczny: Ka�dy program korzystaj cy z funkcji printf() powinien zawiera� dyrektyw� preprocesora: #include <stdio.h> zanim nast pi wywołanie funkcji printf(). [???] A JE�LI ZAPOMNIAŁEM O <STDIO.H> ??? ________________________________________________________________ Mo�esz nada� plikowi z tekstem �ródłowym programu rozszerzenie .C zamiast .CPP. W kompilatorach Borlanda powoduje to przy domy�lnych ustawieniach kompilatora wywołanie kompilatora C zamiast C++. C jest bardziej tolerancyjny i dokona kompilacji (wy�wietli jedynie komunikat ostrzegawczy - Warning). Kompilator C++ jest mniej tolerancyjny. Je�li zapomnisz doł czy� odpowiedni plik nagłówkowy mo�e pojawi� si� komunikat: Error: Function printf() should have a prototype in function main (Funkcja printf() powinna mie� prototyp) Wi�cej o zawarto�ci i znaczeniu plików nagłówkowych *.h dowiesz si� z nast�pnych lekcji. Na razie postaraj si� pomi�ta� o doł czeniu wskazanego w przykładzie pliku. ________________________________________________________________ [???] Sk d to wiadomo? ________________________________________________________________ Je�li masz w tpliwo�ci, jaki plik nagłówkowy nale�ałoby doł czy� - najpro�ciej zajrze� do systemu pomocy - Help. Na pasku głównego menu w IDE masz napis Help. Menu Help mo�esz rozwin � myszk lub naciskaj c kombinacj� klawiszy [Alt]+[H]. Je�li w menu wybierzesz rozkaz Index (Spis) przeniesiesz si� do okienka z alfabetycznym spisem haseł. S tam słowa kluczowe, nazwy funkcji i jeszcze wiele innych interesuj cych rzeczy. Powiniene�

teraz wykona� nast�puj ce czynno�ci: * posługuj c si� klawiszami kursora (ze strzałkami) odszuka� w spisie nazw� funkcji albo * rozpocz � pisanie nazwy funkcji na klawiaturze (system Help sam wyszuka w spisie wypisan w ten sposób nazw�) * nacisn � [Enter] Przeniesiesz si� do okienka opisu danej funkcji. Na samym pocz tku w okienku ka�dej funkcji podana jest nazwa pliku nagłówkowego, w którym znajduje si� prototyp funkcji. Nawet je�li nie jeste� biegłym anglist , łatwo rozpoznasz pliki nagłówkowe - po charakterystycznych rozszerzeniach .H (rzadziej .HPP. Charakterystyczne rozszerzenie *.H pochodzi od "plik nagłówkowy" - ang. Header file). ________________________________________________________________ Funkcja printf() zwraca warto�� całkowit typu int: * liczb� bajtów przesłanych na standardowe urz dzenie wyj�cia; * w przypadku wyst pienia bł�du - kod znaku EOF. [S!] EOF - End Of File - znak ko�ca pliku. EOL - End Of Line - znak ko�ca linii. Indicator - znak, wska�nik (nie myli� z pointerem !) [???] SKD TO WIADOMO ? ________________________________________________________________ Kody EOF, EOL s tzw. predefiniowanymi stałymi. Ich szyfrowanie (przypisywanie tym identyfikatorom okre�lonej stałej warto�ci liczbowej) dokonuje si� z zastosowaniem preprocesora C++. To, �e nie musisz si� zastanawia� ile to wła�ciwie jest EOF (zero ? czy -1 ?) zawdzi�czamy te� doł czanym plikom typu *.H, w których np. przy u�yciu dyrektywy #define zostały PREDEFINIOWANE (zdefiniowane wst�pnie) niektóre stałe. Je�li jeste� bardzo dociekliwy, zajrzyj do wn�trza pliku STDIO.H (view, edit, type). Znajdziesz tam mi�dzy innymi taki wiersz: #define EOF (-1) //End of file indicator ________________________________________________________________ Składnia prototypu (ang. syntax): int printf(const char *format [arg1, arg2,.....]); lub troch� pro�ciej: printf(format, arg1, arg2,.....argn); Liczba argumentów mo�e by� zmienna. C++ oferuje wiele funkcji o podobnym działaniu - np.: cprintf(), fprintf(), sprintf(), vprintf(), vsprintf(), itp. Poniewa� FORMAT brzmi mo�e troch� obco, nazwijmy go WZORCEM. Jak wiesz, wszystkie informacje przechowywane s w pami�ci komputera jako ci gi zer i jedynek. Jest to forma troch� niewygodna dla człowieka, wi�c zanim informacja trafi na ekran musi zosta� zamieniona na posta� dla nas wygodniejsz - np. na cyfry dziesi�tne, litery itp.. Taki proces nazywany jest KONWERSJ, a podany w funkcji printf() FORMAT - WZORZEC to upraszczaj c, rozkaz dokonania takiej wła�nie konwersii. Mo�esz wi�c zarz da� przedstawienia liczby na ekranie w postaci np. SZESNASTKOWEJ lub DZIESI�TNEJ - tak, jak Ci wygodniej. Wzorce konwersji w najprostszym przypadku maj posta� %s, %d, %f, itp.: I tak: %s - wyprowad� ła�cuch znaków (s - String - ła�cuch) Przykład: printf("%s","jakis napis"); ale tak�e printf("Jakis napis"); poniewa� format "%s" jest formatem domy�lnym dla funkcji

Page 19: C++ - lekcje

19

printf(). Przykład: printf("%39s","jakis napis"); spowoduje uzupełnienie napisu spacjami do zadanej długo�ci 39 znaków (Sprawd�!). Funkcja printf() operuje tzw. POLEM WYJ�CIOWYM. Długo�� pola wyj�ciowego mo�emy okre�li� przy pomocy liczb wpisanych pomi�dzy znaki % oraz typ - np. s. Mo�emy tak�e okre�li� ilo�� cyfr przed i po przecinku. %c - wyprowad� pojedynczy znak (c - Character - znak) Przykład: printf("%c",'X'); (spowoduje wydrukowanie litery X) %d - wyprowad� liczb� całkowit typu int w postaci dziesi�tnej �����(d - Decimal - dziesi�tny). Przykład: printf("%d", 1994); %f - wyprowad� liczb� rzeczywist typu float w postaci dziesi�tnej (f - Floating point - zmienny przecinek). Przykład: printf("%f", 3.1416); printf("%f3.2", 3.14159); %o - wyprowad� liczb� całkowit typu int w postaci ósemkowej (o - Octal - ósemkowa). Przykład: printf("%o", 255); %x - wyprowad� liczb� całkowit typu int w postaci szesnastkowej �����(x - heXadecimal - szesnastkowa). %x lub %X - cyfry szesnastkowe a,b,c,d,e,f lub A,B,C,D,E,F. %ld - liczba całkowita "długa" - long int. %Lf - liczba rzeczywista poczwórnej precyzji typu long double float. %e - liczba w formacie wykładniczym typu 1.23e-05 (0.0000123) %g - automatyczny wybór formatu %f albo %e. Po przytoczeniu przykładów uogólnijmy sposób zastosowania wzorca formatu: %[przeł czniki][szeroko��_pola][.precyzja][rozmiar]Typ Posługuj c si� ró�nymi sposobami formatowania liczb mo�emy za� da� wydrukowania liczb w najwygodniejszej dla nas formie. W programie przykładowym dokonujemy zamiany liczb dziesi�tnych na szesnastkowe. [P006.CPP] // Program przykladowy 10na16.CPP #include <stdio.h> #include <conio.h> int liczba; int main() { clrscr(); printf("Podaj liczbe dziesietna calkowita ? \n"); scanf("%d", &liczba); printf("\nSzesnastkowo to wynosi: "); printf("%x",liczba); getch(); return 0;

} Ten program pozwala zamieni� dziesi�tne liczby całkowite na liczby szesnastkowe. Zakres dost�pnych liczb wynika z zadeklarowanego typu int. Wi�cej na ten temat dowiesz si� z nast�pnych lekcji. Spróbujmy odwrotnie: [P007.CPP] // Program przykladowy 16na10.CPP //UWAGA: Sam doł cz pliki nagłówkowe int liczba; int main() { clrscr(); printf("Podaj liczbe SZESNASTKOWA-np. AF - DUZE LITERY: \n"); scanf("%X", &liczba); printf("%s","\nDziesietnie to wynosi: "); printf("%d",liczba); getch(); return 0; } My�l�, �e program 16NA10.CPP mo�na pozostawi� bez dodatkowego komentarza. Zwró� uwag�, �e funkcja scanf() "formatuje" dane wej�ciowe bardzo podobnie do funkcji printf(). Pewnie dziwi Ci� troch� "dualny" zapis: liczba i &liczba. Zagadka zostanie niebawem wyja�niona. W trakcie nast�pnych Lekcji zajmiemy si� dokładniej zmiennymi, i ich rozmieszczeniem w pami�ci a na razie wracamy do funkcji printf(). Jako si� rzekło wcze�niej - funkcja printf() mo�e mie� wiele argumentów. Pozwala nam to przy pomocy jednego wywołania funkcji wyprowadza� zło�one napisy. Przykład: printf("Iloczyn 3 %c 5 %8s %d", '*', "wynosi ",15); Działanie: "Iloczyn_3_ - wyprowad� jako ła�cuch znaków. %c - tu wyprowad� pojedynczy znak - '*'. _5_ - wyprowad� jako ła�cuch znaków. %8s - wyprowad� ła�cuch "wynosi_" uzupełniaj c go z przodu spacjami do długo�ci 8 znaków. %d - wyprowad� 15 jako liczb� dziesi�tn . UWAGA: Znakiem podkre�lenia w tek�cie ksi �ki "_" oznaczyłem spacj�, spacja to te� znak. Przykład: printf("Iloczyn 3 %c 5 %9s %f", 'x', "wynosi ", 3*5); Zwró� uwag�, �e tym razem kazali�my komputerowi samodzielnie policzy� ile wynosi nasz iloczyn, tzn. zastosowali�my jako argument funkcji printf() nie stał , a WYRAENIE. Działanie mo�esz prze�ledzi� przy pomocy programu przykładowego: [P008.CPP] // Program WYRAZ.CPP - Doł cz pliki nagłówkowe int main() { clrscr(); printf("Skomplikowany napis:\n"); printf("Iloczyn 3 %c 5 %8s %d", '*', "wyniosi ", 15); getch(); printf("\nWyrazenie jako argument:\n"); printf("Iloczyn 3 %c 5 %9s %d", 'x', "wynosi ", 3*5); printf("\n\n\n"); printf("Przyjrzyj sie i nacisnij klawisz..."); getch(); return 0; }

Page 20: C++ - lekcje

20

Wyja�nijmy jeszcze jedno "dziwactwo" - znaki steruj ce rozmieszczeniem napisów na ekranie. Oto tabelka z najcz��ciej u�ywanymi znakami specjalnymi: ________________________________________________________________ Znak Nazwa Działanie ________________________________________________________________ \n New Line Przej�cie na pocz tek nowego wiersza \b BackSpace Cofni�cie kursora o jeden znak \f Form feed O stronic� w dół \r Carriage return Powrót na pocz tek bie�. wiersza \t Horizontal Tab Tabulacja pozioma \v Vertical Tab Tabulacja pionowa \a Sound a beep Pisk gło�niczka \\ Displ. backslash Wy�wietl znak \ \' Display ' Wy�wietl znak ' (apostrof) \" Display " Wy�wietl znak " (cudzysłów) ________________________________________________________________ UWAGA: Trzy ostatnie "backlash-kody" pozwalaj wyprowadzi� na ekran znaki specjalne \ ' i ", co czasami si� przydaje. Szczególnie \\ jest cz�sto przydatny. [Z] Spróbuj samodzielnie: 1. Napisa� i uruchomi� program wykonuj cy konwersj� liczb ósemkowych na dziesi�tne i odwrotnie. 2. Przy pomocy pojedynczego wywołania funkcji printf() wydrukowa� kilka zło�onych napisów typu: * suma 2+4 to 6 * działanie 5*7*27+6-873 daje wynik...( wła�nie, ile?). 3. Sprawd� działanie tabulacji pionowej \v. Ile to wierszy? [???] DYSKIETKA NIE JEST Z GUMY !!! ________________________________________________________________ Je�li podczas kompilacji programów w okienku b�dzie si� uporczywie, bez widocznego powodu pojawiał napis "Errors" - bł�dy, a w okienku komunikatów "Message" pojawi si� napis: Fatal A:\PROGRAM.C: Error writing output file (Fatalny bł d podczas kompilacji pliku A:\PROGRAM.C: Bł d przy zapisie pliku wyj�ciowego), to znak, �e na dyskietce zabrakło miejsca. Pora zmieni� katalog wyj�ciowy kompilatora C++. Aby to zrobi� nale�y: 1. Rozwin � menu Option - [Alt]-[O]. 2. Wybra� rozkaz Directories... - [D]. 3. Przej�� do okienka "Output Directory" - 2 razy [Tab]. 4. Wpisa� do okienka katalog z dysku stałego, np.: C:\ 5. Nacisn � [Enter]. 6. Powtórzy� kompilacj� programu, przy której nast piło przepełnienie dyskietki. 7. Usun � z dyskietki A: zb�dne pliki *.EXE (TYLKO *.EXE !!!). Oczywi�cie lepiej posługiwa� si� własnym katalogiem na dysku stałym, ale dysk te� niestety nie jest z gumy. Zło�liwi twierdz nawet, �e ka�dy dysk jest za mały a ka�dy procesor zbyt wolny (to pono� tylko kwestia czasu...). ________________________________________________________________ [!!!] Dla dociekliwych - Przykłady programów. ________________________________________________________________ Je�li zajrzysz ju� do systemu Help, przwi� cierpliwie tekst opisu funkcji do ko�ca. W wi�kszo�ci funkcji na ko�cu umieszczony jest krótki "firmowy" program przykładowy. Nie musisz go przepisywa�! W menu Edit IDE masz do dyspozycji rozkaz Edit | Copy Example (Skopiuj przykład) Przykład zostanie skopiowany do Schowka (Clipboard). Po wyj�ciu z systemu pomocy warto rozkazem File | New otworzy� nowe okno robocze a nast�pnie rozkazem Edit | Paste (Wstaw) wstawi� program przykładowy ze schowka. Mo�esz go teraz uruchamia�, modyfikowa� a nawet wstawi� jako fragment do swojego programu.

Podobnie jak wi�kszo�� edytorów tekstu zintegrowany edytor �rodowiska IDE pozwala manipulowa� fragmentami blokami tekstu i wykonywa� typowe operacje edytorskie zarówno w obr�bie pojedynczego okna, jak i pomi�dzy ró�nymi okienkami. Słu� do tego celu nast�puj ce operacje: * Select/Mark text block - zaznaczenie fragmentu tekstu. Mo�esz dokona� tego klawiszami- np.: [Shift]+[-->], b d� naciskaj c i przytrzymuj c lewy klawisz myszki i "przeje�d�aj c nad odpowiednim fragmentem tekstu". Wybrany fragment tekstu zostanie wyró�niony pod�wietleniem. * Edit | Cut - wytnij. Zaznaczony wcze�niej fragment tekstu zostanie skopiowany do Schowka i jednocze�nie usuni�ty z ekranu. * Edit | Copy - skopiuj. Zaznaczony wcze�niej fragment tekstu zostanie skopiowany do Schowka i bez usuwania z ekranu. * Edit | Paste - wstaw. Zaznaczony wcze�niej w Schowku fragment tekstu zostanie skopiowany na ekran pocz wszy od miejsca wskazanego w danej chwili kursorem. LEKCJA 8. Jakich słów kluczowych u�ywa C++. W trakcie tej lekcji dowiesz si�: * Jakie znaczenie maj słowa kluczowe j�zyka C++. * Jakie jeszcze dziwne słowa mog pojawia� si� w programach w pisanych C++. * Troch� wi�cej o wczytywaniu i wyprowadzaniu danych. * Co to jest i do czego słu�y zmienna. _______________________________________________________________ Ka�dy j�zyk musi operowa� tzw. słownikiem - zestawem słów zrozumiałych w danym j�zyku. Jak wiesz z do�wiadczenia, komputer jest pedantem i wymaga dodatkowo (my, ludzie, tego nie wymagamy), aby znaczenie słów było absolutnie jednoznaczne i precyzyjne. Aluzje, kalambury i zabawne niedomówienia s na razie w dialogu z komputerem niedopuszczalne. Pami�ci asocjatywne (oparte na skojarzeniach), sieci neuronowe (neural networks), tworzone bardzo cz�sto wła�nie przy pomocy C++ - systemy expertowe, - systemy z tolerancj bł�dów - np. OCR - systemy optycznego rozpoznawania pisma, - "rozmyta" arytmetyka i logika (fuzzy math) - logika wi�kszo�ciowa i mniejszo�ciowa - algorytmy genetyczne (genetic algorithms) i inne pomysły matematyków oraz informatyków rozpocz�ły ju� proces "humanizowania" komputerowego my�lenia. Powstała nawet specjalna "mutacja" neural C i neural C++, ale to temat na oddzieln ksi �k�. Na razie traktujemy nasz komputer jako automat cyfrowy pozbawiony całkowicie wyobra�ni i poczucia humoru, a j�zyk C++, jako �rodek porozumiewania si� z tym "ponurakiem". Podobnie do słów j�zyka naturalnego (rzeczowników, czasowników) i słowa j�zyka programowania mo�na podzieli� na kilka grup ró�ni cych si� przeznaczeniem. Takie niby - słowa czasem nazywa si� równie� tokenami lub JEDNOSTKAMI LEKSYKALNYMI (leksykon - inaczej słownik) a sposoby tworzenia wyra�e� (expressions) nazywane s syntaktyk j�zyka (st d bierze si� typowy komunikat o bł�dach "Syntax Error" - bł d syntaktyczny, czyli niewła�ciwa składnia). Słownik j�zyka C++ składa si� z: * Słów kluczowych * Identyfikatorów * Stałych liczbowych i znakowych * Stałych tekstowych (ła�cuchów znaków - napisów) * Operatorów (umownych znaków operacji) * Znaków interpunkcyjnych * Odst�pów UWAGA: Zarówno pojedyncza spacja czy ci g spacji, tabulator poziomy, znak nowej linii, jak i komentarz dowolnej długo�ci (!) s traktowane przez kompilator jak pojedyncza spacja. Od zarania dziejów informatyki twórcy uniwersalnych j�zyków programowania starali si� upodobni� słowa tych j�zyków do zrozumiałych dla człowieka słów j�zyka naturalnego - niestety - angielskiego (swoj drog , mo�e to i lepiej, �e C++ nie wymy�lili Japo�czycy...). Najwa�niejsz cz��ci słownika s tzw. SŁOWA KLUCZOWE (keywords).

Page 21: C++ - lekcje

21

SŁOWA KLUCZOWE w C++. Oto pełna lista słów kluczowych Turbo C++ v 1.0 z krótkim wyja�nieniem ich znaczenia. Zaczynam od listy podstawowej wersji kompilatora, poniewa� rozwa�ania o niuansach dotycz cych kilku specyficznych słów kluczowych (np. friend, template) pozostawiam sobie na pó�niej. Krótkie wyja�nienie - jak to krótkie wyja�nienie - pewnie nie wyja�ni wszystkiego od razu, ale na pewno pomo�e zrozumie� znaczenie wi�kszo�ci słów kluczowych. [S] Keywords - słowa kluczowe. asm Pozwala wstawi� kod w ASEMBLERZE bezpo�rednio do programu napisanego w C lub C++. auto - zmienna lokalna. Przyjmowane domy�lnie. break - przerwij. case - w przypadku. cdecl - spec. konwencja nazewnictwa/przekazania parametrów zgodna ze standardem j�z. C. char - znak, typ zmiennej - pojedynczy bajt. class - klasa. const - stała, konstanta. continue - kontynuuj. default - przyjmij domy�lnie. delete - skasuj obiekt. do - wykonaj. double - podwójna (długo��/precyzja). else - w przeciwnym wypadku. enum - wylicz kolejno. _export - dotyczy tylko OS/2, ignorowany. extern - zewn�trzna. far - dalekie. Wska�nik - podwójne słowo (w zakresie do 1 MB). float - zmiennoprzecinkowy, rzeczywisty. for - dla (wskazanie zmiennej roboczej w p�tli). friend - zaprzyja�niona funkcja z dost�pem do prywatnych i ����������chronionych członków danej klasy. goto - skocz do (skok bezwarunkowy). huge - daleki, podobnie do far. if - je�eli (pod warunkiem, �e...). inline - funkcja z rozwini�tym wstawionym kodem int - typ zmiennej, liczba całkowita, dwa bajty interrupt - przerwanie. _loadds - podobne do huge, ustawia rejestr DS (Data Segment). long - długi. near - bliski, wska�nik o dł. 1 słowa. Obszar max. 64 K. new - nowy, utwórz nowy obiekt. operator - operator, okre�la nowy sposób działania operatora. pascal - deklar. funkcji zgodnej ze standardem przekazywania parametrów przyj�tym w Pascalu.

private - prywatna, wewn�trzna, niedost�pna z zewn trz. protected - chroniona, cz��� danych i funkcji, do których dost�p. jest ograniczony. public - publiczna, dost�pna z zewn trz. register - zmienn przechwaj nie w pami�ci a w rejestrze CPU. return - powrót, zwrot warto�ci. _saveregs - save registers, zachowaj zawarto�� rejestrów a nast�pnie odtwórz rejestry przed powrotem. _seg - segment. short - krótka (mała ilo�� cyfr). signed - ze znakiem (+/-). unsigned - bez znaku (+/-). sizeof - podaj wielko��. static - statyczna. struct - struktura. switch - przeł cz. this - ten, wstazanie bie� cego, własnego obiektu (tylko C++). typedef - definicja typu. union - unia, zmienna wariantowa. virtual - wirtualna, pozorna. void - nieokre�lona. volatile - ulotna. while - dopóki. Panuje mnienanie, �e j�zyk C++ posługuje si� stosunkowo skromnym zestawem słów kluczowych. To prawda, ale nie cała prawda o j�zyku C++. Zauwa�yłe� zapewne, �e nie ma tu: define, include, printf i innych znanych Ci ju� słów. To po prostu jeszcze nie cały słownik j�zyka. Zdaj c sobie spraw� z nieprecyzyjno�ci tego porównania mo�esz przyj �, �e to co� na kształt listy czasowników. A s przecie� jeszcze i inne słowa - o innej roli i przeznaczeniu. [???]�A GDZIE SI� PODZIAŁY REJESTRY ??? Nazwy rejestrów mikroprocesora Intel 80X86: _AX�������_AL�������_AH�������_SI�������_CS _BX�������_BL�������_BH�������_SP�������_DS _CX�������_CL�������_CH�������_BP�������_ES _DX�������_DL�������_DH�������_DI�������_SS _FLAGS Takie oznaczenia wynikaj z architektury konkretnej rodziny mikroprocesorów, nie mog stanowi� uniwersalnego standardu j�zyka C++. Efekt dostosowania C++ do IBM PC to np. odnosz ce si� do modeli pami�ci słowa kluczowe near, far i huge. Wymóg zgodno�ci ze standardem ANSI C spowodował, �e w C++ nazwy rejestrów pozostaj nazwami o zastrze�onym znaczeniu, ale nazywaj si� PSEUDOZMIENNYMI REJESTROWYMI (ang.: Register Pseudovariables). Próba u�ycia słowa o zastrze�onym znaczeniu w jakiejkolwiek innej roli (np. jako nazwa Twojej zmiennej) mo�e spowodowa� wadliwe działanie programu lub uniemo�liwi� kompilacj�. Unikaj przypadkowego zastosowania słów o zastrze�onym znaczeniu! [???] A SKD MAM WIEDZIEC ?

Page 22: C++ - lekcje

22

List� nazw, które maj ju� nadane �ci�le okre�lone znaczenie w C++ znajdziesz w Help. Dost�p do spisu uzyskasz przez: * Rozwini�cie menu Help [Alt]-[H]; * Wybranie z menu Help rozkazu Index (spis). Wróci� do edytora IDE C++ mo�esz przez [Esc]. SŁOWA TYPOWE DLA PROGRAMÓW OBIEKTOWYCH. W porównaniu z klasycznym j�zykiem C (wobec którego C++ jest nadzbiorem - ang. superset), w nowoczesnych programach obiektowych i zdarzeniowych pisanych w C++ mog pojawia� si� i inne słowa. Przyjrzyjmy si� na troch� inn technik� programowania - bardziej charakterystyczn dla C++. Procesy wprowadzania i wyprowadzania danych do- i z- komputera nazywaj si� Input i Output - w skrócie I/O (lub bardziej swojsko We/Wy). Obsługa We/Wy komputera to sała obszerna wiedza, na pocz tek b�dzie nam jednak potrzebne tylko kilka najbardziej istotnych informacji. PROBLEM ˙WEJ�CIA/WYJ�CIA W PROGRAMACH - troch� bardziej ogólnie. Operacje wej�cia i wyj�cia s zwykle kontrolowane przez pracuj cy wła�nie program. Je�li uruchomiłe� program, który nie korzysta z klawiatury i nie oczekuje na wprowadzenie przez u�ytkownika �adnych informacji - mo�esz naciska� dowolne klawisze - program i tak ma to w nosie. Podobnie, je�li w programie nie przewidziano wykorzystania drukarki, cho�by� "wyłaził ze skóry", �adne informacje nie zostan przesłane do drukarki, dla programu i dla u�ytkownika drukarka pozostanie niedost�pna. Aby programy mogły zapanowa� nad Wej�ciem i Wyj�ciem informacji, wszystkie j�zyki programowania musz zawiera� specjalne rozkazy przeznaczone do obsługi Wej�cia/Wyj�cia (ang. Input/Output commands, lub I/O instructions). Bez umiej�tno�ci obsługi We/Wy, czyli bez mo�liwo�ci porozumiewania si� ze �wiatem zewn�trznym psu na bud� zdałby si� ka�dy j�zyk programowania. Ka�dy program musi w wi�kszym, b d� mniejszym stopniu pobiera� informacje ze �wiata zewn�trznego do komputera i wysyła� informacje z komputera na zewn trz. Podobnie, jak wszystkie uniwersalne j�zyki programowania - j�zyk C++ zawiera pewn ilo�� rozkazów przeznaczonych do zarz dzania obsług wej�cia i wyj�cia. Dla przykładu, mo�emy w j�zyku C++ zastosowa� OBIEKT cout obsługuj cy strumie� danych wyj�ciowych. Obiekt cout (skonstruowany przez producenta i zdefiniowany w pliku nagłówkowym IOSTREAM.H) pozwala programi�cie przesła� dane tekstowe i/lub numeryczne do strumienia wyj�ciwego i umie�ci� tekst na ekranie monitora. Wczytaj plik �ródłowy z programem COUT1.CPP lub wpisz samodzielnie nast�puj cy program przykładowy. Program drukuje tekst na ekranie monitora. [P009.CPP] #include <iostream.h> <-- zwró� uwag� na inny, nowy plik #include <conio.h> void main(void) { clrscr(); cout << "Stosujemy obiekt cout:\n"; cout << "Tekst pierwszy\n"; cout << "Tekst drugi...\n"; getch(); } Jak widzisz, ka�dy rozkaz z u�yciem obiektu cout tworzy pojedyncz lini� tekstu (wiersz) na ekranie monitora. Kompilator j�zyka C++ wie, �e chcesz wysła� tekst na ekran monitora dzi�ki słowu cout i znakowi << (znak << to tzw. operator przesyłania do strumienia). Wysłany na ekran zostaje tekst umieszczony po operatorze << i (obowi zkowo, podobnie jak w funkcji printf()) uj�ty w cudzysłów ("). Tekst uj�ty w cudzysłów nazywa si� ła�cuchem znakowym (ang. string literal). [S] String literal - ła�cuch znaków. Ła�cuch znaków to grupa znaków alfanumerycznych (tekstowych).

Ła�cuch znaków to taki ci g znaków, który komputer mo�e rozpatrywa� wył cznie jako cało�� i posługiwa� si� nim tylko tak, jak go wpisałe�. Aby komputer poprawnie rozpoznawał ła�cuchy tekstowe - nale�y ujmowa� je w cudzysłów. Ła�cuch znaków mo�e by� nazywany równie� literałem, b d� literałem ła�cuchowym. [!!!] Dla dociekliwych - jak C++ zapami�tuje tekst? Pojedyncze znaki mo�na zapisywa� w C++ tak: 'A' - pojedynczy znak reprezentowany w pami�ci komutera jako jeden bajt zawieraj cy liczb� - numer litery A według kodu ASCII. W tym przypadku byłaby to liczba 65 (dwójkowo i szesnastkowo- odpowiednio: 0100 0001 i 41). "A" - jednoelementowy ła�cuch znaków zajmuj cy w pami�ci dwa bajty (kod litery A i znak ko�ca ła�cucha - \0). Reprezentacja w pami�ci wygl dałaby tak: Bajt Nr X 0100 0001 - kod ASCII litery A Bajt Nr X+1 0000 0000 - kod ASCII 0 - znak ko�ca Wiesz ju�, �e clrscr(); stanowi wywołanie gotowej funkcji (tzw. funkcji bibliotecznej). Informacja dotycz ca tej funkcji (tzw. prototyp funkcji) znajduje si� w pliku CONIO.H, dlatego doł czyli�my ten plik nagłówkowy na pocz tku programu dyrektyw #include. A có� to za dziwol g ten "cout" ? Po cout nie ma pary nawiasów okr głych (gdyby to była funkcja - powinno by� cout()) - nie jest to zatem wywołanie funkcji. ˙Strumie� danych wyj�ciowych cout - JEST OBIEKTEM (ang. I/O stream object - obiekt: strumie� Wej�cia/Wyj�cia). Ale nie przestrasz si�. Popularne wyobra�enie, �e programowanie obiektowe jest czym� bardzo skomplikowanym nie ma z prawd wi�cej wspólnego, ni� powszechny do�� pogl d, �e baba z pustym wiadrem jest gorsza od czarnego kota. W gruncie rzeczy jest to proste. Strumie� to nic innego jak zwyczajny przepływ informacji od jednego urz dzenia do innego. W tym przypadku strumie� (przepływ) danych oznacza przesłanie informacji (tekstu) z pami�ci komputera na ekran monitora. Trójk tne nawiasy (<< lub >>) wskazuj kierunek przepływu informacji. Przesyłanie nast�puje w naszym przypadku z pami�ci do strumienia Pojawiło si� tu wa�ne słowo - OBIEKT. Obiekt podobnie jak program komputerowy jest to grupa danych i funkcji działaj cych wspólnie i przeznaczonych razem do wykonania jakich� zada�. Dla przykładu obiekt cout słu�y do obsługi przesyłania danych na ekran monitora. Słowo "obiekt" jest cz�sto u�ywane w opisach nowoczesnych technik programowania - tzw. PROGRAMOWANIA OBIEKTOWEGO. Programowanie obiektowe, ta "wy�sza szkoła jazdy" dla programistów z lat 80-tych jest ju� wła�ciwie w naszych czasach norm . Zreszt widzisz sam - napisałe� program obiektowy i co - i nic strasznego si� nie stało. Na pocz tek musisz wiedzie� tylko tyle, �e aby posługiwa� si� obiektami - strumieniami wej�cie i wyj�cia - nale�y doł czy� w C++ plik nagłówkowy IOSTREAM.H. Dlatego dyrektywa #include <iostream.h> znajduje si� na pocz tku przykładowego programu. KILKA ARGUMENTÓW FUNKCJI w praktyce. Jak starałem si� wykaza� w przykładzie z sinusem, funkcja mo�e otrzymac jako argument stał - np. okre�lon liczb�, b d� zmienn (niewiadom ). Niektóre funkcje mog otrzymywa� w momencie ich wywołania (u�ycia w programie) wi�cej ni� jeden argument. Rozwa�my to dokładniej na przykładzie funkcji fprintf() zbli�onej w działaniu do printf(), lecz bardziej uniwersalnej. Funkcja fprintf() pozwala wyprowadza� dane nie tylko na monitor, ale tak�e na drukark�. Skoro urz dzenia wyj�cia mog by� ró�ne, trzeba funkcji przekaza� jako jeden z jej argumentów informacj� o tym - na które urz dzenie �yczymy sobie w danej chwili wyprowadza� dane. Słowo stdout jest pierwsz informacj� (tzw. parametrem, b d� argumentem funkcji) przekazanym do funkcji fprintf(). Słowo stdout jest skrótem od Standard Output - standardowe wyj�cie. Oznacza to w skrócie typowe urz dzenie wyj�ciowe podł czone do komputera ˙i umo�liwiaj ce wyprowadzenie informacji z komputera. W komputerach osobistych zgodnych ze standardem IBM PC tym typowym urz dzeniem wyj�ciowym jest prawie zawsze ekran monitora.

Page 23: C++ - lekcje

23

Tekst, który ma zosta� wydrukowany na ekranie monitora jest drug informacj przekazywan do funkcji fprintf() - inaczej - stanowi drugi parametr funkcji. Tekst - ła�cuch znaków - musi zosta� uj�ty w znaki cudzysłowu. A je�li zechcesz wyprowadzi� tekst na drukark�? W C++ zapisuje si� to bardzo łatwo. Wystarczy słowo stdout (oznaczaj ce monitor) zamieni� na słowo stdprn. Słowo stdprn to skrót od Standard Printer Device - standardowa drukarka. Oto przykład praktycznego u�ycia funkcji fprintf(). Program przesyła tekst na drukark�. Przed uruchomieniem programu pami�taj o wł czeniu drukarki. [P010.CPP] #include <stdio.h> #include <conio.h> int main(void) { clrscr(); fprintf(stdout, "Drukuje...\n"); fprintf(stdprn, "Pierwsza proba drukowania\n"); fprintf(stdprn, "Autor: ...................."); fprintf(stdout, "Koniec drukowania."); fprintf(stdout, "Skonczylem, nacisnij cosik..."); getch(); return 0; } Gdyby w programie nie było wiersza: fprintf(stdout, "Drukuje...\n"); - u�ytkownik przez pewien czas nie mógłby si� zorientowa�, czym ˙wła�ciwie zajmuje si� komputer. Wszystko stałoby si� jasne dopiero wtedy, gdy drukarka rozpocz�łaby drukowanie tekstów. Jest uznawane za dobre maniery praktyczne stosowanie dwóch prostych zasad: BZU - Bez Zb�dnych Udziwnie� DONU - Dbaj O Nerwy U�ytkownika Je�li efekty działania programu nie s natychmiast zauwa�alne, nale�y poinformowa� u�ytkownika CO PROGRAM ROBI. Je�li u�ytkownik odnosi wra�enie, �e komputer nic nie robi - ma zaraz w tpliwo�ci. Cz�sto próbuje wtedy wykona� reset komputera i wypowiada mnóstwo słów, których nie wypada mi tu zacytowa�. Nietrudno zgadn �, �e C++ powinien posiada� tak�e �rodki obsługi wej�cia. W C++ jest specjalny obiekt (ang. input stream object) o nazwie cin słu� cy do pobierania od u�ytkownika tekstów i liczb. Zanim zajmiemy si� dokładniej obiektem cin i obsług strumienia danych wej�ciowych - powiniene� zapozna� si� ze ZMIENNYMI (ang. variables). ZMIENNE. Gdy wprowadzisz jakie� informacje do komputera - komputer umieszcza je i przechowuje w swojej pami�ci (ang. memory - pami��). Pami�� komputera mo�e by� jego pami�ci stał . Taka pami�� "tylko do odczytu" nazywa si� ROM (read only memory - to wła�nie "tylko do odczytu"). Pami�� o swobodnym dost�pie, do której i komputer i Ty mo�ecie zapisywa� wszystko, co Wam si� spodoba - nazywa si� RAM (od Random Access Memory - pami�� o swobodnym dost�pie). Pami�ci ROM i RAM podzielone s na małe "komóreczki" nazywane Bajtami, Ka�dy bajt w pami�ci ma swój numer. Ten numer nazywany jest adresem w pami�ci. Poniewa� nie wszystko da si� pomie�ci� w jednym bajcie (to tylko 8 bitów - miejsca wystarczy na zapami�tanie tylko jednej litery), bajty (zwykle kolejne) mog by� ł czone w wi�ksze komórki - tzw. pola pami�ci (ang. memory fields). Najcz��ciej ł czy si� bajty: 2 Bajty = 16 bitów = Słowo (WORD) 4 Bajty = 32 bity = Podwójne słowo (DOUBLE WORD - DWORD) W uproszczeniu mo�esz wyobrazi� sobie pami�� komputera jako miliony pojedynczych komórek, a w ka�dej z komórek jaka� jedna warto�� (ang. value) zakodowana w postaci ZER i JEDYNEK. Ka�da taka "szara" komórka ma numer-adres. Numeracja komórek rozpoczyna si� nie od 1 lecz od zera (pierwsza ma numer 0).

Ilo�� tych komórek w Twoim komputerze zale�y od tego ile pami�ci zainstalujesz (np. 4MB RAM to 4x1024x124x8 bitów - chcesz - policz sam ile to bitów). Przeliczaj c zwró� uwag�, �e kilobajt (KB to nie 1000 - lecz 1024 bajty a megabajt - 1024 kB). Zastanówmy si�, sk d program mo�e wiedzie� gdzie, w której komórce zostały umieszczone dane i jak si� do nich dobra�, gdy stan si� potrzebne. Wła�nie do takich celów potrzebne s programowi ZMIENNE (ang. variables). Dawno, dawno temu rozwi zywałe� zapewne zadania typu: 3 + [ ] = 5 Otó� to [ ] było pierwszym sposobem przedstawienia Ci zmiennej. Jak wida� - zmienna to miejsce na wpisanie jakiej� (czasem nieznanej w danej chwili warto�ci). Gdy przeszedłe� do nast�pnej klasy, zadania skomplikowały si�: 3 + [ ] = 5 147.968 + [ ] = 123876.99875 Na ró�ne zmienne mo�e by� potrzeba ró�na ilo�� miejsca i na kartce i w pami�ci komputera. Gdy "zestarzałe� si�" jeszcze troch� - te same zadania zacz�to Ci zapisywa� tak: 3 + x = 5 147.968 + y = 123876.99875 Jak wida�, zmienne mog posiada� tak�e swoje nazwy - identyfikatory (z których ju� niestety nie wynika jasno, ile miejsca potrzeba do zapisania bie� cej warto�ci zmiennej). [???] Jak C++ wskazuje adres w pami�ci? Podobnie, jak w bajeczce o zabawie w chowanego kotka i myszki (myszka mówiła: "Gdyby� mnie długo nie mógł znale�� - b�d� czeka� na czwartej półce od góry..."), niektórzy producenci gier komputerowych �ycz sobie czasem przy uruchamianiu gry podania hasła umieszczonego: "W instrukcji na str. 124 w czwartym wierszu do góry" No có�. Zamiast nazywa� zmienne - niewiadome x, y, czy z, b d� rezerwowa� dla nich puste miejsce [ ], mo�emy jeszcze wskaza� miejsce, w którym nale�y ich szuka�. Takie wskazanie to trzeci sposób odwoływania si� do danych. W C++ mo�e si� to nazywa� referencj do zmiennej lub wskazaniem adresu zmiennej w pami�ci przy pomocy wska�nika. Wska�nik w C++ nazywa si� "pointerem". Pointerem mo�na wskaza� tak�e funkcje - podaj c ich adres startowy (pocz tek kodu funkcji w pami�ci RAM). Zmienne mo�esz sobie wyobrazi� jako przegródki w pami�ci komputera zaopatrzone w nazw� - etykietk�. Poniewa� nazwy dla tych przegródek nadaje programista w programie - czyli Ty sam, mo�esz wybra� sobie prawie ka�d , dowoln nazw�. Zwykle nazwy nadaje si� w taki sposób, by program stał si� bardziej czytelny i łatwiejszy do zrozumienia. Dla przykładu, by nie przepadły z pami�ci komputera wyniki gier komputerowych cz�sto stosuje si� zmienn o nazwie WYNIK (ang. Score). Za ka�dym razem, gdy zmienia si� wynik gracza (ang. player's score) w pami�ci komputera (w to samo miejsce) zostaje zapisana nowa liczba. W taki sposób pewien niewielki (a zawsze ten sam) fragment pami�ci komputera przechowuje dane potrzebne do pracy programu. PRZYPISYWANIE ZMIENNYM KONKRETNEJ WARTO�CI. Aby komputer mogł pobra� informacje od u�ytkownika, mo�esz zastosowa� w programie np. obiekt - strumie� wej�ciowy - cin (ang. input stream object). Obiekt cin i zmienne chodz zwykle parami. Przy obiekcie cin musisz zawsze poda� operator pobierania ze strumienia wej�ciowego >> i nazw� zmiennej. Zapis cin >> nazwa_zmiennej; oznacza ˙w C++ : pobierz dane ze strumienia wej�ciowego i umie�� w zmiennej o nazwie "nazwa_zmiennej".Te informacje, które zostan ˙wczytane, C++ przechowuje w przgródce oznaczonej nazw , któr nadajesz zmiennej. Oto program przykładowy ilustruj cy zapami�tywanie danych wprowadzonych przez u�ytkownika z

Page 24: C++ - lekcje

24

klawiatury, wczytanych do programu przy pomocy obiektu cin i zapami�tanych w zadeklarowanej wcze�niej zmiennej x: [P011.CPP] #include <iostream.h> #include <conio.h> void main(void) { int x; cout << "Podaj liczbe calkowita 0 - 1000 do zapamietania: "; cin >> x; cout << "Pamietam! "; cout << "Wielokrotnosci liczby: \n": cout << "x, 2x, 3x: " << x << " " << 2*x << " " << 3*x; cout << "\n ...Nacisnij dowolny klawisz..."; getch(); } Zapis cin >> x oznacza: "pobierz dane ze strumienia danych wej�ciowych i umie�� je w pami�ci przeznaczonej dla zmiennej x". x - to nazwa (identyfikator) zmiennej. Ta nazwa jest stosowana przez komputer do identyfikacji przegródki w pami�ci, w której b�dzie przechowywana liczba wpisana przez u�ytkownika jako odpowied� na zadane pytanie. Kompilator C++ zarezerwuje dla zmiennej x jak � komórk� pami�ci i umie�ci tam wpisan przez Ciebie liczb�. W trakcie pracy kompilator C++ tworzy dla własnego u�ytku tzw. tablic� symboli, któr posługuje si� do rozmieszczania danych w pami�ci. Je�li chcesz, mo�esz sprawdzi� przy pomocy Debuggera (Debug | Inspect) w których bajtach RAM C++ umie�cił Twoj zmienn . [???] Ile miejsca trzeba zarezerwowa�? To, ile miejsca trzeba zarezerwowa� dla danej zmiennej kompilator "wie" dzi�ki Twojej deklaracji, jakiego typu dane b�d przechowywane w miejscu przeznaczonym dla zmiennej. Dla przykładu: - je�li napiszesz int x; Kompilatoer zarezerwuje 2 bajty - je�li napiszesz float y; Kompilatoer zarezerwuje 4 bajty itp...(szczegóły - patrz ni�ej). Zwykle nie musisz si� przejmowa� tym, w którym miejscu kompilator rozmie�cił Twoje dane. Wszystkie czynno�ci C++ wykona automatycznie. Aby jednak wszystko przebiegało poprawnie - zanim zastosujesz jak kolwiek zmienn w swoim programie - musisz ZADEKLAROWA� ZMIENN. Deklaracja zmiennej to informacja dla kompilatora, ile i jakich zmiennych b�dziemy stosowa� w programie. Deklaracja zawiera nie tylko nazw� zmiennej, ale równie� typ warto�ci, jakie ta zmienna mo�e przybiera�. Przykładem deklaracji jest wiersz: int x; Słowo kluczowe int okre�la typ danych. Tu oznacza to, �e zmienna x mo�e przechowywa� jako warto�ci liczby całkowite (ang. INTeger - całkowity) o wielko�ci zawartej w przedziale - 32768...+32767. Po okre�leniu typu danych nast�puje w deklaracji nazwa zmiennej i �rednik. [S] Variable Declaration - Dekaracja Zmiennej. Deklaracja zmiennej w C++ to okre�lenie typu warto�ci zmiennej i podanie nazwy zmiennej. Zwró� uwag� w przykładowym programie, �e kieruj c kolejno dane do strumienia wyj�ciwego cout mo�emy je poustawia� w tzw. ła�cuch (ang. chain). Przesyłanie danych do obiektu cout operatorem << jest bardzo elastyczne. Wysyłamy na ekran zarówno tekst jak i liczb� - bie� c warto�� zmiennej x oraz wyniki obliczenia warto�ci wyra�e� ( 2*x i 3*x). Posługuj c si� ł czonym w "ła�cuch" operatorem << mo�na wyprowadza� na ekran wiersz zbudowany z ró�nych elementów. Operator przesyłania danych do strumienia wyj�ciowego << (ang. insertor - dosł. - operator wstawiania) powoduje przesłanie do obiektu cout kolejno wszystkich (ró�nego typu) elementów. Zwró� uwag� na u�ycie znaku \n na pocz tku nowego wiersza, na ko�cu wiersza tekstu (mo�na go

zastosowa� nawet w �rodku wiersza tekstu - sprawd�). Zwró� uwag� w jaki sposób C++ rozpoznaje ró�nic� pomi�dzy: - ła�cuchem znaków - napisem (napis powinien by� podany tak): cout << "x, 2x, 3x"; - warto�ci zmiennej: cout << x; Wida� tu wyra�nie, dlaczego znak cudzysłowu jest dla kompilatora istotny. Je�li pominiemy cudzysłów, C++ b�dzie próbował zinterpretowa� liter� (tekst) jako nazw� zmiennej a nie jako napis. RODZAJE ZMIENNYCH: ZMIENNE NUMERYCZNE I ZMIENNE TEKSTOWE. Zmienne mog w C++ by� bardzo elastyczne. Dokładnie rzecz bior c, zmienne mog by�: RÓNYCH TYPÓW - mog by� liczbami, mog tak�e by� tekstami. Uruchom program jeszcze raz i zamiast liczby naci�nij w odpowiedzi na pytanie klawisz z liter . Program wydrukuje jakie� bzdury. Dzieje si� tak dlatago, �e program oczekuje podania liczby i zakłada, �e wprowadzone przez u�ytkownika dane s liczb . [???] A je�li u�ytkownik nie czyta uwa�nie??? C++ zakłada, �e u�ytkownik wie co robi gdy podaje warto�� zmiennej. Je�li wprowadzone zostan dane niewła�ciwego typu - C++ nie przerywa działania programu i nie ostrzega przed niebezpiecze�stwem bł�du. Sam dokonuje tzw. konwersji typów - tzn. przekształca dane na warto�� typu zgodnego z zadeklarowanym w programie typem zmiennej. To programista musi dopilnowa�, by pobrane od u�ytkownika dane okazały si� warto�ci odpowiedniego, oczekiwanego przez program typu, lub przewidzie� w programie sposób obsługi sytuacji bł�dnych. Mo�na utworzy� zmienn przeznaczon do przechowywania w pami�ci tekstu - napisu. Aby to zrobi� musimy zadeklarowa� co� jako�ciowo nowego tzw. TABLIC� ZNAKOW (ang. character array). Jest to nazwa, przy pomocy której komputer lokalizuje w pami�ci zbiór znaków. Aby zadeklarowa� zmienn (tablic�) znakow w C++ musimy zacz � od słowa kluczowego char (ang. CHARacter - znak). Nast�pnie podajemy nazw� zmiennej a po nazwie w nawiasach kwadratowych ilo�� znaków, z których mo�e składa� si� zmienny tekst, który zamierzamy przechowywa� w pami�ci pod t nazw . W programie poni�ej zmienna x nie jest ju� miejscem w pami�ci słu� cym do przechowywania pojedynczej liczby. Tym razem nazwa (identyfikator zmiennej) x oznacza tablic� znakow , w której mo�na przechowywa� tekst o długo�ci do 20 znaków. W C++ ostatnim znakiem w ła�cuchu znakowym (tek�cie) b d� w tablicy znakowej zwykle jest tzw. NULL CHARACTER - niewidoczny znak o kodzie ASCII 0 (zero). W C++ znak ten podaje si� przy pomocy szyfru '\0'. Przy pomocy tego znaku C++ odnajduje koniec tekstu, ła�cucha znaków, b d� koniec tablicy znakowej. Tak wi�c w tablicy x[20] w rzeczywisto�ci mo�na przechowa� najwy�ej 19 dowolnych znaków plus na ko�cu obowi zkowy NULL (wartownik). [P012.CPP] #include <conio.h> #include <iostream.h> void main(void) { char x[20]; //<---- deklaracja tablicy znakowej. clrscr(); cout << "Podaj mi swoje imie: : "; cin >> x; cout << "\nNazywasz sie " << x << ", ladne imie!\n"; cout << "...Nacisnij dowolny klawisz..."; getch(); } [Z] 1. Spróbuj w przykładowych programach z poprzednich lekcji zast pi� funkcje obiektami - strumieniami We/Wy:

Page 25: C++ - lekcje

25

printf() - cout << scanf() - cin >> 2. Spróbuj napisa� program zawieraj cy i funkcje i obiekty. Czy program pracuje bezkonfliktowo? Pami�taj o doł czeniu odpowiednich plików nagłówkowych. LEKCJA 9: O SPOSOBACH ODWOŁYWANIA SI� DO DANYCH. ________________________________________________________________ W trakcie tej lekcji poznasz: * sposoby wyprowadzania napisów w ró�nych kolorach * sposoby zapami�tywania tekstów * sposoby odwoływania si� do danyc i zmiennych przy pomocy ich nazw - identyfikatorów. ________________________________________________________________ Mo�emy teraz po�wi�ci� chwil� na zagadnienie kolorów, które pojawiaj si� na monitorze. Po uruchomieniu program przykładowy poni�ej wygeneruje krótki d�wi�k i zapyta o imi�. Po wpisaniu imienia program zapiszczy jeszcze raz i zapyta o nazwisko. Po wpisaniu nazwiska program zmieni kolor na ekranie monitora i wypisze komunikat kolorowymi literami. Ró�ne kolory zobaczysz oczywi�cie tylko wtedy, gdy masz kolorowy monitor. Dla popularnego zestawu VGA mono b�d to ró�ne odcienie szaro�ci. Tekst powinien zmienia� kolor i "miga�" (ang. - blinking text). [P012.CPP] #include <conio.h> #include <iostream.h> main() { char imie[20]; char nazwisko[20]; clrscr(); cout << "\aPodaj imie: "; cin >> imie; cout << "\aPodaj nazwisko: "; cin >> nazwisko; cout << '\n' << imie << ' ' << nazwisko << '\n'; textcolor(4+128); cprintf("\nPan(i), %s %s? Bardzo mi milo!", imie, nazwisko); getch(); cout << '\a'; return 0; } Wyja�nijmy kilka szczegółów technicznych: cout << "\aPodaj nazwisko? "; /* \a to kod pisku gło�niczka (beep) */ cin >> nazwisko; textcolor(4+128); <---- funkcja zmienia kolor tekstu cprintf("\nPan(i), %s %s? Bardzo mi milo!", imie, nazwisko); � � �___ tu funkcja wstawi "string" nazwisko | |________ a tu wstawi "string" imie |_________ funkcja wyprowadza tekst na ekran w kolorach (cprintf = Color PRINTing Function) Operator >> pobiera ze strumienia danych wej�ciowych cin wpisane przez Ciebie imi� i zapisuje ten tekst do tablicy znakowej imie[20]. Po wypisaniu na ekranie nast�pnego pytania nast�puje pobranie drugiego ła�cucha znaków (ang. string) wpisanego przez Ciebie jako odpowied� na pytanie o nazwisko i umieszczenie tego ła�cucha w tablicy znakowej nazwisko[]. Wywołana nast�pnie funkcja textcolor() powoduje zmian� roboczego koloru wyprowadzanego tekstu. Tekst nie tylko zmieni kolor, lecz tak�e b�dzie "miga�" (blink). Funkcja cprintf() wyprowadza na ekran ko�cowy napis. Funkcja cprintf() to Color PRINTing Function - funkcja drukowania w kolorze. Funkcja textcolor() pozwala na zmian� koloru tekstu wyprowadzanego na monitor. Mo�na przy pomocy tej funkcji tak�e "zmusi�" tekst do migotania. Aby funkcja zadziałała - musimy

przekaza� jej ARGUMENT. Argument funkcji to numer koloru. Zwró� jednak uwag�, �e zamiast prostego, zrozumiałego zapisu: textcolor(4); /* 4 oznacza kolor czerwony */ mamy w programie podany argument w postaci wyra�enia (sumy dwu liczb): textcolor(4+128); // to samo, co: textcolor(132); Wbrew pierwszemu mylnemu wra�eniu te dwie liczby stanowi jeden argument funkcji. C++ najpierw dokona dodawania 4+128 a dopiero uzyskany wynik 132 przeka�e funkcji textcolor jako jej argument (parametr). Liczba 4 to kod koloru czerwonego, a zwi�kszenie kodu koloru o 128 powoduje, �e tekst b�dzie migał. Numery (kody) kolorów, które mo�esz przekaza� jako argumenty funkcji textcolor() podano w tabeli poni�ej. Je�li tekst ma miga� - nale�y doda� 128 do numeru odpowiedniego koloru. Kod koloru przekazywany do funkcji textcolor(). ________________________________________________________________ Kod Kolor (ang) Kolor (pol) Stała n (przykład) ________________________________________________________________ 0 Black Czarny BLACK 1 Blue Niebieski BLUE 2 Green Zielony GREEN 3 Cyan Morski CYAN 4 Red Czerwony 5 Magenta Fioletowy 6 Brown Br zowy 7 White Biały 8 Gray Szary 9 Light blue Jasno niebieski 10 Light green Jasno zielony 11 Light cyan Morski - jasny 12 Light red Jasno czerwony 13 Light magenta Jasno fio;etowy (fiol-ró�owy) 14 Yellow ółty 15 Bright white Biały rozja�niony 128 + n Blinking Migaj cy BLINK ________________________________________________________________ [!!!]UWAGA: ________________________________________________________________ * W pliku CONIO.H s predefiniowane stałe (skrajna prawa kolumna - przykłady), które mo�esz stosowa� jako argumenty funkcji. Kolor tła mo�esz ustawi� np. przy pomocy funkcji textbackground() - np. textbacground(RED); * Manipuluj c kolorem tekstu musisz pami�ta�, �e je�li kolor napisu: - foreground color, text color i kolor tła: - background color oka� si� identyczne - tekst zrobi si� NIEWIDOCZNY. Je�li ka�esz komputerowi pisa� czerwonymi literami na czerwonym tle - komputer wykona rozkaz. Jednak�e wi�kszo�� ludzi ma kłopoty z odczytywaniem czarnego tekstu na czarnym tle. Jest to jednak metoda stosowana czasem w praktyce programowania do kasowania tekstów i elementów graficznych na ekranie. ________________________________________________________________ Powołuj c si� na nasze wcze�niejsze porównanie (NIE TRAKTUJ GO ZBYT DOSŁOWNIE!),zajmiemy si� teraz czym�, co troch� przypomina rzeczowniki w normalnym j�zyku. O IDENTYFIKATORACH - DOKŁADNIEJ. Identyfikatorami (nazwami) mog by� słowa, a dokładniej ci gi liter, cyfr i znaków podkre�lenia rozpoczynaj ce si� od litery lub znaku podkre�lenia (_). Za wyj tkiem słów kluczowych, (które to słowa kluczowe - MUSZ ZAWSZE BY� PISANE MAŁYMI LITERAMI) mo�na stosowa� i małe i du�e litery. Litery du�e i małe s rozró�niane. Przykład:

Page 26: C++ - lekcje

26

[P013.CPP] #include <stdio.h> #include <conio.h> float PI = 3.14159; <-- stała PI float r; <-- zmienna r int main(void) { clrscr(); printf("Podaj promien ?\n"); scanf("%f", &r); printf("\nPole wynosi P = %f", PI*r*r ); getch(); return 0; } * U�yte w programie słowa kluczowe: int, float, void, return. * Identyfikatory - nazwy funkcji (zastrze�one): main, printf, scanf, getch, clrscr. - nazwy zmiennych (dowolne): PI, r. * Dyrektywy preprocesora: # include Zwró� uwag�, �e w wierszu: float PI = 3.14159; nie tylko DEKLARUJEMY, zmienn PI jako zmiennoprzecinkow , ale tak�e od razu nadajemy liczbie PI jej warto��. Jest to tzw. ZAINICJOWANIE zmiennej. [Z] ________________________________________________________________ 1. Uruchom program przykładowy. Spróbuj zamieni� identyfikator zmiennej PI na pisane małymi literami pi. Powinien wyst pi� bł d. ________________________________________________________________ Dla porównania ten sam program w wersji obiektowo-strumieniowej: [P013-1.CPP] #include <stdio.h> #include <conio.h> const float PI = 3.14159; <-- stała PI float r; <-- zmienna r int main(void) { clrscr(); cout << "Podaj promien ?\n"; cin >> r; cout << "\nPole wynosi P = " << PI*r*r; getch(); return 0; } LITERAŁY. Literałem nazywamy reprezentuj cy dan NAPIS, na podstawie którego mo�na jednoznacznie zidentyfikowa� dan , jej typ, warto�� i inne atrybuty. W j�zyku C++ literałami mog by�: * ła�cuchy znaków - np. "Napis"; * pojedyncze znaki - np. 'X', '?'; * liczby - np. 255, 3.14 [!!!] Uwaga: BARDZO WANE !!! ________________________________________________________________ * Rol� przecinka dziesi�tnego spełnia kropka. Zapis Pi=3,14 jest nieprawidłowy. * Próba zastosowania przecinka w tej roli SPOWODUJE BŁ�DY ! ________________________________________________________________ Liczby całkowite mog by�:

* Dziesi�tne (przyjmowane domy�lnie - default); * Ósemkowe - zapisywane z zerem na pocz tku: 017 = 1*8 + 7 = 15 (dziesi�tnie); * Szesnastkowe - zapisywane z 0x na pocz tku: 0x17 = 1*16 + 7 = 23 (dziesi�tnie); 0x100 = 16^2 + 0 + 0 = 256 . Liczby rzeczywiste mog zawiera� cz��� ułamkow lub by� zapisane w postaci wykładniczej (ang. scientific format) z liter "e" poprzedzaj c wykładnik pot�gi. Przykład: Zapis liczby��������Warto�� dziesi�tna .0123���������������0.0123 123e4���������������123 * 10^4 = 1 230 000 1.23e3���������������1.23 * 10^3 = 1230 123e-4��������������0.0123 Literały składaj ce si� z pojedynczych znaków maj jedn z trzech postaci: * 'z' - gdzie z oznacza znak "we własnej osobie"; * '\n' - symboliczne oznaczenie znaku specjalnego - np. steruj cego - tu: znak nowej linii; * '\13' - nr znaku w kodzie ASCII. UWAGA: '\24' - kod Ósemkowy ! (dziesi�tnie 20) '\x24' - kod SZESNASTKOWY ! (dziesi�tnie 36) [S]��SLASH, BACKSLASH. �����Kreska "/" nazywa si� SLASH (czyt. "slasz") - łamane, uko�nik zwykły. Kreska "\" nazywa si� BACKSLASH (czyt. "bekslasz") - uko�nik odwrotny. Uzupełnimy teraz list� symboli znaków z poprzedniej lekcji. Znak ����ÓSEMKOWO��ASCII (10)�����ZNACZENIE \a�������'\7'������7��������������- sygn. d�wi�kowy BEL \n�������'\12'�����10�������������- nowy wiersz LF \t�������'\11'�����9��������������- tabulacja pozioma HT \v������ '\13'�����11�������������- tabulacja pionowa VT \b�������'\10'�����8��������������- cofni�cie kursora o 1 znak \r�������'\15'�����13�������������- powrót do pocz tku linii CR \f�������'\14'�����12�������������- nowa strona (form feed) FF \\�������'\134'����92�������������- poprostu znak backslash "\" \'�������'\47'�����39�������������- apostrof "'" \"�������'\42'�����34�������������- cudzysłów (") \0�������'\0'������0��������������- NULL (znak pusty) Komputer przechowuje znak w swojej pami�ci jako "krótk ", bo zajmuj c tylko jeden bajt liczb� całkowit (kod ASCII znaku). Na tych liczbach wolno Ci wykonywa� operacje arytmetyczne ! (Od czego mamy komputer?) Przekonaj si� o tym uruchamiaj c nast�puj cy program. [P014.CPP] # include <stdio.h> //prototypy printf() i scanf() # include <conio.h> //prototypy clrscr() i getch() int liczba; //deklaracja zmiennej "liczba" int main(void) { clrscr(); printf("Wydrukuje A jako \nLiteral znakowy:\tKod ASCII:\n"); printf("%c", 'A'); printf("\t\t\t\t%d", 'A'); printf("\nPodaj mi liczbe ? "); scanf("%d", &liczba); printf("\n%c\t\t\t\t%d\n", 'A'+liczba, 'A'+liczba); scanf("%d", &liczba); printf("\n%c\t\t\t\t%d", 'A'+liczba, 'A'+liczba); getch(); return 0; } Uruchom program kilkakrotnie podaj c ró�ne liczby całkowite z zakresu od 1 do 100.

Page 27: C++ - lekcje

27

Przyjrzyj si� sposobowi formatowania wyj�cia: %c, %d, \t, \n Je�li pami�tasz, �e kody ASCII kolejnych liter A,B,C... i kolejnych cyfr 1, 2, 3 s kolejnymi liczbami, to zauwa�, �e wyra�enia: '5' + 1 = '6' oraz 'A' + 2 = 'C' (czytaj: kod ASCII "5" + 1 = kod ASCII "6") s poprawne. [!!!]Jak sprawdzi� kod ASCII znaku? ________________________________________________________________ Mo�na oczyw�cie nauczy� si� tabeli kodów ASCII na pami�� (dla pocz tkowych i najwa�niejszych stronic kodowych - przede wszystkom od 0 do 852). Dla hobbystów - stronica kodowa 1250 i 1252 te� czasem si� przydaje. (to oczywi�cie �art - autor nie zna ani jednego faceta o tak genialnej pami�ci) Mo�na skorzysta� z edytora programu Norton Commander. W trybie Edit [F4] po wskazaniu kursorem znaku w górnym wierszu po prawej stronie zostanie wy�wietlony jego kod ASCII. ________________________________________________________________ CZY PROGRAM NIE MÓGŁBY CHODZI� W KÓŁKO? Twoja intuicja programisty z pewno�ci podpowiada Ci, �e gdyby zmusi� komputer do pracy w p�tli, to nie musiałby� przykładowych programów uruchamia� wielokrotnie. Spróbujmy nakaza� programowi przykładowemu chodzi� "w kółko". To proste - dodamy do programu: * na ko�cu rozkaz skoku bezwarunkowego goto (id� do...), * a �eby wiedział dok d ma sobie i�� - na pocz tku programu zaznaczymy miejsce przy pomocy umownego znaku - ETYKIETY. Zwró� uwag�, �e pisz c pliki wsadowe typu *.BAT w j�zyku BPL (Batch Programming Language - j�zyk programowania wsadowego) stawiasz dwukropek zawsze na pocz tku etykiety: :ETYKIETA (BPL) a w j�zyku C++ zawsze na ko�cu etykiety: ETYKIETA: (C/C++) Przyst�pujemy do opracowania programu. [P015.CPP] # include <stdio.h> short int liczba; int main(void) { clrscr(); printf("Wydrukuje A jako \nLiteral znakowy:\tKod ASCII:\n"); printf("%c", 'A'); printf("\t\t\t\t%d", 'A'); etykieta: printf("\npodaj mi liczbe ? "); scanf("%d", &liczba); printf("\n%c\t\t\t\t%d\n", 'A'+liczba, 'A'+liczba); goto etykieta; return 0; } Skompiluj program do wersji *.EXE: Compile | Make (rozkazem Make EXE file z menu Compile). Musisz nacisn � nast�puj ce klawisze: [Alt]-[C], [M]. (lub [F9]) * Je�li wyst piły bł�dy, popraw i powtórz prób� kompilacji. * Uruchom program [Alt]-[R], [R] (lub [Ctrl]-[F9]). * Podaj kilka liczb: np. 1,2,5,7,8 itp. * Przerwij działanie programu naciskaj c kombinacj� klawiszy [Ctrl]+[Break] lub [Ctrl]+[C]. * Sprawd�, jaki jest katalog wyj�ciowy kompilatora.

- Rozwi� menu Options [Alt]-[O], - Otwórz okienko Directories... [D], - Sprawd� zawarto�� okienka tekstowego Output Directory. Teraz wiesz ju� gdzie szuka� swojego programu w wersji *.EXE. - Uruchom program poza �rodowiskiem IDE. - Sprawd� reakcj� programu na klawisze: [Esc], [Ctrl]-[C], [Ctrl]-[Break]. Uruchom powtórnie kompilator C++ i załaduj program rozkazem: BC A:\GOTOTEST.CPP Wykonaj od nowa kompilacj� programu [F9]. [???] ... is up to date... ________________________________________________________________ Je�li C++ nie zechce powtórzy� kompilacji i odpowie Ci: ��������������������Making A:\GOTOTEST.CPP ���������������is up to date (Program w takiej wersji ju� skompilowałem, wi�cej nie b�d�!) nie przejmuj si�. Dokonaj jakiejkolwiek pozornej zmiany w programie (np. dodaj spacj� lub pusty wiersz w dowolnym miejscu). Takich pozornych zmian wystarczy by oszuka� C++. C++ nie jest na tyle inteligentny, by rozró�nia� zmiany rzeczywiste w pliku �ródłowym od pozornych. ________________________________________________________________ Powtórz kompilacj� programu. Nie musisz uruchamia� programu. Zwró� uwag� tym razem na pojawiaj ce si� w okienku komunikatów ostrze�enie: Warning: A:\GOTOTEST.CPP 14: Unreachable code in function main. (Uwaga: Kod programu zawiera takie rozkazy, które nigdy nie zostan wykonane inaczej - "s nieosi galne"). O co chodzi? Przyjrzyj si� tekstowi programu. Nawet je�li po rozkazie skoku bezwarunkowego: goto etykieta; dopiszesz jakikolwiek inny rozkaz, to program nigdy tego rozkazu nie wykona. Wła�nie o to chodzi. Program nie mo�e nawet nigdy wykona� rozkazu "return 0", który dodali�my "z przyzwyczajenia". P�tla programowa powinna by� wykonywana w niesko�czono��. Taka p�tla nazywa si� p�tl niesko�czon (ang. infinite loop). Mimo to i w �rodowisku IDE (typowy komunikat: User break) i w �rodowisku DOS t� p�tl� uda Ci si� przerwa�. Kto wobec tego przerwał działanie Twojego programu? Niesko�czon p�tl� programow przerwał DOS. Program zwrócił si� do systemu DOS, a konkretnie do której� z DOS'owskich funkcji obsługi WEJ�CIA/WYJ�CIA i to DOS wykrył, �e przycisn łe� klawisze [Ctrl]-[C] i przerwał obsług� Twojego programu. Nast�pnie DOS "wyrzucił" twój program z pami�ci operacyjnej komputera i zgłosił gotowo�� do wykonania dalszych Twoich polece� - swoim znakiem zach�ty C:\>_ lub A:\>_. Spróbujmy wykona� taki sam "face lifting" i innych programów przykładowych, dodaj c do nich najprostsz p�tl�. Zanim jednak omówimy szczegóły techniczne p�tli programowych w C++ rozwa�my prosty przykład. Wyobra�my sobie, �e chcemy wydrukowa� na ekranie kolejne liczby całkowite od 2 do np. 10. Program powinien zatem liczy� ilo�� wykonanych p�tli, b d� sprawdza�, czy liczba przeznaczona do drukowania nie stała si� zbyt du�a. W C++ do takich konstrukcji u�ywa si� kilku bardzo wa�nych słów kluczowych: [S] some important keywords - kilka wa�nych słów kluczowych ________________________________________________________________ for - dla (znaczenie jak w Pascalu i BASICu) while - dopóki do - wykonuj if - je�eli

Page 28: C++ - lekcje

28

break - przerwij wykonywanie p�tli continue - kontynuuj p�telkowanie goto - skocz do wskazanej etykiety ________________________________________________________________ Nasz program mógłby przy zastosowaniu tych słów zosta� napisany np. tak: [LOOP-1] #include <iostream.h> void main() { int x = 2; petla: cout << x << '\n'; x = x + 1; if (x < 11) goto petla; } Mo�emy zastosowa� rozkaz goto w postaci skoku bezwarunkowego, a p�telkowanie przerwa� rozkazem break: [LOOP-2] #include <iostream.h> void main() { int x = 2; petla: cout << x << '\n'; x = x + 1; if(x > 10) break; goto petla; } Mo�emy zastosowa� p�tl� typu for: [LOOP-3] #include <iostream.h> int main(void) { for(int x = 2; x < 11; x = x + 1) { cout << x << '\n'; } return 0; } Mo�emy zastosowa� p�tl� typu while: [LOOP-4] #include <iostream.h> int main(void) { int x = 2; while (x < 11) { cout << x << '\n'; x = x + 1; } return 0; } Mo�emy tak�e zastosowa� p�tl� typu do-while: [LOOP-5] #include <iostream.h> int main(void) { int x = 2; do { cout << x << '\n'; x = x + 1; }while (x < 11); return 0; } Mo�emy wreszcie nie precyzowa� warunków p�telkowania w nagłówku

p�tki for, lecz przerwa� p�tl� w jej wn�trzu (po osi gni�ciu okre�lonego stanu) przy pomocy rozkazu break: [LOOP-6] #include <iostream.h> int main(void) { for(;;) { cout << x << '\n'; x++; if( x > 10) break; } return 0; } Wszytkie te p�tle (sprawd�!) b�d działa� tak samo. Spróbuj przy ich pomocy, zanim przejdziesz dalej, wydrukowa� np. liczby od 10 do 100 i wykonaj jeszcze kilka innych eksperymentów. Dokładniejszy opis znajdziesz w dalszej cz��ci ksi �ki, ale przykład - to przykład. Wró�my teraz do "face-liftingu" naszych poprzednich programów. Poniewa� nie mo�emy sprecyzowa� �adnych warunków, ka�emy programowi przykładowemu wykonywa� p�tl� bezwarunkowo. Wpisz tekst programu: [P016.CPP] // Przyklad FACELIFT.CPP // Program przykladowy 10na16.CPP / 16na10.CPP FACE LIFTING. # include <stdio.h> int liczba; int main() { clrscr(); printf("Kropka = KONIEC \n"); for(;;) { printf("Podaj liczbe dziesietna calkowita ? \n"); scanf("%d", &liczba); printf("Szesnastkowo to wynosi:\n"); printf("%X",liczba); getch(); printf("Podaj liczbe SZESNASTKOWA-np.DF- DUZE LITERY: \n"); scanf("%X", &liczba); printf("%s","Dziesietnie to wynosi: "); printf("%d",liczba); if(getch() == '.') break; } return 0; } - Uruchom program Run, Run. - Dla przetestowania działania programu: * podaj kolejno liczby o ró�nej długo�ci 1, 2, 3, 4, 5, 6 cyfrowe; * zwró� uwag�, czy program przetwarza poprawnie liczby dowolnej długo�ci? - Przerwij program naciskaj c klawisz z kropk [.] - Zapisz program na dysk [F2]. - Wyjd� z IDE naciskaj c klawisze [Alt]-[X]. Zwró� uwag� na dziwny wiersz: if(getch() == '.') break; C++ wykona go w nast�puj cej kolejno�ci: 1) - wywoła funkcj� getch(), poczeka na naci�ni�cie klawisza i wczyta znak z klawiatury: getch() 2) - sprawdzi, czy znak był kropk : (getch() == '.') ? 3) - je�li TAK - wykona rozkaz break i przerwie p�tl�, if(getch() == '.') break; - je�li NIE - nie zrobi nic i p�tla "potoczy si�" dalej.

Page 29: C++ - lekcje

29

if(getch() != '.') ...--> printf("Podaj liczbe dziesietna... [Z] ________________________________________________________________ 2. Opracuj program pobieraj cy znak z klawiatury i podaj cy w odpowiedzi kod ASCII pobranego znaku dziesi�tnie. 3. Opracuj program pobieraj cy liczb� dziesi�tn i podaj cy w odpowiedzi: * kod ósemkowy, * kod szesnastkowy, * znak o zadanym ** dziesi�tnie ** szesnastkowo kodzie ASCII. _______________________________________________________________ LEKCJA 10 Jakie operatory stosuje C++. _______________________________________________________________ Podczas tej lekcji: * Poznasz operatory j�zyka C++. * Przetestujesz działanie niektórych operatorów. * Dowiesz si� wi�cej o deklarowaniu i inicjowaniu zmiennych. _______________________________________________________________ Słów kluczowych jest w j�zyku C++ stosunkowo niewiele, za to operatorów wyra�nie wi�cej ni� np. w Basicu. Z kilku operatorów ju� korzystałe� w swoich programach. pełn list� operatorów wraz z krótkim wyja�nieniem przedstawiam poni�ej. Operatory C++ s podzielone na 16 grup i mo�na je scharakteryzowa�: * priorytetem ** najwy�szy priorytet ma grupa 1 a najni�szy grupa 16 - przecinek, np. mno�enie ma wy�szy priorytet ni� dodawanie; ** wewn trz ka�dej z 16 grup priorytet operatorów jest równy; * ł czno�ci (wi zaniem). [S!] Precedence - kolejno��, priorytet. ________________________________________________________________ Dwie cechy opertorów C++ priorytet i ł czno�� decyduj o sposobie obliczania warto�ci wyra�e�. Precedence - kolejno��, priorytet. Associativity - asocjatywno��, ł czno��, wi zanie. Operator jest ł czny lewo/prawo-stronnie, je�li w wyra�eniu zawieraj cym na tym samym poziomie hierarchii nawiasów min. dwa identyczne operatory najpierw jest wykonywany operator lewy/prawy. Operator jest ł czny, je�li kolejno�� wykonania nie wpływa na wynik. ________________________________________________________________ Przykład: a+b+c+d = (a+d)+(c+b) [S] ________________________________________________________________ ASSIGN(ment) - Przypisanie. EQAL(ity) - Równy, odpowiadaj cy. BITWISE - bit po bicie (bitowo). REFERENCE - odwołanie do..., powołanie si� na..., wskazanie na... . Funkcje logiczne: OR - LUB - suma logiczna (alternatywa). AND - I - iloczyn logiczny. XOR (eXclusive OR) - ALBO - alternatywa wył czaj ca. NOT - NIE - negacja logiczna. ________________________________________________________________ Oznaczenia ł czno�ci przyj�te w Tabeli: {L->R} ���(Left to Right) z lewa na prawo. {L<<-R} ��(Right to Left) z prawa na lewo. Lista operatorów j�zyka C++. ________________________________________________________________ Kategoria�| Operator�����| ���Co robi / jak działa ----------|--------------|-------------------------------------- 1. Highest| ()�����������| * ogranicza wyra�enia, (Najwy�szy|Parentheses | * izoluje wyra�enia warunkowe, priorytet)|��������������| * wskazuje na wywołanie funkcji,

{L->R}���|��������������| grupuje argumenty funkcji. ����������|--------------|-------------------------------------- ����������| []�����������| zawarto�� jedno- lub wielowymiarowych ����������|Brackets | tablic ����������|--------------|-------------------------------------- ����������| . |(direct component selector) ����������| -> |(indirect, or pointer, selection) ����������|��������������| Bezpo�rednie lub po�rednie wskazanie | | elementu unii b d� struktury. |--------------|-------------------------------------- | :: | Operator specyficzny dla C++. | | Pozwala na dost�p do nazw GLOBALNYCH, | | nawet je�li zostały "przysłoni�te" | | przez LOKALNE. ----------|--------------|-------------------------------------- 2. �������| ! �����������| Negacja logiczna (NOT) Jednoar-�|--------------|------------------------------------ gumentowe | ~ | Zamiana na kod KOMPLEMENTARNY bit po (Unary) | | bicie. Dotyczy liczb typu int. {L<<-R} |--------------|-------------------------------------- | + | Bez zmiany znaku (Unary plus) |--------------|-------------------------------------- | - | Zmienia znak liczby / wyra�enia | | (Unary minus) |--------------|-------------------------------------- | ++ | PREinkrementacja/POSTinkrementacja |--------------|-------------------------------------- | -- | PRE/POSTdekrementacja |--------------|-------------------------------------- | & | Operator adresu(Referencing operator) |--------------|-------------------------------------- | * | Operator wskazania | | (Dereferencing operator) |--------------|-------------------------------------- | sizeof | Zwraca wielko�� argumentu w bajtach |--------------|-------------------------------------- | new | Dynamiczne zarz dzanie pami�ci : | delete | new - przydziela pami��, | | delete - likwiduje przydział pami�ci ----------|--------------|-------------------------------------- 3. Multi- | * | Mno�enie (UWAGA: Druga rola "*") plikatywne|--------------|-------------------------------------- {L->R} | / | Dzielenie |--------------|-------------------------------------- | % | Reszta z dzielenia (modulo) ----------|--------------|-------------------------------------- 4. Dost�pu| .* | Operatory specyficzne dla C++. (Member |(dereference) | Skasowanie bezpo�redniego wskazania access) | | na członka klasy (Class Member). {L->R} |--------------|-------------------------------------- | ->* | Skasowanie po�redniego wskazania typu objektowe | | "wska�nik do wska�nika" ----------|--------------|-------------------------------------- 5. Addy - | + | Dodawanie dwuargumentowe. tywne |--------------|-------------------------------------- {L->R} | - | Odejmowanie dwuargumentowe. ----------|--------------|-------------------------------------- 6. Przesu-| << | Binarne przesuni�cie w lewo. ni�cia |--------------|-------------------------------------- (Shift) | >> | Binarne przesuni�cie w prawo. {L->R} | | (bit po bicie) ----------|--------------|-------------------------------------- 7. Relacji| < | Mniejsze ni�... {L->R} |--------------|-------------------------------------- | > | Wi�ksze ni�.... |--------------|-------------------------------------- | <= | Mniejsze lub równe. |--------------|-------------------------------------- | >= | Wi�ksze lub równe. ----------|--------------|-------------------------------------- 8.Równo�ci| == | Równe (równa si�). {L->R} | != | Nie równe. ----------|--------------|-------------------------------------- 9. | & | AND binarnie (Bitwise AND) {L->R} | | UWAGA: Druga rola "&". ----------|--------------|-------------------------------------- 10. | ^ | XOR binarnie (Alternatywa wył czna). {L->R} | | UWAGA: To nie pot�ga ! ----------|--------------|------------------------------------- 11.{L->R} | | | OR binarnie (bit po bicie) ----------|--------------|------------------------------------- 12.{L->R} | && | Iloczyn logiczny (Logical AND). ----------|--------------|-------------------------------------

Page 30: C++ - lekcje

30

13.{L->R} | || | Suma logiczna (Logical OR). ----------|--------------|-------------------------------------- 14. Oper. | ?: | Zapis a ? x : y oznacza: Warunkowy | | "if a==TRUE then x else y" Conditional | gdzie TRUE to logiczna PRAWDA "1". {L<<-R} | | ----------|--------------|-------------------------------------- 15. Przy- | = | Przypisz warto�� (jak := w Pascalu) pisania |--------------|-------------------------------------- {L<<-R} | *= | Przypisz iloczyn. Zapis X*=7 | | oznacza: X=X*7 (o 1 bajt krócej!). |--------------|-------------------------------------- | /= | Przypisz iloraz. |--------------|-------------------------------------- | %= | Przypisz reszt� z dzielenia. |--------------|-------------------------------------- | += | Przypisz sum� X+=2 oznacza "X:=X+2" |--------------|-------------------------------------- | -= | Przypisz ró�nic� X-=5 ozn. "X:=X-5" |--------------|-------------------------------------- | &= | Przypisz iloczyn binarny ( Bitwise | | AND) | | bit po bicie. |--------------|-------------------------------------- | ^= | Przypisz XOR bit po bicie. |--------------|-------------------------------------- | |= | Przypisz sum� log. bit po bicie. |--------------|-------------------------------------- | <<= | Przypisz wynik przesuni�cia o jeden | | bit w lewo. |--------------|-------------------------------------- | >>= | j. w. o jeden bit w prawo. ----------|--------------|-------------------------------------- 16. Prze- | , | Oddziela elementy na li�cie argu - cinek | | mentów funkcji, (Comma) | | Stosowany w specjalnych wyra�eniach {L->R} | | tzw. "Comma Expression". ----------|--------------|------------------------------------- UWAGI: * Operatory # i ## stosuje si� tylko w PREPROCESORZE. * Operatory << i >> mog w C++ przesyła� tekst do obiektów cin i cout dzi�ki tzw. Overloadingowi (rozbudowie, przeci �eniu) operatorów. Takiego rozszerzenia ich działania dokonali ju� programi�ci producenta w pliku nagłówkowym IOSTREAM.H> Gdyby okazało si�, �e oferowane przez powy�szy zestaw operatory nie wystarczaj Ci lub niezbyt odpowiadaj , C++ pozwala na tzw. OVERLOADING, czyli przypisanie operatorom innego, wybranego przez u�ytkownika działania. Mo�na wi�c z operatorami robi� takie same sztuczki jak z identyfikatorami. S dz� jednak, �e ten zestaw nam wystarczy, w ka�dym razie na kilka najbli�szych lekcji. Podobnie, jak pieni dze na bezludnej wyspie, niewiele warta jest wiedza, której nie mo�na zastosowa� praktycznie. Przejd�my wi�c do czynu i przetestujmy działanie niektórych operatorów w praktyce. TEST OPERATORÓW JEDNOARGUMENTOWYCH. Otwórz plik nowego programu: * Open [F3], * Wpisz: A:\UNARY.CPP * Wybierz klawisz [Open] w okienku lub naci�nij [Enter]. Wpisz tekst programu: [P017.CPP ] // UNARY.CPP - operatory jednoargumentowe # include <stdio.h> # include <conio.h> float x; void main(void) { clrscr();

for(;;) { printf("\n Podaj liczbe...\n"); scanf("%f", &x); printf("\n%f\t%f\t%f\n", x, +x, -x ); printf("\n%f", --x ); printf("\t%f", x ); printf("\t%f", ++x); if(getch() = '.') break; }; } Zwró� uwag�, �e po nawiasie zamykaj cym p�tl� nie ma tym razem �adnego rozkazu. Nie wyst pi tak�e ostrze�enie (Warning:) przy kompilacji. Uruchom program Run | Run. Popraw ewentualne bł�dy. Podaj c ró�ne warto�ci liczby x: - dodatnie i ujemne, - całkowite i rzeczywiste, przeanalizuj działanie operatorów. Przerwij program naciskaj c klawisz [.] Zmodyfikuj w programie deklaracj� typu zmiennej X wpisuj c kolejno: - float x; (rzeczywista) - int x; ������(całkowita) - short int x; �����(krótka całkowita) - long int x;�����(długa całkowita) Zwró� uwag�, �e zmiana deklaracji zmiennej bez JEDNOCZESNEJ zmiany formatu w funkcjach scanf() i printf() spowoduje komunikaty o bł�dach. Spróbuj samodzielnie dobra� odpowiednie formaty w funkcjach scanf() i printf(). Spróbuj zastosowa� zamiast funkcji printf() i scanf() strumienie cin i cout. Pami�taj o doł czeniu wła�ciwych plików nagłówkowych. Je�li miałe� kłopot z dobraniem stosownych formatów, nie przejmuj si�. Przyjrzyj si� nast�pnym przykładowym programom. Zajmijmy si� teraz dokładniej INKREMENTACJ, DEKREMENTACJ i OPERATORAMI PRZYPISANIA. 1. Zamknij zb�dne okna na ekranie. Pamui�taj o zapisaniu programów na dyskietk�/dysk w tej wersji, która poprawnie działa lub w ostatniej wersji roboczej. 2. Otwórz plik: ASSIGN.CPP 3. Wpisz tekst programu: [P018.CPP] # include <stdio.h> # include <conio.h> long int x; short int krok; char klawisz; int main() { clrscr(); printf("Test operatora przypisania += \n"); x=0; printf("Podaj KROK ? \n"); scanf("%d",&krok); for(;;) { printf("\n%d\n", x+=krok); printf("[Enter] - dalej [K] - Koniec\n"); klawisz = getch(); if (klawisz=='k'|| klawisz=='K') goto koniec; } koniec: printf("\n Nacisnij dowolny klawisz..."); getch(); return 0; }

Page 31: C++ - lekcje

31

W tym programie ju� sami "r�cznie" sprawdzamy, czy nie pora przerwa� p�tl�. Zamiast u�y� typowej instrukcji break (przerwij) stosujemy nielubiane goto, gdy� jest bardziej uniwersalne i w przeciwie�stwie do break pozwala wyra�nie pokaza� dok d nast�puje skok po przerwaniu p�tli. Zwró� uwag� na nowe elementy w programie: * DEKLARACJE ZMIENNYCH: long int x; (długa, całkowita) short int krok; ����(krótka, całkowita) char klawisz;�����(zmienna znakowa) * INSTRUKCJ� WARUNKOW: if (KLAWISZ=='k'|| KLAWISZ=='K') goto koniec; (JEELI zmienna KLAWISZ równa si� "k" LUB równa si� "K" id� do etykiety "koniec:") * Warunek sprawdzany po słowie if jest uj�ty w nawiasy. * Nadanie warto�ci zmiennej znakowej char klawisz przez funkcj�: klawisz = getch(); 4. Skompiluj program. Popraw ewentualne bł�dy. 5. Uruchom program. Podaj c ró�ne liczby (tylko całkowite!) prze�led� działanie operatora. 6. Zapisz poprawn wersj� programu na dysk/dyskietk� [F2]. 7. Je�li masz ju� do��, wyjd� z TC - [Alt]-[X], je�li nie, pozamykaj tylko zb�dne okna i mo�esz przej�� do zada� do samodzielnego rozwi zania -> [Z]! [Z] ________________________________________________________________ 1. Do programu przykładowego wstaw kolejno ró�ne operatory przypisania: *=, -=, /= itp. Prze�led� działanie operatorów. 2. W programie przykładowym zmie� typ zmiennych: long int x; na float x; short int KROK; float KROK; Przetestuj działanie operatorów w przypadku liczb zmiennoprzecinkowych. 3. Zapisz w j�zyku C++ * negacj� iloczynu logicznego, * sum� logiczn negacji dwu warunków. ________________________________________________________________ TEST OPERATORÓW PRE/POST-INKREMENTACJI. W nast�pnym programie zilustrujemy działanie wszystkich pi�ciu operatorów inkrementacji (dekrementacja to te� inkrementacja tylko w przeciwn stron�). [P019.CPP] # include <stdio.h> # include <conio.h> int b,c,d,e; int i; int STO = 100; void main(void) { clrscr(); printf("Demonstruje dzialanie \n"); printf(" PREinkrementacji POSTinkrementacji"); printf("\nNr����--X������++X������������X--�������X++ \n"); b = c = d = e = STO; for(i=1; i<6; i++) { printf("%d\t%d\t%d\t\t%d\t%d\t\n", i,--b,++c,d--,e++); } getch(); } [S!] PRE / POSTINKREMENTACJA. ________________________________________________________________

INKREMENTACJA oznacza zwi�kszenie liczby o jeden, DEKREMENTACJA oznacza zmniejszenie liczby o jeden. PRE oznacza wykonanie in/de-krementacji przed u�yciem zmiennej, POST - in/de-krementacj� po u�yciu zmiennej. ________________________________________________________________ Działanie mo�esz prze�ledzi� na wydruku, który powinien Ci da� program przykładowy INDEKREM.CPP: Demonstruje dzialanie PREinkrementacji POSTinkrementacji Nr����--X������++X������������X--�������X++ 1 99 101 100 100 2 98 102 99 101 3 97 103 98 102 4 96 104 97 103 5 95 105 96 104 JAK KORZYSTA� Z DEBUGGERA? Uruchom program powtórnie naciskaj c klawisz [F7]. Odpowiada to poleceniu Trace into (wł cz �ledzenie) z menu Run. Prze�ledzimy działanie programu przy pomocy Debuggera. Po wykonaniu kompilacji (lub odst pieniu od kompilacji, je�li nie dokonałe� zmian w programie) pojawił si� na ekranie pasek wyró�nienia wokół funkcji main(), bo to od niej rozpoczyna si� zawsze wykonanie programu. Naci�nij powtórnie [F7]. Pasek przesun ł si� na funkcj� clrscr();. Mign ł na chwil� ekran u�ytkownika, ale na razie nie ma po co tam zagl da�, wi�c wykonamy kolejne kroki. Podam klejno: [Klawisz]-[wiersz]. [F7] - printf("Demonstruj�..."); Zagl damy na ekran u�ytkownika [Alt]-[F5].....[Enter] - wracamy do edytora. [F7],[F7]... doszli�my do wiersza b=c=d=e=STO; Zapraszamy teraz debugger do pracy wydaj c mu polecenie "Wykonaj Inspekcj�" [Alt]-[D] | Inspect. Pojawia si� okienko dialogowe "Inspect". * Wpisz do okienka tekstowego nazw� zmiennej b i naci�nij [Enter]. Pojawiło si� okienko dialogowe "Inspecting b" zawieraj ce fizyczny adres pami�ci RAM, pod którym umieszczono zmienn b i warto�� zmiennej b (zero; instrukcja przypisania nada jej warto�� 100). Naci�nij [Esc]. Okienko znikn�ło. [F7] - for(i=1; i<6; i++); * Naprowad� kursor na zmienn d w tek�cie programu i wykonaj inspekcj� powtórnie [Alt]-[D], [I]. Jak widzisz w okienku zmiennej d została nadana warto�� 100. Naci�nij [Esc]. Dokonamy teraz modyfikacji warto�ci zmiennej przy pomocy polecenia Evaluate and Modify (sprawd� i zmodyfikuj) z menu Debug. * Naci�nij klawisze [Alt]-[D], [E]. Pojawiło si� okienko dialogowe "Evaluate and Modify". W okienku tekstowym "Expression" (wyra�enie) widzisz swoj zmienn d. * Przejd� przy pomocy [Tab] do okienka tekstowego "New Value" (nowa warto��) i wpisz tam liczb� 1000. Naci�nij [Enter] a nast�pnie [Esc]. Okienko zamkn�ło si�. Zmiana warto�ci zmiennej została dokonana. [F7] - printf("...") - wn�trze p�tli for. [F7] - wykonała si� p�tla. Obejrzyjmy wyniki [Alt]-[F5]. W czwartej kolumnie widzisz efekt wprowadzonej zmiany: Demonstruje dzialanie PREinkrementacji POSTinkrementacji Nr����--X������++X������������X--�������X++ 1 99 101 1000 100 2 98 102 999 101

Page 32: C++ - lekcje

32

3 97 103 998 102 4 96 104 997 103 5 95 105 996 104 Zwró� uwag� w programie przykładowym na: * Zliczanie ilo�ci wykonanych przez program p�tli. int i; (deklaracja, �e i b�dzie zmienn całkowit ) ... i=1; (zainicjowanie zmiennej, nadanie pocz tkowej warto�ci) ... i++; (powi�kszanie i o 1 w ka�dej p�tli) ... i<6 (warunek kontynuacji) * Mo�liwo�� grupowej deklaracji zmiennych tego samego typu: int b,c,d,e; [Z] ________________________________________________________________ 4. Zmie� w programie przykładowym warto�� pocz tkow STO na dowoln inn - np. zero. Przetestuj działanie programu. 5. Sprawd�, czy mo�na wszystkie zmienne u�ywane w programie przykładowym zadeklarowa� wspólnie (jeden wiersz zamiast trzech). ________________________________________________________________ LEKCJA 11. Jak deklarowa� zmienne. Co to jest wska�nik. ________________________________________________________________ W trakcie tej lekcji: 1. Dowiesz si� wi�cej o deklaracjach. 2. Poprawisz troch� system MS DOS. 3. Dowiesz si� co to jest wska�nik i do czego słu�y. ________________________________________________________________ Wi�cej o deklaracjach. Deklarowa� mo�na w j�zyku C++: * zmienne; * funkcje; * typy (chodzi oczywi�cie o typy "nietypowe"). Zmienne w j�zyku C++ mog mie� charakter: * skalarów - którym przypisuje si� nierozdzielne dane np. całkowite, rzeczywiste, wskazuj ce (typu wska�nik) itp. * agregatów - którym przypisuje si� dane typu strukturalnego np. obiektowe, tablicowe czy strukturowe. Powy�szy podział nie jest tak całkiem precyzyjny, poniewa� pomi�dzy wska�nikami a tablicami istnieje w j�zyku C++ do�� specyficzna zale�no��, ale wi�cej na ten temat dowiesz si� z pó�niejszych lekcji. Zmienne mog by�: * deklarowane, * definiowane i * inicjowane. Stała to to taka zmienna, której warto�� mo�na przypisa� tylko raz. Z punktu widzenia komputera niewiele si� to ró�ni, bo miejsce w pami�ci i tak, stosownie do zadeklarowanego typu zarezerwowa� trzeba, umie�ci� w tablicy i zapami�ta� sobie identyfikator i adres te�. Jedyna praktyczna ró�nica polega na tym, �e zmiennej zadeklarowanej jako stała, np.: const float PI = 3.142; nie mo�na przypisa� w programie �adnej innej warto�ci, innymi słowy zapis: const float PI = 3.14; jest jednocze�nie DEKLARACJ, DEFINICJ i ZAINICJOWANIEM stałej PI.

Przykład : float x,y,z;�����������������������(DEKLARACJA) const float TEMP = 36.6;�����������(DEFINICJA) x = 42;�����������������������������(ZAINICJOWANIE zmiennej) [S!] constant/variable - STAŁA czy ZMIENNA. ________________________________________________________________ const - (CONSTant) - stała. Deklaracja stałej, słowo kluczowe w j�zyku C. var - (VARiable) - zmienna. W j�zyku C przyjmowane domy�lnie. Słowo var (stosowane w Pascalu) NIE JEST słowem kluczowym j�zyka C ani C++ (!). ________________________________________________________________ Skutek praktyczny: * Ma sens i jest poprawna deklaracja: const float PI = 3.1416; * Niepoprawna natomiast jest deklaracja: var float x; Je�li nie zadeklarowano stałej słowem const, to "zmienna" (var) przyjmowana jest domy�lnie. Definicja powoduje nie tylko okre�lenie, jakiego typu warto�ciami mo�e operowa� dana zmienna b d� funkcja, która zostaje od tego momentu skojarzona z podanym identyfikatorem, ale dodatkowo powoduje: * w przypadku zmiennej - przypisanie jej warto�ci, * W przypadku funkcji - przyporz dkowanie ciała funkcji. Zdefiniujmy dla przykładu kilka własnych funkcji. Przykład: void UstawDosErrorlevel(int n) /* nazwa funkcji*/ { exit(n); /* skromne ciało funkcji */ } Przykład int DrukujAutora(void) { printf("\nAdam MAJCZAK AD 1993/95 - C++ w 48 godzin!\n"); printf("\n Wydanie II Poprawione i uzupełnione.") return 0; } Przykład void Drukuj_Pytanie(void) { printf("Podaj liczbe z zakresu od 0 do 255"); printf("\nUstawie Ci ERRORLEVEL\t"); } W powy�szych przykładach zwró� uwag� na: * sposób deklarowania zmiennej, przekazywanej jako parametr do funkcji - n i err; * definicje funkcji i ich wywołanie w programie (podobnie jak w Pascalu). Zilustrujemy zastosowanie tego mechanizmu w programie przykładowym. Funkcje powy�sze s PREDEFINIOWANE w pliku FUNKCJE1.H na dyskietce doł czonej do ksi �ki. Wpisz i uruchom program: [P020.CPP] # include "stdio.h" # include "A:\funkcje1.h" int err; void main(void) { DrukujAutora(); Drukuj_Pytanie(); scanf("%d", &err); UstawDosErrorlevel(err); } Wykorzystajmy te funkcje praktycznie, by zilustrowa� sposób przekazywania informacji przez pracuj cy program do systemu DOS.

Page 33: C++ - lekcje

33

Zmienna otoczenia systemowego DOS ERRORLEVEL mo�e by� z wn�trza programu ustawiona na zadan - zwracan do systemu warto��. [Z] ________________________________________________________________ 1. Sprawd�, w jakim pliku nagłówkowym znajduje si� prototyp funkcji exit(). Opracuj najprostszy program PYTAJ.EXE ustawiaj cy zmienn systemow ERRORLEVEL według schematu: main() { printf("....Pytanie do u�ytkownika \n..."); scanf("%d", &n); exit(n); } 2. Zastosuj program PYTAJ.EXE we własnych plikach wsadowych typu *.BAT według wzoru: @echo off :LOOP cls echo 1. Wariant 1 echo 2. Wariant 2 echo 3. Wariant 3 echo Wybierz wariant działania programu...1,2,3 ? PYTAJ IF ERRORLEVEL 3 GOTO START3 IF ERRORLEVEL 2 GOTO START2 IF ERRORLEVEL 1 GOTO START1 echo Chyba zartujesz...? goto LOOP :START1 'AKCJA WARIANT 1 GOTO KONIEC :START2 'AKCJA WARIANT 2 GOTO KONIEC :START3 'AKCJA WARIANT 3 :KONIEC 'AKCJA WARIANT n - oznacza dowolny ci g komend systemu DOS, np. COPY, MD, DEL, lub uruchomienie dowolnego programu. Do utworzenia pliku wsadowego mo�esz zastosowa� edytor systemowy EDIT. 3. Skompiluj program posługuj c si� oddzielnym kompilatorem TCC.EXE. Ten wariant kompilatora jest pozbawiony zintegrowanego edytora. Musisz uruchomi� go pisz c odpowiedni rozkaz po DOS-owskim znaku zach�ty C:\>. Zastosowanie przy kompilacji małego modelu pami�ci pozwol Ci uzyska� swój program w wersji *.COM, a nie *.EXE. Wydaj rozkaz: c:\borlandc\bin\bcc -mt -lt c:\pytaj.cpp Je�li pliki znajduj si� w ró�nych katalogach, podaj wła�ciwe �cie�ki dost�pu (path). ________________________________________________________________ [???] CO TO ZA PARAMETRY ??? ________________________________________________________________ Przez sw "ułomno��" - 16 bitow szyn� i segmentacj� pami�ci komputery IBM PC wymusiły wprowadzenie modeli pami�ci: TINY, SMALL, COMPACT, MEDIUM, LARGE, HUGE. Wi�cej informacji na ten temat znajdziesz w dalszej cz��ci ksi �ki. Parametry dotycz sposobu kompilacji i zastosowanego modelu pami�ci: -mt - kompiluj (->*.OBJ) wykorzystuj c model TINY -lt - konsoliduj (->*.COM) wykorzystuj c model TINY i zatem odpowiednie biblioteki (do ka�dego modelu jest odpowiednia biblioteka *.LIB). Mo�esz stosowa� tak�e: ms, mm, ml, mh, ls, lm, ll, lh. ________________________________________________________________ Po instalacji BORLAND C++/Turbo C++ standardowo jest przyjmowany

model SMALL. Zatem kompilacja, któr wykonujesz z IDE daje taki sam efekt, jak zastosowanie kompilatora bcc/tcc w nast�puj cy sposób: tcc -ms -ls program.c Mog wyst pi� kłopoty z przerobieniem z EXE na COM tych programów, w których wyst�puj funkcje realizuj ce arytmetyk� zmiennoprzecinkow (float). System DOS oferuje Ci do takich celów program EXE2BIN, ale lepiej jest "panowa�" nad tym problemem na etapie tworzenia programu. PODSTAWOWE TYPY DANYCH W J�ZYKU C++. J�zyk C/C++ operuje pi�cioma podstawowymi typami danych: * char (znak, numer znaku w kodzie ASCII) - 1 bajt; * int (liczba całkowita) - 2 bajty; * float (liczba z pływaj cym przecinkiem) - 4 bajty; * double (podwójna ilo�� cyfr znacz cych) - 8 bajtów; * void (nieokre�lona) 0 bajtów. Zakres warto�ci przedstawiono w Tabeli poni�ej. Podstawowe typy danych w C++. ________________________________________________________________ Typ Znak Bajtów Zakres warto�ci ________________________________________________________________ char signed 1 -128...+127 int signed 2 -32768...+32767 float signed 4 +-3.4E+-38 (dokładno��: 7 cyfr) double signed 8 1.7E+-308 (dokładno��: 15 cyfr) void nie dotyczy 0 bez okre�lonej warto�ci. ________________________________________________________________ signed - ze znakiem, unsigned - bez znaku. Podstawowe typy danych mog by� stosowane z jednym z czterech modyfikatorów: * signed / unsigned - ze znakiem albo bez znaku * long / short - długi albo krótki Dla IBM PC typy int i short int s reprezentowane przez taki sam wewn�trzny format danych. Dla innych komputerów mo�e by� inaczej. Typy zmiennych w j�zyku C++ z zastosowaniem modyfikatorów (dopuszczalne kombinacje). ________________________________________________________________ Deklaracja Znak Bajtów Warto�ci Dyr. assembl. ________________________________________________________________ char signed 1 -128...+127 DB int signed 2 -32768...+32767 DB short signed 2 -32768...+32767 DB short int signed 2 -32768...+32767 DB long signed 4 -2 147 483 648... DD +2 147 483 647 long int signed 4 -2 147 483 648... DW +2 147 483 647 unsigned char unsigned 1 0...+255 DB unsigned unsigned 2 0...+65 535 DW unsigned int unsigned 2 0...+65 535 DW unsigned short unsigned 2 0...+65 535 DW signed int signed 2 -32 768...+32 767 DW signed signed 2 -32 768...+32 767 DW signed long signed 4 -2 147 483 648... DD +2 147 483 647 enum unsigned 2 0...+65 535 DW float signed 4 3.4E+-38 (7 cyfr) DD double signed 8 1.7E+-308 (15 cyfr) DQ long double signed 10 3.4E-4932...1.1E+4932 DT far * (far pointer, 386) 6 unsigned 2^48 - 1 DF, DP ________________________________________________________________ UWAGI: * DB - define byte - zdefiniuj bajt; DW - define word - zdefiniuj słowo (16 bitów);

Page 34: C++ - lekcje

34

DD - double word - podwójne słowo (32 bity); DF, DP - define far pointer - daleki wska�nik w 386; DQ - quad word - poczwórne słowo (4 * 16 = 64 bity); DT - ten bytes - dziesi�� bajtów. * zwró� uwag�, �e typ wyliczeniowy enum wyst�puje jako odr�bny typ danych (szczegóły w dalszej cz��ci ksi �ki). ________________________________________________________________ Poniewa� nie ma liczb ani short float, ani unsigned short float, słowo int mo�e zosta� opuszczone w deklaracji. Poprawne s zatem deklaracje: short a; unsigned short b; Zapis +-3.4E-38...3.4E+38 oznacza: -3.4*10^+38...0...+3.4*10^-38...+3.4*10^+38 Dopuszczalne s deklaracje i definicje grupowe z zastosowaniem listy zmiennych. Zmienne na li�cie nale�y oddzieli� przecinkami: int a=0, b=1, c, d; float PI=3.14, max=36.6; Po�wi�cimy teraz chwil� drugiej funkcji, któr ju� wielokrotnie stosowali�my - funkcji wej�cia - scanf(). FUNKCJA scanf(). Funkcja formatowanego wej�cia ze standardowego strumienia wej�ciowego (stdin). Funkcja jest predefiniowana w pliku STDIO.H i korzystaj c z funkcji systemu operacyjnego wczytuje dane w postaci tekstu z klawiatury konsoli. Interpretacja pobranych przez funkcj� scanf znaków nast pi zgodnie z �yczeniem programisty okre�lonym przez zadany funkcji format (%f, %d, %c itp.). Wywołanie funkcji scanf ma posta�: scanf(Format, Adres_zmiennej1, Adres_zmiennej2...); dla przykładu scanf("%f%f%f", &X1, &X2, &X3); wczytuje trzy liczby zmiennoprzecinkowe X1, X2 i X3. Format decyduje, czy pobrane znaki zostan zinterpretowane np. jako liczba całkowita, znak, ła�cuch znaków (napis), czy te� w inny sposób. Od sposobu interpretacji zale�y i rozmieszczenie ich w pami�ci i pó�niejsze "si�gni�cie do nich", czyli odwołanie do danych umieszczonych w pami�ci operacyjnej komputera. Zwró� uwag�, �e podaj c nazwy (identyfikatory) zmiennych nale�y poprzedzi� je w funkcji scanf() operatorem adresowym [&]. Zapis: int X; ... scanf("%d", &X); oznacza, �e zostan wykonane nast�puj ce działania: * Kompilator zarezerwuje 2 bajty pomi�ci w obszarze pami�ci danych programu na zmienn X typu int; * W momencie wywołania funkcji scanf funkcji tej zostanie przekazany adres pami�ci pod którym ma zosta� umieszczona zmienna X, czyli tzw. WSKAZANIE DO ZMIENNEJ; * Znaki pobrane z klawiatury przez funkcj� scanf maj zosta� przekształcone do postaci wynikaj cej z wybranego formatu %d - tzn. do postaci zajmuj cej dwa bajty liczby całkowitej ze znakiem. [???] A JE�LI PODAM INNY FORMAT ? ________________________________________________________________ C++ wykona Twoje rozkazy najlepiej jak umie, niestety nie sprawdzaj c po drodze formatów, a z zer i jedynek zapisanych w pami�ci RAM �aden format nie wynika. Otrzymasz bł�dne dane. ________________________________________________________________ Poni�ej przykład skutków bł�dnego formatowania. Doł cz pliki

STDIO.H i CONIO.H. [P021.CPP] //UWAGA: Doł cz wła�ciwe pliki nagłówkowe ! void main() { float A, B; clrscr(); scanf("%f %f", &A, &B); printf("\n%f\t%d", A,B); getch(); } [Z] ________________________________________________________________ 3 Zmie� w programie przykładowym, w funkcji printf() wzorce formatu na %s, %c, itp. Porównaj wyniki. ________________________________________________________________ Adres w pami�ci to taka sama liczba, jak wszystkie inne i wobec tego mo�na ni manipulowa�. Adresami rz dz jednak do�� specyficzne prawa, dlatego te� w j�zyku C++ wyst�puje jeszcze jeden specjalny typ zmiennych - tzw. ZMIENNE WSKAZUJCE (ang. pointer - wska�nik). Twoja intuicja podpowiada Ci zapewne, �e s to zmienne całkowite (nie ma przecie� komórki pami�ci o adresie 0.245 ani 61/17). Poj�cia "komórka pami�ci" a nie np. "bajt" u�ywam �wiadomie, poniewa� obszar zajmowany w pami�ci przez zmienn mo�e mie� ró�n długo��. Aby komputer wiedział ile kolejnych bajtów pami�ci zajmuje wskazany obiekt (liczba długa, krótka, znak itp.), deklaruj c wska�nik trzeba poda� na co b�dzie wskazywał. W sposób "nieoficjalny" ju� w funkcji scanf korzystali�my z tego mechanizmu. Jest to zjawisko specyficzne dla j�zyka C++, wi�c zajmijmy si� nim troch� dokładniej. POJ�CIE ZMIENNEJ WSKAZUJCEJ I ZMIENNEJ WSKAZYWANEJ. Wska�nik to zmienna, która zawiera adres innej zmiennej w pami�ci komputera. Istnienie wska�ników umo�liwia po�rednie odwoływanie si� do wskazywanego obiektu (liczby, znaku, ła�cucha znaków itp.) a tak�e stosunkowo proste odwołanie si� do obiektów s siaduj cych z nim w pami�ci. Załó�my, �e: x - jest umieszczon gdzie� w pami�ci komputera zmienn całkowit typu int zajmuj c dwa kolejne bajty pami�ci, a px - jest wska�nikiem do zmiennej x. Jednoargumentowy operator & podaje adres obiektu, a zatem instrukcja: px = &x; przypisuje wska�nikowi px adres zmiennej x. Mówimy, �e: px wskazuje na zmienn x lub px jest WSKA�NIKIEM (pointerem) do zmiennej x. Jednoargumentowy operator * (naz. OPERATOREM WYŁUSKANIA) powoduje, �e zmienna "potraktowana" tym operatorem jest traktowana jako adres pewnego obiektu. Zatem, je�li przyjmiemy, �e y jest zmienn typu int, to działania: y = x; oraz px = &x; y = *px; b�d mie� identyczny skutek. Zapis y = x oznacza: "Nadaj zmiennej y dotychczasow warto�� zmiennej x"; a zapis y=*px oznacza: "Nadaj zmiennej y dotychczasow warto�� zmiennej, której adres w pami�ci wskazuje wska�nik px" (czyli wła�nie x !). Wska�niki tak�e wymagaj deklaracji. Poprawna deklaracja w opisanym powy�ej przypadku powinna wygl da� tak:

Page 35: C++ - lekcje

35

int x,y; int *px; main() ...... Zapis int *px; oznacza: "px jest wska�nikiem i b�dzie wskazywa� na liczby typu int". Wska�niki do zmiennych mog zamiast zmiennych pojawia� si� w wyra�eniach po PRAWEJ STRONIE, np. zapisy: int X,Y; int *pX; ... pX = &X; ....... Y = *pX + 1; ������/* to samo, co Y = X + 1 */ printf("%d", *pX);�������/* to samo, co printf("%d", X); */ Y = sqrt(*pX);�����������/* pierwiastek kwadrat. z X */ ...... s w j�zyku C++ poprawne. Zwró� uwag�, �e operatory & i * maj wy�szy priorytet ni� operatory arytmetyczne, dzi�ki czemu * najpierw nast�puje pobranie spod wskazanego przez wska�nik adresu zmiennej; * potem nast�puje wykonanie operacji arytmetycznej; (operacja nie jest wi�c wykonywana na wska�niku, a na wskazywanej zmiennej!). W j�zyku C++ mo�liwa jest tak�e sytuacja odwrotna: Y = *(pX + 1); Poniewa� operator () ma wy�szy priorytet ni� * , wi�c: najpierw wska�nik zostaje zwi�kszony o 1; potem zostaje pobrana z pami�ci warto�� znajduj ca si� pod wskazanym adresem (w tym momencie nie jest to ju� adres zmiennej X, a obiektu "nast�pnego" w pami�ci) i przypisana zmiennej Y. Taki sposób poruszania si� po pami�ci jest szczególnie wygodny, je�li pod kolejnymi adresami pami�ci rozmie�cimy np. kolejne wyrazy z tablicy, czy kolejne znaki tekstu. Przyjrzyjmy si� wyra�eniom, w których wska�nik wyst�puje po LEWEJ STRONIE. Zapisy: *pX = 0;������������i���������X = 0; *pX += 1;�����������i���������X += 1; (*pX)++;������������i���������X++; /*3*/ maj identyczne działanie. Zwró� uwag� w przykładzie /*3*/, �e ze wzgl�du na priorytet operatorów () - najwy�szy - najpierw pobieramy wskazan zmienn ; ++ - ni�szy, potem zwi�kszmy wskazan zmienn o 1; Gdyby zapis miał posta�: *pX++; najpierw nast piłoby - zwi�kszenie wska�nika o 1 i wskazanie "s siedniej" zmiennej, potem - wyłuskanie, czyli pobranie z pami�ci zmiennej wskazanej przez nowy, zwi�kszony wska�nik, zawarto�� pami�ci natomiast, tj. wszystkie zmienne rozmieszczone w pami�ci pozostałyby bez zmian. [???] JAK TO WŁA�CIWIE JEST Z TYM PRIORYTETEM ? ________________________________________________________________ Wszystkie operatory jednoargumentowe (kategoria 2, patrz Tabela) maj taki sam priorytet, ale s PRAWOSTRONNIE ŁCZNE {L<<-R}. Oznacza to, �e operacje b�d wykonywane Z PRAWA NA LEWO. W wyra�eniu *pX++; oznacza to: najpierw ++ potem *

Zwró� uwag�, �e kolejno�� {L<<-R} dotyczy WSZYSTKICH operatorów jednoargumentowych. ________________________________________________________________ Je�li dwa wska�niki wskazuj zmienne takiego samego typu, np. po zadeklarowaniu: int *pX, *pY; int X, Y; i zainicjowaniu: pX = &X; pY = &Y; mo�na zastosowa� operator przypisania: pY = pX; Spowoduje to skopiowanie warto�ci (adresu) wska�nika pX do pY, dzi�ki czemu od tego momentu wska�nik pY zacznie wskazywa� zmienn X. Zwró� uwag�, �e nie oznacza to bynajmniej zmiany warto�ci zmiennych - ani wielko�c X, ani wielko�� Y, ani ich adresy w pami�ci NIE ULEGAJ ZMIANIE. Zatem działanie instrukcji: pY = pX; i *pY = *pX; jest RÓNE a wynika to znowu z priorytetu operatorów: najpierw * wyłuskanie zmiennych spod podanych adresów, potem = przypisanie warto�ci (ale ju� zmiennym a nie wska�nikom!) C++ ch�tnie korzysta ze wskazania adresu przy przekazywaniu danych - parametrów do/od funkcji. Asekuruj c si� na całej linii i podkre�laj c, �e nie zawsze wygl da to tak prosto i ładnie, posłu�� si� do zademonstrowania działania wska�ników przykładowym programem. Wpisz i uruchom nast�puj cy program: [P022-1.CPP wersja 1] # include "stdio.h" # include "conio.h" int a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,x=9,y=10,i; int *ptr1; long int *ptr2; void main() { clrscr(); ptr1=&a; ptr2=&a; printf("Skok o 2Bajty Skok o 4Bajty"); for(i=0; i<=9; i++) { printf("\n%d", *(ptr1+i)); printf("\t\t%d", *(ptr2+i)); } getch(); } [P022-2.CPP wersja 2] int a=11,b=22,c=33,d=44,e=55,f=66,g=77,h=88,x=99,y=10,i; int *ptr1; long int *ptr2; void main() { clrscr(); ptr1=&a; ptr2=&a; for (i=0; i<=9; i++) { printf("\n%d", *(ptr1+i)); printf("\t%d", *(ptr2+i)); getch();

Page 36: C++ - lekcje

36

} } W programie wykonywane s nast�puj ce czynno�ci: 1. Deklarujemy zmienne całkowite int (ka�da powinna zaj � 2 bajty pami�ci) i nadajemy im warto�ci w taki sposób aby łatwo mo�na je było rozpozna�. 2. Deklarujemy dwa wska�nki: ptr1 - poprawny - do dwubajtowych zmiennych typu int; ptr2 - niepoprawny - do czterobajtowych zmiennych typu long int. 3. Ustawiamy oba wska�niki tak by wskazywały adres w pami�ci pierwszej liczby a=11. 4. Zwi�kszamy oba wska�niki i sprawdzamy, co wskazuj . Je�li kompilator rozmie�ci nasze zmienne w kolejnych komórkach pami�ci, to powinni�my uzyska� nast�puj cy wydruk: Skok o 2B Skok o 4B 11�������������11 22�������������33 33�������������55 44�������������77 55�������������99 66�������������27475 77�������������28448 88�������������8258 99�������������27475 10�������������2844 Zwró� uwag�, �e to deklaracja wska�nika decyduje, co praktycznie oznacza operacja *(ptr + 1). W pierwszym przypadku wska�nik powi�ksza si� o 2 a w drugim o 4 bajty. Te odpowiednio 2 i 4 bajty stanowi długo�� komórki pami�ci lub precyzyjniej, pola pami�ci przeznaczonego dla zmiennych okre�lonego typu. Warto�ci pojawiaj ce si� w drugiej kolumnie po 99 s przypadkowe i u Ciebie mog okaza� si� inne. C++ pozwala wska�nikom nie tylko wskazywa� adres zmiennej w pami�ci. Wska�nik mo�e równie� wskazywa� na inny wska�nik. Takie wskazania: int X; ���int pX; ��int ppX; pX = &X;��ppX = &pX; oznaczamy: *pX��- pX wskazuje BEZPO�REDNIO zmienn X; **ppX�- ppX skazuje PO�REDNIO zmienn X (jest wska�nikiem do wska�nika). ***pppX - pppX wskazuje po�rednio wska�nik do zmiennej X itd. [Z] ________________________________________________________________ 4 Wybierz dowolne dwa przykładowe programy omawiane wcze�niej i przeredaguj je posługuj c si� zamiast zmiennych - wska�nikami do tych zmiennych. Pami�taj, �e przed u�yciem wska�nika nale�y: * zadeklarowa� na jaki typ zmiennych wskazuje wska�nik; * przyporz dkowa� wska�nik okre�lonej zmiennej. 5 Zastanów si�, co oznacza ostrze�enie wypisywane podczas uruchomienia programu przykładowego: Warning 8: Suspicious pointer conversion in function main. ________________________________________________________________ LEKCJA 12. Wska�niki i tablice w C i C++. ________________________________________________________________ W czasie tej lekcji: 1. Dowiesz si� wi�cej o zastosowaniu wska�ników. 2. Zrozumiesz, co maj wspólnego wska�niki i tablice w j�zyku C/C++. ________________________________________________________________ WSKA�NIKI I TABLICE W C i C++. W j�zyku C/C++ pomi�dzy wska�nikami a tablicami istnieje bardzo �cisły zwi zek. Do ponumerowania elementów w tablicy słu� tzw.

INDEKSY. W j�zyku C/C++ * KADA OPERACJA korzystaj ca z indeksów mo�e zosta� wykonana przy pomocy wska�ników; * posługiwanie si� wska�nikiem zamiast indeksu na ogół przyspiesza operacj�. Tablice, podobnie jak zmienne i funkcje wymagaj przed u�yciem DEKLARACJI. Upraszczaj c problem - komputer musi wiedzie� ile miejsca zarezerwowa� w pami�ci i w jaki sposób rozmie�ci� kolejne OBIEKTY, czyli kolejne elementy tablicy. [???] CO Z TYMI OBIEKTAMI ? ________________________________________________________________ OBIEKTEM w szerokim znaczeniu tego słowa jest ka�da liczba, znak, ła�cuch znaków itp.. Takimi klasycznymi obiektami j�zyki programowania operowały ju� od dawien dawna. Prawdziwe programowanie obiektowe w dzisiejszym, w��szym znaczeniu rozpoczyna si� jednak tam, gdzie obiektem mo�e sta� si� tak�e co� "nietypowego" - np. rysunek. Jest to jednak wła�ciwy chyba moment, by zwróci� Ci uwag�, �e z punktu widzenia komputera obiekt to co�, co zajmuje pewien obszar pami�ci i z czym wiadomo jak post�powa�. ________________________________________________________________ Deklaracja: int A[12]; oznacza: nale�y zarezerwowa� 12 KOLEJNYCH komórek pami�ci dla 12 liczb całkowitych typu int (po 2 bajty ka�da). Jednowymiarowa tablica (wektor) b�dzie si� nazywa� "A", a jej kolejne elementy zostan ponumerowane przy pomocy indeksu: - zwró� uwag�, �e w C zaczynamy liczy� OD ZERA A NIE OD JEDYNKI; A[0], A[1], A[2], A[3], .... A[11]. Je�li chcemy zadeklarowa�: - indeks i; - wska�nik, wskazuj cy nam pocz tek (pierwszy, czyli zerowy element) tablicy; - sam tablic�; to takie deklaracje powinny wygl da� nast�puj co: int i; int *pA; int A[12]; Aby wska�nik wskazywał na pocz tek tablicy A[12], musimy go jeszcze zainicjowa�: pA = &A[0]; Je�li poszczególne elementy tablicy s zawsze rozmieszczane KOLEJNO, to: *pA[0] oznacza: "wyłuskaj zawarto�� komórki pami�ci wskazanej przez wska�nik", czyli inaczej - pobierz z pami�ci pierwszy (zerowy!) element tablicy A[]. Je�li deklaracja typów elementów tablicy i deklaracja typu wska�nika s zgodne i poprawne, nie musimy si� dalej martwi� ile bajtów zajmuje dany obiekt - element tablicy. Zapisy: *pA[0];��������*pA;�����������A[0] *(pA[0]+1)�����*(pA+1)��������A[1] *(pA[0]+2)�����*(pA+2)��������A[2]������itd. s równowa�ne i oznaczaj kolejne wyrazy tablicy A[]. Je�li tablica jest dwu- lub trójwymiarowa, pocz tek tablicy oznacza zapis: A[0][0]; A[0][0][0]; itd. Zwró� uwag�, �e wska�nik do tablicy *pA oznacza praktycznie wska�nik do POCZTKOWEGO ELEMENTU TABLICY:

Page 37: C++ - lekcje

37

*pA == *pA[0] To samo mo�na zapisa� w j�zyku C++ w jeszcze inny sposób. Je�li A jest nazw tablicy, to zapis: *A oznacza wskazanie do pocz tku tablicy A, a zapisy: *(A+1)������������*(pA+1)��������A[1] *(A+8)������������*(pA+8)��������A[8] itd. s równowa�ne. Podobnie identyczne znaczenie maj zapisy: x = &A[i]��������x=A+i *pA[i]���������*(A+i) Nale�y jednak podkre�li�, �e pomi�dzy nazwami tablic (w naszym przykładzie A) a wska�nikami istnieje zasadnicza ró�nica. Wska�nik jest ZMIENN, zatem operacje: pA = A; pA++; s dopuszczalne i sensowne. Nazwa tablicy natomiast jest STAŁ, zatem operacje: A = pA;��������������LE ! A++;�����������������LE ! s niedopuszczalne i próba ich wykonania spowoduje bł�dy ! DEKLAROWANIE I INICJOWANIE TABLIC. Elementom tablicy, podobnie jak zmiennym mo�emy nadawa� watro�ci. Warto�ci takie nale�y podawa� w nawiasach klamrowych, a wielko�� tablicy - w nawiasach kwadratowych. Przykład int WEKTOR[5]; Tablica WEKTOR jest jednowymiarowa i składa si� z 5 elementów typu int: WEKTOR[0]....WEKTOR[4]. Przykład float Array[10][5]; Tablica Array jest dwuwymiarowa i składa si� z 50 elementów typu float: Array[0][0], Array[0][1]......Array[0][4] �������Array[1][0], Array[1][1]......Array[1][4] �����........................................... ����� Array[9][0], Array[9][1]......Array[9][4] Przykład const int b[4]={1,2,33,444}; Elementom jednowymiarowej tablicy (wektora) b przypisano warto��i: b[0]=1; b[1]=2; b[2]=33; b[3]=444; Przykład int TAB[2][3]={{1, 2, 3},{2, 4, 6}}; �����TAB[0][0]=1����TAB[0][1]=2����TAB[0][2]=3 �����TAB[1][0]=2����TAB[1][1]=4����TAB[1][2]=6 Przykład : Tablica znakowa. Obie formy zapisu daj ten sam efekt. char hej[5]="Ahoj"; char hej[5]={'A', 'h', 'o', 'j'}; �����hej[0]='A'�����hej[1]='h'�����hej[2]='o' itp. Przykład : Tablica uzupełniona zerami przez domniemanie. float T[2][3]={{1, 2.22}, {.5}}; kompilator uzupełni zerami do postaci: �����T[0][0]=1������T[0][1]=2.22��������T[0][2]=0 �����T[1][0]=0.5����T[1][1]=0�����������T[1][2]=0 Je�li nawias kwadratowy zawieraj cy wymiar pozostawimy pusty, to

kompilator obliczy jego domnieman zawarto�� w oparciu o podan zawarto�� tablicy. Nie spowoduje wi�c bł�du zapis: char D[]="Jakis napis" int A[][2]={{1,2}, {3,4}, {5,6}} Je�li nie podamy ani wymiaru, ani zawarto�ci: int A[]; kompilator "zbuntuje si�" i wyka�e bł d. Dla przykładu, skompiluj program przykładowy. Zwró� uwag� na sposób zainicjowania wska�nika. [P023.CPP] # include "stdio.h" # include <conio.h> int a[][2]={ {1,2},{3,4},{5,6},{7,8},{9,10},{11,12} }; char b[]={ "Poniedzialek" }; int i; int *pa; char *pb; void main() { pa = &a[0][0]; pb = b; // lub pb = b[0]; clrscr(); for (i=0; i<12; i++) printf("%d\t%c\n", *(pa+i), *(pb+i)); getch(); } Zwró� uwag�, �e w C++ ka�dy wymiar tablicy musi mie� swoj par� nawiasów kwadratowych. Dla przykładu, tablic� trójwymiarow nale�y deklarowa� nie tak TAB3D[i, j, k] lecz tak: int i, j, k; ... TAB3D[i][j][k]; Jest w dobrym stylu panowa� nad swoimi danymi i umieszcza� je w tzw. BUFORACH, czyli w wydzielonych obszarach pami�ci o znanym adresie, wielko�ci i przeznaczeniu. W nast�pnym programie przykładowym utworzymy taki bufor w postaci tablicy bufor[20] i zastosujemy zamiast funkcji scanf() czytaj cej bezpo�rednio z klawiatury par� funkcji: gets() - GET String - pobierz ła�cuch znaków z klawiatury do bufora; sscanf(bufor) - odczytaj z bufora (z pami�ci). Aby unikn � nielubianego goto stosujemy konstrukcj� for - break. Dokładniej p�tl� for omówimy w trakcie nast�pnej lekcji. Poniewa� mam nadziej�, �e "podstawow " posta� p�tli for pami�tasz z przykładów LOOP-n: for(i=1; i<100; i++) { ... } pozwalam sobie troch� wyprzedzaj co zastosowa� j w programie. Niepodobny do Pascala ani do Basica zapis wynika wła�nie z tego, �e skok nast�puje bezwarunkowo. Nagłówek p�tli for * nie inicjuje licznika p�tli (zb�dne typowe i=1); * nie sprawdza �adnego warunku (zb�dne i<100), * nie liczy p�ti (i=i+1 lub i++ te� zb�dne !). [P024.CPP] # include <stdio.h> # include <conio.h> int liczba, ile = 0, suma = 0; void main() {

Page 38: C++ - lekcje

38

char bufor[20]; clrscr(); printf("podaj liczby - ja oblicze SREDNIA i SUMA\n"); printf("ZERO = KONIEC\n"); for(;;) // Wykonuj petle BEZWARUNKOWO { gets(bufor); sscanf(bufor, "%d", &liczba); suma += liczba; ile++; if (liczba == 0) break; // JESLI ==0 PRZERWIJ PETLE } printf("Suma wynosi: %d\n", suma); printf("Srednia wynosi: %d\n", (suma / ile)); getch(); } Poni�ej troch� bardziej "elegancka wersja" z zastosowaniem p�tli typu while. Wi�cej o p�tlach dowiesz si� z nast�pnej Lekcji. [P025.CPP] # include <stdio.h> # include <conio.h> int liczba, ile=1, suma=0; void main() { char bufor[20]; clrscr(); printf("podaj liczby - ja oblicze SREDNIA i SUMA\n"); printf("ZERO = KONIEC\n"); gets(bufor); sscanf(bufor, "%d", &liczba); while (liczba != 0) { suma += liczba; gets(bufor); sscanf(bufor, "%d", &liczba); if(liczba == 0) printf("I to by bylo na tyle...\n"); else ile++; } printf("Suma wynosi: %d\n", suma); printf("Srednia wynosi: %d\n", suma / ile); getch(); } Program powy�szy, cho� operuje tablic , robi to troch� jakby za kulisami. Utwórzmy zatem inn - bardziej "dydaktyczn " tablic�, której elementy byłyby łatwo rozpoznawalne. PRZYKŁADY TABLIC WIELOWYMIAROWYCH. Dzi�ki matematyce bardziej jeste�my przyzwyczajeni do zapisu tablic w takiej postaci: ����������a11��a12��a13��a14��a15��a16 ����������a21��a22��a23��a24��a25��a26 ����������a31��a32��a33��a34��a35��a36 ����������a41��a42��a43��a44��a45��a46 gdzie a i,j /** indeks**/ oznacza element tablicy zlokalizowany w: - wierszu i - kolumnie j Przypiszmy kolejnym elementom tablicy nast�puj ce warto�ci: ����������11���12���13���14���15���16 ����������21���22���23���24���25���26 ����������31���32���33���34���35���36 ����������41���42���43���44���45���46 Jest to tablica dwuwymiarowa o wymiarach 4WIERSZE X 6KOLUMN, czyli krócej 4X6. Liczby b�d ce elementami tablicy s typu całkowitego. Je�li zatem nazwiemy j TABLICA, to zgodnie z zasadami przyj�tymi w j�zyku C/C++ mo�emy j zadeklarowa�:

int TABLICA[4][6]; Pami�tajmy, �e C++ liczy nie od jedynki a od zera, zatem TABLICA[0][0] = a11 = 11, TABLICA[2][3] = a34 = 34 itd. Znaj c zawarto�� tablicy mo�emy j zdefiniowa�/zainicjowa�: int TABLICA[4][6]={{11,12,13,14,15,16},{21,22,23,24,25,26} {31,32,33,34,35,36},{41,42,43,44,45,46}}; Taki sposób inicjowania tablicy, aczkolwiek pomaga wyja�ni� metod�, z punktu widzenia programistów jest troch� "nieelegancki". Liczb� przypisywan danemu elementowi tablicy mo�na łatwo obliczy�. TABLICA[i][j] = (i+1)*10 + (j+1); Przykładowo: TABLICA[2][5] = (2+1)*10 +(5+1) = 36 Najbardziej oczywistym rozwi zaniem byłoby napisanie p�tli int i, j; for (i=0; i<=3; i++) �����{ for (j=0; j<=5; j++) ����������{ TABLICA[i][j] = (i+1)*10 + (j+1);} �����} Spróbujmy prze�ledzi� rozmieszczenie elementów tablicy w pami�ci i odwoła� si� do tablicy na kilka sposobów. [P026.CPP] int TABLICA[4][6]={{11,12,13,14,15,16},{21,22,23,24,25,26}, {31,32,33,34,35,36},{41,42,43,44,45,46}}; # include <stdio.h> # include <conio.h> int *pT; int i, j; void main() { clrscr(); printf("OTO NASZA TABLICA \n"); for (i=0; i<=3; i++) { for (j=0; j<=5; j++) printf("%d\t", TABLICA[i][j]); printf("\n"); } printf("\n\Inicjujemy wska�nik na poczatek tablicy\n"); printf("i INKREMENTUJEMY wska�nik *pT++ \n"); pT=&TABLICA[0][0]; for (i=0; i<4*6; i++) printf("%d ", *(pT+i)); getch(); } Zwró� uwag�, �e je�li tablica ma wymiary A * B (np. 3 * 4) i składa si� z k=A*B elementów, to w C++ zakres indeksów wynosi zawsze 0, 1, 2, .....A*B-2, A*B-1. Tak wi�c tablica 10 x 10 (stuelementowa) b�dzie składa� si� z elementów o numerach 0...99, a nie 1...100. [P027.CPP] # include <stdio.h> # include <conio.h> int TABLICA[4][6]; int *pT; int i, j; void main() { clrscr(); printf("Inicjujemy tablice\n"); for (i=0; i<4; i++)

Page 39: C++ - lekcje

39

�����for (j=0; j<6; j++) �����{ TABLICA[i][j] = (i+1)*10 + (j+1); } // INDEKS! printf("OTO NASZA TABLICA \n"); for (i=0; i<=3; i++) { for (j=0; j<=5; j++) printf("%d\t", TABLICA[i][j]); printf("\n"); } printf("\n\Inicjujemy wska�nik na poczatek tablicy\n"); printf("i INKREMENTUJEMY wska�nik *pT++ \n"); pT=&TABLICA[0][0]; for (i=0; i<4*6; i++) printf("%d ", *(pT+i)); getch(); } R�CZNE I AUTOMATYCZNE GENEROWANIE TABLIC WIELOWYMIAROWYCH. Aby nabra� wprawy, spróbujmy pomanipulowa� inn tablic , znan Ci prawie "od urodzenia" - tabliczk mno�enia. Jest to kwadratowa tablica 10 x 10, której ka�dy wyraz opisuje si� prost zale�no�ci T(i,j)=i*j. Je�li przypomnimy sobie, �e indeksy w C++ zaczn si� nie od jedynki a od zera, zapis ten przybierze nast�puj c form�: int T[10][10]; T[i][j] = (i+1)*(j+1); Do pełni szcz��cia brak jeszcze wska�nika do tablicy: int *pT; i jego zainicjowania pT = &T[0][0]; I ju� mo�emy zaczyna�. Mogliby�my oczywi�cie zainicjowa� tablic� "na piechot�", ale to i nieeleganckie, i pracochłonne, i o pomyłk� łatwiej. Pami�taj, �e komputer myli si� rzadziej ni� programista, wi�c zawsze lepiej jemu zostawi� mo�liwie jak najwi�cej roboty. [P028.CPP] # include <stdio.h> # include <conio.h> int T[10][10]; int *pT; int i, j, k; char spacja = ' '; void main() { clrscr(); printf("\t TABLICZKA MNOZENIA (ineksy)\n"); for (i=0; i<10; i++) { for (j=0; j<10; j++) { T[i][j] = (i+1)*(j+1); if (T[i][j]<10) printf("%d%c ", T[i][j], spacja); else printf("%d ", T[i][j]); } printf("\n"); } printf("\n Inicjujemy i INKREMENTUJEMY wska�nik *pT++ \n\n"); pT=&T[0][0]; for (k=0; k<10*10; k++) { if (*(pT+k) < 10) printf("%d%c ", *(pT+k) , spacja); else printf("%d ", *(pT+k)); if ((k+1)%10 == 0) printf("\n"); } getch(); } Po wynikach jednocyfrowych dodajemy trzy spacje a po

dwucyfrowych dwie spacje. Po dziesi�ciu kolejnych wynikach trzeba wstawi� znak nowego wiersza. Sprawdzamy te warunki: if (*(pT+k) < 10) - je�li wynik jest mniejszy ni� 10... lub if (T[i][j] < 10); if ((k+1) % 10 == 0) - je�li k jest całkowit wielokrotno�ci 10, czyli - je�li reszta z dzielenia równa si� zero... Zastosowane w powy�szych programach nawiasy klamrowe {} spełniaj rol� INSTRUKCJI GRUPUJCEJ i pozwalaj podobnie jak para BEGIN...END w Pascalu zamkn � w p�tli wi�cej ni� jedn instrukcj�. Instrukcje uj�te w nawiasy klamrowe s traktowane jak pojedyncza instrukcja prosta. Tablice mog zawiera� liczby, ale mog zawiera� tak�e znaki. Przykład prostej tablicy znakowej zawiera nast�pny program przykładowy. [P029.CPP] # include <stdio.h> # include <conio.h> char T[7][12]={"Poniedzialek", "Wtorek", "Sroda", "Czwartek", "Piatek", "Sobota", "Niedziela"}; char *pT; int i, j, k; char spacja=' '; void main() { clrscr(); pT =&T[0][0]; printf("\t TABLICA znakowa (ineksy)\n\n"); for (i=0; i<7; i++) { for (j=0; j<12; j++) printf("%c ", T[i][j] ); printf("\n"); } printf("\n\t Przy pomocy wska�nika \n\n"); for (k=0; k<7*12; k++) { printf("%d ", *(pT+k) ); //TU! - opis w tek�cie if ((k+1)%12 == 0) printf("\n"); } getch(); } Nazwy dni maj ró�n długo��, czym wi�c wypełniane s puste miejsca w tablicy? Je�li w miejscu zaznaczonym komentarzem //TU! zmienisz format z printf("%c ", *(pT+k) ); na printf("%d ", *(pT+k) ); uzyskasz zamiast znaków kody ASCII. TABLICA znakowa (ineksy) P o n i e d z i a l e k W t o r e k S r o d a C z w a r t e k P i a t e k S o b o t a N i e d z i e l a Przy pomocy wska�nika: 80 111 110 105 101 100 122 105 97 108 101 107 87 116 111 114 101 107 0 0 0 0 0 0 83 114 111 100 97 0 0 0 0 0 0 0 67 122 119 97 114 116 101 107 0 0 0 0 80 105 97 116 101 107 0 0 0 0 0 0 83 111 98 111 116 97 0 0 0 0 0 0

Page 40: C++ - lekcje

40

78 105 101 100 122 105 101 108 97 0 0 0 Oka�e si�, �e puste miejsca zostały wypełnione zerami. Zero w kodzie ASCII - NUL - '\0' jest znakiem niewidocznym, nie było wi�c widoczne na wydruku w formie znakowej printf("%c"...). [Z] ________________________________________________________________ 1. Posługuj c si� wska�nikiem i inkrementuj c wska�nik z ró�nym krokiem - np. pT += 2; pT += 3 itp., zmodyfikuj programy przykładowe tak, by uzyska� wydruk tylko cz��ci tablicy. 2. Spróbuj zast pi� inkrementacj� wska�nika pT++ dekrementacj , odwracaj c tablic� "do góry nogami". Jak nale�ałoby poprawnie zainicjowa� wska�nik? 3. Napisz program drukuj cy tabliczk� mno�enia w układzie szesnastkowym - od 1 * 1 do F * F. 4. Wydrukuj nazwy dni tygodnia pionowo i wspak. 5. Zinterpretuj nast�puj ce zapisy: int *pt_int; float *pt_float; int p = 7, d = 27; float x = 1.2345, Y = 32.14; void *general; pt_int = &p; *pt_int += d; general = pt_int; pt_float = &x; Y += 5 * (*pt_float); general = pt_float; const char *name1 = "Jasio"; // wska�nik do STALEJ char *const name2 = "Grzesio"; // wska�nik do STALEGO ADRESU ________________________________________________________________ LEKCJA 13. Jak tworzy� w programie p�tle i rozgał�zienia. _______________________________________________________________ W trakcie tej lekcji: 1. Dowiesz si� znacznie wi�cej o p�tlach. 2. Przeanalizujemy instrukcje warunkowe i formułowanie warunków. _______________________________________________________________ Zaczniemy t� lekcj� nietypowo - od słownika, poniewa� dobrze jest rozumie� dokładnie co si� pisze. Tym razem słownik jest troch� obszerniejszy. Pozwalam sobie przytoczy� niektóre słowa powtórnie - dla przypomnienia i Twojej wygody. Do organizacji p�tli b�d nam potrzebne nast�puj ce słowa: [S!] conditional expressions - wyra�enia warunkowe structural loops - p�tle strukturalne ________________________________________________________________ if - je�eli (poprzedza warunek do sprawdzenia); else - a je�li nie, to (w przeciwnym wypadku...); for - dla; while - dopóki (dopóki nie spełnimy warunku); do - wykonaj, wykonuj; break - przerwij (wykonanie p�tli); switch - przeł cz; case - przypadek, wariant (jedna z mo�liwo�ci); goto - id� do... default - domy�lny, (automatyczny, pozostały); continue - kontynuuj (p�tl�); ________________________________________________________________ UWAGA: W C/C++ nie stosuje si� słowa THEN. P�TLA TYPU for. Ogólna posta� p�tli for jest nast�puj ca: for (W_inicjuj ce; W_logiczne; W_kroku) Instrukcja; gdzie skrót W_ oznacza wyra�enie. Ka�de z tych wyra�e� mo�e zosta� pomini�te (patrz --> for(;;)). Wykonanie p�tli for przebiega nast�puj co:

1. Wykonanie JEDEN raz WYRAENIA INICJUJCEGO. 2. Obliczenie warto�ci LOGICZNEJ wyra�enia logicznego. 3. Je�li W_logiczne ma warto�� PRAWDA (TRUE) nast pi wykonanie Instrukcji. 4. Obliczenie wyra�enia kroku. 5. Powtórne sprawdzenie warunku - czy wyra�enie logiczne ma warto�� ró�n od zera. Je�li wyra�enie logiczne ma warto�� zero, nast pi zako�czenie p�tli. Warunek jest testowany PRZED wykonaniem instrukcji. Je�li zatem nie zostanie spełniony warunek, instrukcja mo�e nie wykona� si� ANI RAZ. Instrukcja mo�e by� INSTRUKCJ GRUPUJC, składaj c si� z instrukcji prostych, deklaracji i definicji zmiennych lokalnych: { ci g deklaracji lub definicji; ci g instrukcji; } Ogromnie wa�ny jest fakt, �e C++ ocenia warto�� logiczn wyra�enia według zasady: 0 - FALSE, FAŁSZ, inaczej ZERO LOGICZNE je�li WYRAENIE == 0 lub jest fałszywe w znaczeniu logicznym; 1 - TRUE, PRAWDA, JEDYNKA LOGICZNA, je�li wyra�enie ma DOWOLN WARTO�� NUMERYCZN RÓN OD ZERA (!) lub jest prawdziwe w sensie logicznym. Przykład: "Klasycznie" zastosowana p�tla for oblicza pierwiastki kwadratowe kolejnych liczb całkowitych. #include <math.h> #include <stdio.h> void main() { int n; for (n=0; n<=100; n++) printf("%f\t", sqrt(n)); getch(); } Wyra�enie inicjuj ce mo�e zosta� pomini�te. Innymi słowy zmienna mo�e zosta� zainicjowana na zewn trz p�tli, a p�tla przejmie j tak jaka jest w danym momencie. Przykładowo: ..... { float n; n=(2*3)/(3*n*n - 1.234); ...... for (; n<=100; n++) printf("%f4.4\t", sqrt(n)); Przykład: Warunek przerwania p�tli mo�e mie� tak�e inny charakter. W przykładzie p�tla zostanie przerwana, je�li ró�nica pomi�dzy kolejnymi pierwiastkami przekroczy 3.0. void main() { float y=0, n=0; for (; (sqrt(n)-y)<=3.0; n++) �����{ y=sqrt(n); ����� printf("%f2.3\t", y); �����} getch(); } UWAGA: Sprawd�, czy nawias (sqrt(n)-y)<=3 mo�na pomin �? Jaki jest priorytet operatorów w wyra�eniach: (sqrt(n)-y)<=3.0 i sqrt(n)-y<=3.0 Jaki b�dzie wynik? Dlaczego? Przykład:

Page 41: C++ - lekcje

41

Instrukcja stanowi ca ciało p�tli mo�e by� instrukcj pust a wszystkie istotne czynno�ci mog zosta� wykonane w ramach samego "szkieletu" for. Program przykładowy sprawdza ile kolejnych liczb całkowitych trzeba zsumowa� by uzyska� sum� nie mniejsz ni� tysi c. void main() { float SUMA=0, n=0; for (; SUMA < 1000; SUMA+=(++n)); printf("%f", n); getch(); } [???] CZY NIE MONA JA�NIEJ ??? ________________________________________________________________ Mo�na, ale po nabraniu wprawy takie skróty pozwol Ci przyspieszy� tworzenie programów. Zmniejszenie wielko�ci pliku tekstowego jest w dzisiejszych czasach mniej istotne. Rozszyfrujmy zapis SUMA+=(++n). Preinkrementacja nast�puje PRZED u�yciem zmiennej n, wi�c: 1. Najpierw ++n, czyli n=n+1. 2. Potem SUMA=SUMA+ (n+1). Dla wyja�nienia przedstawiam dwie wersje (obie z p�tl for): void main() { void main() float SUMA=0; { float SUMA=0, n=0; int n; for (; SUMA < 1000; SUMA+=(++n)); } clrscr(); for (n=0; SUMA<=1000; n++) { SUMA=SUMA+n; } } ________________________________________________________________ To jeszcze nie koniec pokazu elastyczno�ci C/C++. W p�tli for wolno nam umie�ci� wi�cej ni� jedno wyra�enie inicjuj ce i wi�cej ni� jedno wyra�enie kroku oddzielaj c je przecinkami. flat a, b, c; const float d=1.2345; void main() { for (a=5,b=3.14,c=10; c; ++a,b*=d,c--) printf("\n%f\t%f\t%f", a,b,c); getch(); } Zwró� uwag�, �e zapisy warunku: if (c)...; i if (c != 0)...; s w C++ równowa�ne. Przykład: Program b�dzie pisał kropki a� do naci�ni�cia dowolnego klawisza, co wykryje funkcja kbhit(), b�d ca odpowiednikem KeyPressed w Pascalu. Zapis !kbhit() oznacza "NIE NACI�NI�TO KLAWISZA", czyli w buforze klawiatury nie oczekuje znak. Zwró� uwag�, �e funkcja getch() mo�e oczekiwa� na klawisz w niesko�czono��. Aby unikn � kłopotliwych sytuacji, czasem znacznie wygodniej jest zastosowa� kbhit(), szczególnie, je�li czekamy na DOWOLNY klawisz. void main() { for (; !kbhit(); printf(".")); } Przykład: Wska�nik w charakterze zmiennej roboczej w p�tli typu for. P�tla powoduje wypisanie napisu. char *Ptr = "Jakis napis"; void main() { for (; (*Ptr) ;)

printf("%c",*Pt++); getch(); } AUTOMATYCZNE GENEROWANIE TABLIC W P�TLI for Na dyskietce znajdziesz jeszcze kilka przykładów FORxx.CPP u�ycia p�tli. A teraz, zanim b�dziemy kontynuowa� nauk� - przykładowy program do zabawy. P�tla for słu�y do wykrywania zgodno�ci klawisza z elementami tablicy TABL[]. W tablicy D[] umieszczone zostały cz�stotliwo�ci kolejnych d�wi�ków, które program oblicza sam, wykorzystuj c przybli�ony współczynnik. [P030.CPP] # include "conio.h" # include "dos.h" # include "math.h" # include "stdio.h" char TABL[27]={"zsxdcvgbhnjm,ZSXDCVGBHNJM<"}; char k; float D[26]; int i; void main() { clrscr(); printf("[A]- KONIEC, dostepne klawisze: \n"); printf(" ZSXDCVGBHNJM,i [Shift]"); D[0]=200.0; for(i=1; i<26; i++) D[i]=D[i-1]*1.0577; for (;;) //patrz przyklad {*} { k = getch(); for(i=0; i<27; i++) { if (k==TABL[i]) { sound(D[i]); delay(100); nosound(); } }; if (k=='a'|| k=='A') break; //Wyj�cie z p�tli. k = '0'; }; } Po uruchomieniu programu klawisze działaj w sposób przypominaj cy prosty klawiszowy instrument muzyczny. Automatyczne zainicjowanie tablicy wielowymiarowej mo�emy pozostawi� C++. Wielko�� tablicy mo�e by� znana na etapie kompilacji programu, lub okre�lona w ruchu programu. C++ traktuje stał (const) jako szczególny przypadek wyra�enia stałowarto�ciowego (ang. true constant expression). Je�li zadeklarowali�my zmienn wymiar jako stał , mo�emy zastosowa� j np. do zwymiarowania tablicy TAB[]. Przykład poni�ej przedstawia takie wła�nie zastosowanie stałych w C++. [P031.CPP] /* Inicjowanie tablicy przy pomocy stałej */ # include <iostream.h> main() { const int wymiar = 7; //Deklaracja stałej char TAB[wymiar]; //Deklaracja tablicy cout << "\n Wielkosc tablicy TAB[] wynosi: " << sizeof TAB; cout << " bajtow."; return 0; } Umo�liwia to dynamiczne inicjowanie tablic pod warunkiem rygorystycznego przestrzegania zasady, �e do zainicjowana stałej mo�emy zastosowa� wył cznie wyra�enie stałowarto�ciowe. . [S] sizeof - wielko�� w bajtach. DANE PREDEFINIOWANE.

Page 42: C++ - lekcje

42

Dla ułatwienia �ycia programi�cie producenci kompilatorów C++ stosuj stałe predefiniowane w plikach nagłówkowych, np.: _stklen - wielko�� stosu, O_RDONLY - tryb otwarcia pliku "tylko do odczytu", GREEN - numer koloru w palecie, itp., itp. Predefiniowanych stałych mo�emy u�ywa� do deklarowania indeksów/rozmiarów tablic. P�TLA TYPU while. P�tl� typu while stosuje si� na ogół "do skutku", tj. do momentu spełnienia warunku, zwykle wtedy, gdy nie jeste�my w stanie przewidzie� potrzebnej ilo�ci cykli. Konstrukcja p�tli while wygl da nast�puj co: while (Wyra�enie_logiczne) Instrukcja; Je�li Wyra�enie_logiczne ma warto�� ró�n od zera, to zostanie wykonana Instrukcja. Sprawdzenie nast�puje PRZED wykonaniem Instrukcji, tote� Instrukcja mo�e nie zosta� wykonana ANI RAZU. Instrukcja mo�e by� INSTRUKCJ GRUPUJC. Przykład Stosujemy p�tl� while do programu pisz cego kropki (patrz wy�ej). void main() { while (!kbhit()) printf("."); } Przykład Stosujemy p�tl� while w programie obliczaj cym sum�. void main(){ float SUMA=0, n=0; clrscr(); while (SUMA<1000) SUMA+=(++n); printf("SUMA: %4.0f ostatnia liczba: %3.0f", SUMA, n); getch(); } [P032.CPP] char *Pointer1="Koniec napisu to \0, *Pointer==0 "; char *Pointer2="Koniec napisu to \0, *Pointer==0 "; void main(){ clrscr(); while (*Pointer1) printf("%c", *Pointer1++); printf("\nZobacz ten NUL na koncu lancucha znakow\n"); while (*Pointer2) printf("%c", *Pointer2++); printf("%d", *Pointer2); getch(); } P�TLA do...while. Konstrukcja dwuczłonowa do...while tworzy p�tl�, która: * jest wykonywana zawsze CO NAJMNIEJ JEDEN RAZ, poniewa� warunek jest sprawdzany nie na wej�ciu do p�tli, a na wyj�ciu z p�tli; * przerwanie p�tli powodowane jest przez NIESPEŁNIENIE WARUNKU. Schemat p�tli do...while jest nast�puj cy: do Instrukcja while (Wyra�enie_logiczne); Instrukcja mo�e by� instrukcj grupuj c . Przykład:

void main() { do {printf(".");} while (!kbhit()); printf("Koniec petli...."); } INSTRUKCJA WARUNKOWA if, if...else i if...else...if.. Instrukcja warunkowa ma posta�: if (Wyra�enie) Instrukcja; if (Wyra�enie) Instrukcja1 else Instrukcja2; Je�li Wyra�enie ma warto�� ró�n od zera (LOGICZN b d� NUMERYCZN !) to zostanie wykonana Instrukcja1, w przeciwnym razie wykonana zostanie Instrukcja2. Instrukcje mog by� instrukcjami grupuj cymi. Słowa kluczowe if i else mog by� stosowane wielokrotnie. Pozwala to tworzy� np. tzw. drzewa binarne. Przykład: void main() { float a; scanf("%f", &a); if (a<0) printf("Ujemna!"); �����else if (a==0) printf("Zero!"); ����������else printf("Dodatnia!"); } Przykład: if (a>0) if (a<100) printf("Dwucyfrowa"); else printf("100+"); inaczej: if(a>0) {if(a<100) printf("Dwucyfrowa"); else printf("100+");} Wyra�enie mo�e zawiera� operatory logiczne: if (a>0 && a<100) printf("Dwucyfrowa"); else printf("100+"); Zapis 100+ oznacza "sto i wi�cej". Przykład: C++ pozwala na krótszy zapis instrukcji warunkowej: (a>b)? MAX=a : MAX=b; inaczej: if (a>b) MAX=a; else MAX=b; INSTRUKCJE break i continue. Instrukcja break powoduje natychmiastowe bezwarunkowe opuszczenie p�tli dowolnego typu i przej�cie do najbli�szej instrukcji po zako�czeniu p�tli. Je�li w p�tli for opu�cimy wyra�enie logiczne, to zostanie automatycznie przyj�te 1. P�tla b�dzie zatem wykonywana bezwarunkowo w niesko�czono��. W przykładzie poni�ej niesko�czon p�tl� przerywa po podaniu z kalwiatury zera instrukcja break. Przykład: float a, sigma=0; void main(){ for (;;) { printf("\n Podaj liczbe do sumowania\n"); scanf("%f", &a); if (a==0) break; sigma+=a; printf("\n SUMA: %f",sigma); } printf("Nastapil BREAK"); getch(); }

Page 43: C++ - lekcje

43

Instrukcja continue. Instrukcja continue powoduje przedwczesne, bezwarunkowe zako�czenie wykonania wewn�trznej instrukcji p�tli i podj�cie próby realizacji nast�pnego cyklu p�tli. Próby, poniewa� najpierw zostanie sprawdzony warunek kontynuacji p�tli. Program z przykładu poprzedniego zmodyfikujemy w taki sposób, by * je�li liczba jest dodatnia - dodawał j do sumy sigma; * je�li liczba jest ujemna - nie robił nic, pomijał bie� c p�tl� przy pomocy rozkazu continue; (Poniewa� warunek wej�ciowy p�tli jest zawsze spełniony, to p�tl� zawsze uda si� kontynuowa�.) * je�li liczba równa si� zero - przerywał p�tl� instrukcj break Przykład: float a, sigma=0; void main() { for (;;) { printf("\n Sumuje tylko liczby dodatnie\n"); scanf("%f", &a); if (a<0) continue; if (a==0) break; sigma+=a; printf("\n SUMA: %f",sigma); } printf("Nastapil BREAK"); getch(); } INSTRUKCJE switch i case. Instrukcja switch dokonuje WYBORU w zale�no�ci od stanu wyra�enia przeł czaj cego (selector) jednego z mo�liwych przypadków - wariantów (case). Ka�dy wariant jest oznaczony przy pomocy stałej - tzw. ETYKIETY WYBORU. Wyra�enie przeł czaj ce mo�e przyjmowa� warto�ci typu int. Ogólna posta� istrukcji jest nast�puj ca: switch (selector) { case STAŁA1: Ci g_instrukcji-wariant 1; case STAŁA2: Ci g_instrukcji-wariant 2; ............................... case STAŁAn: Ci g_instrukcji-wariant n; default : Ostatni_ci g_instrukcji; } Nale�y podkre�li�, �e po dokonaniu wyboru i skoku do etykiety wykonane zostan równie� WSZYSTKIE INSTRUKCJE PONIEJ DANEJ ETYKIETY. Je�li chcemy tego unikn �, musimy doda� rozkaz break. [P033.CPP] # define pisz printf //dla przypomnienia # include <stdio.h> void main() { int Numer_Dnia; pisz("\nPodaj numer dnia tygodnia\n"); scanf("%d", &Numer_Dnia); switch(Numer_Dnia) { case 1: pisz("PONIEDZIALEK."); case 2: pisz("WTOREK"); case 3: pisz("SRODA."); case 4: pisz("CZWARTEK."); case 5: pisz("PIATEK."); case 6: pisz("SOBOTA."); case 7: pisz("NIEDZIELA."); default: pisz("\n *********************"); } } Zwró� uwag�, �e w przykładzie wariant default zostanie wykonany ZAWSZE, nawet je�li podasz liczb� wi�ksz ni� 7.

[P034.CPP] # define pisz printf # include <stdio.h> void main() { int Numer_Dnia; pisz("\nPodaj numer dnia tygodnia\n"); scanf("%d", &Numer_Dnia); switch(Numer_Dnia) { case 1: pisz("PON."); break; case 2: pisz("WTOR"); break; case 3: pisz("SRO."); break; case 4: pisz("CZW."); break; case 5: pisz("PIO."); break; case 6: pisz("SOB."); break; case 7: pisz("NIEDZ."); break; default: pisz("\n ?????"); } } Instrukcja break przerywa wykonanie. Wariant default zostanie wykonany TYLKO w przypadku podania liczby wi�kszej ni� 7. INSTRUKCJA POWROTU return. Słu�y do zako�czenia wykonania zawieraj cej j funkcji i mo�e mie� posta�: return; return stała; return Wyra�enie; return (wyra�enie); Przykład: Definiujemy funkcj� _dodaj() zwracaj c , poprzez instrukcj� return warto�� przekazanego jej w momencie wywołania argumentu powi�kszon o 5. float _dodaj(float x) { x+=5; return x; } Funkcja _dodaj() zwraca warto�� i nadaje t� warto�� zmiennej wynik zadeklarowanej nazewn trz funkcji i znanej w programie głównym. A oto program w cało�ci. [P035.CPP] float funkcja_dodaj(float x) { x += 5; return x; } float dana = 1, wynik = 0; void main() { clrscr(); wynik = funkcja_dodaj(dana); printf("%f", wynik); } INSTRUKCJA SKOKU BEZWARUNKOWEGO goto I ETYKIETY. Składnia instrukcji skoku goto jest nast�puj ca: goto Identyfikator_etykiety; UWAGA: Po ka�dej etykiecie musi wyst pi� CO NAJMNIEJ JEDNA INSTRUKCJA. Je�li etykieta oznacza koniec programu, to musi po niej wyst pi� instrukcja pusta. Instrukcja goto nie cieszy si� powodzeniem ani dobr sław (niesłusznie!). Ostro�ne i umiej�tne jej stosowanie jeszcze nikomu nie zaszkodziło. Nale�y tu zaznaczy�, �e etykieta nie wymaga deklaracji.

Page 44: C++ - lekcje

44

Przykład: Program poni�ej generuje d�wi�ki i "odlicza". [P036.CPP] #include <dos.h> #include <stdio.h> void main() { int czestotliwosc=5000, n=10, milisekundy=990; printf("\n"); start: { sound(czestotliwosc); delay(milisekundy); nosound(); czestotliwosc/=1.2; printf("%d\b", --n); if (n) goto start; //petle strukturalne zrob sam(a) } koniec: ; } // Tu jest instrukcja pusta. [S!] DOS API function names - nazwy funkcji z interfejsu DOS ________________________________________________________________ sound - d�wi�k; delay - opó�nienie, zwłoka; nosound - bez d�wi�ku (wył cz d�wi�k); ________________________________________________________________ [Z] ________________________________________________________________ 1. Bior c pod uwag�, �e iloraz cz�stotliwo�ci kolejnych d�wi�ków jest stały tzn. Fcis/Fc=Ffis/Ff=....=const oraz, �e oktawa to podwojenie cz�stotliwo�ci, opracuj program i oblicz cz�stotliwo�ci poszczególnych d�wi�ków. 2. Spróbuj zastosowa� w programie przykładowym kolejno p�tle for, while, do...while. 3. Zastosuj we własnym programie do�wiadczalnym instrukcj� switch. LEKCJA 14. Jak tworzy� i stosowa� struktury. ________________________________________________________________ W trakcie tej lekcji poznasz poj�cia: * Klasy zmiennej. * Struktury. * Pola bitowego. * Unii. Dowiesz si� tak�e wi�cej o operacjach logicznych. ________________________________________________________________ CO TO JEST KLASA ZMIENNEJ? W j�zyku C i C++ programista ma wi�kszy wpływ na rozmieszczenie zmiennych w pami�ci operacyjnej komputera i w rejestrach mikroprocesora. Mo�e to mie� decyduj cy wpływ na dost�pno�� danych z ró�nych miejsc programu i szybko�� działania programu. Nale�y podkre�li�, �e TYP ZMIENNEJ (char, int, float itp.) decyduje o sposobie interpretacji przechowywanych w pami�ci zer i jedynek, natomiast KLASA ZMIENNEJ decyduje o sposobie przechowywania zmiennej w pami�ci. W C++ wyst�puj cztery klasy zmiennych. ZMIENNE STATYCZNE - static. Otrzymuj stał lokalizacj� w pami�ci w momencie uruchamiania programu. Zachowuj swoj warto�� przez cały czas realizacji programu, chyba, �e �wiadomie za� damy zmiany tego stanu - np. instrukcj przypisania. Przykład deklaracji: static float liczba; W wi�kszo�ci kompilatorów C++ zmienne statyczne, które nie zostały jawnie zainicjowane w programie, otrzymuj po zadeklarowaniu warto�� ZERO.

ZMIENNE AUTOMATYCZNE - auto. Otrzymuj przydział miejsca w pami�ci dynamicznie - na stosie procesora, w momencie rozpocz�cia wykonania tego bloku programu, w którym zmienne te zostały zadeklarowane. Przydzielenie pami�ci nie zwalnia nas z obowi zku zainicjowania zmiennej (wcze�niej warto�� zmiennej jest przypadkowa). Zmienne automatyczne "znikaj " po zako�czeniu wykonywania bloku. Pami�� im przydzielona zostaje zwolniona. Przykład: auto long suma; ZMIENNE REJESTROWE - register. Zmienne rejestrowe s tak�e zmiennymi lokalnymi, widocznymi tylko wewn trz tego bloku programu, w którym zostały zadeklarowane. C++ mo�e wykorzysta� dwa rejestry mikroprocesora - DI i SI do przechowywania zmiennych. Je�li zadeklarujemy w programie wi�cej zmiennych jako zmienne rejestrowe - zostan one umieszczone na stosie. Znaczne przyspieszenie działania programu powoduje wykorzystanie rejestru do przechowywania np. licznika p�tli. Przykład: register int i; ..... for (i=1; i<1000; i++) {.....} ZMIENNE ZEWN�TRZNE - extern. Je�li zmienna została - raz i TYLKO RAZ - zadeklarowana w pojedynczym segmencie du�ego programu, zostanie w tym�e segmencie umieszczona w pami�ci i potraktowana podobnie do zmiennych typu static. Po zastosowaniu w innych segmentach deklaracji extern zmienna ta mo�e by� dost�pna w innym segmencie programu. Przykład: extern int NUMER; STRUKTURY. Poznane wcze�niej tablice mog zawiera� wiele danych, ale wszystkie te dane musz by� tego samego typu. Dla zgrupowania powi zanych ze sob logicznie danych ró�nego typu C/C++ stosuje STRUKTURY, deklarowane przy pomocy słowa struct. Kolejne pola struktury s umieszczane w pami�ci zgodnie z kolejno�ci ich deklarowania. Struktur�, podobnie jak zmienn , MUSIMY ZADEKLAROWA�. Struktura jest objektem bardziej zło�onym ni� pojedyncza zmienna, wi�c i deklaracja struktury jest bardziej skomplikowana. Deklaracja struktury składa si� z nast�puj cych elementów: 1. Słowo kluczowe struct (obowi zkowe). 2. Nazwa (opcjonalna). Je�li podamy nazw�, to nazwa ta b�dzie oznacza� dany typ struktury. 3. Nawias klamrowy { 4. Deklaracje kolejnych składników struktury. 5. Nawias klamrowy } 6. Lista nazw struktur okre�lonego powy�ej typu (mo�e zosta� zadeklarowana oddzielnie). Przykład. Deklaracja ogólnego typu struktury i okre�lenie wewn�trznej postaci struktury. struct Ludzie { char Imiona[30]; char Nazwisko[20]; int wiek; char pokrewienstwo[10] }; Je�li okre�limy ju� typ struktury - czyli rodzaj, wielko�� i przeznaczenie poszczególnych pól struktury, mo�emy dalej tworzy� - deklarowa� i inicjowa� konkretne struktury danego typu.

Page 45: C++ - lekcje

45

Przykład. Deklaracja zmiennych - struktur tego samego typu. struct Ludzie Moi, Twoi, Jego, Jej, Szwagra; Deklaracj� struktur mo�na poł czy�. Przykład. Poł czona deklaracja struktur. struct Ludzie { char pokrewienstwo[10]; char Imiona[30]; int wiek; } Moi, Twoi, Szwagra; Struktury statyczne * maj stałe miejsce w pami�ci w trakcie całego programu; * s "widoczne" i dost�pne w całym programie. Zadeklarujemy teraz typ struktury i zainicjujemy dwie struktury. Przykład. Zainicjowanie dwu struktur statycznych. struct Ludzie { char pokrewienstwo[10]; char Imiona[30]; int wiek; }; struct Ludzie Moi, Szwagra; static struct Ludzie Moi = { "Stryjek", "Walenty", 87 }; static struct Ludzie Szwagra = { "ciotka", "Ala", 21 }; Zapis static struct Ludzie Szwagra; oznacza: statyczna struktura typu "Ludzie" pod nazw "Szwagra". Do struktury w cało�ci mo�emy odwoływa� si� za pomoc jej nazwy a do poszczególnych elementów struktury poprzez nazw� struktury i nazw� pola struktury - ROZDZIELONE KROPK ".". Zademonstrujmy to na przykładzie. Zwró� uwag� na ró�ne sposoby przekazywania danych pomi�dzy strukturami: C4.Wiek=Czlowiek2.Wiek; - przekazanie zawarto�ci pojedynczego pola numerycznego; C4=Czlowiek3; - przekazanie zawarto�ci całej struktury Czlowiek3 do C4. Przykład. Program manipuluj cy prost struktur . [P037.CPP] int main() { struct Ludzie { char Imie[20]; int Wiek; char Status[30]; char Tel_Nr[10]; }; static struct Ludzie Czlowiek1={"Ala", 7, "Ta, co ma Asa","?"}, Czlowiek2={"Patrycja", 13, "Corka", "8978987"}, Czlowiek3={"Krzysztof", 27, "Kolega z przedszkola", "23478"}; struct Ludzie C4, C5; C4=Czlowiek3; C4.Wiek=Czlowiek2.Wiek; C5=Czlowiek1; clrscr(); printf("%s %d %s\n", C4.Imie, C4.Wiek, C4.Status); printf("%s %s",C5.Imie, C5.Status);

return 0; } Tablice mog by� elementami struktur, ale i odwrotnie - ze struktur, jak z cegiełek mo�na tworzy� konstrukcje o wy�szym stopniu zło�ono�ci - struktury struktur i tablice struktur. Je�li tablica składa si� z liczb typu int, to deklarujemy j : int TABLICA[10]; je�li tablica składa si� ze struktur, to deklarujemy j : struct TABLICA[50]; W przykładzie poni�ej przedstawiono * deklaracj� jednowymiarowej tablicy LISTA[50], * elementami tablicy s struktury typu SCzlowiek, * jednym z elementów ka�dej struktury SCzlowiek jest struktura "ni�szego rz�du" typu Adres; [P038.CPP] int main() { struct Adres { char Ulica[30]; int Nr_Domu; int Nr_Mieszk; }; struct SCzlowiek { char Imie[20]; int Wiek; struct Adres Mieszkanie; }; struct SCzlowiek LISTA[50]; LISTA[1].Wiek=34; LISTA[1].Mieszkanie.Nr_Domu=29; printf("%d", LISTA[1].Mieszkanie.Nr_Domu); return 0; } Zapis printf("%d", LISTA[1].Mieszkanie.Nr_Domu oznacza: * wybierz element nr 1 z tablicy LISTA; (jak wynika z deklaracji tablicy, ka�dy jej element b�dzie miał wewn�trzn struktur� zorganizowan tak, jak opisano w deklaracji struktury SCzlowiek); * wybierz ze struktury typu SCzlowiek pole Mieszkanie; (jak wynika z deklaracji, pole Mieszkanie b�dzie miało wewn�trzn organizacj� zgodn ze struktur Adres); * ze struktury typu Adres wybierz pole Nr_Domu; * Wydrukuj zawarto�� pola pami�ci interpretuj c j jako liczb� typu int - w formacie %d. Słowo struktura tak doskonale pasuje, �e chciałoby si� powiedzie�: je�li struktura struktur jest wielopoziomowa, to podobnie, jak przy wielowymiarowych tablicach, ka�dy poziom przy nadawaniu warto�ci musi zosta� uj�ty w dodatkow par� nawiasów klamrowych. [???] A CO Z ŁA�CUCHAMI ZNAKOWYMI ? ________________________________________________________________ J�zyk C++ oferuje do kopiowania ła�cuchów znakowych specjaln funkcj� strcpy(). Nazwa funkcji to skrót STRing CoPY (kopiuj ła�cuch). Sposób wykorzystania tej funkcji: strcpy(Dok d, Sk d); lub strcpy(Dok d, "ła�cuch znaków we własnej osobie");

Page 46: C++ - lekcje

46

Szczegóły - patrz Lekcja o ła�cuchach znakowych. ________________________________________________________________ STRUKTURY I WSKA�NIKI. Wska�niki mog wskazywa� struktur� w cało�ci lub element struktury. J�zyk C/C++ oferuje specjalny operator -> który pozwala na odwoływanie si� do elementów struktury. W przykładzie poni�ej przedstawiono ró�ne sposoby odwołania si� do elementów trzech identycznych struktur STA, STB, STC. [P039.CPP] int main() { struct { char Tekst[20]; int Liczba1; float Liczba2; } STA, STB, STC, *Pointer; STA.Liczba1 = 1; STA.Liczba2 = 2.2; strcpy(STA.Tekst, "To jest tekst"); STB=STA; Pointer = &STC; Pointer->Liczba1 = 1; Pointer->Liczba2 = 2.2; strcpy(Pointer->Tekst, STA.Tekst); printf("\nLiczba1-STA Liczba2-STB Tekst-STC\n\n"); printf("%d\t", STA.Liczba1); printf("%f\t", STB.Liczba2); printf("%s", Pointer->Tekst); return 0; } Rozszyfrujmy zapis: strcpy(Pointer->Tekst, STA.Tekst); Skopiuj ła�cuch znaków z pola Tekst struktury STA do pola Tekst struktury wskazywanej przez pointer. Prawda, �e to całkiem proste? [???] CZY MUSIMY TO ROZDZIELA� ? ________________________________________________________________ Jak zauwa�yłe�, liczby mogliby�my zapisywa� tak�e jako ła�cuchy znaków, ale wtedy nie mogliby�my wykonywa� na tych liczbach działa�. Konwersj� liczba - ła�cuch znaków lub odwrotnie ła�cuch znaków - liczba wykonuj w C specjalne funkcje np.: atoi() - Ascii TO Int.; itoa() - Int TO Ascii itp. Wi�cej informacji na ten temat i przykłady znajdziesz w dalszej cz��ci ksi �ki. ________________________________________________________________ Elementami struktury mog by� zmienne dowolnego typu, ł dznie z innymi strukturami. Ciekawostka: ________________________________________________________________ Wska�nik do deklarowanej struktury mo�e by� w j�zyku C/C++ jak jeden z jej WŁASNYCH elementów. Je�li wska�nik wchodz cy w skład struktury wskazuje na WŁASN struktur�, to nazywa si� to AUTOREFERENCJ STRUKTURY. ________________________________________________________________ POLA BITOWE. Cz�sto zdarza si�, �e jaka� zmienna ma zaw��ony zakres warto�ci. Dla przykładu zmienne logiczne (tzw. flagi) to zawsze tylko 0

lub 1. Wiek rzadko przekracza 255 lat a liczba dzieci zwykle nie jest wi�ksza ni� 15. Nawet najbardziej niestali panowie nie zd � o�eni� si� i rozwie�� wi�cej ni� 7 razy. Gdyby�my zatem chcieli zapisa� informacje * płe� 0 - m��czyzna, 1 - kobieta ( 1 bit ); * wiek 0 - 255 lat (8 bitów); * ilo�� dzieci 0 - 15 (4 bity); * kolejny numer mał�e�stwa 0 - 7 (3 bity); to przecie� wszystkie te informacje mog nam si� zmie�ci� w jednym szesnastobitowym rejestrze lub w dwu bajtach pami�ci. Takie kilka bitów wydzielone i maj ce okre�lone znaczenie to wła�nie pole bitowe. C++ pozwala tak�e na uwzgl�dnianie znaku w polach bitowych. Pola bitowe mog by� typu int i unsigned int (czyli takie jak w przykładzie poni�ej). Je�li jakie� dane chcemy przechowywa� w postaci pola bitowego, w deklaracji struktury sygnalizujemy to dwukropkiem. Stwarza to dwie istotne mo�liwo�ci: * bardziej ekonomicznego wykorzystania pami�ci; * łatwego dodatkowego zaszyfrowania danych. [P040.CPP] //Pamietaj o dolaczeniu plikow naglowkowych ! int main() { struct USC { int Sex : 1; unsigned Wiek : 8; unsigned Dzieci : 4; unsigned Ktora : 3; } Facet; int bufor; clrscr(); Facet.Sex = 0; printf("\n Ile ma lat ? : "); scanf("%d", &bufor); Facet.Wiek = bufor; printf("\n Ktore malzenstwo ? : "); scanf("%d", &bufor); Facet.Ktora = bufor; printf("\n Ile dzieci ? : "); scanf("%d", &bufor); Facet.Dzieci = bufor; printf("\n\n"); if (Facet.Ktora) printf("Facet ma %d zone", Facet.Ktora); printf("\nPlec: Dzieci: Wiek (lat): \n\n"); printf("%d\t%d\t%d", Facet.Sex, Facet.Dzieci, Facet.Wiek); getch(); return 0; } Uruchom program i sprawd� co si� stanie, je�li Facet b�dzie miał np. 257 lat lub 123 �on�. Przekroczenie zadeklarowanego zakresu powoduje obci�cie cz��ci bitów. Aby uzyska� "wyrównanie" pola bitowego do pocz tku słowa nale�y przed interesuj cym naspolem bitowym zdefiniowa� tzw. pole puste: * pole bitowe bez nazwy; * długo�� pola pustego powinna wynosi� 0. Poni�ej przedstawiam przykład pola bitowego zajmuj cego trzy kolejne słowa 16 bitowe. Dodanie pola pustego wymusza rozpocz�cie pola pole_IV od pocz tku trzeciego słowa maszynowego (zakładamy, �e pracujemy z komputerem 16 bitowym). struct { unsigned pole_I:4; unsigned pole_II:10; unsigned pole_III:4; unsigned :0; /* to jest pole puste */ unsigned pole_IV:5; } pole_przykladowe; Zwró� uwag�, �e cz��� bitów w drugim i trzecim słowie maszynowym

Page 47: C++ - lekcje

47

nie zostanie wykorzystana. UNIE czyli ZMIENNE WARIANTOWE. Unie to specyficzne struktury, w których pola pami�ci przeznaczone na objekty ró�nego typu nakładaj si�. Je�li jaka� zmienna mo�e by� reprezentowana na kilka sposobów (wariantów) to sensowne jest przydzielenie jej nie struktury a unii. W danej chwili pole pami�ci nale� ce do unii mo�e zawiera� TYLKO JEDEN WARIANT. W przykładzie - albo cyfr� (która znakowo jest widziana jako znak ASCII o kodzie 2,3,4 itd.) albo napis. Do zadeklarowania unii słu�y słowo kluczowe union. [P041.CPP] #include "string.h" #include "stdio.h" int BUFOR, i; int main() { union { int Cyfra; char Napis[20]; } Unia; for (i=1; i<11; i++) { printf("\n Podaj liczbe jednocyfrowa: "); scanf("%d", &BUFOR); if (BUFOR<0 || BUFOR>9) strcpy(Unia.Napis, "TO NIE CYFRA !"); else Unia.Cyfra = BUFOR; printf("\n Pole jako Cyfra Pole jako Napis \n"); /* Tu wyswietlimy warianty: Pole jako cyfra i jako napis*/ /* Petla pozwoli Ci przeanalizowac wszystkie cyfry 0...9 */ printf(" %d\t\t\t%s", Unia.Cyfra, Unia.Napis); } return 0; } P�tla w przykładzie nie ma znaczenia. Słu�y tylko dla Twojej wygody - dzi�ki niej nie musisz uruchamia� programu przykładowego wielokrotnie. Podobnie zmienne BUFOR oraz i maj znaczenie pomocnicze. Zwró� uwag�, �e nieprawidłowa interpretacja zawarto�ci pola unii mo�e spowodowa� wadliwe działanie programu. [Z] ________________________________________________________________ 1. W programie przykładowym zamie� uni� na struktur�. Porównaj działanie. 2 Przydziel na Wiek w strukturze Facet o jeden bit mniej. Ile lat mo�e teraz mie� Facet ? 3. Zmodyfikuj program przykładowy tak, by napis o liczbie m��ów/�on zale�ał od płci - pola Sex. 4. Zamieniwszy uni� na struktur� w programie, sprawd�, czy wpływa to na wielko�� pliku *.EXE. ________________________________________________________________ OPERACJE LOGICZNE. Zaczniemy od operacji logicznych na pojedynczych bitach liczb całkowitych. W C++ mamy do dyspozycji nast�puj ce operatory: ~����Zaprzeczenie (NOT) ~0=1; ~1=0; |����Suma (OR) 0|0=0; 0|1=1; 1|0=1; 1|1=1; &����Iloczyn (AND) 0&0=0; 0&1=0; 1&0=0; 1&1=1; ^����Alternatywa wył czna ALBO...ALBO (XOR) �����0^0=0; 0^1=1; 1^0=1; 1^1=0; <<���Przesuni�cie bitów w lewo (Shift Left) �����<< 00001000 = 00010000 dzie�. 8<<1=16 >>���Przesuni�cie bitów w prawo (Shift Right) �����>> 00001000 = 00000100 dzie�. 8>>2=2

Miło byłoby poogl da� to troch� dokładniej w przykładowych programach, ale potrzebne nam do tego b�d funkcje. Zajmijmy si� wi�c uwa�niej funkcjami. LEKCJA 15. Jak posługiwa� si� funkcjami. ________________________________________________________________ W trakcie tej lekcji dowiesz si� wi�cej o: * funkcjach i prototypach funkcji; * przekazywaniu argumentów funkcji; * współpracy funkcji ze wska�nikami. _______________________________________________________________ Aby przedstawi� działanie operatorów logicznych opracujemy własn funkcj� Demo() i zastosujemy j w programie przykładowym [najwa�niejszy fragment]. int Demo(int Liczba) { int MaxNr=15; for (; MaxNr>=0; MaxNr--) { if ((Liczba>>MaxNr)&1) printf("1"); else printf("0"); } return 0; //Funkcja nie musi nic zwracac } Funkcja przesuwa liczb� o kolejno 15, 14, 13 itd. bitów w prawo i sprawdza, czy 16, 15, 14 bit jest jedynk , czy zerem. Iloczyn logiczny z jedynk ( 0000000000000001 ) gwarantuje nam, �e wpływ na wynik operacji b�dzie miał tylko ten jeden bit (patrz wy�ej - jak działaj operatory logiczne). [P042.CPP] # include <stdio.h> int Demo(int Liczba) { int MaxNr=15; for (; MaxNr>=0; MaxNr--) if ((Liczba>>MaxNr)&1) printf("1"); else printf("0"); return 0; } char odp; int main() { int X, Y; clrscr(); printf("\nPodaj dwie liczby calkowite od -32768 do +32767\n"); printf("\nLiczby X i Y rozdziel spacja"); printf("\nPo podaniu drugiej liczby nacisnij [Enter]"); printf("\nLiczby ujemne sa w kodzie dopelniajacym"); printf("\nskrajny lewy bit oznacza znak 0-Plus, 1-Minus"); for(;;) { printf("\n"); scanf("%d %d", &X, &Y); printf("\nX:\t"); Demo(X); printf("\nY:\t"); Demo(Y); printf("\n~Y:\t"); Demo(~Y); printf("\nX&Y:\t"); Demo(X&Y); printf("\nX|Y:\t"); Demo(X|Y); printf("\nX^Y:\t"); Demo(X^Y); printf("\nY:\t"); Demo(Y); printf("\nY>>1:\t"); Demo(Y>>1); printf("\nY<<2:\t"); Demo(Y<<2); printf("\n\n Jeszcze raz? T/N"); odp=getch(); if (odp!='T'&& odp!='t') break; } return 0; } Je�li operacje maj by� wykonywane nie na bitach a na logicznej warto�ci wyra�e�:

Page 48: C++ - lekcje

48

|| oznacza sum� (LUB); && oznacza iloczyn (I); ! oznacza negacj� (NIE). Przykłady: (x==0 || x>5) - x równa si� 0 LUB x wi�kszy ni� 5; (a>5 && a!=11) - a wi�ksze ni� 5 I a nie równe 11; (num>=5 && num!=6 || a>0) num nie mniejsze ni� 5 I num nie równe 6 LUB a dodatnie; Wyra�enia logiczne sprawdzane instrukcj if MUSZ by� uj�te w nawiasy okr głe. Do wytworzenia warto�ci logicznej wyra�enia mo�e zosta� u�yty operator relacji: < <= == >= > != . Je�li tak si� nie stanie, za warto�� logiczn wyra�enia przyjmowane jest: 1, PRAWDA, TRUE, je�li warto�� numeryczna wyra�enia jest ró�na od zera. 0, FAŁSZ, FALSE, je�li warto�� numeryczna wyra�enia jest równa zero. Porównaj: if (a<=0) ... if (a) ... if (a+b) ... Konwersja - przykłady. C++ dysponuje wieloma funkcjami wykonuj cymi takie działania, np: itoa() - Integer TO Ascii - zamiana liczby typu int na ła�cuch znaków ASCII; ltoa() - Long int TO Ascii - zamiana long int -> ASCII; atoi() - zamiana Ascii -> int; atol() - zamiana Asdii -> long int . Wszystkie wymienione funkcje przekształcaj c liczby na ła�cuchy znaków potrzebuj trzech parametrów: p1 - liczby do przekształcenia; p2 - bufora, w którym b�d przechowywa� wynik - ła�cuch ASCII; p3 - podstawy (szesnastkowa, dziesi�tna itp.). Je�li chcemy korzysta� z tych funkcji, powinni�my doł czy� plik nagłówkowy z ich prototypami - stdlib.h (STandarD LIBrary - standardowa biblioteka). A oto przykład. [P043.CPP] # include "stdio.h" # include "stdlib.h" main() { int i; char B10[10], B2[20], B16[10]; //BUFORY for (i=1; i<17; i++) printf("%s %s %s\n", itoa(i, B10[i], 10), itoa(i, B2[i], 2), itoa(i, B16[i], 16)); return 0; } [Z] ________________________________________________________________ 1. Opracuj program testuj cy działanie funkcji atoi(). ________________________________________________________________ KILKA SŁÓW O TYPACH DANYCH i KONWERSJI W C/C++ . Przed przyst pieniem do obszernego zagadnienia "funkcje w C" krótko zasygnalizujemy jeszcze jedno zjawisko. Wiesz z pewno�ci , �e wykonywane na liczbach dwójkowych mno�enie mo�e da� wynik o długo�ci znacznie wi�kszej ni� mno�na i mno�nik. W programach mo�e si� poza tym pojawi� konieczno�� np. mno�enia liczb zmiennoprzecinkowych przez całkowite. Jak w takich przypadkach post�puje C++ ?

Po pierwsze: C/C++ mo�e sam dokonywa� konwersji, czyli zmiany typów danych naogół zgodnie z zasad nadawania zmiennej "mniej pojemnego" rodzaju typu zmiennej "bardziej pojemnego" rodzaju przed wykonaniem operacji; Po drugie: my sami mo�emy zmusi� C++ do zmiany typu FORSUJC typ �wiadomie w programie. W przykładzie poni�ej podaj c w nawiasach � dany typ zmiennej forsujemy zmian� typu int na typ float. [P044.CPP] # include "stdio.h" void main() { int a=7; printf("%f", (float) a); } Konwersja typów nazywana bywa tak�e "rzutowaniem" typów (ang. type casting). A oto kilka przykładów "forsowania typów": int a = 2; float x = 17.1, y = 8.95, z; char c; c = (char)a + (char)x; c = (char)(a + (int)x); c = (char)(a + x); c = a + x; z = (float)((int)x * (int)y); z = (float)((int)x * (int)y); z = (float)((int)(x * y)); z = x * y; c = char(a) + char(x); c = char(a + int(x)); c = char(a + x); c = a + x; z = float(int(x) * int(y)); z = float(int(x) * int(y)); z = float(int(x * y)); z = x * y; FUNKCJE BIBLIOTECZNE I WŁASNE W J�ZYKU C/C++ . Poj�cie funkcji obejmuje w C/C++ zarówno pascalowe procedury, jak i basicowe podprogramy. Funkcji zdefiniowanych w C++ przez prducenta jest bardzo du�o. Dla przykładu, funkcje arytmetyczne, które mo�esz wykorzysta� do oblicze� numerycznych to np.: abs() - warto�� bezwzgl�dna, cos() - cosinus, sin() - sinus, tan() - tangens, asin(), atan(), acos(), - funkcje odwrotne ARCUS SINUS... funkcje hiperboliczne: sinh(), cosh(), tanh(), wykładnicze i logarytmiczne: exp() - e^x log() - logarytm naturalny, log10() - logarytm dziesi�tny. Je�li skorzystasz z systemu Help i zajrzysz do pliku math.h (Help | Index | math.h), znajdziesz tam jeszcze wiele przydatnych funkcji. Funkcja mo�e, ale nie musi zwraca� warto�� do programu - dokładniej do funkcji wy�szego poziomu, z której została wywołana. W ciele funkcji słu�y do tego instrukcja return. U�ytkownik mo�e w C++ definiowa� własne funkcje. Funkcja mo�e by� bezparametrowa. Oto przykład bezparametrowej funkcji, zwracaj cej zawsze liczb� całkowit trzyna�cie: int F_Trzynascie() { return 13; }

Page 49: C++ - lekcje

49

Poprawne wywołanie naszej funkcji w programie głównym miałoby posta�: int main() { ...... int X; ........ // Funkcja typu int nie musi byc deklarowana. X = F_Trzynascie(); ...... } Je�li funkcja musi pobra� jakie� parametry od programu (funkcji wy�szego poziomu, wywołuj cej)? Zwró� uwag�, �e program główny w C/C++ to te� funkcja - main(). Przykład nast�pny pokazuje definicj� funkcji obliczaj cej pi t pot�g� pobranego argumentu i wywołanie tej funkcji w programie głównym. Przykład: int F_XdoPiatej(int argument) { int robocza; //automatyczna wewnetrzna zmienna funkcji robocza = argument * argument; robocza = robocza * robocza * argument; return (robocza); } int main() { int Podstawa, Wynik, a, b; ... /* Funkcja nie jest deklarowana przed uzyciem */ Wynik = F_XdoPiatej(Podstawa); ..... a = F_XdoPiatej(b); ..... return 0; } Zwró� uwag�, �e definiuj c funkcj� podajemy nazw� i typ ARGUMENTU FORMALNEGO funkcji - tu: argument. W momencie wywołania na jego miejsce podstawiany jest rzeczywisty bie� cy argument funkcji. Aby zapewni� wysok dokładno�� oblicze� wymienione wy�ej funkcje biblioteczne sqrt(), sin() itp. "uprawiaj " arytmetyk� na długich liczbach typu double. Funkcj� tak przed u�yciem w swoim programie MUSISZ ZADEKLAROWA�. Przykład: [P045.CPP] main() { double a, b; double sqrt(); // tu skasuj deklaracje funkcji sqrt() // a otrzymasz bledny wynik ! clrscr(); printf("Podaj liczbe\n"); scanf("%lf", &a); b = sqrt(a); printf("\n %Lf", (long double) b); getch(); return 0; } PROTOTYPY FUNKCJI, czyli jeszcze o deklaracjach funkcji. Prototyp funkcji to taka deklaracja, która: * została umieszczona na pocz tku programu poza funkcj main(), * zawiera deklaracj� zarówno typu funkcji, jak i typów argumentów. Przykład prototypu (funkcja2.cpp): double FUNKCJA( double X, double Y); main() {

double A=0, B=3.14; printf("Wynik działania funkcji: \n"); printf("%lf", FUNKCJA(A,B)); return 0; } double FUNKCJA(double X, double Y) { return ((1+X)*Y); } Prototyp mógłby równie dobrze wygl da� tak: double FUNKCJA(double, double); nazwy parametrów formalnych nie s istotne i mo�na je pomin �. Je�li prototyp funkcji wygl da tak: int Funkcja(int, char*, &float) oznacza to, �e parametrami funkcji s wska�niki do zmiennych, b d� referencje do zmiennych. Przy rozszyfrowywaniu takiej "abrakadabry" warto wiedzie�, �e char* oraz char * int& oraz int & ma w tym przypadku identyczne znaczenie. W C++ wolno nie zwraca� warto�ci funkcjom typu void. To dlatego wła�nie cz�sto rozpoczynali�my programy od void main() Skutek praktyczny: Je�li w ciele funkcji typu void wyst�puje instrukcja return (nie musi wyst pi�) to instrukcja ta nie mo�e mie� argumentów. Oto przykład prototypu, definicji i wywołania funkcji typu void: [P046.CPP] #include <stdio.h> #include <conio.h> void RYSUJPROSTOKAT( int Wys, int Szer, char Wzorek); void main() { clrscr(); RYSUJPROSTOKAT(5, 20, '�'); // klocek ASCII 176 - [Alt]-[176] getch(); RYSUJPROSTOKAT(15, 15, '�'); //[Alt]-[177] getch(); } void RYSUJPROSTOKAT( int Wys, int Szer, char Wzorek) { int i, j; // automatyczne zmienne wewn�trzne funkcji for(i=1; i<=Wys; i++) { for(j=1; j<=Szer; j++) printf("%c",Wzorek); printf("\n"); } } Prototypy wszystkich funkcji standardowych znajduj si� w plikach nagłówkowych *.H (ang. Header file). Skutek praktyczny: JE�LI DOŁCZYSZ DO PROGRAMU STOSOWNE PLIKI NAGŁÓWKOWE *.h,mo�esz ZREZYGNOWA� Z DEKLARACJI FUNKCJI. Dodaj c do programu wiersz: #include <math.h> doł czaj cy plik zawieraj cy prototyp funkcji sqrt(), mo�esz napisa� program tak: #include <stdio.h> #include <math.h> main()

Page 50: C++ - lekcje

50

{ double a, b; clrscr(); printf("Podaj liczbe\n"); scanf("%lf", &a); b = sqrt(a); printf("\n %Lf", (long double) b); getch(); return 0; } PRZEKAZYWANIE PARAMETRÓW DO FUNKCJI. W C++ cz�sto przekazuje si� parametry do funkcji przy pomocy wska�ników. Aby prze�ledzi� co dzieje si� wewn trz funkcji wpisz i uruchom podany ni�ej program przykładowy. Najpierw skonstruujemy sam program a nast�pnie zmodyfikujemy go w taki sposób, aby� mógł sobie popodgl da� cały proces. Przy pomocy funkcji printf() ka�emy wydrukowa� kolejne stany zmiennych, stan programu i funkcji, a funkcja getch() pozwoli Ci obejrze� to "krok po kroku". Mogłoby si� wydawa�, �e program poni�ej skonstruowany jest poprawnie... void FUNKCJA( int ); //Prototyp, deklaracja funkcji void main() { int Zmienna; //Zmienna funkcji main, rzeczywisty argument clrscr(); Zmienna = 7; FUNKCJA( Zmienna); //Wywolanie funkcji printf("%d", Zmienna); //Wydruk wyniku } void FUNKCJA( int Argument) //Definicja funkcji { Argument = 10 * Argument + Argument; } FUNKCJA() jest jak wida� trywialna. b�dzie zamienia� np. 2 na 22, 3 na 33 itp. tylko w tym celu, by łatwo było stwierdzi�, czy funkcja zadziałała czy nie. Rozbudujmy program tak by prze�ledzi� kolejne stadia. [P047.CPP] void FUNKCJA( int ); //Prototyp int Zmienna; void main() { clrscr(); printf("Stadium: \tZmienna Argument"); printf("\nStadium 1\t%d\tnie istnieje\n", Zmienna); Zmienna = 7; printf("Stadium 2\t%d\tnie istnieje\n", Zmienna ); FUNKCJA( Zmienna); printf("Stadium 3\t%d", Zmienna); // printf("%d", Argument); // taka proba sie NIE UDA ! getch(); } void FUNKCJA( int Argument) //Definicja funkcji { printf("jestesmy wewnatrz funkcji\n"); printf("Nastapilo kopiowanie Zmienna -> Argument\n" ); printf("\t\t%d\t%d\n", Zmienna, Argument); getch(); Argument = 10*Argument + Argument; printf("\t\t%d\t%d\n", Zmienna, Argument); getch(); }

Próba wydrukowania zmiennej Argument gdziekolwiek poza wn�trzem FUNKCJI() nie uda si� i spowoduje komunikat o bł�dzie. Oznacza to, �e POZA FUNKCJ zmienna Argument NIE ISTNIEJE. Jest tworzona na stosie jako zmienna automatyczna na wył czny u�ytek funkcji, w której została zadeklarowana i znika po wyj�ciu z funkcji. Przy takiej organizacji funkcji i programu funkcja otrzymuje kopi� zmiennej, na niej wykonuje swoje działania, natomiast zmienna (zmienne) wewn�trzna funkcji znika po wyj�ciu z funkcji. Problem przekazania parametrów pomi�dzy funkcjami wywołuj cymi ("wy�szego rz�du" - tu: main) i wywoływanymi (tu: FUNKCJA) mo�na rozwi za� przy pomocy * instrukcji return (zwrot do programu jednej warto�ci) lub * wska�ników. Mo�emy przecie� funkcji przekaza� nie sam zmienn , a wska�nik do zmiennej (robili�my to ju� w przypadku funkcji scanf() - dlatego, �e samej zmiennej jeszcze nie było - miała zosta� dopiero pobrana, ale istniało ju� przeznaczone na t now zmienn - zarezerwowane dla niej miejsce. Mogł zatem istnie� wska�nik wskazuj cy to miejsce). wska�nik nale�y oczywi�cie zadeklarowa�. Nasz program przybrałby zatem now posta�. Wska�nik do zmiennej nazwiemy *Argument. [P048.CPP] //Pamietaj o plikach naglowkowych ! void FUNKCJA( int *Argument); //Prototyp int Zmienna; void main() { clrscr(); printf("Stadium: \tZmienna Argument"); printf("\nStadium 1\t%d\tnie istnieje\n", Zmienna); Zmienna = 7; printf("Stadium 2\t%d\tnie istnieje\n", Zmienna ); FUNKCJA( &Zmienna); //Pobierz do funkcji ADRES Zmiennej printf("Stadium 3\t%d", Zmienna); // printf("%d", Argument); // taka proba sie NIE UDA ! getch(); } void FUNKCJA( int *Argument) // Definicja funkcji { printf("jestesmy wewnatrz funkcji\n"); printf("Nastapilo kopiowanie ADRESOW a nie zmiennej\n" ); printf("ADRESY:\t\t %X\t%X\n", &Zmienna, Argument); getch(); *Argument = 10* *Argument + *Argument; /* DZIALANIE */ printf("\t\t%d\t%d\n", Zmienna, *Argument); getch(); } W linii /* DZIALANIE */ mno�ymy i dodajemy to, co wskazuje wska�nik, czyli Zmienn . Funkcja działa zatem nie na własnej kopii zmiennej a bezpo�rednio na zmiennej zewn�trznej. Zwró� uwag� na analogi� w sposobie wywołania funkcji: FUNKCJA( &Zmienna ); scanf( "%d", &Zmienna ); A je�li argumentem funkcji ma by� tablica? Rozwa� przykładowy program. Program zawiera pewn nadmiarowo�� (ku wi�kszej jasno�ci mechanizmów). [P049.CPP] # include <conio.h> # include <stdio.h> SUMA( int k, int Tablica[] ) { int i, SumTab=0; for (i=0; i<k; i++) { SumTab = SumTab + Tablica[i]; printf("%d + ", Tablica[i]);

Page 51: C++ - lekcje

51

} printf("\b\b= %d", SumTab); return SumTab; } int suma=0, N; char Odp; int TAB[10] = {1,2,3,4,5,6,7,8,9,10}; main() { clrscr(); do { printf("\n Ile wyrazow tablicy dodac ??? \n"); scanf("%d", &N); if (N>10) { printf("TO ZA DUZO ! - max. 10"); continue; } suma = SUMA( N,TAB ); printf("\nTO JEST suma z progr. glownego %d", suma); printf("\n Jeszcze raz ? T/N"); Odp = getch(); } while (Odp!='N' && Odp!='n'); return 0; } Kompilacja w C++ jest wieloprzebiegowa (PASS 1, PASS 2), wi�c definicja funkcji mo�e by� zarówno na pocz tku jak i na ko�cu. A oto nast�pny przykład. Operuj c adresem - wska�nikiem do obiektu (tu wska�nikami do dwu tablic) funkcja Wypelniacz() zapisuje pod wskazany adres ci g identycznych znaków. Na ko�cu ka�dego ła�cucha znaków zostaje dodany NUL - (\0) jako znak ko�ca. Taki format zapisu ła�cuchów znakowych nazywa si� ASCIIZ. [P050.CPP] void Wypelniacz(char *BUFOR, char Znak, int Dlugosc); char TAB2D[5][10]; // Tablica 5 X 10 = 50 elementow char TAB_1D[50]; // Tablica 1 X 50 = 50 elementow int k; main() { clrscr(); Wypelniacz( TAB_1D, 'X', 41); //Wypelnia X-ami printf("%s\n\n", TAB_1D); for (k=0; k<5; k++) Wypelniacz( TAB2D[k], 65+k, 9); //ASCII 65 to 'A'; 66 to 'B' itd. for (k=0; k<5; k++) printf("%s\n", TAB2D[k]); getch(); return 0; } void Wypelniacz( char *BUFOR, char Znak, int Dlugosc ) { int i; for ( i=0; i<=(Dlugosc-1); i++) *(BUFOR+i) = Znak; *(BUFOR+Dlugosc) = '\0'; } Zwró� uwag�, �e: * NAZWA TABLICY (tu: TAB_1D i TAB2D) funkcjonuje jako wska�nik PIERWSZEGO ELEMENTU TABLICY. FUNKCJE TYPU WSKA�NIKOWEGO. Funkcje mog zwraca� do programu zarówno warto�ci typu int, czy float, jak i warto�ci typu ADRESU. Podobnie jak wska�nik wymaga deklaracji i podania w deklaracji na jakiego typu obiekty b�dzie wskazywał, podobnie funkcja takiego typu wymaga w deklaracji okre�lenia typu wskazywanych obiektów. Wiesz ju�, �e zale�y od tego tzw. krok wska�nika. W przykładzie poni�ej funkcja Minimum() poszukuje najmniejszego elementu tablicy i zwraca wska�nik do tego� elementu. Znaj c lokalizacj� najmniejszego elementu mo�emy utworzy� now tablic�, ale ju� uporz dkowan

według wielko�ci. [P051.CPP] int BALAGAN[10]; int PORZADEK[10]; // Tablica koncowa - uporzadkowana int k, *pointer , MAX=10000 ; int *Minimum(int Ilosc, int *TABL); main() { clrscr(); printf("Podaj 10 liczb calkowitych od -10000 do 10000\n"); for (k=0; k<=9; k++) scanf("%d", &BALAGAN[k]); printf("Po kolei: \n\n"); for ( k=0; k<=9; k++ ) { �����pointer=Minimum(10, BALAGAN); �����PORZADEK[k]=*pointer; �����*pointer=MAX; } for(k=0; k<=9; k++) printf("%d ", PORZADEK[k]); getch(); return 0; } int *Minimum( int Ilosc, int *TABL ) { int *pMin; int i; pMin=TABL; for (i=1; i<Ilosc; i++) { if (*(TABL+i) < *pMin) pMin=(TABL+i); } return (pMin); } WSKA�NIKI DO FUNKCJI. W C++ mo�emy nie tylko podstawi� dan w miejsce zmiennej (co jest trywialn i oczywist operacj we wszystkich j�zykach programowania), ale mo�emy tak�e podstawia� na miejsce funkcji stosowanej w programie t� funkcj�, która w danym momencie jest nam potrzebna. Aby wskaza� funkcj� zastosujemy, jak sama nazwa wskazuje - WSKA�NIK DO FUNKCJI. Aby unikn � deklarowania funkcji standardowych i by� w zgodzie z dobrymi manierami nie zapomnimy o doł czeniu pliku z prototypami. Deklaracj� double ( *FUNKCJA ) (double); nale�y rozumie�: "Przy pomocy wska�nika do funkcji *FUNKCJA wolno nam wskaza� takie funkcje, które * pobieraj jeden argument typu double float; * zwracaj do programu warto�� typu double float. " Dost�pne s dla nas zatem wszystkie standardowe funkcje arytmetyczne z pliku MATH.H (MATH pochodzi od MATHematics - matematyka.) [P052.CPP] # include <conio.h> # include <math.h> double NASZA( double ); //Deklaracja zwyklej funkcji double (*Funkcja)(double ARG); //pointer do funkcji double Liczba, Wynik; //Deklaracje zmiennych int WYBOR; main() { clrscr(); printf("Podaj Liczbe \n"); scanf("%lf", &Liczba); printf("CO MAM ZROBIC ?\n"); printf("1 - Sinus \n"); printf("2 - Pierwiastek\n"); printf("3 - Odwrotnosc 1/x\n");

Page 52: C++ - lekcje

52

scanf("%d", &WYBOR); switch(WYBOR) { case 1: Funkcja=sin; break; case 2: Funkcja=sqrt; break; case 3: Funkcja=NASZA; break; } Wynik=Funkcja(Liczba); // Wywolanie wybranej funkcji printf("\n\nWYNIK = %lf", Wynik); getch(); return 0; } double NASZA(double a) { printf("\n A TO NASZA PRYWATNA FUNKCJA\n"); if (a!=0) a=1/a; else printf("???\n"); return a; } main() - FUNKCJA SPECJALNA. Ta ksi �ka sił rzeczy, ze wzgl�du na swoj skromn obj�to�� i skal� zagadnienia o którym traktuje (autor jest zdania, �e j�zyk C to cała filozofia nowoczesnej informatyki "w pigułce") pełna jest skrótów. Nie mo�emy jednak pozostawi� bez, krótkiego cho�by, opisu pomijanego dyskretnie do tej pory problemu PRZEKAZANIA PARAMETRÓW DO PROGRAMU. Konwencja funkcji w j�zyku C/C++ wyra�nie rozgranicza dwa ró�ne punkty widzenia. Funkcja pozwala na swego rodzaju separacj� �wiata wewn�trznego (lokalnego, własnego) funkcji od �wiata zewn�trznego. Nie zdziwi Ci� wi�c zapewne, �e i sposób widzenia parametrów przekazywanych programowi przez DOS i sposób widzenia "od wewn trz" argumentów pobierabych przez funkcj� main() jest diametralnie ró�ny. To, co DOS widzi tak: PROGRAM PAR1 PAR2 PAR3 PAR4 PAR5 [...][Enter] funkcja main() widzi tak: main(int argc, char **argv, char **env) lub tak: main(int argc, char *argv[], char *env[]) [???]CO TO JEST ??? ________________________________________________________________ Zapisane zgodnie z obyczajami stosowanymi w prototypach funkcji: int argc - liczba całkowita (>=1, bo parametr Nr 1 to nazwa samego programu, za po�rednictwem której DOS wywołuje funkcj� main). Liczba argumentów - parametrów mo�e by� zmienna. UWAGA: J�zyk programowania wsadowego BPL przyjmuje nazw� programu za parametr %0 a C++ uznaje j za parametr o numerze argv[0], tym niemniej, nawet je�li nie ma �adnych parametrów argc = 1. argv - to tablica zawieraj ca wska�niky do ła�cuchów tekstowych reprezentowanych w kodzie ASCIIZ - nazw kolejnych paramentrów, z którymi został wywołany program. Pierszy element tej tablicy to nazwa programu. Ostatni element tej tablicy, o numerze argv - 1 to ostatni niezerowy parametr wywołania programu. env - to tak�e tablica zawieraj ca wska�niki do ła�cuchów znakowych w kodzie ASCIIZ reprezentuj cych parametry �rodowiska (environment variables). Wska�nik o warto�ci NUL sygnalizuje koniec tablicy. W Turbo C++ istnieje tak�e predefiniowana zmienna globalna (::), przy pomocy której mo�na uzyska� dost�p do �rodowiska operacyjnego - environ . ________________________________________________________________ Przykłady poni�ej przedstawiaj sposób wykorzystania parametrów wej�ciowych programu.

[P053.CPP] # include "stdio.h" # include "stdlib.h" main(int argc, char *argv[], char *env[]) { printf("Parametry srodowiska DOS: \n"); int i = 0; do { printf("%s \n", env[i]); i++; }; while (env[i] != NULL); printf("Lista parametrow programu: \n"); for(i=1; i<= argc - 1; i++) printf("%s \n", argv[i]); printf("Nazwa programu: \n"); printf("%s", argv[0]); return 0; } Poniewa� C++ traktuje nazw� tablicy i wska�nik do tablicy w specjalny sposób, nast�puj ce zapisy s równowa�ne: *argv[] oraz **argv *env[] oraz **env Nazwy argumentów argc, argv i env s zastrze�one i musz wyst�powa� zawsze w tej samej kolejno�ci. Argumenty nie musz wyst�powa� zawsze w komplecie. Dopuszczalne s zapisy: main(int argc, char **argv, char **env) main(int argc, char *argv[]) main(int argc) main() ale niedopuszczalny jest zapis: main(char *env[]) Nawet je�li nie zamierzamy wykorzysta� "wcze�niejszych" parametrów - MUSIMY JE PODA�. [Z] ________________________________________________________________ 1. Spróbuj tak zmodyfikowa� funkcj� Demo(), by liczba w formie dwójkowej była pisana "od tyłu". Do cofania kursora w funkcji printf u�yj sekwencji \b\b. 2. Zinterpretuj zapis: if (MIANOWNIK) printf("%f", 1/MIANOWNIK); else exit(1); 3 Spróbuj przeprowadzi� rzutowanie typu we własnym programie. 4 Przeka� warto�� w programie przykładowym posługuj c si� instrukcj : return (10*Argument + Argument); 5 Rozszerz zestaw funkcji do wyboru w programie przykładowym. LEKCJA 16 - ASEMBLER TASM i BASM. ________________________________________________________________ W trakcie tej lekcji: * dowiesz si� , jak ł czy� C++ z assemblerem * poznasz wewn�trzne formaty danych ________________________________________________________________ WEWN�TRZNY FORMAT DANYCH I WSPÓŁPRACA Z ASSEMBLEREM. W zale�no�ci od wybranej wersji kompilatora C++ zasady współpracy z asemblerem mog si� troch� ró�ni�. Generalnie, kompilatory współpracuj z tzw. asemblerami in-line (np. BASM), lub asemblerami zewn�trznymi (stand alone assembler np. MASM, TASM). Wstawki w programie napisane w assemblerze powinny zosta� poprzedzone słowem asm (BORLAND/Turbo C++), b d� _asm (Microsoft

Page 53: C++ - lekcje

53

C++). Przy kompilacji nale�y zatem stosownie do wybranego kompilatora przestrzega� specyficznych zasad współpracy. Np. dla BORLAND/Turbo C++ mo�na stosowa� do kompilacji BCC.EXE/TCC.EXE przy zachowaniu warunku, �e TASM.EXE jest dost�pny na dysku w bie� cym katalogu. Typowymi sposobami wykorzystania assemblera z poziomu C++ s : * umieszczenie ci gu instrukcji assemblera bezpo�rednio w �ródłowym tek�cie programu napisanym w j�zyku C/C++, * doł czeniu do programu zewn�trznych modułów (np. funkcji) napisanych w assemblerze. W C++ w tek�cie �ródłowym programu blok napisany w asemblerze powinien zosta� poprzedzony słowem kluczowym asm (lub _asm): # pragma inline void main() { asm mov dl, 81 asm mov ah, 2 asm int 33 } Program b�dzie drukował na ekranie liter� "Q" (ASCII 81). JAK POSŁUGIWA� SI� DANYMI W ASEMBLERZE. Napiszemy w asemblerze program drukuj cy na ekranie napis "tekst - test". Rozpczynamy od zadeklarowania ła�cucha znaków: void main() { char *NAPIS = "tekst - test$"; /* $ - ozn. koniec */ Umie�cili�my w pami�ci ła�cuch, b�d cy w istocie tablic składaj c si� z elementów typu char. Wska�nik do ła�cucha mo�e zosta� zast piony nazw -identyfikatorem tablicy. Zwró� uwag�, �e po ła�cuchu znakowym dodali�my znak '$'. Dzi�ki temu mo�emy skorzysta� z DOS'owskiej funkcji nr 9 (string-printing DOS service 9). Mo�emy utworzy� kod w asemblerze: asm mov dx, NAPIS asm mov ah, 9 asm int 33 Cały program b�dzie wygl dał tak: [P054.CPP] # pragma inline void main() { char *NAPIS = "\n tekst - test $"; asm { MOV DX, NAPIS MOV AH, 9 INT 33 } } Zmienna NAPIS jest pointerem i wskazuje adres w pami�ci, od którego rozpoczyna si� ła�cuch znaków. Mo�emy przesła� zmienn NAPIS bezpo�rednio do rejestru i przekaza� wprost przerywaniu Int 33. Program assemblerowski (tu: TASM) mógłby wygl da� np. tak: [P055.ASM] .MODEL SMALL ;To zwylke robi TCC .STACK 100H ;TCC dodaje standardowo 4K .DATA NAPIS DB 'tekst - test','$' .CODE START: MOV AX, @DATA MOV DS, AX ;Ustawienie segmentu danych ASM:

MOV DX, OFFSET NAPIS MOV AH, 9 INT 21H ;Drukowanie KONIEC: MOV AH, 4CH INT 21H ;Zako�czenie programu END START Inne typy danych mo�emy stosowa� podobnie. Wygodn taktyk jest deklarowanie danych w tej cz��ci programu, która została napisana w C++, aby inne fragmenty programu mogły si� do tych danych odwoływa�. Mo�emy we wstawce asemblerowskiej odwoływa� si� do tych danych w taki sposób, jakgdyby zostały zadeklarowane przy u�yciu dyrektyw DB, b d� DW. WEWN�TRZNE FORMATY DANYCH W C++. LICZBY CAŁKOWITE typów char, short int i long int. Liczba całkowita typu short int stanowi 16-bitowe słowo i mo�e zosta� zastosowana np. w taki sposób: [P056.CPP] #pragma inline void main() { char *napis = "\nRazem warzyw: $"; int marchewki = 2, pietruszki = 5; asm { MOV DX, napis MOV AH, 9 INT 33 MOV DX, marchewki ADD DX, pietruszki ADD DX, '0' MOV AH, 2 INT 33 } } Zdefiniowali�my dwie liczby całkowite i ła�cuch znaków - napis. Poniewa� obie zmienne (ła�cuch znków jest stał ) maj długo�� jednego słowa maszynowego, to efekt jest taki sam, jakgdyby zmienne zostały zadeklarowane przy pomocy dyrektywy asemblera DW (define word). Mo�emy pobra� warto�� zmiennej marchewki do rejestru instrukcj MOV DX, marchewki ;marchewki -> DX W rejestrze DX dokonujemy dodawania obu zmiennych i wyprowadzamy na ekran sum�, posługuj c si� funkcj 2 przerywania DOS 33 (21H). W wyniku działania tego programu otrzymamy na ekranie napis: Razem warzyw: 7 Jeczsze jeden szczegół techniczny. Poniewa� stosowana funkcja DOS pracuje w trybie znakowym i wydrukuje nam znak o kodzie ASCII przechowywanym w rejestrze, potrzebna jest manipulacja: ADD DX, '0' ;Dodaj kod ASCII "zera" do rejestru Mo�esz sam sprawdzi�, �e po przekroczeniu warto�ci 9 przez sum� wszystko si� troch� skomplikuje (kod ASCII zera - 48). Z równym skutkiem mo�naby zastosowa� rozkaz ADD DX, 48 Je�li prawidłowo dobierzemy format danych, fragment programu napisany w asemblerze mo�e korzysta� z danych dokładnie tak samo, jak ka�dy inny fragment programu napisany w C/C++. Mo�emy zastosowa� dane o jednobajtowej długo�ci (je�li drugi, pusty bajt nie jest nam potrzebny). Zwró� uwag�, �e posługujemy si� w tym przypadku tylko "połówk " rejestru DL (L - Low - młodszy). [P057.CPP] #pragma inline

Page 54: C++ - lekcje

54

void main() { const char *napis = "\nRazem warzyw: $"; char marchewki = 2, pietruszki = 5; asm { MOV DX, napis MOV AH, 9 INT 33 MOV DL, marchewki ADD DL, pietruszki ADD DL, '0' MOV AH, 2 INT 33 } } W tej wersji zadeklarowali�my zmienne marchewki i pietruszki jako zmienne typu char, co jest równoznaczne zadeklarowaniu ich przy pomocy dyrektywy DB. Zajmijmy si� teraz maszynow reprezentacj liczb typu unsigned long int (długie całkowite bez znaku). Ze wzgl�du na specyfik� zapisu danych do pami�ci przez mikroprocesory rodziny Intel 80x86 długie liczby całkowite (podwójne słowo - double word) np. 12345678(hex) s przechowywane w pami�ci w odwróconym szyku. Zamieniony miejscami zostaje starszy bajt z młodszym jak równie� starsze słowo z młodszym słowem. Liczba 12345678(hex) zostanie zapisana w pami�ci komputera IBM PC jako 78 56 34 12. Gdy inicjujemy w programie zmienn long int x = 2; zostaje ona umieszczona w pami�ci tak: 02 00 00 00 (hex). Młodsze słowo (02 00) jest umieszczone jako pierwsze. To wła�nie słowo zawiera interesuj c nas informacj�, mo�emy wczyta� to słowo do rejestru rozkazem MOV DX, X Je�li b�dzie nam potrzebna druga połówka zmiennej - starsze słowo (umieszczone w pami�ci jako nast�pne), mo�emy zastosowa� pointer (czyli poda� adres nast�pnego słowa pami�ci). [P058.CPP] # pragma inline void main() { unsigned long marchewki = 2, pietruszki = 5; const char *napis = "\nRazem warzyw: $"; asm { MOV DX, napis MOV AH, 9 INT 33 MOV DX, marchewki ADD DX, pietruszki ADD DX, '0' MOV AH, 2 INT 33 } } W przypadku liczb całkowitych ujemnych C++ stosuje zapis w kodzie komplementarnym. Aby móc manipulowa� takimi danymi ka�dy szanuj cy si� komputer powinien mie� mo�liwo�� stosowania liczb ujemnych. Najstarszy bit w słowie, b d� bajcie (pierwszy z lewej) mo�e spełnia� rol� bitu znakowego. O tym, czy liczba jest ze znakiem, czy te� bez decyduje wył cznie to, czy zwracamy uwag� na ten bit. W liczbach bez znaku, oboj�tnie, czy o długo�ci słowa, czy bajtu, ten bit równie� jest (i był tam zawsze!), ale traktowali�my go, jako najstarszy bit nie przydaj c mu poza tym �adnego szczególnego znaczenia. Aby liczba stała si� liczb ze znakiem - to my musimy zacz � j traktowa� jako liczb� ze znakiem, czyli zacz � zwraca� uwag� na ten pierwszy bit. Pierwszy, najstarszy bit liczby ustawiony do stanu 1 b�dzie

oznacza�, �e liczba jest ujemna - je�li zechcemy j potraktowa� jako liczb� ze znakiem. Filozofia post�powania z liczbami ujemnymi opiera si� na banalnym fakcie: (-1) + 1 = 0 Twój PC "rozumuje" tak: -1 to taka liczba, która po dodaniu 1 stanie si� 0. Czy mo�na jednak�e wyobrazi� sobie np. jednobajtow liczb� dwójkow , która po dodaniu 1 da nam w rezultacie 0 ? Wydawałoby si�, �e w dowolnym przypadku wynik powinien by� conajmniej równy 1. A jednak. Je�li ograniczymy swoje rozwa�ania do o�miu bitów jednego bajtu, mo�e wyst pi� taka, absurdalna tylko z pozoru sytuacja. Je�li np. dodamy 255 + 1 (dwójkowo 255 = 11111111): 1111 1111 hex FF dec 255 + 1 + 1 + 1 ___________ _____ _____ 1 0000 0000 100 256 otrzymamy 1 0000 0000 (hex 100). Dla Twojego PC oznacza to, �e w o�miobitowym rejestrze pozostanie 0000 0000 , czyli po prostu 0. Nast pi natomiast przeniesienie (carry) do dziewi tego (nie zawsze istniej cego sprz�towo bitu). Wyst pienie przeniesienia powoduje ustawienie flagi CARRY w rejestrze FLAGS. Je�li zignorujemy flag� i b�dziemy bra� pod uwag� tylko te osiem bitów w rejestrze, oka�e si�, �e otrzymali�my wynik 0000 0000. Krótko mówi c FF = (-1), poniewa� FF + 1 = 0. Aby odwróci� wszystkie bity bajtu, b d� słowa mo�emy w asemblerze zastosowa� instrukcj� NOT. Je�li zawarto�� rejestru AX wynosiła np. 0000 1111 0101 0101 (hex 0F55), to instrukcja NOT AX zmieni j na 1111 0000 1010 1010 (hex F0AA). Dokładnie tak samo działa operator bitowy ~_AX w C/C++. W zestawie rozkazów mikroprocesorów rodziny Intel 80x86 jest tak�e instrukcja NEG, powoduj ca zamian� znaku liczby (dokonuj c konwersji liczby na kod komplementarny). Instrukcja NEG robi to samo, co NOT, ale po odwróceniu bitów dodaje jeszcze jedynk�. Je�li rejestr BX zawierał 0000 0000 0000 0001 (hex 0001), to po operacji NEG AX zawarto�� rejestru wyniesie 1111 1111 1111 1111 (hex FFFF). Zastosujmy praktycznie uzupełnienia dwójkowe przy współdziałaniu asemblera z C++: [P059.CPP] #pragma inline void main() { const char *napis = "\nRazem warzyw: $"; int marchewki = -2, pietruszki = 5; asm { MOV DX, napis MOV AH, 9 INT 33 MOV DX, marchewki NEG DX ADD DX, pietruszki ADD DX, '0' MOV AH, 2 INT 33 } } Dzi�ki zamianie (-2) na 2 przy pomocy instrukcji NEG DX otrzymamy wynik, jak poprzednio równy 7. Przypomnijmy prezentacj� działania operatorów bitowych C++. Wykorzystaj program przykładowy do przegl du bitowej reprezentacji liczb typu int (ze znakiem i bez). [P060.CPP]

Page 55: C++ - lekcje

55

/* Program prezentuje format liczb i operatory bitowe */ # include "iostream.h" # pragma inline void demo(int liczba) //Definicja funkcji { int n = 15; for (; n >= 0; n--) if ((liczba >> n) & 1) cout << "1"; else cout << "0"; } char odp; char *p = "\nLiczby rozdziel spacja $"; int main() { int x, y; cout ˙<< "\nPodaj dwie liczby calkowite od -32768 do +32767\n"; asm { mov dx, p mov ah, 9 int 33 } cout << "\nPo podaniu drugiej liczby nacisnij [Enter]"; cout << "\nLiczby ujemne sa w kodzie dopelniajacym"; cout << "\nSkrajny lewy bit oznacza znak 0-Plus, 1-Minus"; for(;;) { cout << "\n"; cin >> x >> y; cout << "\nX: "; demo(x); cout << "\t\tY: "; demo(y); cout << "\n~X: "; demo(~x); cout << "\t\t~Y: "; demo(~y); cout << "\nX & Y: "; demo(x & y); cout << "\nX | Y: "; demo(x | y); cout << "\nX ^ Y: "; demo(x ^ y); cout << "\n Y: "; demo(y); cout << "\nY >> 1: "; demo(y >> 1); cout << "\nY << 2: "; demo(y << 2); cout << "\n\n Jeszcze raz? T/N: "; cin >> odp; if (odp!='T'&& odp!='t') break; } } Wstawka asemblerowa nie jest w programie niezb�dna, ale w tym miejscu wydaje si� by� "a propos". Przy pomocy programu przykładowego mo�esz zobaczy� "na własne oczy" jak wygl da reprezentacja bitowa liczb całkowitych i ich kody komplementarne. Praca bezpo�rednio ze zmiennymi jest jednym ze sposobów komunikowania si� z programem napisanym w C++. Mog jednak wyst pi� sytuacje bardziej skomplikowane, kiedy to nie b�dziemy zna� nazwy zmiennej, przekazywanej do funkcji. Je�li napiszemy w asemblerze funkcj� w celu zast pienia jakiej� funkcji bibliotecznej C++ , program wywołuj c funkcj� przeka�e jej parametry i b�dzie oczekiwał, i� funkcja pobierze sobie te parametry ze stosu. Rozwa�my si� to zagadnienie dokładniej. Typow sytuacj jest pisanie w asemblerze tylko kilku funkcji (zwykle takich, które powinny działa� szczególnie szybko). Aby to zrobi�, musimy nauczy� si� odczytywa� parametry, które program przekazuje do funkcji w momencie jej wywołania. Zaczynamy od trywialnej funkcji, która nie pobiera w momencie wywołania �adnych parametrów. W programie mo�e to wygl da� np. tak: [P061.CPP] //*TEKST to znany funkcji zewn�trzny wska�nik #pragma inline

char *TEKST = "\ntekst - test$"; void drukuj(void); //Prototyp funkcji void main() { drukuj(); //Wywołanie funkcji drukuj() } void drukuj(void) //Definicja funkcji { asm MOV DX, TEKST asm MOV AH, 9 asm INT 33 } Funkcja mo�e oczywi�cie nie tylko zgłosi� si� napisem, ale tak�e zrobi� dla nas co� po�ytecznego. W kolejnym programie przykładowym czy�cimy bufor klawiatury (flush), co czasami si� przydaje, szczególnie na starcie programów. [P062.CPP] # pragma inline char *TEKST = "\nBufor klawiatury PUSTY. $"; void czysc_bufor(); //Te� prototyp funkcji void main() { czysc_bufor(); //Czyszczenie bufora klawiatury } void czysc_bufor(void) //Definicja funkcji { START: asm MOV AH, 11 asm INT 33 asm OR AL, AL asm JZ KOMUNIKAT asm MOV AH, 7 asm INT 33 asm JMP START KOMUNIKAT: asm MOV DX, TEKST asm MOV AH, 9 asm INT 33 } Póki nie wyst pi problem przekazania parametrów, napisanie dla C++ funkcji w asemblerze jest banalnie proste. Zwró� uwag�, �e zmienne wskazywane w programach przez pointer *TEKST zostały zadeklarowane poza funkcj main() - jako zmienne globalne. Dzi�ki temu nasze funkcje drukuj() i czysc_bufor() maj dost�p do tych zmiennych. Spróbujemy przekaza� funkcji parametr. Nazwiemy nasz funkcj� wyswietl() i b�dziemy j wywoływa� przekazuj c jej jako argument znak ASCII przeznaczony do wydrukowania na ekranie: wyswietl('A'); . Pojawia si� zatem problem - gdzie program "pozostawia" argumenty przeznaczone dla funkcji przed jej wywołaniem? W Tabeli poni�ej przedstawiono w skrócie "konwencj� wywoływania funkcji" (ang. Function Calling Convention) j�zyka C++. Konwencje wywołania funkcji. ________________________________________________________________ J�zyk Argumenty na stos Posta� Typ wart. zwrac. ________________________________________________________________ BASIC Kolejno offset adresu Return n C++ Odwrotnie warto�ci Return Pascal Kolejno warto�ci Return n ________________________________________________________________ Return n oznacza liczb� bajtów zajmowanych ł cznie przez wszystkie odło�one na stos parametry. W C++ parametry s odkładane na stos w odwróconej kolejno�ci.

Page 56: C++ - lekcje

56

Je�li chcemy, by parametry zostały odło�one na stos kolejno, powinni�my zadeklarowa� funkcj� jako "funkcj� z Pascalowskimi manierami" - np.: pascal void nazwa_funkcji(void); Dodatkowo, w C++ argumenty s przekazywane poprzez swoj warto��, a nie przez wskazanie adresu parametru, jak ma to miejsce np. w BASICU. Istnieje tu kilka wyj tków przy przekazywaniu do funkcji struktur i tablic - bardziej szczegółowo zajmiemy si� tym w dalszej cz��ci ksi �ki. Rozbudujemy nasz przykładowy program w taki sposób, by do funkcji były przekazywane dwa parametry - litery 'A' i 'B' przeznaczone do wydrukowania na ekranie przez funkcj�: # pragma inline void wyswietl(char, char); //Prototyp funkcji void main() { wyswietl('A', 'B'); //Wywolanie funkcji } void wyswietl(char x, char y) //Definicja (implementacja) { .... Parametry zostan odło�one na stos: PUSH 'B' PUSH 'A' Ka�dy parametr (mimo typu char) zajmie na stosie pełne słowo. C++ nie potrafi niestety układa� na stosie bajt po bajcie. Funkcja wyswietl() musi uzyska� dost�p do przekazanych jej argumentówów. Odwołamy si� do zmiennych C++ w taki sposób, jak robiłaby to ka�da inna funkcja w C++: [P063.CPP] # pragma inline void wyswietl(char, char); //Prototyp funkcji void main() { _AH = 2; //BEEEEE ! wyswietl('A', 'B'); //Wywolanie funkcji } void wyswietl(char x, char y) //Definicja (implementacja) { _DH = 0; // To C++ nie TASM, to samo, co asm MOV DH, 0 _DL = x; // asm MOV DL, x asm INT 33 _DH = 0; // asm MOV DH, 0 _DL = y; // asm MOV DL, y asm INT 33 } Aby pokaza� jak dalece BORLAND C++ jest elastyczny wymieszali�my tu w jednaj funkcji instrukcje C++ (wykorzystuj c pseudozmienne) i instrukcje assemblera. Mo�e tylko przesadzili�my troch� ustawiaj c rejestr AH - numer funkcji DOS dla przerywania int 33 przed wywołaniem funkcji wyswietl() w programie głównym. To brzydka praktyka (ozn. //BEEEE), której autor nie zaleca. Jak widzisz, przekazanie parametrów jest proste. LEKCJA 17: TROCH� SZCZEGÓLÓW TECHNICZNYCH. ________________________________________________________________ W trakcie tej lekcji dowiesz si� wi�cej o szczegółach działania komputera widzianych z poziomu assemblera. ________________________________________________________________ LICZBY ZMIENNOPRZECINKOWE TYPU float. To, �e C++ przy wywołaniu funkcji jest "przyzwyczajony" do odkładania argumentów na stos zawsze po dwa bajty mo�e nam sprawi� troch� kłopotów, gdy zechcemy zastosowa� argument typu float, double, b d� long - znacznie przekraczaj cy długo�ci

dwubajtowe słowo maszynowe. # include <.... .... # pragma inline void main() { float liczba = 3.5; .... Je�eli zajrzymy do pami�ci naszego PC, oka�e si�, �e liczba 3.5 została tam "zaszyfrowana" jako 00 00 60 40. Dlaczego? Format liczb zmiennoprzecinkowych jest znacznie bardziej skomplikowany. Liczba dziesi�tna w rodzaju 123.4 to 1*102 + 2*101 + 3*100 + 4*10-1 {* !UWAGA SKLAD tu cyfry potegi wyzej *} Ale PC mo�e posługiwa� si� wył cznie zerami i jedynkami, i liczy� wył cznie w systemie dwójkowym. Liczb� dziesi�tn 3.5 mo�naby przedstawi� dwójkowo np. tak: 1*21 + 1*20 + 1*2-1 = 2 + 1 + 1/2 {* !UWAGA SKLAD: potegi *} czyli 0000 0000 0000 0011.1000 0000 0000 0000 Kropka oznacza przecinek oddzielaj cy cz��� całkowit od cz��ci ułamkowaj - "przecinek dwójkowy" (a nie dziesi�tny!). Ka�d liczb� dziesi�tna mo�na zamieni� na liczb� dwójkow . Przykładowodziei�tne 7.75 mo�na zamieni� na 4 + 2 + 1 + 1/2 + 1/4 = 0000 0000 0000 0111.1100 (dwójkowo) Pozostaje jednak pewien problem. Komputer nie ma mo�liwo�ci zaznaczenia przecinka, dlatego te� przecinek musi by� ustawiany zawsze w tej samej pozycji - blisko pocz tku liczby. Liczby zmiennoprzecinkowe s poddawane "normalizacji" (ang. noralized). Nasza liczba 0000 0000 0000 0011.1000 po normalizacji b�dzie wygl da� tak: 1.110 0000 0000... * 2^1. Odbywa si� to zupełnie tak samo, jak normalizacja liczb dziesi�tnych. Przesuni�cie przecinka powoduje, �e 12345.67 = 1.234567 * 10^4. Aby wróciła do swojej starej "zwykłej" postaci (jest to tzw. "rozwini�cie" liczby - ang. expand) nale�y przesun � przecinek o jedno miejsce w prawo - otrzymamy znowu 11.1 . W liczbach dziesi�tnych pierwsza cyfra mo�e by� ró�na (tylko nie zero), a w dowolnej poddanej normalizacji zmiennoprzecinkowej liczbie dwójkowej pierwsz cyfr jest zawsze 1. Skoro w formacie liczb zmiennoprzecinkowych pierwsza jedynka jest przyjmowana "z definicji" (ang. implicit), wi�c mo�na j pomin �. Zostanie nam zatem zamiast 1.11 tylko 11 i ta przechowywana cz��� liczby jest nazywana jej cz��ci znacz c (ang. significant). To jeszcze nie wszystko - powinien tam by� wykładnik pot�gi. Wystarczy zapami�ta� wykładnik, bo podstawa jest zawsze ta sama - 2. Niestety wykładniki s przechowywane nie w sposób naturalny, a po dodaniu do nich tzw. przesuni�cia (ang. offset lub bias). Pozwala to unikn � kłopotów z okre�laniem znaku wykładnika pot�gi. Dla liczb typu float offset wykładnika wynosi +127 a dla liczb double float +1023. Wrócmy do naszej przykładowej liczby. Je�li nasza liczba 3.5 = 11.1(B) ma by� zapisana w postaci zmiennoprzecinkowej - float, zapisany w pami�ci wykładnik pot�gi wyniesie: 1 + 127 = 128 = 80 (hex) A teraz znak liczby. Pierwszy bit ka�dej liczby zmiennoprzecinkowej okre�la znak liczby (ang. sign bit). Liczby zmiennoprzecinkowe nie s przechowywane w postaci dwójkowych uzupełnie�. Je�li pierwszy bit - bit znaku równy jest 1 - liczba jest ujemna. natomiast je�eli 0, liczba jest dodatnia. Jest to jedyna ró�nica pomi�dzy dodatnimi a ujemnymi liczbami zmiennoprzecinkowymi. Nasza liczba 3.5 = 11.1 zostanie zakodowana jako: znak liczby - 0 wykładnik pot�gi - 1000 0000

Page 57: C++ - lekcje

57

cyfry znacz ce liczby - 110000000.... Poniewa� wiemy, �e mamy do dyspozycji dla liczb float 4 bajty (mo�esz to sprawdzi� sizeof(float x=3.5)), uzupełnijmy brakuj ce do 32 bity zerami: 3.5 = 0100 0000 0110 0000 0000 0000 0000 0000 = 40 60 00 00 zapis 40600000 to oczywi�cie szesnastkowa posta� naszej liczby. Je�li teraz we�miemy pod uwag�, �e nasz PC zamieni miejscami starsze słowo z młodszym 00 00 40 60 a nast�pnie w obr�bie ka�dego słowa dodatkowo starszy bit z młodszym, to zrozumiemy, dlaczego nasza liczba "siedziała" w pami�ci w zaszyfrowanej postaci 00 00 60 40. Rozpatrzmy szkielet programu wykorzystuj cego funkcj� z "długim" argumentem. Aby zapanowa� nad zapisem liczby zmiennoprzecinkowej do pami�ci naszego PC mo�emy na poziomie assemblera post pi� np. tak: # include <..... # pragma inline void funkcja(long int) //Prototyp funkcji main() { long liczba = 0xABCDCDEF; //Deklaracja argumentu ..... funkcja(liczba); //Wywołanie w programie .... } void funkcja(long int x) //Implementacja funkcji { ..... } // x - argument formalny Argument przekazywany funkcji() jest zmienn 4 - bajtow typu long int. Mo�emy j zamieni� na dwa słowa, zanim przeka�emy j do wykorzystania w asemblerowskiej cz��ci programu. funkcja(long int x) { int x1starsze, x2mlodsze; //Wewn�trzne zmienne pomocnicze x2mlodsze = (int) x; x >> 16; x1starsze = (int) x; _DX = x1starsze; _BX = x2mlodsze; asm { ...... //Tu funkcja ju� mo�e działa� Forsuj c konwersj� typu na (int), spowodujemy, �e młodsze słowo zostanie przypisane zwyczajnej krótkiej zmiennej x2mlodsze. Nast�pnie zawarto�� długiej zmiennej zostanie przesuni�ta o 16 bitów w prawo (starsze słowo zostanie przesuni�te na miejsce młodszego). Powtórzenie operacji przypisania spowoduje przypisanie zmiennej x1starsze starszej połówki słowa. Od tej chwili mo�emy odwoła� si� do tych zmiennych w naszym fragmencie napisanym w asemblerze. Post�pujemy tak, by to C++ martwił si� o szczegóły techniczne i sam manipulował stosem i jednocze�nie pilnował poprawno�ci konwersji danych. ZWROT WARTO�CI PRZEZ FUNKCJ�. A teraz kilka słów o tym, co si� dzieje, gdy funkcja zapragnie zwróci� jak � warto�� do programu. Wykorzystanie przez funkcje rejestrów do zwrotu warto�ci. ________________________________________________________________ Typ warto�ci Funkcja u�ywa rejestru (lub pary) ________________________________________________________________ signed char / unsigned char AL short AX int AX enum AX long para DX:AX (starsze słowo DX, młodsze AX) float AX = Adres (je�li far to DX:AX)

double AX = Adres (je�li far to DX:AX) struct AX = Adres (je�li far to DX:AX) near pointer AX far pointer DX:AX ________________________________________________________________ Zale�nie od typu warto�ci zwracanej przez funkcj� (okre�lonej w prototypie funkcji), C++ odczytuje zawarto�� odpowiedniego rejestru: AL, AX lub DX:AX. Je�li funkcja ma np. zwróci� warto�� o długo�ci jednego bajtu, to przed wyj�ciem z funkcji nale�y j "zostwi�" w rejestrze AL. Je�li wywołuj c funkcj� C++ oczekuje zwrotu warto�ci jednobajtowej, to po powrocie z funkcji automatycznie pobierze bajt z rejestru AL. Krótkie warto�ci (typu short int) s "pozostawiane" przez funkcj� w AX, a długie w parze rejestrów: DX - starsze, AX - młodsze słowo. Zastosujmy to w programie. Funkcja b�dzie odejmowa� dwie liczby całkowite. Pobierze dwa argumenty typu int, wykona odejmowanie i zwróci wynik typu int (return (_AX)). Dla modelu pami�ci small b�dzie to wygl da� tak: [P064.CPP] # include <iostream.h> # pragma inline int funkcja(int, int); //Prototyp funkcji void main() { cout << "\nWynik 7 - 8 = " << funkcja(7, 8); } int funkcja(int x, int y) //Implementacja funkcji { asm { MOV AX, x SUB AX, y } return (_AX); //Zwró� zawarto�� rejestru AX } Zwró� uwag�, �e po return(_AX); stawiamy �rednik, natomiast po instrukcjach assemblera nie: asm MOV AX, DX chyba, �e chcemy umie�ci� kilka instrukcji assemblera w jednej linii (patrz ni�ej). C++ i assembler s równoprawnymi partnerami. C++ mo�e odwoływa� si� do zmiennych i funkcji assemblera, je�li zostały zadeklarowane, jako publiczne (public) oraz zewn�trzne (EXTeRNal) i vice versa. C++ oczekuje, �e zewn�trzne identyfikatory b�d si� rozpoczyna� od znaku podkre�lenia "_". Je�li w programie pisanym w BORLAND C++ zastosujemy zewn�trzne zmienne i funkcje, C++ sam automatycznie doda do identyfikatorów znak podkre�lenia. Turbo Assembler nie robi tego automatycznie i musimy zadba� o to "r�cznie". Przykładowo, współpraca pomi�dzy programem P .CPP i modułem MODUL.ASM b�dzie przebiega� poprawnie: [P065.CPP] extern int UstawFlage(void); //Prototyp funkcji int Flaga; void main() { UstawFlage(); } [MODUL.ASM] .MODEL SMALL .DATA EXTRN _Flaga:WORD .CODE PUBLIC _UstawFlage _UstawFlage PROC

Page 58: C++ - lekcje

58

CMP [_Flaga], 0 JNZ SKASUJ_Flage MOV [_Flaga], 1 JMP SHORT KONIEC SKASUJ_Flage: MOV [_Flaga], 0 KONIEC: RET _UstawFlage ENDP END Kompilacja mo�e przebiega� oddzielnie wg schematu: PROGRAM.CPP --> PROGRAM.OBJ MODUL.ASM --> MODUL.OBJ TLINK PROGRAM.OBJ MODUL.OBJ --> PROGRAM.EXE Lub mo�emy powierzy� t� prac� kompilatorowi, który sam wywoła TASM i TLINK: TCC PROGRAM.CPP MODUL.ASM W BORLAND C++ 3.1 mamy do dyspozycji zintegrowany assembler (ang. in-line) - BASM. Ma on jednak w stosunku do "wolnostoj cego" Turbo Assemblera pewne ograniczenia: * ma zaw��ony w stosunku do TASM zestaw dyrektyw (tylko DB, DD, DW, EXTRN); * nie pozwala na stosowanie składni typowej dla trybu "Ideal mode"; * nie pozwala na zastosowanie makra; * nie pozwala stosowa� instrukcji charakterystycznych dla 386 ani 486. Mo�esz stosowa� kilka rozkazów assemblera w jednej linii, ale powiniene� rozdziela� je wewn trz linii �rednikami: asm { POP AX; POP DX; POP DS IRET } Komentarz we wstawce assemblerowskiej musi zosta� poprzedzony typowym dla C - /* (sam �rednik, jak w TASM jest niedopuszczalny): asm { MOV DX, 1 ;TAK NIE MONA W BASM ! ... asm { ADD AX, BX; /* Taki komentarz mo�e by� */ [???] KŁOPOTY Z REJESTRAMI ? ________________________________________________________________ Je�li zastosujesz rejestry DI i SI we wstawce assemblerowaj, kompilator C++ nie b�dzie miał gdzie umie�ci� zmiennych klasy register z programu głónego. Zastanów si� - co si� bardziej opłaca. ________________________________________________________________ O WEKTORACH PRZERYWA� DOS Mikroprocesory Intel 80X86 rezerwuj w pami�ci naszych PC pocz tkowe 1024 Bajty (adresy fizyczne 00000...00400 hex) na 256 wektorów przerywa� (ka�dy wektor składa si� z dwu słów i mo�e by� traktowany jako DW, b d� far pointer). Nast�pne 256 bajtów (00400...00500 hex) zajmuje BIOS, a kolejne 256 (00500...00600 hex) wykorzystuje DOS i Basic. Wektor to w samej rzeczy pełny adres pocz tku procedury obsługuj cej przerywanie o danym numerze UWAGA: Wektor zapisywany jest w pami�ci w odwrotnej kolejno�ci: Adres pami�ci: 0000:0000 [OFFSET Wekt. int 0] 0000:0002 [SEGMENT int 0] 0000:0004 [OFFSET Wekt. int 1] 0000:0006 [SEGMENT int 1] 0000:0008 [OFFSET int 2] .... .... Procesory 80X86 zamieniaj jeszcze dodatkowo starszy bajt z młodszym.

Posługuj c si� systemowym debuggerem DEBUG mo�esz łatwo przejrze� tablic� wektorów przerywa� własnego komputera. Je�li wydasz rozkaz: C:\DOS\DEBUG -D 0:0 zobaczysz zawarto�� pierwszych 32 wektorów int #0...int#31, czyli pierwsze 128 bajtów pami�ci: -d 0:0 0000:0000 FB 91 32 00 F4 06 70 00-78 F8 00 F0 F4 06 70 00 0000:0010 F4 06 70 00 54 FF 00 F0-53 FF 00 F0 53 FF 00 F0 0000:0020 A5 FE 00 F0 87 E9 00 F0-23 FF 00 F0 23 FF 00 F0 0000:0030 23 FF 00 F0 CE 02 00 C8-57 EF 00 F0 F4 06 70 00 0000:0040 D1 0C BD 1B 4D F8 00 F0-41 F8 00 F0 74 07 70 00 0000:0050 39 E7 00 F0 4A 08 70 00-2E E8 00 F0 D2 EF 00 F0 0000:0060 00 00 FF FF FB 07 70 00-5D 0C 00 CA 9F 01 BD 1B 0000:0070 53 FF 00 F0 A0 7C 00 C0-22 05 00 00 2F 58 00 C0 Po zdeszyfrowaniu oka�e si�, �e pierwszy wektor (przerywanie 0) wskazuje na adres startowy: 0032:91FB (adres absolutny 0951B). Generalnie mo�liwe s cztery sytuacje. Wektor mo�e wskazywa�: * adres startowy procedur ROM-BIOS: blok F - Fxxx:xxxx, * adres funkcji DOS, * adres funkcji działaj cego wła�nie debuggera (DEBUG przejmuje obsług� niektórych przerywa�), lub innego programu rezyduj cego w pami�ci - np. NC.EXE, * wektor mo�e by� pusty - 00 00:00 00 je�li dane przerywanie nie jest obsługiwane. Je�li zechcesz sprawdzi�, jak obsługiwane jest dane przerywanie mo�esz znów zastosowa� debugger, wydaj c mu rozkaz zdezasamblowania zawarto�ci pami�ci pocz wszy od wskazanego adresu: -u 32:91FB 0032:91FB BE6B47 MOV SI,476B 0032:91FE 2E CS: 0032:91FF 8B1E7E47 MOV BX,[477E] 0032:9203 2E CS: 0032:9204 8E16D73D MOV SS,[3DD7] 0032:9208 BCA007 MOV SP,07A0 0032:920B ˙˙˙˙˙˙˙˙˙˙˙E80200 ˙˙˙˙˙˙˙˙˙˙CALL ˙˙˙˙˙˙˙˙˙˙9210 0032:920E EBDA JMP 91EA 0032:9210 ˙˙˙˙˙˙˙˙˙˙˙16 ˙˙˙˙˙˙˙˙˙˙˙PUSH ˙˙˙˙˙˙˙˙˙˙˙SS 0032:9211 07 POP ES 0032:9212 ˙˙˙˙˙˙˙˙˙˙˙16 ˙˙˙˙˙˙˙˙˙˙˙PUSH ˙˙˙˙˙˙˙˙˙˙˙SS 0032:9213 1F POP DS 0032:9214 C606940308 MOV BYTE PTR [0394],08 0032:9219 C606920316 MOV BYTE PTR [0392],16 Z poziomu assemblera do wektora i odpowiednio do funkcji obsługuj cej przerywanie mo�esz odwoła� si� instrukcj INT numer. Zmienna numer mo�e tu przyjmowa� warto�ci od 00 do FF. Je�li wydasz taki rozkaz, komputer zapisze na stos (�eby sobie nie zapomnie�) zawarto�� rejestrów CS - bie�. segment rozkazu, IP - bie� cy offset rozkazu i FLAGS. Nast�pnie wykona daleki (far jump) skok do adresu wskazanego przez wektor. Je�li jednak cz��� przerywa� jest "niewykorzystana", lub w Twoim programie trzeba je obsługiwa� inaczej - niestandardowo ? W BORLAND C++ masz do dyspozycji specjalny typ funkcji: interrupt. Aby Twoja funkcja mogła sta� si� "handlerem" przerywania, mo�esz zadeklarowa� j tak: void interrupt MojaFunkcja(bp, di, si, ds .....) Do funkcji klasy interrupt przekazywane s jako argumenty rejestry, nie musisz zatem stosowa� pseudozmiennych _AX, _FLAGS

Page 59: C++ - lekcje

59

itp.. Je�li zadeklarujesz funkcj� jako handler przy pomocy słowa "interrupt", funkcja automatycznie zapami�tuje stan rejestrów: AX, BX, CX, DX, SI, DI, BP, ES i DS. Po powrocie z funkcji rejestry zostan automatycznie odtworzone. Przykładem funkcji obsługuj cej przerywanie mo�e by� piszczek() posługuj cy si� wbudowanym gło�niczkiem i portem: # define us unsigned # include <iostram.h> # include <dos.h> void InstalujWektor(void interrupt (*adres)(), int numer_wekt); void interrupt Piszczek(us bp, us di, us si, us ds, us es, us ax, us bx, us cx, us dx); void main() { ..... } .... Po zadeklarowaniu prototypów dwu funkcji: Piszczek() - nasz handler przerywania; InstalujWektor() - funkcja instaluj ca nasz handler; mo�emy przyst pi� do zdefiniowania oby funkcji. Posłu�ymy si� zmiennymi nowe_bity, stare_bity. Wydawanie d�wi�ku polega na wł czaniu i wył czaniu gło�niczka. Pusta p�tla posłu�y nam do zwłoki w czasie. void interrupt Piszczek(us bp, us di, us si, us ds, us es, us ax, us bx, us cx, us dx) { char nowe_bity, stare_bity, i; int n; unsigned char licznik = ax >> 8; stare_bity = inportb(0x61); for(nowe_bity = stare_bity, n = 0; n <= licznik; n++) { outportb(0x61, 0xFC & nowe_bity); //Wylacz for(i = 1; i < 255; i++) ; //Czekaj outportb(0x61, nowe_bity / 2); //WLACZ for(i = 1; i < 255; i++) ; //Czekaj } outportb(0x61, stare_bity); //Stan poczatkowy } Funkcja instaluj ca handler korzysta z bibliotecznej funkcji C++ setvect() (ustaw wektor przerywania) i potrzebuje dwu argumentów: * numeru wektora przerywania (numer * 4 = adres), * adresu funkcji - handlera - *faddr. void InstalujWektor(void interrupt (*adres)(), int numer_wektora) { cout << "\nInstaluje wektor" << numer_wektora << "\n"; setvect(numer_wektora, adres); } Pozostało nam wygenerowa� przerywanie. Załatwimy to funkcj Start(): void Start(unsigned char licznik, int numer_wektora) { _AH = licznik; geninterrupt(numer_wektora); //generuj przerywanie } Nasz główny program b�dzie zatem wygl da� tak: # include <... ... void main()

{ Instaluj(Piszczek, 10); Start(5, 10); } Nale�y do dobrych manier odtworzy� po wykorzystaniu oryginaln zawarto�� wektora przerywania, który "unowocze�nili�my". W bibliotece BORLAND C++ masz do dyspozycji m. in. funkcje getvect() - pobierz wektor (ten stary) i setvect() - ustaw wektor (ten nasz - nowocze�niejszy). Je�li zechcemy korzysta� z rejestrów 386/486? Je�li mamy komputer z 32 bitowymi rejestrami, to wypadałoby z tego korzysta�. Na poziomie assemblera masz do dyspozycji dyrektywy: .386, .386P i .386C (P oznacza pełny zestaw instrukcji wraz z trybem uprzywilejowanym - 386 privileged instruction set). Mikroprocesor Intel 80386 mo�e obsługiwa� pami�� zgodnie z tradycyjnym podziałem na 64 kilobajtowe segmenty (tryb USE16), lub podzielon na ci głe segmenty po 4 GB (tryb USE32). Rejestry ogólnego przeznaczenia rozrosły si� z 16 do 32 bitów i zyskały w nazwie dodatkow liter� E (Extended - rozszerzony). "Stare" rejestry stały si� młodsz połówk nowych. I tak: EAX = 0...15 to stary AX, 16...31 to rozbudowa do EAX (dokładniej: 0..7 = AL, 8..15 = AH, 0...15 = AX, 0...31 = EAX) BX -> 0...31 EBX: 0...7 BL, 8...15 BH, 0...15 BX CX -> 0...31 ECX DX -> 0...31 EDX wszystkie z dodatkowym podziałem na połówki H i L (np. DX = DH:DL). SI -> 0...31 ESI w tym (SI = 0..15) DI -> 0...31 EDI w tym (DI = 0..15) BP -> 0...31 EBP w tym (BP = 0..15) SP -> 0...31 ESP w tym (SP = 0..15) IP -> 0...31 EIP w tym (IP = 0..15) FLAGS -> 0...31 EFLAGS w tym (FLAGS = 0..15) Wszystkie "stare" połówki dost�pne pod star nazw . Rejestry segmentowe pozostały 16 bitowe, ale jest ich o dwa wi�cej: CS, DS, ES, SS oraz nowe FS i GS. Nowe 32 bitowe rejestry działaj według tych samych zasad: .386 ... MOV EAX, 1 ;zapisz 1 do rejestru EAX SUB EBX, EBX ;wyzeruj rejestr EBX ADD EBX, EAX ;dodaj (EAX)+(EBX) --> EBX Dost�p do starszej połowy rejestru mo�na uzyska� np. poprzez przesuwanie (rotation): .386 ... MOV AX, Liczba_16_bitowa ROR EDX, 16 MOV AX, DX ROR EDX, 16 ... itp. W assemblerze mo�esz stosowa� wobec procesora 386 nowe instrukcje (testowania nie istniej cych wcze�niej bitów, przenoszenia krótkich liczb do 32 bitowych rejestrów z uwzgl�dnieniem zaku i uzupełnieniem zerami itp.): BSF, BSR, BTR, BTS, LFS, LGS, MOVZX, SETxx, BT, BTC, CDQ, CWDE, LSS, MOVSX, SHLD i SHRD. Przy pomocji instrukcji MOV w trybie uprzywilejowanym (tzw. most-privileged level 0 - tylko w trybie .386P) mo�esz dodatkowo uzyska� dost�p do specjalnych rejestrów mikroprocesora 80386. CR0, CR2, CR3, DR0, DR1, DR2, DR3, DR6, DR7 TR6, TR7

Page 60: C++ - lekcje

60

Wyst�puje tu typ danych - FWORD - 48 bitów (6 bajtów). Obok znanych dyrektyw DB i DW pojawia si� zatem nowa DF, a oprócz znajomych wska�ników BYTE PTR, WORD PTR pojawia si� nowy FWORD PTR. Przy pomocy dyrektywy .387 mo�esz skorzysta� z koprocesora. Jak wynika z zestawu dodatkowych insrukcji: FCOS, FSINCOS, FUCOMP, FPREM1, FUCOM, FUCOMPP, FSIN warto dysponowa� koprocesorem, je�li cz�sto korzystasz z grafiki, animacji i funkcji trygonometrycznych (kompilacji nie przyspieszy to niestety ani o 10% - tam odbywaj si� operacje stałoprzecinkowe). Zwró� uwag�, �e procesory 386 i wcze�niejsze wymagały instalacji dodatkowego układu 387 zawieraj cego koprocesor zmiennoprzecinkowy. Procesory 486 je�li maj rozszerzenie DX - zawieraj ju� koprocesor wewn trz układu scalonego. LEKCJA 18 - O ŁA�CUCHACH TEKSTOWYCH ________________________________________________________________ W trakcie tej lekcji dowiesz si�, * jak manipulowa� ła�cuchami tekstowymi i poznasz kilka specjalnych funkcji, które słu� w C++ wła�nie do takich celów; * jak wykonuj si� operacje plikowo-dyskowe. ________________________________________________________________ OPERACJE NA ŁA�CUCHACH TEKSTOWYCH. String, czyli ła�cuch - to gupa znaków "pisarskich" (liter, cyfr i znaków specjalnych typu ?, !, _ itp.). Poniewa� C++ nie ma odzielnego typu danych "string" - ła�cuchy znaków to tablice zło�one z pojedynczych znaków (typowe elementy typu char). Technik obiektow mo�na utworzy� klas� - nowy typ danych "string". W bibliotekach Microsoft C++ istnieje predefiniowana klasa CString, ale zanim przejdziemy do programowania obiektowego i zdarzeniowego - rozwa�my manipulowanie tekstami w sposób najprostszy. Maksymaln mo�liw długo�� napisu nale�y poda� wtedy, gdy w programie deklaruje si� zmienn tekstow : char tekst1[40]; Jest to poprawna deklaracja zmiennej tekstowej o nazwie (identyfikator) tekst1. Maksymalna długo�� tekstu, który mo�na umie�ci� w tej zmiennej tekstowej to - 40 znaków (liter, cyfr, itp.). A je�li chc� zastosowa� tylko pojedynczy znak zamiast całego napisu? To proste: char napis[1]; Skoro długo�� ła�cucha wynosi 1, to przecie� nie jest �aden ła�cuch! Informacja o długo�ci (size - wielko��) wpisywana w nawiasy jest zb�dna. Uproszczona wersja utworzenia zmiennej jednoznakowej i nadania zmiennej nazwy wygl da w tak: char znak; Nie jest to ju� jednak deklaracja zmiennej ła�cuchowej - lecz deklaracja zmiennej znakowej. Ła�cuch znaków (string) to grupa znaków (dokł. tablica znakowa) zako�czona zwykle przez tzw. "wartownika" - znak NULL (zero). A pojedynczy znak to tylko pojedynczy znak. Nie ma tu miejsca (i sensu) dodawanie po pojedynczym znaku "wartownika" ko�ca tekstu - zera. Gdy w deklaracjach zmiennych tekstowych rezerwujesz miejsce w pami�ci dla napisów - zawsze mo�esz za� da� od kompilatora C++ zarezerwowania wi�kszej ilo�ci miejsca - na zapas. Zawsze lepiej mie� zbyt du�o miejsca, ni� zbyt mało. [???] LEPIEJ MIE� NI NIE MIE�.

________________________________________________________________ Upewnij si�, czy kompilator zarezerwował (a Ty zadeklarowałe�) wystarczaj co du�o miejsca dla Twoich tekstów. C++ niestety nie sprawdza tego w trakcie działania programu. Je�li b�dziesz próbował umie�ci� w pami�ci tekst o zbyt du�ej długo�ci (dłu�szy ni� zadeklarowałe� w programie), C++ posłusznie zapisze go do pami�ci, ale mo�e to spowodowa� nieprawidłowe działanie, b d� nawet "zawieszenie" programu. ________________________________________________________________ Inn przydatn w praktyce programowania cech j�zyka C++ jest mo�liwo�� zadeklarowania zawarto�ci zmiennej tekstowej w momencie zadeklarowania samej zmiennej. Takie nadanie pocz tkowej warto�ci nazywa si� zdefiniowaniem, b d� zainicjowaniem zmiennej. W programie zapisuje si� to tak: char napis[] = "To jest jakis napis"; Powoduje to przypisanie zmiennej tekstowej "napis" konkretnego ła�cucha tekstowego "To jest jaki� napis". Zwró� uwag�, �e w nawiasach nie podajemy ilo�ci znaków, z których składa si� tekst. Kompilator sam policzy sobie ilo�� znaków (tu 19) i zarezerwuje miejsce w pami�ci dla napisu. Je�li wolisz sam zadecydowa�, mo�esz zapisa� deklaracj� tak: char napis[35] = "To jest jakis napis"; Je�li to zrobisz, kompilator C++ zarezerwuje w pami�ci miejsce dla 35 znaków, a nie dla 19. W programach cz�sto inicjuje si� teksty posługuj c si� nie tablic znakow - lesz wska�nikiem do tekstu. Deklaracja i zainicjowanie wska�nika (wska�nik wskazuje pierwszy element ła�cucha znakowego) wygl da wtedy tak: char *p = "Jakis tam napis"; Rzu�my okiem na kilka gotowych funkcji, które do manipulowania tekstami oferuje C++. ŁCZENIE TEKSTÓW. [S] String Concatenation - ł czenie ła�cuchów tekstowych. Zlepek/skrót. Słowo strcat w j�zyku C++ znaczy sklej. W praktycznych programach zapewne cz�sto pojawi si� dwa lub wi�cej tekstów, które trzeba b�dzie poł czy� w jeden napis. Wyobra�my sobie, �e imi� i nazwisko u�ytkownika mamy zapisane jako dwa oddzielne ła�cuchy tekstowe. Aby poł czy� te dwa teksty w jeden trzeba przeprowadzi� tzw. sklejanie (ang. concatenation) tekstów. W j�zyku C++ mamy w tym celu do dyspozycji specjaln funkcj�: strcat() - STRing conCATenation - sklejanie ła�cuchów. Aby poł czy� dwa ła�cuchy tekstowe napis1 i napis2 w jeden nale�y zastosowa� t� funkcj� w taki sposób: strcat(napis1, napis2); Funkcja strcat() zadziała w taki sposób, �e ła�cuch znaków napis2 zostanie doł czony do ko�ca ła�cucha napis1. Po zako�czeniu działania funkcji zmienna napis1 zawiera "swój własny" napis i doł czony na ko�cu napis zawarty uprzednio w zmiennej napis2. Program poni�ej przedstawia praktyczny przykład zastosowania funkcji strcat(). [P066.CPP] #include <conio.h> #include <iostream.h> #include <string.h> //W tym pliku jest prototyp strcat() int main(void) { char imie[50], nazwisko[30];

Page 61: C++ - lekcje

61

clrscr(); cout << "Podaj imie: "; cin >> imie; cout << "Podaj nazwisko: "; cin >> nazwisko; strcat(imie, " "); strcat(imie, nazwisko); cout << "\nNazywasz sie: " << imie << '\n'; cout << "Naci�nij dowolny klawisz"; getch(); return 0; } Program zapyta najpierw o imi� a nast�pnie o nazwisko. Po wpisaniu przez Ciebie odpowiedzi program doda do siebie oba teksty i wypisze na ekranie Twoje imi� i nazwisko w cało�ci. Interesuj xe w programie jest poł czenie przy pomocy funkcji C++ strcat() dwu ła�cuchów tekstowych w jeden ła�cuch z dodaniem spacji rozdzielaj cej ła�cuchy znaków. Najistotniejszy fragment programu wraz z komentarzem - poni�ej. strcat(imie, " "); <-- dodaj do ko�ca tekstu spacj� strcat(imie, nazwisko); <-- po doł czonej spacji dodaj drugi tekst - nazwisko Poniewa� prototyp funkcji strcat() znajduje si� w pliku STRING.H - nale�y doł czy� ten plik nagłówkowy dyrektyw #include. DŁUGO�� ŁA�CUCHA TEKSTOWEGO. Ka�dy tekst ma swoj długo��: liczb� znaków, z których si� składa. Dla przykładu ła�cuch znaków: "Przychodzi katecheta do lekarza i płacze, a lekarz na to: Bóg dał - Bóg wzi ł..." ma dla długo�� 71, poniewa� składa si� z 71 znaków (odst�p - spacja to te� znak). Ła�cuch znaków "Ile diabłów mie�ci si� w łebku od szpilki?" ma długo�� 42. Teoretycznie długo�� ła�cuchów znakowych mo�e wynosi� od 0 do niesko�czono�ci, ale w Borland/Turbo C++ wyst�puje ograniczenie: ła�cuch znaków mo�e mie� długo�� zawart w przedziale od 0 do 65536 znaków. Taki np. ła�cuch znaków jest całkiem do przyj�cia: "Nie wa�ne, czy Polska b�dzie bogata, czy biedna - wa�ne, �eby była katolicka (czyli nasza), bo nasze b�d wtedy pieni dze, urz�dy i nasza władza. Lepiej by� pół-Bogiem w�ród n�dzarzy (oczywi�cie za ich pieni dze, z ich podatków), ni� zarabia� na chleb własn prac ." [S] Null string - Ła�cuch zerowy. ________________________________________________________________ Ła�cuch zerowy (dokładniej: ła�cuch tekstowy o zerowej długo�ci) to taki ła�cuch, który zawiera 0 (zero) znaków. Jak to mo�liwe, by ła�cuch tekstowy zawierał zero znaków? W C++ ła�cuchy znaków zawieraj na ko�cu znak '\0' (zero) jako "wartownika" ko�ca tekstu. Je�li pierwszy element tablicy znakowej b�dzie zerem - powstanie wła�nie ła�cuch znakowy o zerowej długo�ci. Mo�na to zrobi� np. tak: char napis[0] = 0; char *p = ""; char napis[50] = ""; ________________________________________________________________ Kiedy C++ wyznacza długo�� ła�cucha znaków - zlicza kolejne znaki, a� dojdzie do zera. W przykładzie ju� pierwszy znak jest zerem, wi�c C++ uzna, �e długo�� takiego ła�cucha wynosi zero. Czasem w praktyce programowania zainicjowanie takiego pustego ła�cucha pozwala mie� pewno��, �e tablica nie zawiera jakich�

starych, zb�dnych danych. Mo�liwo�� sprawdzenia, jak długo�� ma ła�cuch tekstowy mo�e si� to przyda� np. do rozmieszczenia napisów na ekranie. Dla przykładu, pozycja na ekranie, od której rozpocznie si� wy�wietlanie napisu zale�y od długo�ci tekstu, który został wy�wietlony wcze�niej. Do okre�lania długo�ci tekstu masz w C++ do dyspozycji gotow funkcj�: strlen() - STRing LENgth - długo�� ła�cucha znakowego. Funkcj� strlen() stosuje si� w nast�puj cy sposób: unsigned int dlugosc; char tekst[...]; ... dlugosc = strlen(tekst); Funkcja ma jeden argument - napis, którego długo�� nale�y okre�li� (tu: zmienna nazywa si� tekst). Funkcja strlen() w wyniku swojego działania ZWRACA długo�� ła�cucha tekstowego jako liczb� całkowit bez znaku (nieujemn ). Liczba zwrócona jako wynik przez funkcj� strlen() mo�e zosta� u�yta w dowolny sposób - jak ka�da inna warto�� numeryczna. Funkcja strlen() nie podaje w odpowiedzi na wywołanie (m drze nazywa si� to "zwraca do programu warto��") długo�ci ła�cucha tekstowego, która została zadeklarowana (maksymalnej teoretycznej), lecz FAKTYCZN DŁUGO�� tekstu. Je�li, dla przykładu, zadeklarujemy zmienn tekstow tak: char string1[30] = "Lubie C++ "; zadeklarowana maksymalna długo�� ła�cucha znakowego wynosi 30, natomiast faktyczna długo�� ła�cucha znakowego wynosi 10 znaków. Je�li wywołamy strlen() i ka�emy jej okre�li� długo�� ła�cucha znakowego string1: unsigned int dlugosc = strlen(string1); funkcja przypisze zmiennej dlugosc warto�� 10 a nie 30. Je�li wpisałe� poprzedni program program przykładowy do okienka edycyjnego - wystarczy doda� dwa nowe wiersze. [P067.CPP] #include <conio.h> #include <iostream.h> #include <string.h> main() { char imie[50], nazwisko[20]; int dlugosc; clrscr(); cout << "Podaj imie: "; cin >> imie; cout << "Podaj nazwisko: "; cin >> nazwisko; strcat(imie, " "); strcat(imie, nazwisko); cout << "\nNazywasz sie: " << imie << '\n'; dlugosc = strlen(imie); cout<<"Imie i nazwisko sklada sie z: "<<dlugosc<<"znakow\n"; cout << "Nacisnij dowolny klawisz"; getch(); return 0; } W programie z Listingu 5.2 nie musisz stosowa� dodatkowej zmiennej dlugosc. Taki sam efekt uzyskasz pisz c zamiast dwu wierszy jeden: cout << "Wszystkich znakow bylo: " << strlen(imie) << '\n'; POBIERANIE I WYSZUKIWANIE WYCINKA TEKSTU - substring. Podobnie łatwo do ł czenia ła�cuchów mo�esz dokona� podziału

Page 62: C++ - lekcje

62

wi�kszych tekstów na mniejsze fragmenty. "Du�e" pierwone ła�cuchy nazywaj si� "string", a te mniejsze fragmenty - "substring". Do podziału ła�cuchów na "podła�cuchy" j�zyk C++ dysponuje specjalnymi funkcjami: strncpy() i strcpy() - STRiNg CoPY - kopiuj string. [S] Substring - Cz��� składowa wi�kszego ła�cucha znaków. ________________________________________________________________ Substring to mniejszy ła�cuch znaków stanowi cy cz��� wi�kszego ła�cucha znaków. Np. substring BAB jest cz��ci wi�kszego ła�cucha BABCIA. source - �ródło (miejsce pochodzenia); destination - miejsce przeznaczenia. ________________________________________________________________ Funkcja strncpy() kopiuje we wskazane miejsce tylko pewn - zadan liczb� pocz tkowych znaków ła�cucha. Funkcj� strncpy() mo�esz zastosowa� w swoich programach w nast�puj cy sposób: char tab_A[80] = "BABCIA"; char tab_B[80] = ""; strncpy(tab_B, tab_A, 3); /* kopiuj 3 pierwsze znaki */ W tym przykładzie wywołujemy funkcj� strncpy() przekazuj c jej przy wywołaniu trzy argumenty: tab_B - destination string - wynikowy ła�cuch tekstowy (ten nowy, który powstanie); tabn_A - source string - ła�cuch �ródłowy (ten, z którego b�dziemy "obcina�" kawałek); 3 - maksymalna liczba znaków, któr nale�y obci � . Obci�te znaki utworz "substring" - "BAB". Pobieranie i "wycinanie" znaków rozpocznie si� od pierwszego znaku ła�cucha �ródłowego tab_A[80], wi�c funkcja wywołana w taki sposób: strncpy(string1, string2, 3); spowoduje pobranie pierwszych 3 znaków z ła�cucha string2 i skopiowanie ich do ła�cucha string1. Funkcja strcpy() (Uwaga! bez "n") powoduje skopiowanie całego ła�cucha znaków. Sposób zastosowania funkcji jest podobny do przykładu z strncpy(), z tym, �e nie trzeba podawa� liczby całkowitej okre�laj cej ilo�� znaków do kopiowania. Jak wszystkie, to wszystkie (jak mawiała babcia), zatem wywołanie funkcji: strcpy(string1, string2); spowoduje skopiowanie całego ła�cucha znaków zawartego w zmiennej string2 do zmiennej string1. Je�li, dla przykładu, zmiennej string2 przypiszemy ła�cuch tekstowy string2 = "BABCIA"; to po zadziałaniu funkcji strcpy(string1, string2) zmiennej string1 zostanie przypisany dokładnie taki sam ła�cuch. Rozwa�my program przykładowy. Po uruchomieniu program poprosi o wpisanie ła�cucha tekstowego. Wpisz dowolny tekst. Tekst powinien zawiera� wi�cej ni� 3 znaki. Po pobraniu wyj�ciowego/�ródłowego tekstu od u�ytkownika, program pobierze z tego tekstu kilka mniejszych ła�cuchów tekstowych typu "substring" i wy�wietli je na ekranie. [P068.CPP] #include <conio.h> #include <iostream.h> #include <string.h> #include <stdio.h> main() { char napis1[80] = ""; char napis2[80] = ""; char napis3[80] = "";

clrscr(); cout << "Wpisz jakis tekst: "; gets(napis1); strcpy(napis2, napis1); strncpy(napis3, napis1, 3); cout << "\nKopia tekstu: "; cout << '*' << napis2 << "*\n"; cout << "Pierwsze 3 znaki tekstu: "; cout << '\'' << napis3 << '\'' << '\n'; cout << "\n\n...dowolny klawisz..."; getch(); return 0; } [???] A je�li zabraknie znaków? ________________________________________________________________ Spróbuj uruchomi� program podaj c mu ła�cuch tekstowy krótszy ni� 5 znaków. Jest to próba oszukania funkcji, która oczekuje, �e kopiowane 3 znaki powinny istnie�, mało tego, powinny by� zaledwie cz��ci wi�kszego ła�cucha. Jak widzisz, program nie "zawiesza si�". W j�zyku C++ funkcje opracowane s zwykle w taki sposób, �e nawet otrzymuj c bezsensowne parametry potrafi jako� tam wybrn � z sytuacji. Tym niemniej, nawet je�li program si� nie zawiesza, nie oznacza to, �e wyniki działania przy bezsensownych danych wej�ciowych b�d mie� jakikolwiek sens. Jako programista powiniene� wystrzega� si� takich bł�dów (dane z poza zakresu, dane bez sensu merytorycznego) nie licz c na to, �e C++ jako� z tego wybrnie. ________________________________________________________________ Najwa�niejszy fragment tekstu programu wraz z komentarzem: char napis1[80] = ""; <-- deklaracje zmiennych tekstowych char napis2[80] = ""; <-- i nadanie im zerowej zawarto�ci char napis3[80] = ""; <-- długo�� pustego napisu - zero. ... gets(napis1); <-- GET String - pobierz string strcpy(napis2, napis1); <-- kopiowanie całego tekstu strncpy(napis3, napis1, 3); <-- kopiowanie cz��ci tekstu ... Zwró� uwag�, �e program do pobrania danych (tekstu) od u�ytkownika posługuje si� funkcj gets() (ang. GET String - pobierz ła�cuch znaków). Obiekt cin jest bardzo wygodnym �rodkiem słu� cyn do wczytywania danych, ale nie pozwala wprowadza� napisów zawieraj cych spacje. Je�li zastosowaliby�my w programie cin >> string1; i wpisali tekst zawieraj cy spacje, np.: To nie wa�ne, czy Polska... wczytane zostałyby tylko znaki To (do pierwszej spacji). Z kolei funkcja gets() pozwala wczyta� wiersz tekstu zawieraj cy dowolne znaki uznaj c za koniec znak CRLF (powrót karetki, zmiana wiersza) generowany po naci�ni�ciu [Entera]. Przeciwn , symetryczn funkcj do gets() jest funkcja puts() (ang. PUT String - wyprowad� wiersz tekstu). Prototypy funkcji gets() i puts() znajduj si� w pliku nagłówkowym STDIO.H. Dlatego ten plik nagłówkowy został doł czony na pocz tku dyrektyw #include. WYSZUKIWANIE TEKSTÓW. Wyobra�my sobie, �e mamy list� imion i chcemy na tej li�cie odszuka� znajome imi� np. Alfons. Specjalnie do takich celów C++ dysponuje funkcj : strstr() - STRing's subSTRing - cz��� ła�cucha tekstowego Aby wyszuka� w wi�kszym tek�cie mniejszy fragment, powinni�my

Page 63: C++ - lekcje

63

wywołuj c funkcj� przekaza� jej dwie informacje: GDZIE SZUKA� - wskaza� ła�cuch tekstowy do przeszukiwania; i CZEGO SZUKA� - poda� ten tekst, który nas interesuje i który funkcja powinna dla nas odnale��. Funkcja strstr(), powinna zatem mie� dwa argumenty: char Lista[] = "Adam, Buba, Adolf, Magda"; ... gdzie = strstr(Lista, "Adolf"); Funkcja strstr() wyszukuje pierwsze wyst pienie danego tekstu. Po wyszukaniu, funkcja powinna nam w jaki� sposób wskaza�, gdzie znajduje si� interesuj cy nas tekst. Jak wiesz, do wskazywania ró�nych interesuj cych rzeczy słu� w C++ WSKA�NIKI (pointer). W przykładzie powy�ej funkcja strstr() w wyniku swojego działania zwraca wska�nik do szukanego tekstu "Alfons". Aby wska�nik nam nie przepadł, trzeba go zapami�ta�. Funkcja zatem przypisuje wska�nik zmiennej "gdzie". W miejscu przeznaczonym dla tej zmiennej w pami�ci b�dzie odt d przechowywany wska�nik, wskazuj cy nam - gdzie w pami�ci kmputera znajduje si� interesuj cy nas tekst "Alfons\0". Aby komputer zarezerwował miejsce w pami�ci dla wska�nika, trzeba go o to "poprosi�" na pocz tku programu, deklaruj c, �e w programie zamierzamy posługiwa� si� wska�nikiem. Deklaracja wska�nika do zmiennej tekstowej wygl da tak: char *wskaznik; Przykładowy program pni�ej demonstruje sposób zadeklarowania wska�nika i wyszukiwanie tekstu. Program nie oczekuje �adnej informacji wej�ciowej od u�ytkownika. Uruchom program i przeanalizuj wydruk na ekranie porównuj c go z tekstem programu. [P069.CPP] #include <conio.h> #include <iostream.h> #include <string.h> main() { char string1[] = "Ala, Magda, Adam, Alfons, Jasiek, Alfons, As"; char *pointer; clrscr(); cout << "Lista:\n" << string1; pointer = strstr(string1, "Alfons"); cout << "Tekst 'Alfons' wystapil po raz pierwszy:\n"; cout << " " << pointer << '\n'; pointer = strstr(ptr, "Jasiek"); cout << "Tekst 'Jasiek' wystapil po raz pierwszy:\n"; cout << " " << pointer << '\n'; pointer = strstr(pointer, "As"); cout << "Tekst 'As' wystapil:\n"; cout << " " << ptr << '\n' << "\n\nNacisnij cokolwiek"; getch(); return 0; } Inn metod zastosowania funkcji manipuluj cych ła�cuchami tekstowymi mo�e by� "obróbka" tekstu wprowadzonego przez u�ytkownika. Nast�pny program przykładowy pozwala u�ytkownikowi wprowadzi� tekst do przeszukiwania (odpowiednik listy) i tekst do wyszukania (odpowiednik imienia). W wyniku wyszukania wskazanego ła�cucha program wy�wietla list� pocz wszy od wyszukanego pierwszego wyst pienia zadanego ła�cucha znaków. [P070.CPP]

#include <conio.h> #include <iostream.h> #include <string.h> #include <stdio.h> main() { char str1[80], str2[80]; char *ptr; clrscr(); cout << "Wpisz tekst do przeszukania:\n "; gets(str1); cout << "Co mam wyszukac?\n--> "; gets(str2); ptr = strstr(str1, str2); <-- wyszukiwanie tekstu cout << "Znalazlem: " << '\'' << str1 << '\'' << " w "; cout << '\'' << str2 << '\'' << '\n'; cout << ptr; cout << "\n\n ...Nacisnij klawisz..."; getch(); return 0; } DUE I MAŁE LITERY. Litery mog by� małe i du�e. Du�e litery nazywaj si� "capitals". Od słowa CAPitalS pochodzi skrót na klawiszu [Caps Lock]. Innym, u�ywanym do okre�lenia tego samego słowem jest "upper case" (du�e litery) lub "lower case" (małe litery). Czasami pojawia si� potrzeba zaminy du�ych liter na małe, b d� odwrotnie. W C++ słu� tego celu funkcje: strupr() - STRing to UPpeR case - zamie� litery wła�cuchu tekstowym na du�e. strlwr() - STRing to LoWeR case - zamie� litery w ła�cuchu na małe. Program przykładowy poni�ej demonstruje działanie tych funkcji. [P071.CPP] #include <conio.h> #include <iostream.h> #include <string.h> #include <stdio.h> main() { char string1[80]; clrscr(); cout << "Wpisz tekst do zamiany:\n"; gets(string1); cout << "\nNormalnie: " << string1 << '\n'; cout << "TYLKO DUZE: " << strupr(string1) << '\n'; cout << "tylko male: " << strlwr(string1) << '\n'; cout << "\n\n...Nacisnij klawisz..."; getch(); return 0; } [???] DLA DOCIEKLIWYCH. ________________________________________________________________ * Argumenty funkcji - zawsze w tej samej kolejno�ci! Kiedy wywołujesz gotow funkcj� - np. strstr(), argumenty funkcji musz by� podane zawsze w tej samej kolejno�ci (tak, jak funkcja "si� spodziewa"). Wywołanie funkcji: pointer = strstr(string, substring, 3); powiedzie si� i funkcja zadziała zgodnie z oczekiwaniami. Natomiast wywołanie funkcji tak: pointer = strstr(3, substring, string);

Page 64: C++ - lekcje

64

spowoduje bł d przy kompilacji programu. * Przy manipulacji stringami kłopoty mog sprawia� spacje, b d� ich brak. Dla przykładu przy sklejaniu dwóch ła�cuchów tekstowych warto dla czytelno�ci doda� spacj�, by nie uzyskiwa� napisów typu: WaldekKowalski. Łatwo mo�na przegapi� i inne ograniczniki (ang. delimiter). * Ocena długo�ci tekstu. Szczególnie przewiduj cy i ostro�ny musi by� programista wtedy, gdy ła�cuch b�dzie wprowadzany przez u�ytkownika programu. LEKCJA 19: KILKA INNYCH PRZYDATNYCH FUNKCJI. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak zapisa� teksty na dysku i jak jeszcze mo�na nimi manipulowa� przy pomocy gotowych funkcji Borland C++. ________________________________________________________________ Program poni�ej demonstruje zastosowanie trzech przydatnych funkcji: [P072.CPP] #include <conio.h> int main(void) { int i, x = 0, y = 0; clrscr(); for (i = 1; i < 10; i++) { y = i; x = 5*i; textbackground(16-i); textcolor(i); gotoxy(x, y); cprintf("Wspolrzedne: x=%d y=%d", x, y); getch(); } return 0; } textbackground() - ustaw kolor tła pod tekstem texcolor() - ustaw kolor tekstu gotoxy() - rozpocznij drukowanie tekstu od punktu o współrz�dnych ekranowych x - numer kolumny (w normalnym trybie: 1-80) y - numer wiersza (w normalnym trybie: 1-25) [Z] ________________________________________________________________ 1. Rozmie�� na ekranie napisy i znaki semigraficzne tworz ce rysunek tabelki. 2. Opracuj program, w którym pojedyncze znaki, b d� napisy b�d porusza� si� po ekranie. 3. Spróbuj przyspieszy� działanie swojego programu z poprzedniego zadania poprzez wstawk� w assemblerze. ________________________________________________________________ OPERACJE PLIKOWE - NIEOBIEKTOWO. W systemia DOS dane i programy s zgrupowane w pliki. Pliki (ang. file) mog by� TEKSTOWE i BINARNE. Najcz�stszymi operacjami na plikach s : * Utworzenie nowego pliku (ang. CREATE); * Odczyt z pliku (ang. READ); * Zapis do pliku (WRITE); * Otwarcie pliku (OPEN); * Zamkni�cie pliku (CLOSE); * Wyszukanie danej w pliku (SEEK); W kontaktach z urz dzeniami - np. z dyskiem po�rednicz DOS i BIOS. To system DOS wie, gdzie na dysku szuka� pliku (katalogu) o podanej nazwie i w których sektorach dysku znajduj si� fizycznie dane nale� ce do danego pliku. Operacje z plikami opieraj si� o odwoływanie do systemu operacyjnego za po�rednictwem tzw. Deskryptora pliku (File Descriptor - numer

identyfikacyjny pliku). Zestaw "narz�dzi" potrzebnych nam do pracy to: IO.H - prototypy funkcji obsługi WEj�cia/WYj�cia (ang. Input/Output=IO); FCNTL.H - plik zawieraj cy definicje wymienionych poni�ej stałych: O_BINARY - otwarcie pliku w trybie binarnym; O_TEXT - otwarcie pliku w trybie tekstowym; O_RDONLY (Open for Read Only) - otwórz tylko do odczytu; O_WRONLY (...Write Only) - tylko dla zapisu; O_RDWR (Reading and Writing) dozwolony zapis i odczyt; STAT.H - zawiera definicje stałych S_IREAD - plik tylko do odczytu (przydatne dla funkcji creat); S_IWRITE - tylko zapis (przydatne dla funkcji creat); FUNKCJE: int open(p1, p2, p3) - trójparametrowa funkcja otwieraj ca plik; (parametry patrz przykład) zwraca do programu Wynik = -1 (operacja zako�czona niepowodzeniem - np. nie ma pliku) lub Wynik = File Descriptor - numer pliku przekazany przez DOS. int creat(p1, p2) - funkcja tworz ca nowy plik; int read(...) - funkcja czytaj ca z pliku; int write(...) - funkcja zapisu do pliku; imt close(...) - zamkni�cie pliku. Po uruchomieniu program otwiera automatycznie trzy standardowe pliki, zwi zane z urz dzeniami: 0 - stdin - standardowy plik wej�ciowy (norm. klawiatura konsoli); 1 - stdout - standardowy plik wyj�ciowy (norm. monitor); 2 - stderr - standardowy plik wyj�ciowy - diagnostyczny (komunikaty o bł�dach). [S] STD... STandarD INput - standardowe wej�cie. STD OUTput - standardowe wyj�cie. STD ERRors - plik diagnostyczny. //[P072-2.CPP] # include <stdio.h> # include <conio.h> # include <SYS\STAT.H> //Duze litery tylko dla podkreslenia # include <FCNTL.H> # include <IO.H> char *POINTER; int IL_znakow, DLUG_pliku, TRYB_dostepu, Wynik, i; int Plik_1, Plik_2; char BUFOR[20] = {"TEKST DO PLIKU"}; char STOS[3], ZNAK='X'; main() { POINTER = &BUFOR[0]; printf("Wloz dyskietke do A: i nacisnij cos...\n"); Plik_1 = creat( "a:\\plik1.dat", S_IWRITE); if (Plik_1 == -1) printf("\n Nie udalo sie zalozyc plik1.dat..."); Plik_2 = creat( "a:\\plik_2.dat", S_IWRITE); if (Plik_2 == -1) printf("\n Klops przy Plik2.dat"); _fmode = O_BINARY; //Bedziemy otwierac w trybie binarnym Wynik = open( "a:\\plik1.dat", O_WRONLY ); if (Wynik == -1) printf("\n Nie udalo sie otworzyc pliku..."); IL_znakow = 15; //Ilosc znakow do zapisu Wynik =write( Plik_1, POINTER, IL_znakow ); printf("Zapisalem %d znakow do pliku.", Wynik);

Page 65: C++ - lekcje

65

close( Plik_1 ); Plik_1 = open("a:\\Plik1.dat", O_RDONLY ); Plik_2 = open("a:\\Plik_2.dat", O_WRONLY ); POINTER = &STOS[0]; for (i=1; ZNAK; i++) //Kopiuje plik + spacje { STOS[1] = ZNAK; write( Plik_2, POINTER, 2); read( Plik_1, &ZNAK, 1); } close(Plik_1); close(Plik_2); getch(); return 0; } Przykładowy program wykonuje nast�puj ce czynno�ci: 1. Tworzy plik a:\plik1.dat (potrzebny dost�p do dyskietki a:). 2. Tworzy plik a:\plik_2.dat. 3. Otwiera plik a:\plik1.dat w trybie binarnym tylko do zapisu. (ZWRÓ� UWAG�, �e tryb binarny nie przeszkadza zapisa� tekstu.) 4. Dokonuje zapisu do pliku. 5. Zamyka plik a:\plik1.dat. 6. Otwiera plik1.dat w trybie binarnym tylko do odczytu. 7. Otwiera plik_2.dat tylko do zapisu. 8. Kopiuje plik1.dat do plik_2.dat dodaj c spacje. Zwró� uwag� na konstrukcj�: for(i=1; ZNAK; i++) Wyja�nienie. Póki jest znak wykonuj kopiowanie. Przypominam, �e koniec to NUL - '\0'. Je�li czytamy i piszemy po kolei - wszystko jest proste. Je�eli natomiast chcemy wyszuka� w pliku okre�lone miejsce, to b�dzie nam jeszcze dodatkowo potrzebny mechanizm do okre�lenia pozycji w pliku - tzw. WSKA�NIK PLIKOWY. Pozycj� mo�na okre�la� wzgl�dem pocz tku pliku: SEEK_SET - stała okre�laj ca pozycjonowanie wzgl�dem pocz tku pliku; SEEK_CUR - wzgl�dem poło�enia bie� cego (ang. Current - bie� cy); SEEK_END - okre�lenie pozycji wzgl�dem ko�ca pliku; EOF - End Of File - znak ko�ca pliku. Funkcja lseek(): WSK_PLK = long int lseek( plik, o_ile, kierunek); słu�y do pozycjonowania w pliku. Liczba typu long int okre�laj ca pozycj� w pliku nazywana jest WSKA�NIKIEM PLIKOWYM ( w programie przykładowym została oznaczona long int WSK_PLK). W programie przykładowym wykonywane jest kolejno: * utworzenie na dysku pliku PROBA.DAT; * zapis do pliku wprowadzonych z klawiatury liczb całkowitych typu int; * zamkni�cie pliku; * otwarcie pliku do odczytu; * ustawienie wska�nika na ko�cu pliku; * odczyt z pliku od ko�ca; * wyprowadzenie odczytanych z pliku danych na ekran. [P073.CPP] # include "sys\stat.h" # include "conio.h" # include "stdio.h" # include "io.h" # include "fcntl.h" # define Cofnij_o_Zero 0 # define dwa_bajty 2

int Numer = 0; int Plik, L, M, i; long int Dlug_Pliku; main() { clrscr(); creat("A:\PROBA.DAT", S_IWRITE); printf("\nPodaj liczbe rozna od zera, zero - KONIEC"); _fmode=O_BINARY; Plik=open("A:\PROBA.DAT", O_WRONLY); do { printf("\n Nr liczby \t%d\t\t", Numer++); scanf("%d", &L); if (L) write(Plik, &L, 2); } while (L != 0); close(Plik); getch(); printf("\n Teraz odczytam te liczby z pliku \n"); Plik=open("A:\PROBA.DAT", O_RDONLY); Dlug_Pliku=lseek(Plik, 0, SEEK_END); for (i=Dlug_Pliku-dwa_bajty; i>=0; i-=2) { lseek(Plik, i, SEEK_SET); read(Plik, &M, dwa_bajty); printf("%d, ", M); } close(Plik); getch(); return 0; } [Z] ________________________________________________________________ Opracuj program wykonuj cy operacje na tekstach opisane wcze�niej na ła�cuchach tekstowych pobieranych z zewn�trznych plików dyskowych i umieszczanych w wynikowych plikach tekstowych. LEKCJA 20 - JE�LI PROGRAM POWINIEN URUCHOMI� INNY PROGRAM... ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak w C++ mo�na programowa� * procesy potomne * pisa� programy rezyduj ce w pami�ci (TSR) ________________________________________________________________ O programach rezydentnych (TSR) i procesach potomnych. Warunek zewn�trznej zgodno�ci z poprzednimi wersjami DOS wyra�nie hamuje ewolucj� systemu MS DOS w kierunku "powa�nych" systemów operacyjnych umo�liwjaj cych prac� wieloprogramow w trybie "multiuser", "multitasking" i "time sharing". Pewn namiastk� pracy wieloprocesowej daj nam ju� DOS 5/6 i Windows 3.1. Mo�na ju� otwiera� wiele okien programów jednocze�nie, mo�na np. drukowa� "w tle", mo�na wreszcie pisa� rezyduj ce stale w pami�ci programy klasy TSR (ang. Terminated and Stay Resident) uaktywniaj ce si� "od czasu do czasu". O bloku PSP. System DOS przydziela programom blok - "nagłówek" wst�pny nazywany PSP (ang. Program Segment Prefix). Blok ten zawiera informacje o stanie systemu DOS w momencie uruchamiania programu (nazywanego tu inaczej procesem). Znajduj si� tam informacje o bie� cym stanie zmiennych otoczenia systemowego (ang. environment variables) i parametrach uruchomieniowych. Blok PSP zajmuje 256 bajtów na pocz tku kodu programu w zakresie adresów: CS:0000 ... CS:0100 (hex) Wła�ciwy kod programu zaczyna si� zatem od adresu CS:0100.

Page 66: C++ - lekcje

66

Interpreter rozkazów systemu DOS ładuje programy do pami�ci posługuj c si� funkcj systemow nr 75 (4B hex). Wszystko jest proste dopóki mamy do czynienia z programem "krótkim" typu *.COM. Je�li jednak�e program uruchamiany jest w wersji "długiej" - *.EXE, dowolna mo�e by� nie tylko długo�� pliku, ale tak�e pocz tkowa zawarto�� rejestrów CS, SS, SP i IP. W plikach typu *.EXE pocz tek bloku PSP wskazuj rejestry DS (DS:0000) i ES. W Borland C++ masz do dyspozycji specjaln funkcj� getpsp() przy pomocy której mo�esz uzyska� dost�p do bloku PSP programu. Krótki przykład zastosowania tej funkcji poni�ej: /* Przykład zastosowania funkcji getpsp(): */ # include <stdio.h> # include <dos.h> main() { static char TAB[128]; char far *ptr; int dlugosc, i; printf("Blok PSP: %u \n", getpsp()); ptr = MK_FP(_psp, 0x80); dlugosc = *ptr; for (i = 0; i < dlugosc; i++) TAB[i] = ptr[i+1]; printf("Parametry uruchomieniowe: %s\n", TAB); } W normalnych warunkach po wykonaniu "swojej roboty" program zostaje usuni�ty z pami�ci operacyjnej (czym zajmuje si� funkcja systemowa nr 76 - 4C (hex)). Aby tak si� nie stało, program mo�e: * uruchomi� swój proces (program) potomny; * wyj�� "na chwil�" do systemu DOS - tj. uruchomi� jako swój proces potomny interpreter COMMAND.COM; * przekaza� sterowanie programowi COMMAND.COM pozostaj c w pami�ci w postaci "u�pionej" oczekuj c na uaktywninie. Poni�ej kilka prostych przykładów uruchamiania jednych procesów przez inne w Borland C++: /* Funkcja execv(): uruchomienie programu "potomnego"*/ # include <process.h> # include <stdio.h> # include <errno.h> void main(int argc, char *argv[]) { int i; printf("Parametry uruchomieniowe:"); for (i=0; i<argc; i++) printf("\n%d) %s", i, argv[i]); printf("Przekazuje parametry do procesu 2 par_1, par_2...\n"); execv("CHILD.EXE", argv); .... exit (2); } [P074.CPP] /* Funkcja system() - na chwil� do DOS */ # include <stdlib.h> # include <stdio.h> void main() { printf("Wyjscie do DOS i wykonanie jednego rozkazu:\n"); system("dir > c:\plik.dir"); }

/* Funkcje grupy spawn...() : spawnl() */ # include <process.h> # include <stdio.h> # include <conio.h> void main() { int rezultat; rezultat = spawnl(P_WAIT, "program.exe", NULL); if (rezultat == -1) { perror(" Fiasko !"); exit(1); } } /* Funkcja spawnle() */ # include <process.h> # include <stdio.h> # include <conio.h> void main() { int rezultat; rezultat = spawnle(P_WAIT, "program.exe", NULL, NULL); if (rezultat == -1) { perror("Fiasko !"); exit(1); } } Zagadnienie uruchamiania programów potomnych (ang. child process) przez programy macie�yste (ang. parent process) jest rozpracowane w C++ do�� dokładnie i zarazem obszernie. Istnieje wiele gotowych funkcji bibliotecznych, z usług których mo�esz tu skorzysta�. Wszystko to nie jest jednak "prawdziwym" programem TSR. Przyjrzyjmy si� zatem dokładniej dopuszcalnym przez system DOS sposobom zako�czenia programu nie powoduj cym usuni�cia programu z pami�ci. Je�li program rezydentny jest niewielki (kod < 64 K), mo�emy zako�czy� program posługuj c si� przerywaniem INT 39 (27 hex). Je�li natomiast zamierzamy posługiwa� si� dłu�szymi programami, mamy do dyspozycji funkcj� systemow nr 49 (31 hex). Nale�y tu zwróci� uwag�, �e zako�czenie programu w taki sposób (z pozostawieniem w pami�ci) nie spowoduje automatycznego zamkni�cia plików, a jedynie opró�nienie buforów. Programy rezydentne dzieli si� umownie na trzy kategorie: [BP] - background process - procesy działaj ce "w tle"; [SV] - services - programy usługowe - np. PRINT; [PP] - pop up programs - uaktywniane przez okre�lon kombinacj� klawiszy; System DOS dysponuje tzw. przerywaniem multipleksowym (naprzemiennym) wykorzystywanym cz�sto przez programy rezydentne. Jest to przerywanie nr INT 47 (2F hex). MS DOS załatwia takie problemy funkcjami nr 37 (25 hex) - zapisanie wektora przerywania i 53 (35 hex) - odczytanie wektora przerywania. Z jakich funkcji C++ mo�na skorzysta�? W C++ masz do dyspozycji par� funkcji getvect() i setvect() (ang. GET/SET VECTor - pobierz/ustaw wektor przerywania). Poni�ej krótkie przykłady zastosowa� tych funkcji. /* Opcja: Options | Compiler | Code generation | Test Stack Overflow powinna zosta� wył czona [ ] (off) */ # include "stdio.h" # include "dos.h" # include "conio.h" /* INT 28 (1C hex) - Przerywanie zegarowe */

Page 67: C++ - lekcje

67

void interrupt ( *oldhandler)(void); int licznik = 0; void interrupt handler(void) { /* Inkrementacja globalnej zmiennej licznik */ licznik++; /* Wywolujemy stary "handler" zegara */ oldhandler(); } void main() { /* Zapami�taj poprzedni wektor przerywania 28 */ oldhandler = getvect(28); /* Zainstaluj now funkcje obslugi przerywania */ setvect(28, handler); /* Inkrementuj licznik */ for (; licznik < 10; ) printf("licznik: %d\n",licznik); //odtworz stara funkcje obslugi przerywania: interrupt handler setvect(28, oldhandler); } # include <stdio.h> # include <dos.h> void interrupt nowa_funkcja(); // prototyp funkcji - handlera void interrupt (*oldfunc)(); /* interrupt function pointer */ int warunek = 1; main() { printf("\n [Shift]+[Print Screen] = Quit \n"); printf("Zapamietaj, i nacisnij cosik...."); while(!kbhit()); /* zapamietaj stary wektor */ oldfunc = getvect(5); /* INT 5 to przerywanie Sys Rq, albo Print Screen */ /* zainstaluj nowa funkcje obslugi: interrupt handler */ setvect(5, nowa_funkcja); while (warunek) printf("."); /* Odtworz stary wektor przerywania */ setvect(5, oldfunc); printf("\n Udalo sie... nacisnij cosik..."); while(!kbhit()); } /* Definicja nowego handlera */ void interrupt nowa_funkcja() { warunek = 0; /* jesli warunek == 0, petla zostanie przerwana*/ } Je�li nasz program zamierza korzysta� z przerywania multipleksowego INT 47 (2F hex), nale�y pami�ta�, �e przerywanie to wykorzystuj tak�e inne programy systemowe. Rozró�nia� te programy mo�na przy pomocy identyfikatorów (podaj� dziesi�tnie): 01 - PRINT.EXE 06 - ASSIGN.COM 16 - SHARE.EXE (10 hex) 26 - ANSI.SYS 67 - HIMEM.SYS 72 - DOSKEY.COM 75 - TASK SWITCHER 173 - KEYB.COM 174 - APPEND.EXE 176 - GRAFTABL.COM

183 - APPEND.EXE Identyfikator programu TSR jest przekazywany za po�rednictwem rejestru AH. System DOS jest na razie systemem w zasadzie jednozadaniowym i jednou�ytkownikowym, w którym zasoby s przydzielane procesom kolejno (ang. serially reusable resources). Aby uchroni� si� przed potencjalnym konfliktem, powinni�my upewni� si�, czy DOS "nic nie robi". Cz�sto stosowan "sztuczk techniczn " jest zastosowanie flag ErrorMode i InDos systemu oraz wykorzystanie mechanizmów przerywa� nr 36 i 40 (24 i 28 hex). Przydatn informacj jest tak�e identyfikator programu - PID. Na tak ewntualno�� Borland C++ dysponuje makrem getpid zdefiniowanym w pliku nagłówkowym <PROCESS.H>: # define getpid() (_psp) Inn przydatn funkcj mo�e okaza� si� keep() (ang. keep resident - pozosta� rezydentny). Oto krótki przykład zastosowania tej funkcji - znów z wykorzystaniem przerywa� zegarowych. # include <dos.h> # define INTR 0x1C /* przerywanie INT 28 */ # define ATTR 0x7900 /* ograniczenie wielko�ci sterty (heap length) i stosu (stack length): */ extern unsigned _heaplen = 1024; extern unsigned _stklen = 512; void interrupt ( *oldhandler)(void); void interrupt handler(void) { unsigned int (far *ekran)[80]; static int licznik; // Adres pamieci dla monitora barwnego: B800:0000. // Dla monitora monochromatycznego: B000:0000. ekran = MK_FP(0xB800,0); // piloksztaltna zmiana licznika w przedziale 0 ... 9 licznik++; licznik %= 10; ekran[0][79] = licznik + '0' + ATTR; // wywołaj stara funkcje obslugi - old interrupt handler: oldhandler(); } void main() { oldhandler = getvect(INTR); // zainstaluj nowa funkcje interrupt handler setvect(INTR, handler); /* _psp - to adres pocz tku programu, SS:SP to adres stosu, czyli koniec programu. Biorac pod uwage przesuniecie SEGMENT/OFFSET o jedna tetrade: SS:SP = SS + SP/16; */ keep(0, (_SS + (_SP/16) - _psp)); } Kilka istotnych drobiazgów technicznych. W Borland C++ masz do dyspozycji predefiniowane struktury BYTEREGS (rejestry jednobajtowe - "połówki") i WORDREGS (rejestry dwubajtowe). Mo�esz po tych strukturach dziedziczy� i np. tak metod wbudowa� je do swoich własnych klas. Nic nie stoi na przeszkodzie, by utworzy� np. klas� class REJESTRY : public WORDREGS { ... };

Page 68: C++ - lekcje

68

czy te� własn struktur�: struct REJESTRY : WORDREGS { ... }; Definicje tych struktur w Borland C++ wygl daj nast�puj co: struct BYTEREGS { unsigned int al, ah, bl, bh, cl, ch, dl, dh; }; struct WORDREGS { unsigned int ax, bx, cx, dx, si, di, cflag, flags; }; Rejestry segmentowe maj własn struktur�: struct SREGS { unsigned int es, cs, ss, ds; }; Pole WORDREGS::cflag odpowiada stanowi flagi przeniesienia (ang. Carry Flag) rejestru flags mikroprocesora, a pole WORDREGS::flags odpowiada stanowi cało�ci rejestru (w wersji 16 - bitowej). Poniewa� rejestry mog by� widziane alternatywnie jako podzielone na miezale�ne połówki - lub jako cało��, to wła�nie "albo - albo" wyra�a w C++ unia. W Borland C++ taka predefiniowana unia nazywa si� REGS: union REGS { struct WORDREGS x; struct BYTEREGS h; }; Z tych predefiniowanych struktur danych korzystaj m. in. funkcje int86() intdosx() i int86x() ("x" pochodzi od eXtended - rozszerzony). Oto krótkie przykłady zastosowania tych funkcji. # include <stdio.h> # include <conio.h> # include <dos.h> # define INT_NR 0x10 // 10 hex == 16 (Nr przerywania) VIDEO void UstawKursor(int x, int y) { union REGS regs; regs.h.ah = 2; // ustaw kursor regs.h.dh = y; // Wspolrzedne kursora na ekranie regs.h.dl = x; regs.h.bh = 0; // Aktywna stronica ekranu --> video page 0 int86(INT_NR, &regs, &regs); } void main() { clrscr(); UstawKursor(30, 12); printf("Tekst - Test"); while(!kbhit()); } # include <dos.h> # include <process.h> # include <stdio.h> void main() { char nazwapliku[40]; union REGS inregs, outregs; struct SREGS segregs; printf("\nPodaj nazwe pliku: "); gets(nazwapliku); // gets() == GET String inregs.h.ah = 0x43; inregs.h.al = 0x21;

inregs.x.dx = FP_OFF(nazwapliku); segregs.ds = FP_SEG(nazwapliku); int86x(0x21, &inregs, &outregs, &segregs); printf("\n Atrybuty pliku: %X\n", outregs.x.cx); } # include <stdio.h> # include <dos.h> int SkasujPlik(char far*) // Prototyp void main() { int error; err = SkasujPlik("PLIK.DAT"); if (!error) printf("\nSkasowalem plik PLIK.DAT"); else printf("\nNie moge skasowac pliku PLIK.DAT"); } int SkasujPlik(char far *nazwapliku) { union REGS regs; struct SREGS sregs; int wynik; regs.h.ah = 0x41; // Funkcja kasowania pliku regs.x.dx = FP_OFF(nazwapliku); sregs.ds = FP_SEG(nazwapliku); wynik = intdosx(&regs, &regs, &sregs); return(regs.x.cflag ? wynik : 0); // Jesli CF == 1, nastapilo fiasko operacji } I wreszcie na zako�czenie szczegóły techniczne działania funkcji systemowej nr 49 (31 hex) odpowiedzialnej za obsług� programów rezyduj cych w pami�ci (załadowanie procesu z pozostawieniem w pami�ci). 1. Wywołanie funkcji: AL = kod powrotu (ang. return code); AH = 0031 (hex) - nr funkcji; DX = długo�� programu TSR w paragrafach - Size/16 [Bajtów]; 2. Działanie: * funkcja nie zamyka plików, lecz opró�nia bufory; * funkcja odtwarza wektory przerywa� nr 34, 35, 36 (hex 21, 22, 23); * proces macie�ysty mo�e uzyska� kod powrotu przy pomocy funkcji nr 77 (4D hex). Wykorzystanie struktury SDA (ang. Swappable Data Area - obszar wymiennych danych) nie jest praktyk zalecan . Tworz c programy rezydentne b d� bardzo ostro�ny i pami�taj o jednej z podstawowych zasad - NIE JESTE� (tzn Twój program nie jest) SAM. LEKCJA 21: KILKA PROCESÓW JEDNOCZE�NIE. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak to zrobi�, by Twój PC mógł wykonywa� kilka rzeczy jednocze�nie. ________________________________________________________________ Procesy współbie�ne. Sprz�t, czyli PC ma mo�liwo�ci zdecydowanie pozwalaj ce na techniczn realizacj� pracy wielozadaniowej. Nie ma te� �adnych przeciwskaza�, by zamiast koprocesora umo�liwi� w PC instalacj� drugiego (trzeciego) równoległego procesora i uprawia� na PC powa�ne programowanie współbie�ne. Po co? To proste. Wyobra� sobie Czytelniku, �e masz procesor pracuj cy z cz�stotliwo�ci 25 MHz (to 25 MILIONÓW elementarnych operacji na sekund�!). Nawet, je�li wzi � pod uwag�, �e niektóre operacje (dodawanie, mno�enie, itp.) wymagaj wielu cykli - i tak mo�na w uproszczeniu przyj �, �e Twój procesor mógłby wykona� od kilkuset tysi�cy do kilku milionów operacji w ci gu sekundy. Je�li pracujesz np. z edytorem tekstu i piszesz jaki� tekst - znacznie ponad 99% czasu Twój procesor czeka KOMPLETNIE BEZCZYNNIE (!) na naci�ni�cie klawisza. Przecie� Twój komputer mogłby w tym samym czasie np. i formatowa� dyskietk� (dyskietka

Page 69: C++ - lekcje

69

te� jest powolna), i przeprowadza� kompilacj� programu, i drukowa� dokumenty, i przeprowadzi� defragmentacj� drugiego dysku logicznego, itp. itd.. Nawet taka pseudowspółbie�no�� realizowana przez DOS, Windows, czy sie� jest ofert dostatecznie atrakcyjn , by warto było przyjrze� si� mechanizmom PSEUDO-współbie�no�ci w C i C++. Współbie�no�� procesów, mo�e by� realizowana na poziomie * sprz�towym (architektura wieloprocesorowa), * systemowym (np. Unix, OS/2), * nakładki (np. sieciowej - time sharing, token passing) * aplikacji (podział czasu procesora pomi�dzy ró�ne funkcje/moduły tego samego pojedynczego programu). My zajmiemy si� tu współbie�no�ci widzian z poziomu aplikacji. Funkcje setjmp() (ang. SET JuMP buffer - ustaw bufor umo�liwiaj cy skok do innego procesu) i longjmp() (ang. LONG JuMP - długi skok - poza moduł) wchodz w skład standardu C i w zwi zku z tym zostały "przeniesine" do wszystkich kompilatorów C++ (nie tylko Borlanada). Porozmawiajmy o narz�dziach. Zaczniemy od klasycznego zestawu narz�dzi oferowanego przez Borlanda. Aby zapami�ta� stan przerwanego procesu stosowana jest w C/C++ struktura PSS (ang. Program Status Structure) o nazwie jmp_buf (JuMP BUFfer - bufor skoku). W przypadku współbie�no�ci wielu procesów (wi�cej ni� dwa) stosuje si� tablic� zło�on ze struktur typu struct jmp_buf TablicaBuforow[n]; Struktura słu�y do przechowywania informacji o stanie procesu (rejestrach procesora w danym momencie) i jest predefiniowana w pliku SETJMP.H: typedef struct { unsigned j_sp, j_ss, j_flag, j_cs; unsigned j_ip, j_bp, j_di, j_es; unsigned j_si, j_ds; } jmb_buf[1]; Prototypy funkcji: int setjmp(jmp_buf bufor); void longjmp(jmp_buf bufor, int liczba); W obu przypadkach jmp_buf bufor oznacza ten sam typ bufora (niekoniecznie ten sam bufor - mo�e ich by� wiele), natomiast int liczba oznacza tzw. return value - warto�� zwracan po powrocie z danego procesu. Liczba ta mo�e zawiera� informacj�, z którego procesu nast pił powrót (lub inn przydatn w programie), ale nie mo�e by� ZEREM. Je�li funkcja longjmp() otrzyma argument int liczba == 0 - zwróci do programu warto�� 1. Warto�� całkowita zwracana przez funkcj� setjmp() przy pierwszym wywołaniu jest zawsze ZERO a przy nast�pnych wywołaniach (po powrocie z procesu) jest równa parametrowi "int liczba" przekazanemu do ostatnio wywołanej funkcji longjmp(). Przyjrzyjmy si� temu mechanizmowi w praktyce. Wyobra�my sobie, �e chcemy realizowa� współbie�nie dwa procesy - proces1 i proces2. Proces pierwszy b�dzie na�ladował w uproszczeniu wymieniony wy�ej edytor tekstu - pozwoli na wprowadzanie tekstu, który b�dzie powtarzany na ekranie. Proces drugi b�dzie przesuwał w dolnej cz��ci ekranu swój numerek - cyferk� 2 (tylko po to, by było wida�, �e działa). Program główny wywołuj cy oba procesy powinien wygl da� tak: ... void proces1(void); void proces2(void); int main(void)

{ clrscr(); proces1(); proces2(); return 0; } Ale� tu nie ma �adnej współbie�no�ci! Oczywi�cie. Aby zrealizowa� współbie�no�� musimy zadeklarowa� bufor na bie� cy stan rejestrów i zastosowa� funkcje setjmp(): #include <setjmp.h> void proces1(void); void proces2(void); jmp_buf bufor1; int main(void) { clrscr(); if(setjmp(bufor1) != 0) proces1(); //Powrót z procesu2 był? proces2(); return 0; } Po wywołaniu funkcji setjmp() zostanie utworzony bufor1, w którym zostanie zapami�tany stan programu. Funkcja, jak zawsze przy pierwszym wywołaniu zwróci warto�� ZERO, wi�c warunek if(setjmp(bufor1) != 0) ... nie b�dzie spełniony i proces1() nie zostanie wywołany. Program pójdzie sobie dalej i uruchomi proces2(): void proces2(void) { for(;;) { gotoxy(10,20); printf("PROCES 2: "); for(int i = 1; i<40; i++) { printf(".2\b"); delay(5); //UWAGA: delay() tylko dla DOS! } longjmp(bufor1, 1); <--- wró� } �____________ t� jedynk� zwróci setjmp() } Proces 2 b�dzie drukował "biegaj c dwójk�" (zwolnion przez opó�nienie delay(5); o pi�� milisekund), poczym funkcja longjmp() ka�e wróci� z procesu do programu głównego w to miejsce: int main(void) { clrscr(); if(setjmp(bufor1)) proces1(); <--- tu powrót proces2(); return 0; } Zmieni si� tylko tyle, �e powtórnie wywołana funkcja setjmp() zwróci tym razem warto�� 1, zatem warunek b�dzie spełniony i rozpocznie si� proces1(): void proces1(void) { while(kbhit()) { gotoxy(1,1); printf("PROCES1, Pisz tekst: [Kropka - Koniec]"); gotoxy(pozycja,2); znak = getch(); printf("%c", znak); pozycja++; } if(znak == '.') exit (0); } Proces 1 sprawdzi przy pomocy funkcji kbhit() czy w buforze klawiatury oczekuje znak (czy co� napisałe�). Je�li tak -

Page 70: C++ - lekcje

70

wydrukuje znak, je�li nie - zako�czy si� i program przejdzie do procesu drugiego. A oto program w cało�ci: [P075.CPP] #include <stdio.h> #include <process.h> #include <setjmp.h> #include <conio.h> #include <dos.h> void proces1(void); void proces2(void); jmp_buf bufor1, bufor2; char znak; int pozycja = 1; int main(void) { clrscr(); if(setjmp(bufor1)) proces1(); proces2(); return 0; } void proces1(void) { while(kbhit()) { gotoxy(1,1); printf("PROCES1, Pisz tekst: [Kropka - Koniec]"); gotoxy(pozycja,2); znak = getch(); printf("%c", znak); pozycja++; } if(znak == '.') exit (0); } void proces2(void) { for(;;) { gotoxy(10,20); printf("PROCES 2: "); for(int i = 1; i<40; i++) { printf(".1\b"); delay(5); } longjmp(bufor1,1); } } [!!!] UWAGA ________________________________________________________________ Funkcja delay() u�yta dla opó�nienia i zwolnienia procesów b�dzie funkcjonowa� tylko w �rodowisku DOS. Przy uruchamianiu prykładowego programu pod Windows przy pomocy BCW nale�y t� funkcj� poprzedzi� znakiem komentzrza // . ________________________________________________________________ Wyobra�my sobie, �e mamy trzy procesy. Przykład współbie�no�ci trzech procesów oparty na tej samej zasadzie zawiera program poni�ej [P076.CPP] #include <stdio.h> #include <process.h> #include <setjmp.h> #include <conio.h> #include <dos.h> void proces1(void); void proces2(void); void proces3(void); jmp_buf bufor1, bufor2;

char znak; int pozycja = 1; int main(void) { clrscr(); if(setjmp(bufor1)) proces1(); if(setjmp(bufor2)) proces2(); proces3(); return 0; } void proces1(void) { while(kbhit()) { gotoxy(1,1); printf("PROCES1, Pisz tekst: [Kropka - Koniec]"); gotoxy(pozycja,2); znak = getch(); printf("%c", znak); pozycja++; } if(znak == '.') exit (0); } void proces2(void) { for(;;) { gotoxy(10,20); printf("PROCES 2: "); for(int i = 1; i<40; i++) { printf(".2\b"); delay(5); } longjmp(bufor1, 1); } } void proces3(void) { for(;;) { gotoxy(10,23); printf("PROCES 3: "); for(int i = 1; i<40; i++) { printf(".3\b"); delay(2); } longjmp(bufor2,2); } } Procesy odbywaj si� z ró�n pr�dko�ci . Kolejno�� uruchamiania procesów b�dzie: - proces3() - proces2() - proces1() Po uruchomieniu programu zauwa�ysz, �e proces pierwszy (pisania) został spowolniony. Mo�na jednak temu zaradzi� przez ustawienie flag i priorytetów. Je�li dla przykładu uwa�amy, �e pisanie jest wa�niejsze, mo�emy wykrywa� zdarzenie - naci�ni�cie klawisza w ka�dym z mniej wa�nych procesów i przerywa� wtedy procesy mniej wa�ne. Wprowadzanie tekstu w przykładzie poni�ej nie b�dzie spowolnione przez pozostałe procesy. [P077.CPP] #include <stdio.h> #include <process.h> #include <setjmp.h> #include <conio.h>

Page 71: C++ - lekcje

71

#include <dos.h> void proces1(void); void proces2(void); void proces3(void); jmp_buf BuforStanu_1, BuforStanu_2; char znak; int pozycja = 1; int main(void) { clrscr(); if(setjmp(BuforStanu_1)) proces1(); if(setjmp(BuforStanu_2)) proces2(); proces3(); return 0; } void proces1(void) { while(kbhit()) { gotoxy(1,1); printf("PROCES1, Pisz tekst: [Kropka - Koniec]"); gotoxy(pozycja,2); znak = getch(); printf("%c", znak); pozycja++; } if(znak == '.') exit (0); } void proces2(void) { for(;;) { gotoxy(10,20); printf("PROCES 2: "); for(int i = 1; i<40; i++) { if(kbhit()) break; printf(".2\b"); delay(5); } longjmp(BuforStanu_1, 1); } } void proces3(void) { for(;;) { gotoxy(10,23); printf("PROCES 3: "); for(int i = 1; i<40; i++) { if(kbhit()) break; printf(".3\b"); delay(2); } longjmp(BuforStanu_2,2); } } [!!!]UWAGA ________________________________________________________________ W pierwszych dwu przykładach trzymanie stale wci�ni�tego klawisza spowoduje tylko automatyczn repetycj� wprowadzanego znaku. W przykładzie trzecim spowoduje to przerwanie procesów 2 i 3, co b�dzie wyra�nie widoczne na monitorze (DOS). Zwró� uwag�, �e kbhit() nie zmienia stanu bufora klawiatury. ________________________________________________________________ W bardziej rozbudowanych programach mo�na w oparciu o drugi parametr funkcji longjmp() zwracany przez funkcj� setjmp(buf) po

powrocie z procesu identyfikowa� - z którego procesu nast pił powrót i podejmowa� stosown decyzj� np. przy pomocy instrukcji switch: switch(setjmp(bufor)) { case 1 : proces2(); case 2 : proces3(); ..... default : proces0(); } [!!!]UWAGA ________________________________________________________________ * Zmienne steruj ce przeł czaniem procesów powinny by� zmiennymi globalnymi, b d� statycznymi. Tak�e dane, które nie mog ulec nadpisaniu bezpieczniej potraktowa� jako globalne. ________________________________________________________________ W przypadku wielu procesów celowe jest utworzenie listy, b d� kolejki procesów. Przydatny do tego celu bywa mechanizm tzw. "ła�cuchowej referencji". W obiektach klasy PozycjaListy nale�y umie�ci� pole danych - struktur� i pointer do nast�pnego procesu, któremu (zgodnie z ustalonym priorytetem) nale�y przekaza� sterowanie: static jmp_buf Bufor[m]; <-- m - ilo�� procesów ... class PozycjaListy { public: jmp_buf Bufor[n]; <-- n - Nr procesu PozycjaListy *nastepna; } Wyobra�my sobie sytuacj� odrobin� ró�n od powy�szych przykładów (w której zreszt para setjmp() - longjmp() równie cz�sto wyst�puje. #include <setjmp.h> jmp_buf BuforStanu; int Nr_Bledu; int main(void) { Nr_Bledu = setjmp(BuforStanu) <-- tu nast pi powrót if(Nr_Bledu == 0) <-- za pierwszym razem ZERO { /* PRZED powrotem z procesu (ów) */ .... Proces(); <-- Wywołanie procesu } else { /* PO powrocie z procesu (ów) */ ErrorHandler(); <-- obsługa bł�dów } .... return 0; } Taka struktura zapewnia działanie nast�puj ce: - Był powrót z procesu? NIE: Wywołujemy proces! TAK: Obsługa bł�dów, które wyst piły w trakcie procesu. Je�li teraz proces zaprojektujemy tak: void Proces() { int Flaga_Error = 0; ... /* Je�li nast piły bł�dy, flaga w trakcie pracy procesu jest ustawiana na warto�� ró�n do zera */ if(Error) Flaga_Error++; ... if(Fllaga_Error != 0) longjmp(BuforStanu, Flaga_Error);

Page 72: C++ - lekcje

72

... } proces przeka�e sterowanie do programu w przypadku wyst pienia bł�dów (jednocze�nie z informacj o ilo�ci/rodzaju bł�dów). [Z] ________________________________________________________________ 1. Napisz samodzielnie program realizuj cy 2, 3, 4 procesy współbie�ne. Je�li chcesz, by jednym z procesów stał si� całkowivie odr�bny program - skorzystaj z funkcji grupy spawn...() umo�liwiaj cych w C++ uruchamianie procesów potomnych. LEKCJA 22. NA ZDROWY CHŁOPSKI ROZUM PROGRAMISTY. ________________________________________________________________ W trakcie tej lekcji dowiesz si�: * jak przyspiesza� działanie programów w C++ * jakie dodatkowe narz�dzia zyskujesz "przesiadaj c si�" na nowoczesny kompilator C++ ________________________________________________________________ UNIKAJMY P�TLI, które nie s NIEZB�DNE ! Unikanie zb�dnych p�tli nazywa si� fachowo "rozwini�ciem p�tli" (ang. loop unrolling). Zwró� uwag�, �e zast�puj c p�tl� jej rozwini�ciem (ang. in-line code): * zmniejszamy ilo�� oblicze�, * zmniejszamy ilo�� zmiennych. Wyobra�my sobie p�tl�: for (i = 0; i < max; i++) T[i] = i; Je�li "unowocze�nimy" j tak: for (i = 0; i < max; ) { T[i++] = i - 1; T[i++] = i - 1; } ilo�� powtórze� p�tli zmniejszy si� dwukrotnie. Czai si� tu jednak pewne niebezpiecze�stwo: tablica mo�e mie� NIEPARZYST liczb� elementów. Np. dla 3-elementowej tablicy (max = 3) nast piłyby w pierwszym cyklu operacje: i = 0; 0 < 3 ? == TRUE --> T[0] = 0 // Tu nastepuje i++; // T[1] = 1 itd... To, co nast�puje w tak "spreparowanej" tablicy mo�esz prze�ledzi� uruchamiaj c program: [P078.CPP] # include <iostream.h> # include <stdio.h> # include <conio.h> # define p(x) printf("%d\t", x) int T[99+1], i, max; main() { cout << "\nPodaj ilosc elem. tablicy T[] - 2...99 \n"; cin >> max; cout << "T[i]\t\ti\n\n"; for (i = 0; i < max; ) {

T[i++] = i - 1; p(T[i-1]); cout << "\t" << i << "\n"; T[i++] = i - 1; p(T[i-1]); cout << "\t" << i << "\n"; while (!kbhit()); } return 0; } Aby nie spowodowa� próby odwołania do nieistniej cego elementu tablicy, mo�emy zadeklarowa� tablic� T[max + 1]. W przypadku, gdy max jest liczb nieparzyst , tablica wynikowa posiada parzyst liczb� elementów. Je�li natomiast max jest parzyste, tworzymy jeden zb�dny element tablicy, który pó�niej zostanie u�yty, ale kompilator ani program nie b�dzie nam si� "buntował". Mo�na spróbowa� zast pi� w programie bardziej czasochłonne operacje - szybszymi. Dla przykładu, w p�tli for(i = 1; i <= 100; i++) { n = i * 10; ... mo�na wyeliminowa� czasochłonne mno�enie np. tak: for(i = 1, n = 10; i <= 100; i++, n += 10) { ... lub wr�cz wprost, je�li dwie zmienne robocze nie s niezb�dne: for(n = 10; n <= 1000; n += 10) { ... Je�li wiadomo, �e jaka� p�tla powinna wykona� si� z definicji cho�by raz, warto wykorzystywa� konstrukcj� do...while, zamiast analizowa� niepotrzebnie warunek. Je�li stosujemy w programie p�tle zagnie�d�one (ang. nested loops), to p�ta zorganizowana tak: for(i = 1; i < 5; i++) (1) for(j = 1; j < 1000; j++) { A[i][j] = i + j; } zadziała szybciej ni� for(j = 1; j < 1000; j++) (2) for(i = 1; i < 5; i++) { A[i][j] = i + j; } W przypadku (1) zmienna robocza p�tli wewn�trznej b�dzie inicjowana pi�� razy, a w przypadku (2) - tysi c (!) razy. Czasami zdarza si�, �e w programie mo�na poł czy� kilka p�tli w jedn . for(i = 1; i < 5; i++) TAB_1[i] = i; ... for(k = 0; k < 5; k++) TAB_2[k] = k; Zmniejsza to i ilo�� zmiennych, i tekst programu i czas pracy komputera: TAB_2[0] = 0; for(i = 1; i < 5; i++) TAB_1[i] = i; TAB_2[i] = i; Czasami wykonywanie p�tli do ko�ca pozbawione jest sensu. Przerwa� p�tl� w trakcie wykonywania mo�na przy pomocy instrukcji break (je�li p�tle s zagnie�c�one, cz�sto lepiej u�y� niepopularnego goto przerywaj cego nie jedn - a wszystkie p�tle). Stosuj c umiej�tnie break, continue i goto mo�esz zaoszcz�dzi� swojemu komputerowi wiele pracy i czasu. Rutynowym "szkolno-strukturalnym" zap�tlaniem programu main() { char gotowe = 0;

Page 73: C++ - lekcje

73

... while (!gotowe) { znak = wybrano_z_menu(); if (znak == 'q' || znak == 'Q') gotowe = 1; else ....... gotowe = 1; } powodujesz cz�sto zupełnie niepotrzebne dziesi tki operacji, które ju� niczemu nie słu� . char gotowe; main() { ... while (!gotowe) { znak = wybrano_z_menu(); if (znak == 'q' || znak == 'Q') break; //Quit ! else ....... gotowe = 1; } Tym razem to, co nast�puje po else zostanie pomini�te. Wska�niki działaj w C++ szybciej, ni� indeksy, stosujmy je w miar� mo�liwo�ci w p�tlach, przy manipulowaniu tablicami i w funkcjach. INSTRUKCJE STERUJCE I WYRAENIA ARYTMETYCZNE. Na "chłopski rozum" programisty wiadomo, �e na softwarowych rozstajach, czyli na rozgał�zieniach programów prawdopodobie�stwo wyboru ka�dwgo z wariantów działania programu z reguły bywa ró�ne. Kolejno�� sprawdzania wyra�e� warunkowych nie jest zatem oboj�tna. Wyobra�my sobie lekarza, który zwiezionego na toboganie narciarza pyta, czy kto� w rodzinie chorował na �ółtaczk�, koklusz, reumatyzm, podagr�, itp. zamiast zaj � si� najpierw wariantem najbardziej prawdopodobnym - czyli zagipsowaniem nogi nieszcz��nika. Absurdalne, prawda? Ale przecie� (uderzmy si� w piersi) nasze programy czasami post�puj w taki wła�nie sposób... NAJPIERW TO, CO NAJBARDZIE PRAWDOPODOBNE I NAJPROSTSZE. Je�li zmienna x w naszym programie mo�e przyjmowa� (równie prawdopodobne) warto�ci 1, 2, 3, 4, 5, to "przesiew" if (x >= 2) { ... } else if (x == 1) { ... } else { ... } oka�e si� w praktyce skuteczniejszy, ni� if (x == 0) { ... } else if (x == 1) { ... } else { ... } Nale�y pami�ta�, �e w drabince if-else-if po spełnieniu pierwszego warunku - nast�pne nie b�d ju� analizowane. Zasada ta stosuje si� tak�e do wyra�e� logicznych, w których stosuje si� operatory logiczne || (lub) i && (i). W wyra�eniach tych, których ocen� C++ prowadzi tylko do uzyskania pewno�ci, jaka b�dzie warto�� logiczna (a nie koniecznie do ko�ca wyra�enia) nale�y zastosowa� kolejno��: MAX || W1 || W2 || W3 ... MIN && W1 && W2 && W3 ... gdzie MAX - oznacza opcj� najbardziej prawdopodobn , a MIN - najmniej prawdopodobn . Podobnie rzecz ma si� z pracochłonno�ci (zatem i czso-chłonno�ci ) poszczególnych wariantów. Je�li wariant najprostszy oka�e si� prawdziwy, pozostałe mo�liwo�ci mo�emy pomin �.

NIE MNÓ I NIE DZIEL BEZ POTRZEBY. Prawa MATEMATYKI pozostaj w mocy dla IBM PC i pozostan zawsze, nawet dla zupełnie nieznanych nam komputerów, które skonstruuj nasze dzieci i wnuki. Znajomo�� praw de Morgana i zasad arytmetyki jest dla programisty wiedz niezwykle przydatn . Jako próbk� zapiszmy kilka trywialnych to�samo�ci przetłumaczonych na C++: 2 * a == a + a == a << 1 16 * a == a << 4 a * b + a * c == a * (b + c) ~a + ~b == ~(a + b) Mo�naby jeszcze doda�, �e a / 2 == a >> 1, ale to nie zawsze prawda. Przesuni�cie w prawo liczb nieparzystych spowoduje obci�cie cz��ci ułamkowej. W przypadku wyra�e� logicznych: (x && y) || (x && z) == x && (y || z) (x || y) && (x || z) == x || (y && z) W arytmetycznej sumie i iloczynie NIE MA takiej symetrii. !x && !y == !(x || y) !x || !y == !(x && y) Je�li w skomplikowanych wyra�eniach arytmetycznych i logicznych zastosujemy zasady arytmetyki i logiki, zwykle staj si� krótsze i prostsze. Podobnie jak licz c na kartce, mo�emy zastosowa� zmienne pomocnicze do przechowywania cz�sto powtarzaj cych si� wyra�e� składowych. Wyra�enie wynik = (x * x) + (x * x); mo�emy przekształci� do postaci zm_pomocn = x * x; wynik = zm_pomocn << 1; Cz�sto napisane "na logik�" wyra�enia da si� łatwo zoptymalizowa�. Jako przykład zastosujmy funkcj� biblioteczn strcmp() (string compare - porównaj ła�cuchy znaków). Porównanie ła�cuchów if (strcmp(string1, string2) == 0) cout << "identyczne"; else if (strcmp(string1, string2) < 0) cout << "krotszy"; else cout << "dluzszy"; mo�na skróci� tak, by funkcja strcmp() była wywoływana tylko raz: wynik = strcmp(string1, string2); if (wynik == 0) cout << "identyczne"; break; else if (wynik < 0) cout << "krotszy"; else cout << "dluzszy"; Je�li pracuj c nad programem nie b�dziemy zapomina�, �e PC operuje arytmetyk dwójkow , wiele operacji dzielenia i mno�enia (długich i pracochłonnych) b�dziemy mogli zast pi� operacjami przesuni�cia w lewo, b d� w prawo (ang. shift), które nasz PC wykonuje znacznie szybciej. Dla liczb całkowitych dodatnich x * 2 == x << 1; x * 4 == x << 2 itp. .... [???] UWAGA: ________________________________________________________________ Takich skrótów nie mo�na stosowa� w stosunku do operandów typu double, ani float. ________________________________________________________________ Podobnie w przypadku dzielenia przez pot�g� dwójki mo�na zast pi� dzielenia znacznie szybsz operacj iloczynu logicznego.

Page 74: C++ - lekcje

74

x % 16 == x & 0xF; Je�li w programie warto�� zmiennej powinna zmienia� si� w sposób piłokształtny (tj. cyklicznie wzrasta� do MAXIMUM i po osi gni�ciu MAXIMUM spada� do zera), najprostszym rozwi zaniem jest x = (x + 1) % (MAXIMUM + 1); ale dzielenie trwa. Poni�szy zapis spowoduje wygenerowanie kodu znacznie szybszego: if (x == MAXIMUM) x = 0; else x++; stosuj c zamiast if-else operator ? : mo�emy to zapisa� tak: (x == MAXIMUM) ? (x = 0) : (x++); Mno�enie jest zwykle troch� szybsze ni� dzielenie. Zapis a = b / 10; mo�na zatem zast pi� szybszym: a = b * .1; Je�li mamy do czynienia ze stał STALA, to zapis w programie y = x / STALA; --> y = x * (1.0 / STALA); z pozoru bzdurny spowoduje w wi�kszo�ci implementacji wyznaczenie warto�ci mno�nika 1.0/STALA przez kompilator na etapie kompilacji programu (compile-time), a w ruchu (run-time) b�dzie obliczany iloczyn zamiast ilorazu. W programach cz�sto stosuje si� flagi binarne (jest-nie ma). C++ stosujemy jako flagi zmienne typu int lub char a w Windows BOOL. Je�li we�miemy pod uwag� fakt, �e operatory relacji generuj warto�ci typu TRUE/FALSE, typowy zapis: if (a > b) Flaga = 1; else Flaga = 0; zast pimy krótszym Flaga = (a > b); Taki krótszy zapis NIE ZAWSZE powoduje wygenerowanie szybszego kodu. Jest to zale�ne od specyfiki konkretnej implementacji. Je�li natomiast upro�cisz swój program tak: if (x > 1) a = 3; --> a = 3 * (x > 1); else a = 0; spowoduje to wyra�ne spowolnienie programu (mno�enie trwa). Kompilator C++ rozró�nia dwa rodzaje wyra�e�: * general expressions - wyra�enia ogólne - zawieraj ce zmienne i wywołania funkcji, których warto�ci nie jest w stanie okre�li� na etapie kompilacji i * constant expressions - wyra�enia stałe, których warto�� mo�na wyznaczy� na etapie kompilacji. Zapis wynik = 2 * x * 3.14; mo�esz zatem przekształci� do postaci wynik = 2 * 3.14 * x; Kompilator przekształci to wyra�enia na etapie kompilacji do postaci

wynik = 6.28 * x; co spowoduje zmniejszenie ilo�ci operacji w ruchu programu. Aby ułatwi� takie działanie kompilatora trzeba umie�ci� stałe obok siebie. LEKCJA 23 - Co nowego w C++? ________________________________________________________________ Z tej lekcji dowiesz si�, jakie mechanizmy C++ pozwalaj na stosowanie nowoczesnego obiektowego i zdarzeniowego stylu programowania i co programy robi z pami�ci . ________________________________________________________________ W porównaniu z klasycznym C - C++ posiada: * rozszerzony zestaw słów kluczowych (ang. keywords): ** nowe słowa kluczowe C++: class - klasa, delete - skasuj (dynamicznie utworzony obiekt), friend - "zaprzyja�nione" funkcje z dost�pem do danych, inline - wpleciony (funkcje przeniesione w formie rozwini�tej do programu wynikowego), new - utwórz nowy obiekt, operator - przyporz dkuj operatorowi nowe działanie, private - dane i funkcje prywatne klasy (obiektu), do których zewn�trzne funkcje nie maj prawa dost�pu, protected - dane i funkcje "chronione", dost�pne z ograniczeniami, public - dane i funklcje publiczne, dost�pne bez ogranicze�, template - szablon, this - ten, pointer wskazuj cy bie� cy obiekt, virtual - funkcja wirtualna, abstrakcyjna, o zmiennym działaniu. * nowe operatory (kilka przykładów ju� widzieli�my), np.: << - wy�lij do strumienia wyj�ciowego, >> - pobierz ze strumienia wej�ciowego. * nowe typy danych: klasy, obiekty, abstrakcyjne typy danych (ang. ADT). * nowe zasady posługiwania si� funkcjami: funkcje o zmiennej liczbie argumentów, funkcje "rozwijane" inline, funkcje wirtualne, itp.; Przede wszystkim (i od tego wła�nie rozpoczniemy) zobaczymy funkcje o nowych mo�liwo�ciach. ROZSZERZENIE C - FUNKCJE. Funkcje uzyskuj w C++ znacznie wi�cej mo�liwo�ci. Przegl d rozpoczniemy od sytuacji cz�sto wyst�puj cej w praktyce programowania - wykorzystywania domy�lnych (ang. default) parametrów. FUNKCJE Z DOMY�LNYMI ARGUMENTAMI. Prototyp funkcji w C++ pozwala na podanie deklaracji domy�lnych warto�ci argumentów funkcji. Je�li w momencie wywołania funkcji w programie jeden (lub wi�cej) argument (ów) zostanie pomini�te, kompilator wstawi w puste miejsce domy�ln warto�� argumentu. Aby uzyska� taki efekt, prototyp funkcji powinien zosta� zadeklarowany w programie np. tak: void Funkcja(int = 7, float = 1.234); Efekt takiego działania b�dzie nast�puj cy: Wywołanie w programie: Efekt: ________________________________________________________________ Funkcja(99, 5.127); Normalnie: Funkcja(99, 5.127); Funkcja(99); Funkcja(99, 1.234); Funkcja(); Funkcja(7, 1.234); ________________________________________________________________

Page 75: C++ - lekcje

75

[!!!] Argumentów mo�e ubywa� wył cznie kolejno. Sytuacja: Funkcja(5.127); //�LE Funkcja(99); //DOBRZE jest w C++ niedopuszczalna. Kompilator potraktuje liczb� 5.127 jako pierwszy argument typu int i wyst pi konflikt. [P079.CPP] #include <iostream.h> void fun_show(int = 1234, float = 222.00, long = 333L); main() { fun_show(); // Trzy arg. domyslne fun_show(1); // Pierwszy parametr fun_show(11, 2.2); // Dwa parametry fun_show(111, 2.22, 3L); // Trzy parametry return 0; } void fun_show(int X, float Y, long Z) { cout << "\nX = " << X; cout << ", Y = " << Y; cout << ", Z = " << Z; } Uruchom program i przekonaj si�, czy wstawianie argumentów domy�lnych przebiega poprawnie. W KTÓRYM MIEJSCU UMIESZCZA� DEKLARACJE ZMIENNYCH. C++ pozwala deklarowa� zmienne w dowolnym miejscu, z zastrze�eniem, �e deklaracja zmiennej musi nast pi� przed jej u�yciem. Umieszczanie deklaracji zmiennych mo�liwie blisko miejsca ich u�ycia znacznie poprawia czytelno�� (szczególnie du�ych "wieloekranowych") programów. Klasyczny sposób deklaracji zmiennych: int x, y, z; ... main() { ... z = x + y + 1; ... } mo�e zosta� zast piony deklaracj w miejscu zastosowania (w tym np. wewn trz p�tli): main() { ... for ( int i = 1; i <= 10; i++) cout << "Biezace i wynosi: " << i; ... } Nale�y jednak pami�ta� o pewnym ograniczeniu. Zmienne deklarowane poza funkcj main() s traktowane jako zmienne globalne i s widoczne (dost�pne) dla wszystkich innych elementów programu. Zmienne deklarowane wewn trz bloku/funkcji s zmiennymi lokalnymi i mog "przesłania�" zmienne globalne. Je�li wielu zmiennym nadamy te same nazwy-identyfikatory, mo�emy prze�ledzi� mechanim przesłaniania zmiennych w C++. W przykładzie poni�ej zastosowano trzy zmienne o tej samej nazwie "x": [P080.CPP] //Program demonstruje przesłanianie zmiennych #include <iostream.h> int x = 1; //Zmienna globalna void daj_x(void); //Prototyp funkcji main()

{ int x = 22; //Zmienna lokalna funkcji main cout << ::x << " <-- To jest globalny ::x \n"; cout << x << " <-- A to lokalny x \n"; daj_x(); return 0; } void daj_x(void) { cout << "To ja funkcja daj_x(): \n"; cout << ::x << " <-- To jest globalny ::x \n"; cout << x << " <-- A to lokalny x \n"; int x = 333; cout << "A to moja zmienna lokalna - automatyczna ! \n"; cout << x << " <-- tez x "; } Program wydrukuje tekst: 1 <-- To jest globalny ::x 22 <-- A to lokalny x To ja funkcja daj_x(): 1 <-- To jest globalny ::x 1 <-- A to lokalny x A to moja zmienna lokalna - automatyczna ! 333 <-- tez x Zwró� uwag�, �e zmienne deklarowane wewn trz funkcji (tu: main()) nie s widoczne dla innych funkcji (tu: daj_x()). Operator :: (ang. scope) pozwala nam wybiera� pomi�dzy zmiennymi globalnymi a lokalnymi. TYP WYLICZENIOWY enum JAKO ODR�BNY TYP ZMIENNYCH. W C++ od momentu zdefiniowania typu wyliczeniowego enum staje si� on równoprawnym ze wszystkimi innymi typem danych. Program poni�ej demonstruje przykład wykorzystania typu enum w C++. [P081.CPP] # include <iostream.h> enum ciuchy { niewymowne = 1, skarpetka, trampek, koszula, marynarka, czapa, peruka, koniec }; main() { ciuchy n; do { cout << "\nNumer ciucha ? --> (1-7, 8 = quit): "; cin >> (int) n; switch (n) { case niewymowne: cout << "niewymowne"; break; case skarpetka: cout << "skarpetka"; break; case trampek: cout << "trampek"; break; case koszula: cout << "koszula"; break; case marynarka: cout << "marynarka"; break; case czapa: cout << "czapa"; break; case peruka: cout << "peruka"; break; case koniec: break; default: cout << "??? Tego chyba nie nosze..."; } } while (n != koniec);

Page 76: C++ - lekcje

76

return 0; } Zwró� uwag� w programie na forsowanie typu (int) przy pobraniu odpowiedzi-wyboru z klawiatury. Poniewa� w C++ "ciuchy" stanowi nowy (zdefiniowany przed chwil ) typ danych, do uto�samienia ich z typem int niezb�dne jest wydanie takiego polecenia przy pobieraniu danych ze strumienia cin >> . W opcjach pracy kompilatora mo�esz wł czy�/wył czy� opcj� "Treat enums as int" (traktuj typ enum jak int) i wtedy pomin � forsowanie typu w programie. JEDNOCZESNE ZASTOSOWANIE DWU KOMPILATORÓW. Jak ju� wspomnieli�my wcze�niej kompilator C++ składa si� w istocie z dwu ró�nych kompilatorów: * kompilatora C wywoływanego standardowo dla plików *.C, * kompilatora C++ wywoływanego standardowo dla plików *.CPP. Oba kompilatory stosuj RÓNE metody tworzenia nazw zewn�trznych (ang. external names). Je�li zatem program zawiera moduł, w którym funkcje zostały przekompilowane w trybie charakterystycznym dla klasycznego C - C++ powinien zosta� o tym poinformowany. Dla przykładu, C++ * kategorycznie kontroluje zgodno�� typów argumentów, * na swój własny u�ytek dodaje do nazw funkcji przyrostki (ang. suffix) pozwalaj ce na okre�lenie typu parametrów, * pozwala na tworzenie tzw. funkcji polimorficznych (kilka ró�nych funkcji o tej samej nazwie), itp. Zwykły C tego nie potrafi i nie robi. Dlatego te� do wprowadzenia takiego podziału kompetencji nale�y czasem zastosowa� deklaracj� extern "C". Funkcja rand() w programie poni�ej generuje liczb� losow . [P081.CPP] #include <iostream.h> extern "C" { # include <stdlib.h> //Prototyp rand() w STDLIB.H } main() { cout << rand(); return 0; } GENERACJA LICZB LOSOWYCH. Kompilatory C++ umo�liwoaj generacj� liczb pseudolosowych u�ytecznych cz�sto w obliczeniach statystycznych (np. metoda Monte Carlo) i emulacji "rozmytaj" arytmetyki i logiki (ang.fuzzy math). [!!!] UWAGA - Liczby PSEUDO-Losowe. ________________________________________________________________ Funkcja rand() powoduje uruchomienie generatora liczb pseudolosowych. Je�li chcesz uzyska� liczb� pseudolosow z zadanego przedziału warto�ci, najlepiej zastosuj dzielenie modulo: int n = rand % 10; powoduje tzw. normalizacj�. Reszta z dzielenia przez 10 mo�e by� wył cznie liczb z przedziału 0...9. Aby przy ka�dym urichomieniu aplikacji ci g liczb pseudolosowych rozpoczynał si� od innej warto�ci nale�y uruchomi� generator liczb wcze�niej - przed u�yciem funkcji rand() - np.: randomize(); ...

int n = rand() % 100; ... ________________________________________________________________ W programie przykładowym funkcje z STDLIB.H zostan skompilowane przez kompilator C. Okre�lenie trybu kompilacji deklaracj extern "C" jest umieszczane zwykle nie wewn trz programu głównego a w doł czanych plikach nagłówkowych *.H. Jest to mo�liwo�� szczególnie przydatne, je�li dysponujesz bibliotekami funkcji dla C a nie masz ch�ci, czasu, b d� mo�liwo�ci przerabiania ich na wersj� przystosowan do wymaga� C++. Drugi przykład poni�ej zajmuje si� sortowaniem krewnych przy pomocy funkcji C qsort(). [P082.CPP] # include <iostream.h> # include <stdlib.h> # include <string.h> extern "C" int comp(const void*, const void*); main() { int max; for(;;) { cout << "\n Ilu krewnych chcesz posortowac? (1...6): "; cin >> max; if( max > 0 && max < 7) break; cout << "\n Nic z tego..."; } static char* krewni[] = { "Balbina - ciotka", "Zenobiusz - kuzyn", "Kleofas - stryjek", "Ola - kuzynka (ach)", "Waleria - tez niby ciotka", "Halina - stryjenka" }; qsort(krewni, 6, sizeof(char*), comp); for (int n = 0; n < max; n++) cout << "\n" << krewni[n]; return 0; } extern "C" { int comp(const void *x, const void *y) { return strcmp(*(char **)x, *(char **)y); } } Program wykonuje nast�puj ce czynno�ci: * deklaruje prototyp funkcji typu C, * deklaruje statyczn tablic� wska�ników do ła�cuchów znakowych, * sortuje wska�niki, * wy�wietla posortowane ła�cuchy znakowe, * definiuje funkcj� comp() - porównaj, * wykorzystuje funkcj� biblioteczn C - strcmp() - String Compare do porównania ła�cuchów znaków. O PAMI�CI. Program w C++ dzieli dost�pn pami�� na kilka obszarów o okre�lonym z góry przeznaczeniu. Dla zaawansowanego programisty zrozumienie i efektywne wykorzystanie mechanizmów zarz dzania pami�ci w C++ mo�e okaza� si� wiedz wielce przydatn . Zaczniemy, jak zwykle od "elementarza". CO PROGRAM ROBI Z PAMI�CI. W klasycznym C najcz��ciej stosowanymi do zarz dzania pami�ci funkcjami s :

Page 77: C++ - lekcje

77

* malloc() - przyporz dkuj pami��, * farmalloc() - przyporz dkuj odległ pami��, * realloc() - przyporz dkuj powtórnie (zmienion ) ilo�� pami�ci, * calloc() - przydziel pami�� i wyzeruj, * free() - zwolnij pami��. Pami�� dzielona jest w obszarze programu na nast�puj ce bloki: ___________________ niskie adresy --> Ngłówek programu I. Adres startowy KOD: Kod programu ___________________ Zmienne statyczne II. DANE: 1. Zainicjowane Zmienne globalne ___________________ Zmienne statyczne III. DANE: 2. Niezainicjowane Zmienne globalne ___________________ STERTA: (heap) W miar� potrzeby IV. ro�nie w dół. Tu operuj funkcje malloc(), free(). ___________________ POLE NICZYJE: V. ___________________ W miar� potrzeby VI. STOS: (stack) ro�nie w gór�. wysokie adresy --> ___________________ W obszarze kodu (I.) znajduj si� instrukcje. Na stosie przechowywane s : * zmienne lokalne, * argumenty przekazywane funkcji w momencie jej wywołania, * adresy powrotne dla funkcji (RET == CS:IP). Na stercie natomiast przy pomocy funkcji (a jak przekonamy si� za chwil� - tak�e operatorów C++) mo�emy przydziela� pami�� dla ró�nych obiektów tworzonych w czasie pracy programu (ang. run-time memory allocation) - np. tworzy� bufory dla ła�cuchów, tablic, struktur itp.. Zwró� uwag�, �e obszar V. - POLE NICZYJE mo�e by� w czasie pracy programu stopniowo udost�pniany dla stosu (który rozrasta si� "w gór�"), albo dla sterty (która rozrasta si� "w dół"). W przykładowym programie poni�ej podano, w którym obszarze pami�ci zostanie umieszczony dany element programu. # include <alloc.h> int a; // III. int b = 6; // II. main() { char *Dane; ... float lokalna; // VI. ... Dane = malloc(16); // IV. ... } OPERATORY new I delete. Operatory new i delete działaj podobnie do pary funkcji malloc() - free(). Pierwszy przyporz dkowuje - drugi zwalnia pami��. Dokładniej rzecz bior c - operator new mo�e zosta� zastosowany wraz ze wska�nikiem do bloku danych okre�lonego typu: * struktury danych, * tablicy, itp. (wkrótce zastosujemy go tak�e w stosunku do klas i obiektów); - przyporz dkowuje pami�� blokowi danych; - przypisuje pocz kowy adres bloku pami�ci wska�nikowi. - operator delete zwalnia pami�� przyporz dkowan poprzednio blokowi danych, Operatory new i delete mog współdziała� z danymi wieloma typami danych (wcale nie tylko ze strukturami), jednak�e rozpoczniemy

do struktury Data zawieraj cej dat� urodzenia mojej córki. [P083.CPP] # include "iostream.h" struct Data { int dzien; int miesiac; int rok; }; void main() { Data *pointer = new Data; /* Dekl. wskaznik do struct typu Data */ /* Przydziel pamiec dla struktury */ pointer -> miesiac = 11; // pole "miesiac" = 11 pointer -> dzien = 3; pointer -> rok = 1979; cout << "\n URODZINY CORKI: "; cout << pointer -> dzien << '.'; cout << pointer -> miesiac << ". "; cout << "co rok ! od " << pointer -> rok << " r."; delete pointer; //Skasuj wskaznik - zwolnij pamiec. } Program tworzy w pami�ci (dokł. na stercie) struktur� typu Data bez nazwy. O któr struktur� chodzi i gdzie jej szuka� w pami�ci wiemy dzi�ki wska�nikowi do struktury *pointer. Zapis Data *pointer = new Data; oznacza jednoczesn deklaracj� i zainicjowanie wska�nika. TWORZENIE DYNAMICZNYCH TABLIC O ZMIENNEJ WIELKO�CI. Je�li mamy dane wył cznie jednego typu (tu: int), zastosowanie struktury jest wła�ciwie przysłowiowym "strzelaniem z armaty do wróbli". Trójelementowa tablica typu int TAB[3]; zupełnie nam wystarczy. Utworzymy j jednak nie jako tablic� globaln (b d� statyczn ) w obszarze pami�ci danych, lecz dynamicznie - na stercie. [P084.CPP] # include "iostream.h" main() { int *pointer = new int[3]; // Przydziel pamiec pointer[0] = 3; // Tabl_bez_nazwy[0] - dzien pointer[1] = 11; // Tabl_bez_nazwy[1] - miesiac pointer[2] = 1979; cout << "Data urodzenia: "; for(int i = 0; i < 3; i++) cout << pointer[i] << '.'; delete pointer; } Uwa�ny Czytelnik doszedł zapewne do wniosku, �e skoro tablica tworzona jest dynamicznie w ruchu programu (run-time), to kompilator nie musi zna� na etapie kompilacji programu (compile-time) wielko�ci tablicy! Id c dalej, program powinien tak technik tworzy� tablice o takiej wielko��i, jakiej w ruchu za�yczy sobie u�ytkownik. Spróbujmy zrealizowa� to praktycznie. [P085.CPP] # include <conio.h> # include <stdlib.h>

Page 78: C++ - lekcje

78

# include <iostream.h> void main() { for(;;) { cout << "\nPodaj wielkosc tablicy (1...100) --> "; int i, size; cin >> size; /* Na stercie tworzymy dynamiczna tablica: */ int *pointer = new int[size]; /* Wypelniamy tablice liczbami naturalnymi: */ for (i = 0; i < size; i++) pointer[i] = i; cout << "\n TABLICA: \n"; /* Sprawdzamy zawartosc tablicy: */ for (i = 0; i < size; i++) cout << " " << pointer[i]; char k = getch(); if(k == 'a') break; delete pointer; } } Twój dialog z programem powinien wygl da� nast�puj co: Podaj wielkosc tablicy (1...100) --> 20 TABLICA: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Podaj wielkosc tablicy (1...100) --> 100 TABLICA: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 Skoro dynamiczne tablice o zmiennej wielko�ci "chodz ", mo�emy wykorzysta� to w bardziej interesuj cy sposób. [P086.CPP] # include <stdlib.h> # include <string.h> # include <iostream.h> extern "C" { int Fporownaj(const void* x, const void* y) { return (strcmp(*(char **)x, *(char **)y)); } } main() { cout << "Wpisz maksymalna ilosc imion --> "; int ilosc, i; cin >> ilosc; char **pointer = new char *[ilosc]; for (i = 0; i < ilosc; i++) { cout << "Podaj imie Nr: " << i + 1 << "--> "; char *imie = new char[80]; cin >> imie; if (strcmp(imie, "stop") == 0) break; else pointer[i] = new char[strlen(imie)+1]; strcpy(pointer[i], imie); delete imie; }

qsort(pointer, i, sizeof(char *), Fporownaj); for (i = 0; i < ilosc; i++) cout << pointer[i] << '\n'; for (i = 0; i < ilosc; i++) delete pointer[i]; delete pointer; return 0; } Tworzymy dynamicznie przy pomocy operatora new bezimienn tablic� składaj c si� z tablic ni�szego rz�du (ła�cuch znaków to te� tablica tyle, �e jednowymiarowa - ma tylko długo��). Zwró� uwag�, �e w C++ wska�nik do wska�nika (**pointer) odpowiada konstrukcji "tablica składaj ca si� z tablic". Aby program uczyni� bardziej pogl dowym spolszczymy nazwy funkcji przy pomocy preprocesora. [P087.CPP] # define Fporown_string strcmp # define Fkopiuj_string strcpy # define Fsortuj qsort # include <stdlib.h> # include <string.h> # include <iostream.h> extern "C" { int Fporownaj(const void* x, const void* y) { return (Fporown_string(*(char **)x, *(char **)y)); } } main() { cout << "Wpisz maksymalna ilosc imion --> "; int ilosc, i; cin >> ilosc; char **pointer = new char *[ilosc]; for (i = 0; i < ilosc; i++) { cout << "Podaj imie Nr: " << i + 1 << "--> "; char *imie = new char[80]; cin >> imie; if (Fporown_string(imie, "stop") == 0) break; else pointer[i] = new char[strlen(imie)+1]; Fkopiuj_string(pointer[i], imie); delete imie; } /* w tym momencie i == ilosc */ Fsortuj(pointer, i, sizeof(char *), Fporownaj); for (i = 0; i < ilosc; i++) cout << pointer[i] << '\n'; for (i = 0; i < ilosc; i++) delete pointer[i]; delete pointer; return 0; } Wska�nik mo�e wskazywa� dane o ró�nym stopniu zło�ono�ci: zmienn , tablic�, struktur�, obiekt (o czym za chwil�), ale mo�e wskazywa� tak�e funkcj�. JE�LI ZABRAKNIE PAMI�CI - _new_handler. Aby obsługiwa� bł�dn sytuacj� - brakło pami�ci na stercie - potrzebna nam b�dzie funkcja - tzw. HANDLER. Aby jedna było wiadomo, gdzie szuka� handlera, powinni�my operatorowi new

Page 79: C++ - lekcje

79

przekaza� informacj� jaka funkcja obsługuje brak pami�ci i gdzie jej szuka�. Mo�emy podstawia� na miejsce funkcji stosowanej w programie t� funkcj�, która w danym momencie jest nam potrzebna. Jest to praktyka cz�sto stosowana w programach obiekktowych, wi�c przypomnijmy raz jeszcze przykładowy program - tym razem w troch� innym kontek�cie. Aby wskaza� funkcj� zastosujemy wska�nik. . Przypomnijmy deklaracj� double ( *Funkcja ) (double); [P088.CPP] #include <conio.h> #include <math.h> #include <iostream.h> double Nasza_F(double); //Deklaracja zwyklej funkcji double (*Funkcja)(double); //pointer do funkcji double liczba; //zwyczajna zmienna int wybor; int main(void) { clrscr(); cout << "\nPodaj Liczbe \n"; cin >> Liczba; cout << "CO OBLICZYC ?\n________________\n"; cout<<"1 - Sin \n2 - Cos \n3 - Odwrotnosc 1/X\n"; switch(cin >> wybor) { case 1: Funkcja = sin; break; case 2: Funkcja = cos; break; case 3: Funkcja = Nasza_F; break; } cout << "\n\nWYNIK = " << Funkcja(liczba); return (0); } double Nasza_F(double x) { if (x != 0) x = 1/x; else cout << "???\n"; return x; } Komputer nie jest "z gumy" i nie posiada dowolnie du�ej "rozci gliwej" pami�ci. Funkcja malloc(), je�li pami�ci zabraknie, zwraca pusty wska�nik (ang. NULL pointer), co mo�na łatwo przetestowa� w programie. Je�li natomiast stosujemy operator new - konsekwentnie - operator new powinien zwraca� NULL (i próbowa� dokona� przypisania pointerowi zero). To te� mo�na sprawdzi� w programie. W C++ istnieje jednak równie� inny, przydatny do tych celów mechanizm. C++ dysponuje globalnym wska�nikiem _new_handler (wska�nik do funkcji obsługuj cej operator new, je�li zabraknie pami�ci). Dzi�ki istnieniu tego (predefiniowanego) wska�nika mo�emy przyporz dkowa� "handler" - funkcj� obsługuj c wyj�cie przez operator new poza dost�pn pami��. Dopóki nie za�yczymy sobie inaczej, wska�nik _new_handler == NULL // NULL == 0 i operator new w przypadku niepowodzenia próby przyporz dkowania pami�ci zwróci warto�� NULL inicjuj c pusty wska�nik (innymi słowy "wska�nik do nik d"). Je�li jednak _new_handler != NULL to zawarto�� wska�nika zostanie przez operator new uznana za adres startowy funkcji obsługi bł�dnej sytuacji (ang. addres to call). [P089.CPP]

# include <stdlib.h> # include <iostream.h> static void Funkcja() { cout << "\nTo ja ... Funkcja - handler \n"; cout << '\a' << " ! BRAK PAMIECI ! "; exit (1); } extern void (*_new_handler)(); long suma; //Automatycznie suma = 0; void main() { _new_handler = Funkcja; //Inicjujemy wskaznik for(;;) { char *pointer = new char[8192]; suma += 8192; cout << "\nMam juz " << suma << " znakow w RAM\n"; if (pointer != 0) cout << "Pointer != NULL"; } } [!!!] SPRAWD� - KONIECZNIE! ________________________________________________________________ W programach u�ytkowych, a szczególnie w tych oferowanych klientom jako produkty o charakterze komercyjnym nale�y ZAWSZE sprawdza� poprawno�� wykonania newralgicznych operacji - a szczególnie poprawno�� zarz dzania pami�ci i poprawno�� operacji dyskowych. Utrata danych, lub nie zauwa�one i nie wykryte przez program przekłamanie mo�e spowodowa� przykre skutki. Raz utracone dane mog okaza� sie nie do odzyskania. LEKCJA 24 : SKD WZI�ŁY SI� KLASY I OBIEKTY W C++. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, sk d w C++ bior si� obiekty i jak z nich korzysta�. ________________________________________________________________ Zajmiemy si� teraz tym, z czego C++ jest najbardziej znany - zdolno�ci posługiwania si� obiektami. Główn zalet programowania obiektowego jest wy�szy stopie� "modularyzacji" programów. "Mudularyzacja" jest tu rozumiana jako mo�liwo�� podziału programu na niemal niezale�ne fragmenty, które mog opracowywa� ró�ne osoby (grupy) i które pó�niej bez konfliktów mo�na ł czy� w cało�� i uruchamia� natychmiast. C++ powstał, gdy programy stały si� bardzo (zbyt) długie. Mo�liwo�� skrócenia programów nie jest jednak�e jedyn zalet C++. W długich, rozbudowanych programach trudno spami�ta� szczegóły dotycz ce wszystkich cz��ci programu. Je�li grupy danych i grupy funkcji uda si� poł czy� w moduły, do których mo�na pó�niej si�ga�, jak do pewnej odr�bnej cało�ci, znacznie ułatwia to �ycie programi�cie. Na tym, w pewnym uproszczeniu, polega idea programowania obiektowego. JAK STRUKTURY STAWAŁY SI� OBIEKTAMI. W C++ struktury uzyskuj "troch� wi�cej praw" ni� w klasycznym C. Przykładowy program poni�ej demonstruje kilka sposobów posługiwania si� struktur w C++. [P90.CPP] #include <iostream.h> struct Data { int dzien; int miesiac; int rok; };

Page 80: C++ - lekcje

80

Data NaszaStruktura = {3, 11, 1979}; //Inicjujemy strukture Data daty[16]; //Tablca struktur Data *p = daty; //Wskaznik do tablicy void Fdrukuj(Data); //Prototyp funkcji int i; //Licznik automat. 0 int main() { for (; i < 16; i++) { *(p + i) = NaszaStruktura; daty[i].rok += i; cout << "\nDnia "; Fdrukuj(daty[i]); cout << " Patrycja "; if ( !i ) cout << "urodzila sie, wiek - "; if (i > 0 && i < 14) cout << "miala "; if (i > 13) cout << "bedzie miec "; cout << i; if (i == 1) cout << " roczek"; else cout << " lat"; if (i > 1 && i < 5) cout << "ka"; cout << '.'; } return 0; } void Fdrukuj(Data Str) { char *mon[] = { "Stycznia","Lutego","Marca","Kwietnia","Maja","Czerwca", "Lipca","Sierpnia","Wrzesnia","Pazdziernika","Listopada", "Grudnia" }; cout << Str.dzien << ". " << mon[Str.miesiac-1] << ". " << Str.rok; } Prócz danych struktury w C++ mog zawiera� tak�e funkcje. W przykładzie poni�ej struktura Data zawiera wewn trz funkcj�, która przeznaczona jest do obsługi we wła�ciwy sposób danych wchodz cych w skład własnej struktury. [P091.CPP] #include <iostream.h> struct Data //Definicja struktury { int dzien, miesiac, rok; void Fdrukuj(); //Prototyp funkcji Data(); //Konstruktor struktury }; void Data::Fdrukuj() //Definicja funkcji { char *mon[] = { "Stycznia","Lutego","Marca","Kwietnia","Maja","Czerwca", "Lipca","Sierpnia","Wrzesnia","Pazdziernika","Listopada", "Grudnia" }; cout << dzien << ". " << mon[miesiac-1] << ". " << rok; } Data::Data(void) //Poczatkowa data - Konstruktor { dzien = 3; miesiac = 11; rok = 1979; } int main() { Data NStruktura; //Inicjujemy strukture cout << "\n Sprawdzamy: "; NStruktura.Fdrukuj(); //Wywolanie funkcji

cout << " = "; cout << NStruktura.dzien << " . " << NStruktura.miesiac << " . " << NStruktura.rok; for (int i=0; i < 16; i++, NStruktura.rok++) { cout << "\nDnia "; NStruktura.Fdrukuj(); cout << " Patrycja "; if ( !i ) cout << "urodzila sie, wiek - "; if (i > 0 && i < 14) cout << "miala "; if (i > 13) cout << "bedzie miec "; cout << i; if (i == 1) cout << " roczek"; else cout << " lat"; if (i > 1 && i < 5) cout << "ka"; cout << '.'; } return 0; } Zwró� uwag�, �e * odk d dane stały si� elementem struktury, zacz�li�my odwoływa� si� do nich tak: nazwa_struktury.nazwa_pola; * gdy funkcje stały si� elementem struktury, zacz�li�my odwoływa� si� do nich tak: nazwa_struktury.nazwa_funkcji; Pojawiły si� równie� ró�nice w sposobie definiowania funkcji: void Data::Fdrukuj() //Definicja funkcji { ... } oznacza, �e funkcja Fdrukuj() jest upowa�niona do operowania na wewn�trznych danych struktur typu Data i nie zwraca do programu �adnej warto�ci (void). Natomiast zapis: Data::Data(void) //Poczatkowa data - Konstruktor oznacza, �e funkcja Data(void) nie pobiera od programu �adnych parametrów i tworzy (w pami�ci komputera) struktur� typu Data. Takie dziwne funkcje konstruuj ce (inicjuj ce) struktur� (o czym dokładniej w dalszej cz��ci ksi �ki), nazywane w C++ konstruktorami nie zwracaj do programu �adnej warto�ci. Zwró� uwag�, �e konstruktory to specjalne funkcje, które: -- maj nazw� identyczn z nazw typu własnej struktury, -- nie posiadaj wyspecyfikowanego typu warto�ci zwracanej do programu, -- słu� do zainicjowania w pami�ci pól struktury, -- nie s wywoływane w programie w sposób jawny, lecz niejawnie, automatycznie. Podstawowym praktycznym efektem dodania do struktur funkcji stała si� mo�liwo�� skutecznej ochrony danych zawartych na polach struktury przed dost�pem funkcji z zewn trz struktury. Przed dodaniem do struktury jej własnych wewn�trznych funkcji - wszystkie funkcje pochodziły z zewn trz, wi�c "hermetyzacja" danych wewn trz była niewykonalna. Zasady dost�pu okre�la si� w C++ przy pomocy słów: public - publiczny, dost�pny, protected - chroniony, dost�pny z ograniczeniami, private - niedost�pny spoza struktury. Przykładowy program poni�ej demonstruje tzw. "hermetyzacj�" struktury (ang. encapsulation). W przykładzie poni�ej: * definiujemy struktur�; * definiujemy funkcje; * przekazujemy i pobieramy dane do/od struktury typu Zwierzak. Zmienna int schowek powinna sugerowa� ukryt przez struktur� i niedost�pn dla nieuprawnionych funkcji cz��� danych struktury a nie cechy anatomiczne zwierzaka.

Page 81: C++ - lekcje

81

[STRUCT.CPP] # include "iostream.h" //UWAGA: schowek ma status private, jest niedostepny struct Zwierzak { private: int schowek; //DANE PRYWATNE - niedostepne public: void SCHOWAJ(int Xwe); //Funkcje dostepne zzewnatrz int ODDAJ(void); }; void Zwierzak::SCHOWAJ(int Xwe) //definicja funkcji { schowek = Xwe; } int Zwierzak::ODDAJ(void) { return (schowek); } main() { Zwierzak Ciapek, Azor, Kotek; // Struktury "Zwierzak" int Piggy; // zwykla zmienna Ciapek.SCHOWAJ(1); Azor.SCHOWAJ(22); Kotek.SCHOWAJ(-333); Piggy = -4444; cout << "Ciapek ma: " << Ciapek.ODDAJ() << "\n"; cout << "Azor ma: " << Azor.ODDAJ() << "\n"; cout << "Kotek ma: " << Kotek.ODDAJ() << "\n"; cout << "Panna Piggy ma: " << Piggy << "\n"; return 0; } // Proba nieautoryzowanego dostepu do danych prywatnych obiektu: // cout << Ciapek.schowek; // printf("%d", Ciapek.schowek); // nie powiedzie sie Powiedzie sie natomiast próba dost�pu do "zwykłej" zmiennej - dowoln metod - np.: printf("%d", Piggy); //Prototyp ! # include <stdio.h> Je�li podejmiesz prób� odwołania si� do "zakapsułkowanych" danych w zwykły sposób - np.: cout << Ciapek.schowek; kompilator wy�wietli komunikat o bł�dzie: Error: 'Zwierzak::schowek' is not accessible in function main() (pole schowek struktury typu Zwierzak (np. str. Ciapek) nie jest dost�pne z wn�trza funkcji main(). ) Do klas i obiektów ju� tylko male�ki kroczek. Jak przekonasz si� za chwil� - struktura Ciapek jest ju� wła�ciwie obiektem, a typ danych Zwierzak jest ju� wła�ciwie klas obiektów. Wystarczy zamieni� słowo "struct" na słowo "class". [CLASS.CPP] # include "iostream.h" //w klasach schowek ma status private AUTOMATYCZNIE //slowo private stalo sie zbedne class Zwierzak { int schowek; public:

void SCHOWAJ(int Xwe); //Funkcje dostepne zzewnatrz int ODDAJ(void); }; void Zwierzak::SCHOWAJ(int Xwe) { schowek = Xwe; } int Zwierzak::ODDAJ(void) { return (schowek); } main() { Zwierzak Ciapek, Azor, Kotek; // obiekty klasy "Zwierzak" int Piggy; // zwykla zmienna Ciapek.SCHOWAJ(1); Azor.SCHOWAJ(22); Kotek.SCHOWAJ(-333); Piggy = -4444; cout << "Ciapek ma: " << Ciapek.ODDAJ() << "\n"; cout << "Azor ma: " << Azor.ODDAJ() << "\n"; cout << "Kotek ma: " << Kotek.ODDAJ() << "\n"; cout << "Panna Piggy ma: " << Piggy << "\n"; return 0; } Kompilator nawet nie mrugn ł. Zmiana słowa struct na słowo class nie sprawiła mu zatem widocznie przykro�ci. Mało tego, zwró� uwag�, �e długo�� wynikowego pliku STRUCT.EXE i CLASS.EXE jest IDENTYCZNA. Wynikałoby z tego, �e sposób tworzenia wynikowego kodu przez kompilator w obu wypadkach był identyczny. O KLASACH I OBIEKTACH. Klasy słu� do tworzenia formalnego typu danych. W przypadku klas wiadomo jednak "z definicji", �e b�dzie to bardziej zło�ony typ (tzw. agregat) zawieraj cy praktycznie zawsze i dane "tradycyjnych" typów i funkcje (nazywane "metodami"). Podobnie jak definiuj c struktur� tworzysz nowy formalny typ danych, tak i tu - definiuj c klas� tworzysz nowy typ danych. Je�li zadeklarujesz u�ycie zmiennych danego typu formalnego, takie zmienne to wła�nnie obiekty. Innymi słowy, klasy stanowi definicje formalnego typu, natomiast obiekty - to zmienne danego typu (danej klasy). Zamiast słowa struct stosujemy przy klasach słowo class. class Klasa { int prywatna_tab[80] public: int dane; void Inicjuj(void); int Funkcja(int arg); }; Nasza pierwsza �wiadomie tworzona klasa nazywa si� "Klasa" i stanowi nowy formalny typ zmiennych. Je�li zadeklarujesz zmienn takiej klasy (tego typu formalnego), to taka zmienna b�dzie wła�nie OBIEKTEM. Nasza pierwsza prawdziwa Klasa zawiera dane: prywatna_tab[80] - prywatn tablic�; dane - publiczn dan prost typu int; oraz funkcje: Inicjuj() - zainicjuj - utwórz obiekt danej klasy w pami�ci; Funkcja() - jaka� funkcja publiczna. Gdyby była to zwykła struktura, jej definicja w programie wygl dałaby tak: struct Klasa

Page 82: C++ - lekcje

82

{ private: int prywatna_tab[80] public: int dane; void Inicjuj(void); int Funkcja(int arg); }; Je�eli w dalszej cz��ci programu chcieliby�my zastosowa� struktury takiego typu, deklaracja tych struktur musiałaby wygl da� tak: struct rodzaj_struktur { private: int prywatna_tab[80] public: int dane; void Inicjuj(void); int Funkcja(int arg); } str1, str2, .... , nasza_struktura; b d� tak: struct rodzaj_struktur { private: int prywatna_tab[80] public: int dane; void Inicjuj(void); int Funkcja(int arg); }; ... (struct) rodzaj_struktur str1, str2, .... , nasza_struktura; Słowo kluczowe struct jest opcjonalne. Mogliby�my wi�c zadeklarowa� struktur� w programie, wewn trz funkcji main(): struct rodzaj_struktur { private: int prywatna_tab[80] public: int dane; void Inicjuj(void); int Funkcja(int arg); }; main() { ... struct rodzaj_struktur nasza_struktura; //lub równowa�nie: rodzaj_struktur nasza_struktura; Do pól struktury mo�emy odwoływa� si� przy pomocy operatora kropki (ang. dot operator). Podobnie dzieje si� w przypadku klas. Je�li zadeklarujemy zmienn typu Klasa, to ta zmienna b�dzie naszym pierwszym obiektem. class Klasa { int prywatna_tab[80] public: int dane; void Inicjuj(void) int Funkcja(int our_param); } Obiekt; Podobnie jak wy�ej, mo�emy zadeklarowa� nasz obiekt wewn trz funkcji main(): class Klasa { int prywatna_tab[80] public: int dane; void Inicjuj(void) int Funkcja(int argument); };

main() { ... Klasa Obiekt; ... Przypiszemy elementom obiektu warto�ci: main() { ... Klasa Obiekt; Obiekt.dane = 13; ... Tak sam metod , jak stosowali�my do danych - pól struktury, mo�emy odwoływa� si� do danych i funkcji w klasach i obiektach. main() { ... Klasa Obiekt; Obiekt.dane = 13; Obiekt.Funkcja(44); ... Przyporz dkowali�my obiektowi nie tylko dane, ale tak�e funkcje poprzez umieszczenie prototypów funkcji wewn trz deklaracji klasy: class Klasa { ... public: ... void Inicjuj(void) /* Prototypy funkcji */ int Funkcja(int argument); }; [!!!] UWAGA! ________________________________________________________________ W C++ nie mo�emy zainicjowa� danych wewn trz deklaracji klasy: class Klasa { private: int prywatna_tab[80] = { 1, 2, ... }; //�LE ! public: int dane = 123; //�ŁE ! ... ________________________________________________________________ Inicjowanie danych odbywa si� w programie głównym przy pomocy przypisania (dane publiczne), b d� za po�rednictwem funkcji nale� cej do danej klasy i maj cej dost�p do wewn�trznych danych klasy/obiektu (dane prywatne). Inicjowania danych mog dokona� tak�e specjalne funkcje - tzw. konstruktory. Dane znajduj ce si� wewn trz deklaracji klasy mog mie� status public, private, b d� protected. Dopóki nie za� dasz inaczej - domy�lnie wszystkie elementy klasy maj status private. Je�eli cz��� obiektu jest prywatna, to oznacza, �e �aden element programu spoza obiektu nie ma do niej dost�pu. W naszej Klasie prywatn cz��� stanowi tablica zło�ona z liczb całkowitych: (default - private:) int prywatna_tab[80]; Do (prywatnych) elementów tablicy dost�p mog uzyska� tylko funkcje zwi zane (ang. associated) z obiektem danej klasy. Funkcje takie musz zosta� zadeklarowane wewn trz definicji danej klasy i s nazywane członkami klasy - ang. member functions. Funkcje mog mie� status private i sta� si� dzi�ki temu wewn�trznymi funkcjami danej klasy (a w konsekwencji równie� prywatnymi funkcjami obiektów danej klasy). Jest to jedna z najwa�niejszych cech nowoczesnego stylu programowania w C++. Na tym polega idea hermetyzacji danych i funkcji wewn trz klas i obiektów. Gdyby jednak cała zawarto�� (i dane i funkcje) znajduj ce si� w obiekcie zostały dokładnie "zakapsułkowane", to okazałoby si�, �e obiekt stał si� "�lepy i głuchy", a w konsekwencji - niedost�pny i kompletnie nieu�yteczny dla programu i programisty. Po co nam obiekt, do którego nie mo�emy

Page 83: C++ - lekcje

83

odwoła� si� z zewn trz �adn metod ? W naszym obiekcie, w dost�pnej z zewn trz cz��ci publicznej zadeklarowali�my zmienn całkowit dane oraz dwie funkcje - Inicjuj() oraz Funkcja(). Je�li dane i funkcje maj status public, to oznacza, �e mo�emy si� do nich odwoła� z dowolnego miejsca programu i dowolnym sposobem. Takie odwołania przypominaj sposób odwoływania si� do elementów struktury: main() { ... Obiekt.dane = 5; //Przypisanie warto�ci zmiennej. Obiekt.Inicjuj(); //Wywołanie funkcji Inicjuj() ... Obiekt.Funkcja(3); //Wywołanie funkcji z argumentem [!!!] ZAWSZE PUBLIC ! ________________________________________________________________ Dane zawarte w obiekcie, podobnie jak zwykłe zmienne wymagaj zainicjowania. Funkcja inicjuj ca dane - zawarto�� obiektu musi zawsze posiada� status public aby mogła by� dost�pna z zewn trz i zosta� wywołana w programie głównym - funkcji main(). Funkcje i dane dost�pne z zewn trz stanowi tzw. INTERFEJS OBIEKTU. 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 <iostream.h> # include <ctype.h> //Prototyp f. toupper() struct Licznik { char znak; int ile; } licznik; void Inicjuj(char x) { licznik.znak = x; licznik.ile = 0; } void PlusJeden(void)

Page 84: C++ - lekcje

84

{ 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 <ctype.h> # include <iostream.h> class Licznik { char znak; int ile; public: void Inicjuj(char); void PlusJeden(void); char Pokaz(void); int Efekt(void); };

Page 85: C++ - lekcje

85

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. LEKCJA 26: CO TO JEST KONSTRUKTOR. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, w jaki sposób w pami�ci komputera s tworzone obiekty. ________________________________________________________________ C++ zawiera specjaln kategori� funkcji - konstruktory w celu automatyzacji inicjowania struktur (i obiektów). Konstruktory to specjalne funkcje b�d ce członkami struktur (kategorii member functions) które s automatycznie wywoływane i dokonuj zainicjowania struktury zgodnie z naszymi �yczeniami, po napotkaniu w programie pierwszej deklaracji struktury/obiektu danego typu. PRZYKŁADOWY KONSTRUKTOR. Struktura Licznik zawiera funkcj� inicjuj c obiekt (niech obiekt b�dzie na razie zmienn typu struktura): struct Licznik //Typ formalny struktur { char znak; int ile; } licznik; //Przykladowa struktura void Inicjuj(char x) //Funkcja inicjuj ca { licznik.znak = x; licznik.ile = 0; }

Zdefiniujmy nasz struktur� w sposób bardziej "klasowo-obiektowy": struct Licznik { private: char znak; int ile; public: void Inicjuj(char); void PlusJeden(void); }; Funkcja Inicjuj() wykonuje takie działanie jakie mo�e wykona� konstruktor struktury (obiektu), z t jednak ró�nic , �e konstruktor jest wywoływany automatycznie. Je�li wyposa�ymy struktur� Licznik w konstruktor, to funkcja Inicjuj() oka�e si� zb�dna. Aby funkcja Inicjuj() stała si� konstruktorem, musimy zmieni� jej nazw� na nazw� typu struktury, do której konstruktor ma nale�e�. Zwró� uwag�, �e konstruktor, w przeciwie�stwie do innych, "zwykłych" funkcji nie ma podanego typu warto�ci zwracanej: struct Licznik { private: char znak; int ile; public: Licznik(void); //Konstruktor nie pobiera argumentu void PlusJeden(void); }; Teraz powinni�my zdefiniowa� konstruktor. Zrobimy to tak, jak wcze�niej definiowali�my funkcj� Inicjuj(). Licznik::Licznik(void) //Konstruktor nie pobiera argumentu { ile = 0; } Je�li formalny typ struktur (klasa) posiada kostruktor, to po rozpocz�ciu programu i napotkaniu deklaracji struktur danego typu konstruktor zostanie wywołany automatycznie. Dzi�ki temu nie musimy "r�cznie" inicjowa� struktur na pocz tku programu. Jednak�e nasz przykładowy konstruktor nie załatwia wszystkich problemów - nie ustawia w strukturze zmiennej (pola) int znak - okre�laj cego, który znak powinien by� zliczany w liczniku. W tak zainicjowanej strukturze zmienna ile jest zerowana, ale zawarto�� pola znak pozostaje przypadkowa. Niby wszystko w porz dku, ale wygl da to niesolidnie. Czy nie mo�naby przekaza� parametru przy pomocy konstruktora? Mo�na! Konstruktor "bezparametrowy" Licznik::Licznik(void) taki, jak powy�ej to tylko szczególny przypadek - tzw. konstruktor domy�lny (ang. default constructor). PRZEKAZYWANIE ARGUMENTÓW DO KOSTRUKTORA. Czasem chcemy zainicjowa� now struktur� ju� z pewnymi ustawionymi parametrami. Te pocz tkowe parametry struktury mo�emy przekaza� jako argumenty konstruktora. struct Licznik { private: char znak; int ile; public: Licznik(char); //Konstruktor z argumentem typu char void PlusJeden(void); }; Licznik::Licznik(char x) //Konstruktor z jednym argumentem { ... }

Page 86: C++ - lekcje

86

main() { Licznik licznik('A'); //Deklaracja struktury typu Licznik // oznacza to automatyczne wywołanie konstruktora z argumentem .... Poniew� nowy konstruktor pobiera od programu argument typu znakowego char, wi�c i definicj� konstruktora nale�y zmieni�: Licznik::Licznik(char x) //Konstruktor z jednym argumentem { ile = 0; znak = x; } Je�li parametrów jest wi�cej ni� jeden, mo�emy je przekaza� do konstruktora, a konstruktor wykorzysta je do zainicjowania struktury w nast�puj cy sposób: struct Sasiedzi //s siedzi { private: char Tab_imion[4]; ... public: Sasiedzi(char *s1, char *s2, char *s3, char s4); ... }; main() { Sasiedzi chopy("Helmut", "Ulrich", "Adolf", "Walter"); .... Przekazanie konstruktorowi argumentów i w efekcie automatyczne ustawiamie przez konstruktor paramatrów struktury ju� w momencie zadeklarowania struktury w programie rozwi zuje wiele problemów. W C++ istnieje jednak�e pewne do�� istotne ograniczenie - nie mo�emy zadeklarowa� tablicy zło�onej z obiektów posiadaj cych konstruktory, chyba �e wszystkie konstruktory s bezparametrowe (typu default constructors). Udoskonalmy teraz nasz program zliczaj cy wyst pienia w tek�cie litery a posługuj c si� konstruktorem struktury. [P094.CPP] /* Wersja ze struktur */ # include <ctype.h> # include <iostream.h> struct Licznik { private: char znak; int ile; public: Licznik(char); //Konstruktor void PlusJeden(void); char Pokaz(void); int Efekt(void); }; Licznik::Licznik(char x) //Def. konstruktora { znak = x; ile = 0; } void main() { Licznik licznik('A'); //Zainicjowanie przez konstruktor cout << "Sprawdzamy: znak ile? " << "\n\t\t" << licznik.Pokaz() << "\t"; cout << licznik.Efekt(); cout << "\nWpisz tekst zawierajacy litery A"; cout << "\nPierwsze wytapienie litery k lub K"; cout << "\n - oznacza Koniec zliczania: "; for(;;) {

char znak_we; 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 pozostałych funkcji: */ void Licznik::PlusJeden(void) { ile++; } char Licznik::Pokaz(void) { return (znak); } int Licznik::Efekt(void) { return (ile); } Po zamianie słowa kluczowego struct na class (licznik ze struktury stanie si� obiektem, a Licznik - z formalnego typu struktur - klas ) wystarczy w programie zlikwidowa� zb�dne słowo "private" i wersja obiektowa programu jest gotowa do pracy. [P095.CPP] /* Wersja z klas i obiektem */ # include <ctype.h> # include <iostream.h> class Licznik { char znak; int ile; public: Licznik(char); //Konstruktor void PlusJeden(void); char Pokaz(void); int Efekt(void); }; Licznik::Licznik(char x) //Def. konstruktora { znak = x; ile = 0; } void main() { Licznik licznik('A'); //Zainicjowanie obiektu licznik cout << "Sprawdzamy: znak ile? " << "\n\t\t" << licznik.Pokaz() << "\t"; cout << licznik.Efekt(); cout << "\nWpisz tekst zawierajacy litery A"; cout << "\nPierwsze wytapienie litery k lub K"; cout << "\n - oznacza Koniec zliczania: "; for(;;) { char znak_we; 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."; } void Licznik::PlusJeden(void) { ile++; } char Licznik::Pokaz(void) { return znak; } int Licznik::Efekt(void) { return ile; } Pora w tym miejscu zaznaczy�, �e C++ oferuje nam jeszcze jedno specjalne narz�dzie podobnej kategorii. Podobnie, jak do tworzenia (struktur) obiektów mo�emy zastosowa� konstruktor, tak do skasowania obiektu mo�emy zastosowa� tzw. desruktor (ang. destructor). Nazwy konstruktora i destruktora s identyczne z nazw macie�ystego typu struktur (macie�ystej klasy), z tym, �e nazwa destruktora poprzedzona jest znakiem "~" (tylda).

Page 87: C++ - lekcje

87

CO TO JEST DESTRUKTOR. Specjalna funkcja - destruktor (je�li zadeklarujemy zastosowanie takiej funkcji) jest wywoływana automatycznie, gdy program zako�czy korzystanie z obiektu. Konstruktor towrzy, a destruktor (jak sama nazwa wskazuje) niszczy struktur� (obiekt) i zwalnia przyporz dkowan pami��. Przykład poni�ej to program manipuluj cy stosem, rozbudowany tak, by zawierał i konstruktor i destruktor struktury (obiektu). Zorganizujmy zarz dzanie pami�ci przeznaczon dla stosu w taki sposób: struct Stos { private: int *bufor_danych; int licznik; public: Stos(int ile_RAM); /* Konstruktor int Pop(int *ze_stosu); int Push(int na_stos); }; gdzie: *bufor_danych - wska�nik do bufora (wypełniaj cego rol� stosu), licznik - wierzchołek stosu, je�li == -1, stos jest pusty. Stos::Stos(...) - konstruktor inicjuj cy struktur� typu Stos (lub obiekt klasy Stos), ile_RAM - ilo�� pami�ci potrzebna do poprawnego działanie stosu, *ze_stosu - wska�nik do zmiennej, której nale�y przypisa� warto�� zdj�t wła�nie ze stosu, na_stos - liczba przeznaczona do zapisu na stos. Zajmijmy si� teraz definicj konstruktora. Wywołuj c konstruktor w programie (deklaruj c u�ycie w programie struktury typu Stos) przeka�emy mu jako argument ilo�� potrzebnej nam pami�ci RAM w bajtach. Do przyporz dkowznia pami�ci na stercie dla naszego stosu wykorzystamy funkcj� malloc(). Stos::Stos(int n_RAM) //Konstruktor - def. { licznik = -1; bufor_danych = (int *) malloc(n_RAM); } Posługuj c si� funkcj malloc() przyporz dkowujemy buforowi danych, w oparciu o który organizujemy nasz obiekt (na razie w formie struktury) - stos 100 bajtów pami�ci, co pozwala na rozmieszczenie 50 liczb typu int (po 2 bajty ka�da). Liczb� potrzebnych bajtów pami�ci - 100 przekazujemy jako argument konstruktorowi w momencie deklaracji struktury typu Stos. Nasza struktura w programie b�dzie si� nazywa� nasz_stos. main() { ... Stos nasz_stos(100); ... Kiedy wykorzystamy nasz struktur� w programie, mo�emy zwolni� pami�� przeznaczon dla struktury posługuj c si� funkcj biblioteczn C free(). Przykład przydziału pami�ci przy pomocy pary operatorów new - delete ju� był, przedstawimy tu zatem tradycyjn (coraz rzadziej stosowan metod�) opart na "klasycznych" funkcjach z biblioteki C. Funkcj free() posłu�ymy si� w destruktorze struktury nasz_stos - ~Stos(). Destruktory s wywoływane automatycznie, gdy ko�czy si� działanie programu, lub te�, gdy struktura (obiekt) przestaje by� widoczna / dost�pna w programie. Obiekt (struktura) przestaje by� widoczny (podobnie ja zwykła zmienna lokalna/globalna), je�li opuszczamy t� funkcj�, wewn trz której obiekt został zadeklarowany. Jest to wła�ciwo�� bardzo wa�na dla naszego przykładowego stosu. W naszym programie przykładowym pami�� przydzielona strukturze stack pozostaje zarezerwowana "na zawsze", nawet wtedy, gdy nasz stos przestaje by� "widoczny" (ang. out of scope). Obiekt mo�e przesta� by� widoczny np. wtedy, gdy działa funkcja "nie widz ca" obiektu. Id c dalej tym torem rozumowania, je�li

destruktor zostanie wywołany automatycznie zawsze wtedy, gdy obiekt przestanie by� widoczny, istnienie destruktora w definicji typu struktur Stos pozwala na automatyczne wyzerowanie stosu. Deklarujemy destruktor podobnie do konstruktora, dodaj c przed nazw destruktora znak ~ (tylda): struct Stos { ... public: ... ~Stos(void); ... } Je�li program zako�czy si� lub struktura przestanie by� widoczna, zostanie wywołany destruktor struktury nasz_stos i pami�� zostanie zwolniona. Praktycznie oznacza to, �e mo�emy zwolni� pami�c przyporz dkowan strukturze w taki sposób: Stos::~Stos(void) //Definicja destruktora { free(bufor_danych); cout << "\n Destruktor: Struktury juz nie ma..."; } Od momentu zdefiniowania konstruktora i destruktora nie musimy si� ju� przejmowa� technicznymi szczegółami ich działania. W dalszej cz��ci programu destruktor i konstruktor b�d wywoływane automatycznie. Pozostaje nam pami�ta�, �e * stos mo�e si� nazywa� dowolnie, a deklarujemy go tak: Stos nazwa_struktury; i dalej stosem mo�emy posługiwa� si� przy pomocy funkcji: nazwa_struktury.Push() nazwa_struktury.Pop() Wszystkie wewn�trzne sprawy stos b�dzie załatwiał samodzielnie. W tym konkrertnym przypadku cz��� "prac organizacyjnych" zwi zanych z utworzeniem w pami�ci struktury i zainicjowaniem pocz tkowych warto�ci pól załatwi za nas konstruktor i destruktor. Na tym wła�nie polega idea nowoczesnego programowania w C++. Przykładowy program umieszcza liczby na stosie a nast�pnie pobiera je ze stosu i drukuje na ekranie. Pełny tekst programu w wersji ze struktur - poni�ej. [P096.CPP] # include <iostream.h> # include <alloc.h> /* -----------------------poczatek pliku STOS.HPP------------ */ # define OK 1 struct Stos { private: int *bufor_danych; int licznik; public: Stos(int); /* Konstruktor */ ~Stos(void); /* Destruktor */ int Pop(int*); int Push(int); }; Stos::Stos(int n_RAM) //Konstruktor - def. { licznik = -1; bufor_danych = (int *) malloc(n_RAM); cout << "Konstruktor: Inicjuje strukture. "; } Stos::~Stos(void) //Definicja destruktora { free(bufor_danych);

Page 88: C++ - lekcje

88

cout << "\n Destruktor: Struktury juz nie ma..."; } int Stos::Pop(int* ze_stosu) { if(licznik == -1) return 0; else *ze_stosu = bufor_danych[licznik--]; return OK; } int Stos::Push(int na_stos) { if(licznik >= 49) return 0; else bufor_danych[++licznik] = na_stos; return OK; } /* --------------------------koniec pliku STOS.HPP----------- */ void main() { Stos nasz_stos(100); //Dekl. struktury typu Stos int i, Liczba; cout << "\nZAPISUJE NA STOS LICZBY:\n"; for(i = 0; i < 10; i++) { nasz_stos.Push(i + 100); cout << i + 100 << ", "; } cout << "\nKoniec. \n"; cout << "ODCZYTUJE ZE STOSU:\n"; for(i = 0; i < 10; i++) { nasz_stos.Pop(&Liczba); cout << Liczba << ", "; } } W C++ cz�st praktyk jest umieszczanie tzw. implementacji struktur (klas) w plikach nagłówkowych. Szkielet naszego programu mógłby wygl da� wtedy tak: # include <iostram.h> # include <alloc.h> # include <A:\STOS.HPP> void main() { ... } Wyka�emy, �e zamiana struktury na klas� odb�dzie si� całkiem bezbole�nie. Mało tego, je�li dokonamy zmian w implementacji w pliku nagłówkowym (struct --> class i usuniemy słowo private) nasz program główny nie zmieni si� WCALE ! Oto plik nagłówkowy A:\INCLUDE\STOSCL.HPP: [P097.CPP] # include <iostream.h> # include <alloc.h> /* ---------------------poczatek pliku STOSCL.HPP------------ */ # define OK 1 class Stos { int *bufor_danych; int licznik; public: Stos(int); /* Konstruktor */ ~Stos(void); /* Destruktor */ int Pop(int*); int Push(int); };

Stos::Stos(int n_RAM) //Konstruktor - def. { licznik = -1; bufor_danych = (int *) malloc(n_RAM); cout << "Konstruktor: Inicjuje obiekt klasy Stos. "; } Stos::~Stos(void) //Definicja destruktora { free(bufor_danych); cout << "\n Destruktor: Obiektu juz nie ma..."; } int Stos::Pop(int* ze_stosu) { if(licznik == -1) return 0; else *ze_stosu = bufor_danych[licznik--]; return OK; } int Stos::Push(int na_stos) { if(licznik >= 49) return 0; else bufor_danych[++licznik] = na_stos; return OK; } /* ------------------------koniec pliku STOSCL.HPP----------- */ void main() { Stos nasz_stos(100); //OBIEKT Klasy Stos int i, Liczba; cout << "\nZAPISUJE NA STOS LICZBY:\n"; for(i = 0; i < 10; i++) { nasz_stos.Push(i + 100); cout << i + 100 << ", "; } cout << "\nKoniec. \n"; cout << "ODCZYTUJE ZE STOSU:\n"; for(i = 0; i < 10; i++) { nasz_stos.Pop(&Liczba); cout << Liczba << ", "; } } Struktury w robi si� coraz bardziej podobne do czego� nowego jako�ciowo, zmienia si� równie� (dzi�ki tym nowym cechom) styl programowania. [!!!] A CO Z UNIAMI ? ________________________________________________________________ Unie s w C++ traktowane podobnie jak struktury, z tym, �e pola unii mog si� nakłada� (ang. overlap) i wobec tego nie wolno stosowa� słowa kluczowego private w uniach. Wszystkie elementy unii musz mie� status public. Unie mog tak�e posiada� konstruktory. ________________________________________________________________ A JE�LI B�DZIE WI�CEJ KLAS i STRUKTUR ? Po zdefiniowaniu nowego formalnego typu struktur mo�esz zastosowa� w programie wiele zmiennych danego typu. We wszystkich przykładach powy�ej stosowano pojedyncz struktur� WYŁCZNIE DLA ZACHOWANIA JASNO�CI PRZYKŁADU. Mało tego. W C++ ró�ne struktury mog korzysta� z funkcji o tej samej nazwie W RÓNY SPOSÓB. Ta ciekawa zdolno�� nazywa si� rozbudowywalno�ci funkcji (ang. overloading - dosł. "przeci �anie"). Dokładniej tym problemem zajmiemy si� w cz��ci po�wi�conej klasom i obiektom. Teraz jedynie prosty przykład na strukturach. [P098.CPP] #include <iostream.h> #include <stdio.h> #include <time.h> struct Data

Page 89: C++ - lekcje

89

{ int miesiac, dzien, rok; void Display(void); //Metoda "wyswietl" }; void Data::Display(void) { char *mon[] = { "Stycznia","Lutego","Marca","Kwietnia","Maja","Czerwca", "Lipca","Sierpnia","Wrzesnia","Pazdziernika","Listopada", "Grudnia" }; cout << dzien << ". " << mon[miesiac] << ". " << rok; } struct Czas { int godz, minuty, sekundy; void Display(void); // znow metoda "wyswietl" }; void Czas::Display(void) { char napis[20]; sprintf(napis, "%d:%02d:%02d %s", (godz > 12 ? godz - 12 : (godz == 0 ? 12 : godz)), minuty, sekundy, godz < 12 ? "rano" : "wieczor"); cout << napis; } main() { time_t curtime = time(NULL); struct tm tim = *localtime(&curtime); Czas teraz; Data dzis; teraz.godz = tim.tm_hour; teraz.minuty = tim.tm_min; teraz.sekundy = tim.tm_sec; dzis.miesiac = tim.tm_mon; dzis.dzien = tim.tm_mday; dzis.rok = 1900 + tim.tm_year; cout << "\n Jest teraz --> "; teraz.Display(); cout << " dnia "; dzis.Display(); cout << "\a"; return 0; } Funkcja Display() wywoływana jest w programie dwukrotnie przy pomocy tej samej nazwy, ale za ka�dym razem działa w inny sposób. C++ bezbł�dnie rozpoznaje, która wersja funkcji ma zosta� zastosowana i w stosunku do której struktury (których danych) funkcja ma zadziała�. Aby struktura stała si� ju� całkowicie klas , pozostało nam do omówienia jeszcze kilka ciekawych nowych własno�ci. Najwa�niejsz chyba (wła�nie dlatego, �e tworz c zdecydowanie now jako�� w programowaniu) jest mo�liwo�� dziedziczenia cech (ang. inheritance), któr zajmiemy si� w nast�pnej lekcji. [Z] ________________________________________________________________ 1. Sprawd�, czy zamiana struktur na klasy nie zmienia sposobu działania programów, ani długo�ci kodów wynikowych. 2. Opracuj program zliczaj cy wyst pienia ci gu znaków - np. "as" we wprowadzanym tek�cie. LEKCJA 27: O DZIEDZICZENIU. ________________________________________________________________ W trakcie tej lakcji dowiesz si� na czym polega dziedziczenie.

________________________________________________________________ Dziedziczenie (ang inheritance) jest prób na�ladowania w technice programowania najcenniejszego bodaj wynalazku Matki Natury - zdolno�ci przekazywania cech. Je�li wyobrazimy sobie typy struktur konik, lew, sło�, czy krokodyl, to jest oczywiste, �e struktury te b�d posiada� wiele wspólnych cech. Wspólnymi cechami mog by� zarówno wspólne dane (parametry) - np. nogi = 4; jak i wspólne wykonywane przez nie funkcje - np. jedz(), �pij(), oddychaj() itd.. Mog wyst�powa� oczywi�cie i ró�nice, ale wiele danych i funkcji oka�e si� wspólnych. LOGIKA DZIEDZICZENIA. Rozwijaj c dalej my�l naszkicowan we wst�pie, w kategoriach obiegowego j�zyka naturalnego mo�na rzec, �e sło� Trombalski byłby tu struktur typu formalnego Sło�. Funkcjami wewn�trznymi słonia Trombalskiego i np. krokodyla Eugeniusza mogłyby by� wspólne czynno�ci tych struktur (lub obiektów): jedz() �pij() oddychaj() Projektanci C++ wpadli na pomysł na�ladowania mechanizmu dziedziczenia. Zamiast tworzy� te wszystkie struktury oddzielnie, mo�emy zdefiniowa� w C++ jeden ogólny typ struktur (ang. generic structure), nazywany inaczej STRUKTUR BAZOW (ang. base structure). Wszystkie wymienione wy�ej struktury (sło�, krokodyl, itp.) stałyby si� wtedy strukturami pochodnymi (ang. derived structures). Nasza struktura bazowa mogłaby nazywa� si� znów np. Zwierzak. Poniewa� niektóre funkcje s wspólne dla wszystkich struktur (wszystkie Zwierzaki musz je��, spa�, itp.), mogliby�my przyj �, �e ka�da struktura pochodna od bazowego typu Zwierzak musi zawiera� funkcje jedz(), spij() i oddychaj(). Je�li zdefiniujemy struktur� bazow Zwierzak i zadeklarujemy w tej klasie funkcje jedz(), spij() i oddychaj(), mo�emy spodziewa� si�, �e struktura pochodna sło� powinna odziedziczy� funkcje - cechy po strukturze bazowej Zwierzak. . Sło� mo�e oczywi�cie mie� i swoje odr�bne cechy - dane i funkcje - np.: Slon.flaga_ssak Slon.trabie() Slon.tupie() "Gramatyka" C++ przy opisywaniu wzajemnego pokrewie�stwa struktur (i klas) wygl da nast�puj co: struct NazwaStrukturyPochodnej : NazwaStrukturyBazowej { private: Lista danych i funkcji prywatnych public: Lista danych i funkcji publicznych } Lista struktur danego typu; a dla klas i obiektów: class NazwaKlasyPochodnej : dost�p NazwaKlasyBazowej { Lista danych i funkcji prywatnych public: Lista danych i funkcji publicznych } Lista obiektow danej klasy; Bazowy typ struktur w C++ wygl dałaby tak: struct Zwierzak { void jedz(); void spij(); void oddychaj(); }; Je�li chcemy zasygnalizowa�, �e pochodny typ struktur Slon ma odziedziczy� co� po typie bazowym Zwierzak, musimy w definicji klasy pochodnej poda� nazw� klasy bazowej (je�li mamy dziedziczy� - nale�y wskaza� po kim): struct Slon : Zwierzak {

Page 90: C++ - lekcje

90

int trabie(); int tupie(); }; Przed nazw typu struktury (klasy) bazowej (tu: Zwierzak) mo�e pojawi� si� słowo okre�laj ce zasady dost�pu do danych i funkcji (tu: public). [!!!] RÓNIE MONA DZIEDZICZY�... ________________________________________________________________ * Je�li u�yjemy w tym miejscu słowa public (przy strukturach domy�lne), to atrybuty dost�pu zostan odziedziczone wprost. Oznacza to, �e to, co było prywatne w strukturze bazowej zostanie przeniesione jako prywatne do struktury pochodnej, a to, co było publiczne w strukturze bazowej zostanie przeniesione jako publiczne do struktury pochodnej. * Je�li natomiast u�yjemy w tym miejscu słowa private, to wszystko, co struktura pochodna odziedziczy po strukturze bazowej stanie si� w strukturze pochodnej prywatne. ________________________________________________________________ Opracowanie przykładowego programu ilustruj cego mechanizm dziedziczenia rozpoczniemy od zdefiniowania bazowego typu struktur i struktury pochodnej. struct Zwierzak { int nogi; <-- dane void jedz(); <-- funkcje void spij(); void oddychaj(); }; struct Slon : Zwierzak { int flaga_ssak; int trabie(); int tupie(); }; Zdefiniujemy teraz wszystkie funkcje nale� ce do powy�szych struktur. Funkcje b�d tylko zgłasza� si� na ekranie napisem, by prze�ledzi� kolejno�� ich wywołania. void Zwierzak::jedz(void) { cout << "Jem conieco...\n"; } void Zwierzak::spij(void) { cout << "Cosik mi sie sni...\n"; } void Zwierzak::oddychaj(void) { cout << "Dysz� cie�ko...\n"; } void Slon::trabi(void) { cout << "Tra-ta-ta...\n"; } void Slon::tupie(void) { cout << "Kroczem...na zachód\n"; } Aby przekona� si�, co struktura typu Slon rzeczywi�cie odziedziczy "po przodku", zredagujemy program główny. # include <iostream.h> ... void main() { Slon Choleryk; //Deklaracja struktury ... cout << "\nNogi odziedziczylem: " << Choleryk.nogi; cout << "\nA teraz kolejno funkcje: \n"; Choleryk.jedz(); Choleryk.spij(); Choleryk.oddychaj(); Choleryk.trabi(); Choleryk.tupie(); } Mimo, �e tworz c struktur� Sło� nie zadeklarowali�my w jej składzie ani funkcji jedz(), ani spij(), ani danych nogi, mo�emy zastosowa� funkcj� Choleryk.jedz(), poniewa� Choleryk odziedziczył t� funkcj� po strukturze bazowej Zwierzak. Dzi�ki dziedziczeniu mo�emy posługiwa� si� danymi i funkcjami nale� cymi do obu typów struktur - bazowego: Zwierzak i pochodnego: Slon. [???] A CO Z UNIAMI ? _______________________________________________________________

Unie nie mog bra� udziału w dziedziczeniu. Unia nie mo�e by� ani typem bazowym ani typem pochodnym. _______________________________________________________________ Program w cało�ci b�dzie wygl dał tak: [P099.CPP] # include <iostream.h> struct Zwierzak { int nogi; void jedz(); void spij(); void oddychaj(); }; void Zwierzak::jedz(void) { cout << "Jem conieco...\n"; } void Zwierzak::spij(void) { cout << "Cosik mi sie sni...\n"; } void Zwierzak::oddychaj(void) { cout << "Dysze ciezko...\n"; } struct Slon : Zwierzak { int flaga_ssak; void trabi(); void tupie(); }; void Slon::trabi(void) { cout << "Tra-ta-ta...\n"; } void Slon::tupie(void) { cout << "Kroczem...na wschod\n"; } void main() { Slon Choleryk; Choleryk.nogi = 4; Choleryk.flaga_ssak = 1; cout << "\nNogi odziedziczylem: " << Choleryk.nogi; cout << "\nA teraz kolejno funkcje: \n"; Choleryk.jedz(); Choleryk.spij(); Choleryk.oddychaj(); Choleryk.trabi(); Choleryk.tupie(); if(Choleryk.flaga_ssak == 1) cout << "SSak!"; } LEKCJA 28: DZIEDZICZENIE ZŁOONE. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak mo�na odziedziczy� wiele cech po wielu ró�nych przodkach. ________________________________________________________________ Je�li zechcemy dziedziczy� dalej według schematu dziadek-ojciec-syn-wnuk...? Nic nie stoi na przeszkodzie. Przy okazji zwró� uwag�, �e nast�pne pokolenia s coraz bardziej zło�one (tak by� nie musi, ale mo�e). W przykładzie poni�ej dziedziczymy według schematu Punkt-Okr g-Elipsa. [P100.CPP] //Przyklad dziedziczenia "wielopokoleniowego" #include "stdio.h" #include "conio.h" struct punkt //BAZOWY typ struktur - punkt(x, y) { int x; //wspolrzedne punktu na ekranie int y; }; struct kolo: punkt //Str. pochodna - kolo(x, y, R) { int promien; //wspolrzedne srodka x,y dziedziczymy }; struct elipsa: kolo //dziedziczymy x,y i promien { int mniejszy_promien; //Str. pochodna elipsa(x, y, R, r) }; punkt P; //deklarujemy trzy struktury

Page 91: C++ - lekcje

91

kolo C; elipsa E; main() { clrscr(); P.x = C.x = E.x = 1; //Nadajemy wartosci polom struktur P.y = C.y = E.y = 2; C.promien = E.promien = 4; E.mniejszy_promien = 3; //Sprawdzamy zawartosc pol struktur printf("%d %d %d %d %d %d \n", P.x, C.x, E.x, P.y, C.y, E.y); printf("%d %d %d", C.promien, E.promien, E.mniejszy_promien ); getch(); return 0; } Mo�na dziedziczy� po wi�cej ni� jednym przodku tak�e w inny sposób. Kwadrat, dla przykładu, dziedziczy cechy po prostok tach i po rombach jednocze�nie (jest jednocze�nie szczególnym przypadkiem prostok ta i szczególnym przypadkiem rombu). Typ pochodny w tym wypadku, zamiast "dziadka" i "ojca" powinien mie� DWU RÓNYCH OJCÓW (!). W C++ takie dziedziczenie po dwu ró�nych typach bazowych jednocze�nie nazywa si� DZIEDZICZENIEM WIELOBAZOWYM (ang. multi-base inheritance). A oto przykład takiego dziedziczenia. [P101.CPP] #include <iostream.h> struct BAZOWA1 { //Struktura bazowa pierwsza public: void Funkcja_a(void); }; struct BAZOWA2 { //Struktura bazowa druga public: void Funkcja_b(void); }; struct POCHODNA : BAZOWA1, BAZOWA2 //Lista "przodkow" { public: void Funkcja_c(void); }; void BAZOWA1::Funkcja_a(void){cout << "To ja F_a().\n";} void BAZOWA2::Funkcja_b(void){cout << "To ja F_b().\n";} void POCHODNA::Funkcja_c(void){cout << "To ja F_c().\n";} void main() { POCHODNA dziecko; //Dekl. strukt. typu pochodnego dziecko.Funkcja_a(); dziecko.Funkcja_b(); dziecko.Funkcja_c(); } Słowo public jest w strukturach zb�dne. Zostało u�yte wył cznie z pobudek "dydaktycznych" - dla zwrócenia uwagi na status funkcji - członków struktury. Zarówno pokole� w schemacie dziadek-ojciec-syn, jak i struktur (klas) bazowych w schemacie baza_1-baza_2-....-baza_n mo�e by� wi�cej ni� 2. DZIEDZICZENIE KLAS. Oto "klasowo-obiektowa" wersja poprzedniego programu przykładowego ze słonikiem Cholerykiem. Typy struktur Zwierzak i Slon nazwiemy klasami, (odpowiednio - klas bazow i klas pochodn ) a struktur� Slon Choleryk nazwiemy obiektem.

[P102.CPP] #include <iostream.h> class Zwierzak //Klasa bazowa (base class) { public: int nogi; void jedz(); void spij(); void oddychaj(); }; void Zwierzak::jedz(void) { cout << "Jem conieco...\n"; } void Zwierzak::spij(void) { cout << "Cosik mi sie sni...\n"; } void Zwierzak::oddychaj(void) { cout << "Dysze ciezko...\n"; } class Slon : public Zwierzak { public: int flaga_ssak; void trabi(); void tupie(); }; void Slon::trabi(void) { cout << "Tra-ta-ta...\n"; } void Slon::tupie(void) { cout << "Kroczem...na wschod\n"; } void main() { Slon Obiekt; /* obiekt Obiekt klasy Slon */ Obiekt.nogi = 4; Obiekt.flaga_ssak = 1; cout << "\nNogi odziedziczylem: " << Obiekt.nogi; cout << "\nA teraz kolejno funkcje: \n"; Obiekt.jedz(); Obiekt.spij(); Obiekt.oddychaj(); Obiekt.trabi(); Obiekt.tupie(); if(Obiekt.flaga_ssak) cout << "Jestem ssakiem !"; } Pami�taj c o problemie domy�lnego statusu członków struktur/public i klas/private) mo�emy przej�� do klas i obiektów. O KLASACH SZCZEGÓŁOWO. Aby wykaza� mo�liwo�� modularyzacji programu zaprojektujemy moduł w postaci pliku nagłówkowego. Moduł b�dzie zawiera� definicj� naszej prywatnej klasy obiektów ZNAK. Zaczynamy od danych, które b�d nam potrzebne do tworzenia w programach (ró�nych !) obiektów typu Znak. class ZNAK { char znak_dany; //Kod ASCII znaku ... Aby obiekt został zainicjowany (tzn. wiedział jakim znakiem ma by� w danym programie) dodamy do definicji klasy jednoparametrowy konstruktor class ZNAK { char znak_dany; public: ZNAK(...); ... Dane mog by� prywatne, natomiast konstruktor i funkcje-metody powinny by� publiczne, by mo�na było wywoływa� je w programach. Konstruktor b�dziemy wywoływa� w programach tak: ZNAK Obiekt('a'); Znaczy to: Utwórz w RAM obiekt klasy ZNAK pod nazw "Obiekt" i wytłumacz mu, �e jest znakiem 'a'.

Page 92: C++ - lekcje

92

Konstruktor powinien pobiera� od programu jeden argument typu char i przekazywa� go obiektowi klasy ZNAK na jego pole danych znak_dany. Definicja konstruktora b�dzie zatem wygl da� tak: ZNAK::ZNAK(char x) { znak_dany = x; } Zakres dopuszczalnych znaków zaw�zimy np. do kodów ASCII 65...90 (od A do Z). Je�li u�ytkownik "nie trafi", ustawimy zawsze "*" (asterisk). Dodatkowo, dla "elegancji" zamienimy ewentualne małe litery na du�e. ZNAK::ZNAK(char x) { znak_dany = x; if(znak_dany < 65 || znak_dany >122) znak_dany = '*'; if(znak_dany > 97) znak_dany -= 32; } A je�li u�ytkownik nie zechce poda� �adnego znaku i zda si� na domy�lno�� obiektu? aden problem, wystarczy do klasy ZNAK doda� bezparametrowy konstruktor domy�lny. Konstruktory domy�lne spełniaj w C++ tak wła�nie rol�: class ZNAK { char znak_dany; public: ZNAK(char); //Konstruktor zwykly ("jednoznakowy") ZNAK(void); //Konstruktor domy�lny (bezparametrowy) ... Słowo void (tu opcjonalne) mo�e nie wyst pi�. Aby "kłuło w oczy", który konstruktor jest konstruktorem domy�lnym (ang. default konstructor), wi�kszo�� programistów zapisuje to tak: class ZNAK { char znak_dany; public: ZNAK(char); ZNAK(); //Z daleka wida�, �e nic nie ma ! ... Definicja konstruktora bezparametrowego b�dzie wygl da� tak: ZNAK::ZNAK() { znak_dany = 'X'; } W zale�no�ci od sposobu zadeklarowania obiektu w programie C++ wywoła automatycznie albo konstruktor ZNAK(char), albo konstruktor domy�lny ZNAK(): ZNAK obiekt; //Nie sprecyzowano jaki, konstruktor domy�lny ZNAK obiekt('m'); //Wiadomo jaki, konstruktor jednoparametrowy Dzi�ki temu, �e C++ "pedantycznie" sprawdza przed wywołaniem funkcji zgodno�� typów argumentów przekazywanych do funkcji (konstruktor to te� funkcja) i porównuje typ argumentów z �yczeniem programisty wyra�onym w prototypie - bezbł�dnie rozpozna (mimo identycznej nazwy), któr funkcj� nale�y zastosowa�. Dodajmy do klasy ZNAK deklaracje (prototypy) funkcji-metod: class ZNAK { char znak_dany; public: ZNAK(char); ZNAK(); void Pokaz_sie(); void Znikaj(); void Skacz(); };

i zdefiniujmy te metody. void ZNAK::Pokaz_sie(void) { cout << znak_dany << '\a'; } void ZNAK::Znikaj(void) { cout << "\b" << ' '; //'\b' == Back Space } void ZNAK::Skacz(void) { for(int i = 0; i < 100; i++) { gotoxy(rand()%50, rand()%50); cout << znak_dany; getch(); } } Je�li implementacj� klasy ZNAK umie�cimy w pliku nagłówkowym A:\ZNAK.H //_____________________________________________________________ # include <stdlib.h> # include <conio.h> # include <iostream.h> class ZNAK { char znak_dany; public: ZNAK(char); ZNAK(); void Pokaz_sie(); void Znikaj(); void Skacz(); }; ZNAK::ZNAK() { znak_dany = 'X'; } ZNAK::ZNAK(char x) { znak_dany = x; if(znak_dany < 65 && znak_dany >122) znak_dany = '*'; if(znak_dany > 97) znak_dany -= 32; } void ZNAK::Pokaz_sie(void) { cout << znak_dany << '\a'; } void ZNAK::Znikaj(void) { cout << "\b" << ' '; //'\b' == Back Space } void ZNAK::Skacz(void) { for(int i = 0; i < 100; i++) { gotoxy(rand()%50, rand()%50); cout << znak_dany; getch(); } } //_____________ koniec pliku A:\INCLUDE\ZNAK.H _________________ to nasz program mo�e wygl da� tak: [P103.CPP] # include <a:\znak.h>

Page 93: C++ - lekcje

93

void main() { char litera; clrscr(); cout << '\n' << "Podaj znak: "; cin >> litera; ZNAK Obiekt(litera); cout << "\nSTART" << "\n\n\n"; getch(); Obiekt.Pokaz_sie(); getch(); Obiekt.Znikaj(); getch(); Obiekt.Skacz(); ZNAK Obiekt2; //To bedzie domyslny 'X' Obiekt2.Skacz(); } I tu ju� wida� pewne cechy nowoczesnego obiektowego stylu programowania. Tym razem sprwdzenie, czy słowo class mo�na spokojnie zamieni� na słowo struct pozostawim dociekliwym Czytelnikom. LEKCJA 29: FUNKCJE I OVERLOADING. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak jeszcze w C++ mo�na wykorzystywa� funkcje. ________________________________________________________________ w C++ jedna funkcja mo�e by� definiowana wielokrotnie a ka�da z wersji funkcji mo�e by� przystosowana do obsługi innego typu argumentów. C++ wybiera t� wła�ciw wersj� funkcji automatycznie. JEDNA NAZWA FUNKCJI - WIELE ZASTOSOWA�. Overloading funkcji bywa czasem w podr�cznikach dzielony na odr�bne zagadnienia: * funkcja mo�e tolerowa� ró�n liczb� argumentów (co dało si� spokojnie realizowa� równie� w klasycznym C - vide printf()); * funkcja mo�e tolerowa� ró�ne typy argumentów; * funkcja mo�e realizowa� ró�ne operacje dla ró�nych Wyobra�my sobie, �e mamy funkcj� wydrukuj(), która potrafi wysła� na ekran otrzymany znak: void wydrukuj(char znak) { cout << znak; } Tak zdefiniowan funkcj� mo�emy wywoła� w programie w nast�puj cy sposób: wydrukuj('Z'); Czasem jednak wygodniej byłoby, gdyby nasza funkcja była bardziej elastyczna i pozwalała na wykonanie szerszego zakresu operacji, np.: wydrukuj('Z'); wydrukuj(75); // 75 to kod ASCII znaku, zamiast znaku bezpo�r. wydrukuj("Wiecej niz znak - tekst"); W klasycznym j�zyku C wymaga to zdefiniowania nowej funkcji, natomiast w C++ to, �e funkcja wydrukuj() została ju� zdefiniowana w niczym nie przeszkadza. Poni�ej definjujemy tak funkcj�. ... class KLASA { public: void wydrukuj(char znak); void wydrukuj(int kod_ASCII); void wydrukuj(char *string); //wska�nik do lancucha

} Ła�cuch znaków jest widziany jako jednowymiarowa tablica zawieraj ca dane typu znakowego, czyli w taki sposób: char TABLICA[9] ={ "123456789" }; Definice powinny mie� nast�puj c posta�: void KLASA::wydrukuj(char znak) {cout << znak;}; void KLASA::wydrukuj(int kodASCII) {cout << (char) kodASCII;}; void KLASA::wydrukuj(char *string) {cout << string;}; Zapis: cout << (char) kodASCII; oznacza forsowanie typu - zamie� typ int na typ char - przyporz dkowanie kodowi ASCII - znaku. Wywołanie tej funkcji w programie mo�e spowodowa� ró�ne działanie, w zale�no�ci od typu i ilo�ci argumentów, z którym(i) funkcja zostaje wywołana. Wywołania funkcji mog wygl da� np. tak: KLASA Obiekt1, Obiekt2; main() { ... Obiekt1.wydrukuj('A'); //Wydrukuje si� litera A Obiekt1.wydrukuj(99); //Wydrukuje si� litera c Obiekt2.wydrukuj("napis"); //Wydrukuje si� napis. ... } Taki sposób post�powania umo�liwia funkcjom wi�ksz elastyczno�� i pozwala operowa� bez konfliktów na ró�nych rodzajach danych. J�zyk C posiada funkcje słu� ce do kopiowania ła�cuchów znakowych: strcpy() i strncpy(). Funkcja biblioteczna strncpy() przerywa proces kopiowania po zako�czeniu ła�cucha �ródłowego, b d� po skopiowaniu zadanej ilo�ci znaków. Dzi�ki mechanizmowi overloadingu mo�emy utworzy� nasz własn funkcj� kopiuj_string(), która zale�nie od sytuacji zadziała jak strcpy(), b d� tak jak strncpy(). [P104.CPP] # include <iostream.h> /* dwa porototypy - dwie wersje funkcji kopiuj_string() */ /* source: destination: len: */ void kopiuj_string(char*, const char*); //Dwa argumenty void kopiuj_string(char*, const char*, int); //a tu trzy static char Piggie[20], Kermit[32]; main() { kopiuj_string(Piggie, "Panna Piggie"); kopiuj_string(Kermit, "Kermit - to protokul transmisji", 6); cout << Kermit << " oraz " << Piggie; return 0; } void kopiuj_string(char *destin, const char *source) { while((*destin++ = *source++) != '\0') /* instr. pusta */ ; } void kopiuj_string(char *destin, const char *source, int len) { while (len && (*destin++ = *source++) != '\0') --len; while (len--) *destin++ = '\0'; } [S] Source- Destination. ________________________________________________________________ source - tu: �ródłowy ła�cuch znaków. Ogólnie - �ródło. Typowy skrót src. destin - tu: ła�cuch przeznaczenia. Ogólnie destination - miejsce przeznaczenia. Typowy skrót dest, dst, destin. len - tu: długo��.

Page 94: C++ - lekcje

94

________________________________________________________________ O FUNKCJACH WPLECIONYCH - TYPU inline. Czsami zale�y nam na przyspieszeniu działania programu obiektowego (zwykle kosztem zwi�kszenia długo�ci pliku). Je�li w �ródłowym tek�cie programu nast�puje wywołanie funkcji typu inline, to kompilator wstawia w to miejsce całe ciało funkcji (funkcje typu inline nie maj bezpo�redniego ani wył cznego odniesienia do obiektowego stylu programowania). Dla przykładu, je�li nadaliby�my naszej funkcji wydrukuj() status funkcji inline, to fragment programu: obiekt.wydrukuj(65); //Kod ASCII zostałby zast piony wstawionym w to miejsce ciałem funkcji wydrukuj(): .... cout << (char) 65; .... Jest to skuteczna metoda przyspieszenia działania programów. Je�li chcemy zastosowa� technik� funkcji inline w stosunku do metod nale� cych do danej klasy, powinni�my u�y� słowa kluczowego "inline" w definicjach funkcji. Zwró� uwg�, �e w samej definicji klasy słowo inline NIE POJAWIA SI�: [P105.CPP] # include <iostream.h> class Klasa { public: void wydrukuj(char* tekst); void wydrukuj(char Znak); void wydrukuj(int KodASCII); }; inline void Klasa::wydrukuj(char* tekst) { cout << tekst; } inline void Klasa::wydrukuj(char Znak) { cout << Znak; } inline void Klasa::wydrukuj(int KodASCII) { cout << (char) KodASCII; } void main() { Klasa Obiekt; cout << "Obiekt wyprowadza dane: " << '\n'; Obiekt.wydrukuj(65); Obiekt.wydrukuj('B'); Obiekt.wydrukuj("C i juz"); } Wszystkie wersje funkcji wydrukuj() otrzymały status inline. Oznacza to, �e funkcje te nie b�d w programie wywoływane lecz całe ciała funkcji zostan wstawione do programu w miejsca wywoła�. Jest to mechanizm podobny do wstawiania do programu makrorozkazów z t ró�nic , �e w przypadku funkcji inline C++ przeprowadza dodatkowo sprawdzenie zgodno�ci typów argumentów (ang. type checking). W naszym przypadku kompilator C++ wstawi do programu ciało funkcji tyle razy, ile razy funkcja powinna zosta� wywoływana. Zastosowanie funkcji inline jest opłacalne, je�eli ciało funkcji jest stosunkowo krótkie. [!!!] A CZY NIE MONA WEWNTRZ KLASY ? ________________________________________________________________ Mo�na. Je�li umie�cimy pełn definicj� funkcji wewn trz definicji klasy, to taka funkcja staje si� AUTOMATYCZNIE funkcj

typu inline. ________________________________________________________________ Status inline mo�emy nada� wszystkim trzem wersjom funkcji wydrukuj() umieszczaj c definicje funkcji bezpo�rednio wewn trz definicji klasy: class Klasa { public: inline void wydrukuj(char* a) { cout << a; } inline void wydrukuj(char z) { cout << z; } inline void wydrukuj(int kod) { cout << (char) kod; } }; W wi�kszo�ci przypadków daje to efekt pozytywny. Je�li definiujemy funkcje wewn trz klasy, s to zwykle funkcje o krótkim ciele. OVERLOADING KONSTRUKTORÓW. W C++ mo�emy podda� overloadingowi tak�e konstruktory. UWAGA: destruktorów nie mo�na podda� overloadingowi. Overloading konstruktorów nie wyró�nia si� niczym specjalnym. Wyobra�my sobie, �e tworzymy obiekt klasy Klasa o nazwie Obiekt. Je�li chcemy, by konstruktor przy zakładaniu Obiektu przekazał mu ła�cuch znaków "zzzz", mo�emy to zrobi� na dwa sposoby. Raz polecimy konstruktorowi przekaza� do obiektu ła�cuch znaków "zzzz", a za drugim razem polecimy przekaza� do obiektu czterokrotnie znak 'z': Obiekt("zzzz"); /* albo */ Obiekt('z', 4); Je�li w programie zadeklarujemy obiekt danej klasy, spowoduje to automatyczne wywołanie konstruktora z parametrem podanym w momencie deklaracji obiektu. class Klasa { public: Klasa(char*); Klasa(char, int); }; Wersje konstruktora Klasa::Klasa() powinni�my zdefiniowa� tak: Klasa::Klasa(char *tekst) { cout << tekst; } Klasa::Klasa(char Znak, ile = 4); { for(int i = 1; i < ile; i++) cout << Znak; } Dodajmy jeszcze jeden kontruktor domy�lny. Konstruktory domy�lne działaj według zasady, któr w naturalnym j�zyku dałoby si� przekaza� mniej wi�cej tak: "dopóki nie zdecydowano inaczej...". Dopóki nie zdecydowano inaczej - obiekt otrzyma znak 'x'. class Klasa { public: Klasa(); Klasa(char*); Klasa(char, int); }; ... Klasa::Klasa(void) { cout << 'x'; } Praktyczne zastosowanie w programie b�dzie wygl da� tak: [P106.CPP] # include <iostream.h>

Page 95: C++ - lekcje

95

class Klasa { public: Klasa(); Klasa(char*); Klasa(char, int); }; Klasa::Klasa(void) { cout << 'x'; } Klasa::Klasa(char *tekst) { cout << tekst; } Klasa::Klasa(char Znak, int ile = 4) { for(int i = 0; i < ile; i++) cout << Znak; } static char *p = "\nJestem Obiekt."; void main() { Klasa Obiekt1; //Konstr. domy�lny Klasa Obiekt2('A'); // ile - domyslnie == 4 Klasa Obiekt3('B', 3); Klasa Obiekt4(p); } LEKCJA 30: WYMIANA DANYCH MI�DZY OBIEKTAMI. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak mo�na wymienia� dane i informacje pomi�dzy ró�nymi obiektami. ________________________________________________________________ Hermetyzacja danych jest cenn zdobycz , ale od czasu do czasu obiekty powinny dokonywa� pomi�dzy sob wymiany informacji, tak�e tych wewn�trznych - prywatnych. Ten problem mo�e sprawia� programi�cie troch� kłopotów - nale�y zatem po�wi�ci� mu troch� uwagi. DOST�P DO DANYCH PRZY POMOCY FUNKCJI KATEGORII friend. Aby wyja�ni� mechanizmy dost�pu do danych obiektów b�dziemy potrzebowa�: * wielu obiektów; * danych prywatnych obiektów (dost�p do publicznych, "niezakapsułkowanych" danych jest prosty i oczywisty); * funkcji o specjalnych uprawnieniach. Takie funkcje o specjalnych uprawnieniach - z mo�liwo�ci odwoływania si� do prywatnych danych wielu obiektów (a nie tylko swojego) musz w C++ posiada� status "friend" (ang. friend - przyjaciel). Nasz przykładowy program b�dzie operował tablic zło�on z obiektów klasy Licznik. class Licznik { char moja_litera; int ile; public: void Inicjuj_licznik(char); void Skok_licznika(void); void Pokazuj(); }; ... Licznik TAB[MAX]; Obiekty - liczniki b�d zlicza� wyst pienie (ka�dy swojego)

okre�lonego znaku w strumieniu znaków wej�ciowych (wczytywanym z klawiatury). Tablica b�dzie si� składa� z MAX == 26 elementów - obiektów - liczników, po jednym dla ka�dej du�ej litery alfabetu. Tablica b�dzie nazywa� si� TAB[26]. Po zadeklarowaniu: nazwa_klasy TAB[MAX]; kolejne obiekty b�d si� nazywa�: nazwa_klasy Obiekt1 == TAB[0]; //Licznik 1 - 'A' nazwa_klasy Obiekt2 == TAB[1]; //Licznik 2 - 'B' ... ... nazwa_klasy ObiektN == TAB[N-1]; Po wprowadzeniu znaku z klawiatury wywołamy wbudowan do ka�dego obiektu funkcj� Skok_licznika(), która doda jedynk� do wewn�trznego licznika obiektu. Wywołuj c funkcj� zastosujemy zamiast typowej składni ObiektK.Skok_licznika(); odpowiadaj c jej w tym wypadku notacj� TAB[i].Skok_licznika(); Powinni�my jeszcze przed wywołaniem funkcji sprawdzi�, czy znak jest du� liter alfabetu. W przykładowym programie zrobimy to tak: ... cin >> znak; //Pobranie znaku z klawiatury for(int i = 0; i < 26; i++) { if(i == (znak - 'A')) TAB[i].Skok_licznika(); } ... Dzi�ki temu wewn�trzny licznik obiektu TAB[2] zostanie powi�kszony tylko wtedy, gdy znak - 'A' == 2 (znak jest liter C, bo 'C' - 'A' == 2). Mo�na to zapisa� skuteczniej. ... cin >> znak; TAB[znak - 'A'].Skok_licznika(); //Inkrementacja licznika ... b d� jeszcze krócej: ... TAB[getch() - 'A'].Skok_licznika(); ... Istnieje tu wszak�e niebezpiecze�stwo próby odwołania si� do nieistniej cego elementu tablicy, przed czym powinni�my si� wystrzega�. W wyniku działania programu otrzymamy zliczon ilo�� wyst�powania danej litery w strumieniu znaków wej�ciowych. [P107.CPP] # include <ctype.h> //prototyp toupper() # include <iostream.h> class Licznik { char moja_litera; int ile; public: void Inicjuj(char); void Skok_licznika(); void Pokazuj(); }; void Licznik::Inicjuj(char z) { moja_litera = z; ile = 0; }

Page 96: C++ - lekcje

96

void Licznik::Skok_licznika(void) { ile++; } void Licznik::Pokazuj(void) { cout << "Znak " << moja_litera << " wystapil " << ile << " razy" << '\n'; } main() { const MAX = 26; Licznik TAB[MAX]; register int i; /* inicjujemy liczniki: -------------------------------*/ for(i = 0; i < MAX; i++) { TAB[i].Inicjuj('A' + i); } /* pracujemy - zliczamy: -------------------------------*/ cout << "Wpisz ciag zankow zakonczony kropka [.]" << '\n'; for(;;) { char znak; cin >> znak; if(znak == '.') break; for(i = 0; i < MAX; i++) { if(i == (znak - 'A')) TAB[i].Skok_licznika(); } } /* sprawdzamy: ----------------------------------------*/ char sprawdzamy; cout << '\n' << "Podaj znak do sprawdzenia: " << '\n'; cin >> sprawdzamy; cout << "Wyswietlam wyniki zliczania: \n"; TAB[toupper(sprawdzamy) - 'A'].Pokazuj(); return 0; } Je�li chcieliby�my zliczy� ilo�� wszystkich wprowadzonych znaków, powinni�my zsumowa� dane pobrane od wielu obiektów. Je�li dane przechowywane w obiektach maj status danych prywatnych, to dost�p do tych danych mo�e by� utrudniony. Do tego momentu dost�p do danych prywatnych obiektu mogli�my uzyska� tylko posługuj c si� autoryzowan do tego metod - własn funkcj wewn�trzn tego� obiektu. Ale wtedy nie mieli�my dost�pu do danych innych obiektów a tylko do jednego - "własnego" obiektu funkcji. Je�li zatem chcieliby�my zsumowa� zawarto�ci wielu obiektów - liczników, to nale�y do tego zastosowa� tzw. funkcj� "zaprzyja�nion " - friend function. Je�li deklaruj c funkcj� zastosujemy słowo kluczowe friend, to taka zaprzyja�niona z klas funkcja uzyska prawo dost�pu do prywatnych elementów danej klasy. Zadeklarujemy tak przykładow zaprzyja�nion funkcj� o nazwie Suma(). Funkcja b�dzie pobiera� jako parametr ilo�� obiektów do zsumowania i sumowa� zawarto�ci wewn�trznych liczników obiektów. const MAX = 26; class Licznik { char moja_litera; int ile; public: void Inicjuj(char); void Skok_licznika(); void Pokazuj(); friend int Suma(int); } TAB[MAX]; Zadeklarowana w taki sposób zaprzyja�niona funkcja ma prawo dost�pu do prywatnych elementów wszystkich obiektów klasy Licznik. Typowe zastosowanie funkcji typu friend polega wła�nie na dost�pie do danych wielu ró�nych obiektów. Powinni�my

zsumowa� zawarto�� pól TAB[i].ile dla wszystkich obiektów (od i = 0 a� do i = MAX). Zwró� uwag�, �e definiuj c funkcj� Suma() nie stosujemy powtórnie słowa kluczowego friend. A oto definicja: int Suma(int ilosc_obiektow) { int i, suma = 0; for(i = 0; i < ilosc_obiektow; i++) suma += TAB[i].ile; return (suma); } Dzi�ki zastosowaniu słowa "friend", funkcja Suma() jest zaprzyja�niona ze wszystkimi 26 obiektami, poniewa� wszystkie obiekty nale� do tej klasy, w której zadeklarowali�my funkcj�: class ... { ... friend int Suma(...); ... } ... ; Tablica TAB[MAX] zło�ona z obiektów klasy Licznik została zadeklarowana nazewn trz funkcji main() ma wi�c status tablicy GLOBALNEJ. Funkcja Suma() ma dost�p do prywatnych danych wszystkich obiektów, mo�emy wi�c zastosowa� j w programie w nast�puj cy sposób: [P108.CPP] # include <ctype.h> # include <iostream.h> class Licznik { char moja_litera; int ile; public: void Inicjuj(char); void Skok_licznika(); void Pokazuj(); friend int Suma(int); } const MAX = 26; Licznik TAB[MAX]; register int i; main() { /* inicjujemy liczniki: -------------------------------*/ for(i = 0; i < MAX; i++) { TAB[i].Inicjuj('A' + i); } /* pracujemy - zliczamy: -------------------------------*/ cout << "Wpisz ciag zankow zakonczony kropka [.]" << '\n'; for(;;) { char znak; cin >> znak; if(znak == '.') break; for(i = 0; i < MAX; i++) { if(i == (znak - 'A')) TAB[i].Skok_licznika(); } } /* sprawdzamy: ----------------------------------------*/ char sprawdzamy; cout << '\n' << "Podaj znak do sprawdzenia: " << '\n'; cin >> sprawdzamy; cout << "Wyswietlam wyniki zliczania: \n"; TAB[toupper(sprawdzamy) - 'A'].Pokazuj(); cout << "\n Wszystkich liter bylo " << Suma(MAX);

Page 97: C++ - lekcje

97

return 0; } void Licznik::Inicjuj(char zn) { moja_litera = zn; ile = 0; } void Licznik::Skok_licznika(void) { ile++; } void Licznik::Pokazuj(void) { cout << "Znak " << moja_litera << " wystapil " << ile << " razy" << '\n'; } int Suma(int ilosc_obiektow) { int i, suma = 0; for(i = 0; i < ilosc_obiektow; i++) suma += TAB[i].ile; return (suma); } Tak działa funkcja typu friend. Zwró�my tu uwag�, �e funkcja taka nie jest traktowana dokładnie tak samo, jak metoda wchodz ca w skład klasy i obiektu. Metoda, czyli "własna" funkcja obiektu odwołuje si� do jego pola (danych) w taki sposób: void Licznik::Skok_licznika(void) { ile++; //Wiadomo o ktory obiekt chodzi } Funkcja klasy friend odwołuje si� do pól obiektów tak: int Suma(int liczba) { ... suma += TAB[i].ile; /* - wymaga dodatkowo wskazania, o który obiekt chodzi - */ } Nale�y pami�ta�, �e dla funkcji kategorii friend wszystkie obiekty nale� ce do danej klasy maj status public - s dost�pne. O ZAPRZYJA�NIONYCH KLASACH. W C++ mog by� zaprzyja�nione ze sob wzajemnie tak�e klasy. Pozwala to metodom zdefiniowanym wewn trz jednej z klas na dost�p do prywatnych danych obiektów innych klas. W przypadku zaprzyja�nionych klas słowem kluczowym friend poprzedzamy nazw� klasy (a nie ka�dej zaprzyja�nionej metody z osobna, cho� zamierzony skutek wła�nie na tym polega). Oto praktyczny przykład zaprzyja�nionych klas. [P109.CPP] # include <iostream.h> class Data1; //Deklaracja (a nie definicja!) klasy class TEZ_DATA { int dz, rok; public: TEZ_DATA() {} TEZ_DATA(int d, int y) { dz = d; rok = y;} void Pokazuj() {cout << '\n' << rok << '-' << dz;} friend Data1; //"zaprzyjazniona" klasa }; class Data1 //Tu DEFINICJA klasy { int mc, dz, rok; public: Data1(int m, int d, int y) { mc = m; dz = d; rok = y; } operator TEZ_DATA(); };

static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31}; /* ---- funkcja - metoda konwersji - definicja ----------- */ Data1::operator TEZ_DATA(void) { TEZ_DATA DT_Obiekt(0, rok); for (int i = 0; i < mc-1; i++) DT_Obiekt.dz += TAB[i]; DT_Obiekt.dz += dz; return DT_Obiekt; } main() { Data1 dt_Obiekt(11,17,89); TEZ_DATA DT_Obiekt; DT_Obiekt = dt_Obiekt; DT_Obiekt.Pokazuj(); return 0; } Zaprzyja�nione s klasy Data1 i TEZ_DATA. Dzi�ki temu metody zadeklarowane wewn trz zaprzyja�nionej klasy Data1 maj dost�p do prywatnych danych obiektów klasy TEZ_DATA. Poniewa� klasa to nowy formalny typ danych, a obiekt to dane takiego nowego typu, nic nie stoi na przeszkodzie, by obiekty przekazywa� do funkcji jako argumenty (tak jak wcze�niej obiekty typów typowych - int, float itp.). W C++ mamy jeszcze jedn metod� wymiany danych. Mo�emy nada� elementom klas i obiektów status static (statyczny). WYMIANA INFORMACJI PRZY POMOCY DANYCH STATYCZNYCH. Je�li element klasy został zadeklarowany jako element statyczny (przy pomocy słowa kluczowego static), to bez wzgl�du na to jak wiele obiektów danej klasy utworzymy, w pami�ci b�dzie istnie� TYLKO JEDEN EGZEMPLARZ (kopia) tego elementu. W przykładowym programie z obiektami-licznikami mo�emy osi gn c taki efekt nadaj c zmiennej ile (stan licznika) status static int ile: class Licznik { char moja_litera; static int ile; ... }; Je�li utworzymy wiele obiektów takiej klasy, to wszystkie te obiekty b�d posługiwa� si� t sam (wspóln !) zmienn ile. Dla przykładu, je�li zechcemy zlicza� ile razy w strumieniu danych wej�ciowych pojawiły si� np. znaki 'a' , 'b' i 'c', mo�emy utworzy� trzy obiekty - liczniki: licznik_a, licznik_b i licznik_c. wszystkie te liczniki b�d posługiwa� si� wspóln zmienn statyczn ile: class Licznik { public: char moja_litera; static int ile; Licznik(char); //Konstruktor ... }; Do zainicjownia obiektów posłu�ymy si� konstruktorem. Deklaracja obiektu spowoduje automatyczne wywołanie kostruktora i zainicjowanie obiektu w pami�ci. Przy okazji przekazujemy obiektom znaki do zliczania. Licznik licznik_a('a'), licznik_b('b'), licznik_c('c'); Je�li teraz w strumieniu wej�ciowym pojawi si� która� z interesuj cych nas liter (a, b, b d� c), zostanie wywołana wła�ciwa wersja metody Skok_licznika(): int main(void) { char litera;

Page 98: C++ - lekcje

98

... cin >> litera; ... if(litera == licznik_a.moja_litera) licznik_a.Skok_licznika(); if(litera == licznik_b.moja_litera) licznik_b.Skok_licznika(); ... } Zmienna ile jest zmienn statyczn , wi�c wsztstkie trzy funkcje dokonaj inkrementacji zmiennej znajduj cej si� pod tym samym fizycznym adresem pami�ci. Je�li dla wszystkich obiektów danej klasy jaka� zmienna oznacza zawarto�� tego samego adresu pami�ci, mo�emy si� odwoła� do tej zmiennej równie� tak: nazwa_klasy::nazwa_zmiennej Ten sposób mo�na jednak�e stosowa� wył cznie wobec statycznych elementów klasy o statusie danych publicznych. Je�li s to dane prywatne nie mo�na jeszcze dodatkowo zapomina� o hermetyzacji i zasadach dost�pu. Je�eli pole danej klasy jest polem statycznym, mo�emy do niego odwoływa� si� na dwa sposoby. Za po�rednictwem obiektów w taki sposób: identyfikator_obiektu.identyfikator_pola A za po�rednictwem nazwy klasy (podobnie jak do zmiennych globalnych), tak metod : identyfikator_klasy::identyfikator_pola Mo�emy zmodyfikowa� program przykładowy posługuj c si� (globaln ) zmienn statyczn . Zamiast wszystkich liter b�dziemy zlicza� tylko wyst pienia 'a', 'b' i 'c'. [P110.CPP] # include "ctype.h" # include "iostream.h" class Licznik { public: char moja_litera; static int ile; Licznik(char); //Konstruktor void Skok_licznika(); void Pokazuj(); }; void main() { /* inicjujemy liczniki: -------------------------------*/ Licznik licznik_a('a'), licznik_b('b'), licznik_c('c'); /* 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(); if (znak == licznik_b.moja_litera) licznik_b.Skok_licznika(); if (znak == licznik_c.moja_litera) licznik_c.Skok_licznika(); } /* sprawdzamy: ----------------------------------------*/ cout << "Wyswietlam wyniki zliczania: \n"; licznik_a.Pokazuj(); licznik_b.Pokazuj(); licznik_c.Pokazuj(); } Licznik::Licznik(char z) { moja_litera = z; ile = 0;

} void Licznik::Skok_licznika(void) { ile++; } void Licznik::Pokazuj(void) { cout << "Znak " << moja_litera << " wystapil " << ile << " razy" << '\n'; } Tym razem Twój dialog z programem mo�e wygl da� np. tak: C:\>program Wpisz ciag zankow zakonczony kropka [.] aaa bbb cccc qwertyQWERTYPOLIPOLIpijesz? nie ojojojojoj. Wyswietlam wyniki zliczania: Znak a wystapil 10 razy Znak b wystapil 10 razy Znak c wystapil 10 razy Jak wida�, program si� myli. Wszystkie funkcje wy�wietlaj (odwołuj si� do) zawarto�ci tego samego wspólnego pola. Charakter (status) statyczny mo�emy nada� równie� funkcji (metodzie) nale� cej do danej klasy. Je�li funkcja otrzyma status static, w pami�ci b�dzie istnie� tylko jeden egzemplarz danej funkcji i do takiej funkcji mo�na b�dzie odwoływa� si� podobnie jak do zmiennej statycznej posługuj c si� nazw obiektu lub nazw klasy: nazwa_obiektu.Funkcja(...); /* lub */ nazwa_klasy::Funkcja(...); Je�eli funkcja jest tylko jedna, jej działanie nie zale�y od tego ile obiektów danej klasy zostało utworzone i jakie nazwy nadamy tym obiektom. W przykładowym programie powy�ej "a� si� prosi", by nada� status funkcji statycznej metodzie wy�wietlaj cej wyniki zliczania: class Licznik { ... static void Pokazuj(void); ... } Sprawdzenie, czy wtedy program przestanie "robi� bł�dy" pozostawiamy bardziej dociekliwym Czytelnikom jako zadanie domowe. 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(); };

Page 99: C++ - lekcje

99

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 BIECYM 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++

Page 100: C++ - lekcje

100

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 BIEC 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 Referencje i wska�niki mo�na stosowa� a C++ niemal wymiennie (dokładniej - nie jest to wymienno�� wprost, a uzupełnianie na zasadzie odwrotno�ci-komplementarno�ci). [!!!] TO NIE WSZYSTKO JEDNO!. ________________________________________________________________ Mogłoby si� wydawa�, �e operator adresowy & zyskał dwa RÓNE zastosowania: okre�lenie adresu w pam�ci oraz tworzenie wskazania. Aby rozró�ni� te dwie sytuacje zwró� uwag� na "gramatyk�" zapisu. Je�li identyfikator zminnej jest poprzedzony okre�leniem typu zminnej: int &zmienna; /* lub */ int &zmienna = ... ; to zmienn nazywamy "zmienn referencyjn ". Je�li natomiast identyfikator nie został poprzedzony okre�leniem typu: p = &zmienna; to mówimy wtedy o adresie zmiennej. Przekazanie argumentu do funkcji poprzez referencj� jest w istocie zbli�one do przekazania wska�nika do argumentu. Zwró� uwag�, �e przekazanie wska�nika do obiektu mo�e zwykle odby� si� szybciej ni� sporz dzenie kopii obiektu i przekazanie tej kopii do funkcji. Zastosowanie w deklaracji funkcji operatora adresowego & pozwala nam stosowa� syntaktyk� zapisu tak "jak zwykle" - przy przekazaniu przez warto��. Je�li nie chcemy ryzykowa� zmian wprowadzonych do oryginalnego parametru przekazanego funkcji poprzez wskazanie, mo�emy zadeklarowa� oryginalny parametr jako stał (kompilator "dopilnuje" i uniemo�liwi zmian� warto�ci):

Page 101: C++ - lekcje

101

nazwa_funkcji(const &nazwa_obiektu); ________________________________________________________________ Poprosimy C++ by pokazał nam konkretne fizyczne adresy skojarzone z identyfikatorami "zmienna" i "ksywa". Operator & oznacza dla C++ &X --> adres w pami�ci zmiennej X [P112.CPP] /* UWAGA: Program moze potrzebowac modelu wiekszego niz domyslnie ustawiany MODEL SMALL */ # include "iostream.h" main() { int zmienna = 6666; int& ksywa = zmienna; cout << "Zmienna (ADR-hex) Ksywa (ADR-hex): \n\n"; cout << hex << &zmienna << "\t\t" << &ksywa; return 0; } Monolog programu powinien wygl da� tak: Zmienna (ADR-hex) Ksywa (ADR-hex): 0x287efff4 0x287efff4 Fizyczny adres pami�ci, który "kojarzy si�" C++ ze zmienn i ksyw jest identyczny. Referencja nie oznacza zatem ani sporz dzania dodatkowej kopii zmiennej, ani wskazania do zmiennej w rozumieniu wska�nika (pointer). Jest to inna metoda odwołania si� do tej samej pojedynczej zmiennej. LEKCJA 33: WSKA�NIKI DO OBIEKTÓW. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak posługiwa� si� obiektami za po�rednictwem wska�ników. ________________________________________________________________ Wska�niki do obiektów funkcjonuj podobnie jak wska�niki do struktur. Operator -> pozwala na dost�p zarówno do danych jak i do funkcji. Dla przykładu wykorzystamy obiekt naszej prywatnej klasy Licznik. class Licznik { public: char moja_litera; int ile; Licznik(char znak) { moja_litera = z; ile = 0; } void Skok_licznika(void) { ile++; } }; Aby w programie mo�na było odwoła� si� do obiektu nie poprzez nazw� a przy pomocy wska�nika, zadeklarujemy wska�nik do obiektów klasy Licznik: Licznik *p; Wska�nik w programie mo�emy zastosowa� np. tak: p->Skok_licznika(); (czytaj: Wywołaj metod� "Skok_licznika()" w stosunku do obiektu wskazywanego w danym momencie przez wska�nik p) Trzeba pami�ta�, �e sama deklaracja w przypadku referencji i wska�ników nie wystarcza. Przed u�yciem nale�y jeszcze zainicjowa� wska�nik w taki sposób, by wskazywał na nasz obiekt-licznik. Wska�nik do obiektu inicjujemy w taki sam sposób jak ka�dy inny pointer: p = &Obiekt; Mo�emy przyst pi� do utworzenia programu przykładowego.

[P119.CPP] # include "ctype.h" # include "iostream.h" class Licznik { public: char moja_litera; int ile; Licznik(char z) { moja_litera = z; ile = 0; } void Skok_licznika(void) { ile++; } }; void main() { char znak; cout << "\nPodaj litere do zliczania: "; cin >> znak; Licznik Obiekt1(znak), Obiekt2('a'), *p1, *p2; p1 = &Obiekt1; p2 = &Obiekt2; cout << "\n Wpisz ciag znakow"; cout << "zakonczony kropka [.] i [Enter] \n"; for(;;) { cin >> znak; if(znak == '.') break; if(znak == p1->moja_litera) p1->Skok_licznika(); if(znak == p2->moja_litera) p2->Skok_licznika(); } cout << "\nBylo " << p1->ile; cout << " liter: " << p1->moja_litera; p1 = p2; cout << "\nBylo " << p1->ile; cout << " liter: " << p1->moja_litera; } Mo�emy oczywi�cie np. stosowa� przypisanie, inkrementowa� i dekrementowa� pointer oraz realizowa� arytmetyk� na wska�nikach dokładnie tak samo, jak w przypadku innych zmiennych. this - WSKA�NIK SPECJALNY. Po�wi�cimy teraz chwil� uwagi pewnemu specjalnemu wska�nikowi. Specjalnemu (i wa�nemu) na tyle, �e a� "dorobił si�" w C++ własnego słowa kluczowego "this". Ka�dej funkcji - metodzie zadeklarowanej wewn trz klasy zostaje w momencie wywołania w niejawny sposób (ang. implicitly) przekazany wska�nik do obiektu (w stosunku do którego funkcja ma zadziała�). Pointer wskazuje funkcji w pami�ci ten obiekt, którego członkiem jest dana funkcja. Bez istnienia takiego wła�nie wska�nika nie mogliby�my stosowa� spokojnie funkcji, nie mogliby�my odwoływa� si� do pola obiektu, gdyby�my nie wiedzieli jednoznacznie, o który obiekt chodzi. Program posługuje si� automatycznie niejawnym wska�nikiem do obiektu (ang. implicit pointer). Mo�emy wykorzysta� ten istniej cy, cho� do tej pory nie widoczny dla nas pointer posługuj c si� słowem kluczowym this (ten). This pointer wskazuje na obiekt, do którego nale�y funkcja. Korzystaj c z tego wska�nika funkcja mo�e bez cienia w tpliwo�ci zidentyfikowa� wła�nie ten obiekt, z którym pracuje a nie obiekt przypadkowy. [!!!] FUNKCJE KATEGORII static NIE OTRZYMUJ POINTERA this. Nale�y pami�ta�, �e wska�nik this istnieje wył cznie podczas wykonywania metod (ang. class member function execution), za wyj tkiem funkcji statycznych. Je�li w programie zadeklarujemy klas� Klasa: class Klasa { int dane; ... }

Page 102: C++ - lekcje

102

a wewn trz tej klasy metod� Pokazuj(): class Klasa { int dane; public: void Pokazuj(); ... } void Klasa::Pokazuj(void) { cout << dane; } To zdefiniowanie funkcji Pokazuj() z zastosowaniem pointera this i notacji wska�nikowej (p->), jak poni�ej, b�dzie równowa�ne: void Klasa::Pokazuj(void) { cout << this->dane; } Przypomnijmy, �e taka notacja wska�nikowa oznacza: "Wyprowad� zawarto�� pola "dane" obiektu, na który wskazuje wska�nik" (poniewa� jest to wska�nik this, wi�c chodzi o własny obiekt). LEKCJA 34 OVERLOADING OPERATORÓW. ________________________________________________________________ Podczas tej lekcji poznasz mo�liwo�ci dostosowania operatorów C++ do własnego "widzimisi�" i do potrzeb własnych obiektów. ________________________________________________________________ Niemal od pocz tku niniejszej ksi �ki korzystamy z operatorów poddanych overloadingowi. S to operatory << i >> , które pierwotnie wykonywały bitowe przesuni�cie w lewo i w prawo. Owerloading tych operatorów "załatwił" za nas producent (Borland, Microsoft, czy inny). Jak widzisz, nie powoduje to w dalszym u�ytkowaniu tych operatorów �adnych zauwa�alnych komplikacji, a cz�sto ułatwia tworzenie programów. Zwró� uwag�, �e overloading operatorów (jak i definicje klas) mo�e znajdowa� si� w doł czonych plikach nagłówkowych i po jednorazowym wykonaniu mo�e by� "niewidoczny" dla programistów tworz cych programy aplikacyjne. Je�li projektujemy (definiujemy) now klas�, dodajemy do C++ nowy, lecz pełnoprawny typ danych. Autorzy C++ nie byli w stanie przewidzie� jakie klasy i jakie obiekty mog wymy�li� kolejne pokolenia programistów w ramach swojej radosnej twórczo�ci. Wprowadzili zatem do C++ jasne i jednoznaczne algorytmy post�powania z typami "typowymi". C++ doskonale wie jak dodawa�, mno�y�, czy odejmowa� np. liczby int, long, float itp., nie wie jednak jak doda� do siebie obiekty klas CString (CString = Class String = klasa "ła�cuch znaków"), TOdcinek (to taki kawałek prostej) itp.. A przecie� miło byłoby, gdyby rozbudowa� działanie operatorów tak, by było mo�liwe ich typowe zastosowanie w stosunku do naszych własnych, "nietypowych" obiektów: int x, y; int z = x + y; //To operator + załatwia sam float x, y; float z = x + y; Zanim jednak stanie si� mo�liwe post�powanie takie: class CString x, y, z; z = x + y; class Nasza_Klasa obiekt1, obiekt2, obiekt3; obiekt3 = obiekt1 + obiekt2; itp., itd. ... musimy "uzupełni�" C++ i "wyja�ni�" operatorom, co wła�ciwie ma w praktyce oznacza� operacja obiekt1 = obiekt2 + obiekt3; . Jest wyczuwalne intuicyjnie, �e działanie operatorów w stosunku do ró�nych obiektów mo�e by� ró�ne. Dla przykładu - wiesz zapewne, �e inaczej wygl da algorytm mno�enia liczb zespolonych,

a inaczej liczb całkowitych rzeczywistych. Dlatego te� wykonanie operacji mno�enia wymaga od operatora * podj�cia ró�nych działa�: class Liczba_zespolona x, y, z; z = x * y; int x, y, z; z = x * y; Czasem mo�e si� zdarzy�, �e dla dwu ró�nych klas działanie jakiego� operatora jest identyczne, cz��ciej jednak (i tak nale�y si� spodziewa�) działanie operatora dla ka�dej klasy b�dzie odr�bne i unikalne. Pójd�my w tym rozumowaniu o krok dalej. Skoro rozszerzenie obszaru zastosowa� jakiego� operatora na obiekty nowej (nieznanej wcze�niej klasy) wymaga zdefiniowania nowego algorytmu działania operatora, C++ b�dzie potrzebował do tego celu specjalnych �rodków, które powinny by� łatwo rozpoznawalne. Do opisu algorytmów słu� generalnie w C++ funkcje i tu Autorzy nie wprowadzili wyj tku. Zastrzegli jednak dla tych specjalnych funkcji specjaln nazw�: operator ...(); I tak funkcja precyzuj ca nowy algorytm dodawania (nowy sposób działania operatora + ) b�dzie si� nazywa�: operator+(); a np. funkcja okre�laj ca nowy algorytm mno�enia (nowy sposób działania operatora * ) b�dzie si� nazywa�: operator*(); Spróbujmy zastosowa� tak filozofi� w praktyce programowania. [!!!] NIESTETY NIE WSZYSTKIE OPERATORY MONA ROZBUDOWA�. ________________________________________________________________ S w C++ operatory, których nie mo�emy podda� overloadingowi. S to: . :: .* ?: . operator kropki umo�liwia dost�p do pól struktur i obiektów; :: operator "widoczno�ci-przesłaniania" (ang. scope); .* wskazanie członka klasy (ang. pointer-to-member); ?: operator warunkowy. ________________________________________________________________ Wszystkie pozostałe operatory mo�emy podda� overloadingowi i przypisywa� im potrzebne nam działanie. OVERLOADING OPERATORA [+] (DWUARGUMENTOWEGO). Zaczniemy od operatora + nale� cego do grupy "dwuargumentowych operatorów arytmetycznych" (ang. binary arithmetic operator). Zwracamy tu ju� na pocz tku rozwa�a� uwag� na przynale�no�� operatora do okre�lonej grupy, poniewa� overloading ró�nych opertorów nale� cych do tej samej grupy przebiega podobnie. Poniewa� znak + mo�e by� tak�e operatorem jednoargumentowym (ang. unary plus, o czym za chwil�), podkre�lamy, �e tym razem chodzi o plus jako operator dodawania. Overloading operatora przeprowadzimy w stosunku do obiektów prostej, znanej Ci ju� z poprzednich przykładów klasy Data, któr (w celu upodobnienia si� do maniery stosowanej w Windows i bibliotekach klas) nazwiemy tym razem CData. "Namówimy" operator + do przeprowadzenia operacji na obiektach (dokładniej na polach obiektów): CData nowadata = staradata + 7; // W tydzien pozniej Operator + musi oczywi�cie "wiedzie�", na którym polu obiekty klasy CData przechowuj liczb� dni i jak zwi zane s (logicznie) pola obiektu dz, mc, rok. Jest rzecz zrozumiał , �e samo dodanie dni do pola dz mo�e nie wystarczy�, poniewa� data 37.11.93 jest niedopuszczalna. Je�li staradata jest obiektem klasy CData z zawartymi wewn trz

Page 103: C++ - lekcje

103

danymi, to w wyniku działania "nowego" operatora + powinien powsta� obiekt nowadata klasy CData, którego pola zostan w sensowny sposób powi�kszone o dodan liczb� dni. Rozwa� działanie programu (najlepiej skompiluj i uruchom). [P120.CPP] /* Overloading operatora dwuargumentowego + */ # include <iostream.h> class CData { int dz, mc, rok; public: CData() {} //Konstruktor domyslny (pusty) CData(int d, int m, int y) { mc = m; dz = d; rok = y; } void Pokazuj() { cout << dz << '.' << mc << '.' << rok; } CData operator+(int); //TU! overloading operatora + }; static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31}; /* Definicja funkcji operatorowej: ------------------------ */ CData CData::operator+(int n) { CData kopia_obiektu = *this; n += kopia_obiektu.dz; while (n > TAB[kopia_obiektu.mc-1]) { n -= TAB[kopia_obiektu.mc-1]; if (++kopia_obiektu.mc == 13) { kopia_obiektu.mc = 1; kopia_obiektu.rok++; } } kopia_obiektu.dz = n; return (kopia_obiektu); } main() { CData staradata(31, 1, 94); //Kostruktor z argumentami CData nowadata; //Pusty konstruktor cout << "\n Stara data: "; staradata.Pokazuj(); cout << "\n Podaj ile minelo dni --> "; int n; cin >> n; nowadata = staradata + n; cout << "\n Jest zatem --> "; nowadata.Pokazuj(); return 0; } Do tej pory do danych prywatnych obiektu mogli�my si�gn � wył cznie przy pomocy zdefiniowanej wewn trz klasy funkcji-metody. Metod umo�liwiaj c nam dost�p do prywatnych danych obiektu jest tu zadeklarowana wewn trz klasy (a wi�c maj ca "status prawny" metody) funkcja operatorowa. Przyjrzyjmy si� tej funkcji dokładniej: CData CData::operator+(int n) { CData kopia_obiektu = *this; ... return (kopia_obiektu); } Funkcja * została zdefiniowana dla obiektów klasy CData (z innymi post�powa� nie potrafi); Je�li operator + zostanie umieszczony pomi�dzy obiektem klasy CData, a liczb typu int: .... staradata + n; * funkcja pobiera liczb� n jako argument (jawnie); * funkcja pobiera obiekt klasy CData jako swój drugi argument (niejawnie, dzi�ki pointerowi this); * funkcja zwróci obiekt klasy CData (ze zmodyfikowanym polem); Nowy obiekt zwrócony przez funkcj� zostanie przypisany nowadata = ... ; // <-- return(kopia_obiektu);

W prawym polu operatora (operator jest dwuargumentowy, ma wi�c swoje lewe i prawe pole) mo�e pojawi� tak�e stała. Operacja: nowadata = staradata + 14; zostanie wykonana poprawnie. Ale to nie wszystko. Je�li wyst pi układ odwrotny - np.: nowadata = 14 + staradata; nasz operator "zgłupieje". Doszedłszy do operatora + C++ "nie b�dzie jeszcze wiedział" (analizuje wyra�enia arytmetyczne od lewej do prawej), KTÓRY obiekt wyst pi za chwil�. Jedno jest pewne, nie zawsze musi by� to "własny" obiekt funkcji, do którego mamy pointer this. Aby uzyska� jednoznaczno�� sytuacji, funkcja operatorowa powinna tu w jawny sposób pobiera� przed zadziałaniem dwa argumenty: CData operator+(int n, CData obiekt); aby działanie: CData obiekt_wynik; obiekt_wynik = n + obiekt; stało si� wykonalne. Pojawia si� tu wszak�e pewien problem. Wska�nik this wskazuje własny obiekt funkcji-metody, a tym razem funkcja potrzebuje dost�pu nie do pola własnego obiektu, lecz do pola "obcego" obiektu przekazanego jej jako argument. Ale w C++ mo�emy: * zdefiniowa� dwie (i wi�cej) funkcji o tej samej nazwie (ka�da na inn ewentualno��); * mo�emy nada� funkcji status friend (wtedy nie b�d c metod te� uzyska dost�p do danych obiektu). Definicja naszej klasy CData zawieraj ca deklaracje dwu funkcji operatorowych operator+() ró�ni cych si� zastosowaniem i (po czym rozpozna je C++) liczb argumentów, b�dzie wygl da� tak: class CData { int dz, mc, rok; public: CData() {} CData(int d, int m, int y) { mc = m; dz = d; rok = y; } void Pokazuj() { cout << dz << '.' << mc << '.' << rok; } /* Dwie funkcje operatorowe: ------------------------------ */ CData operator+(int); friend CData operator+(int, CData&); }; Zastosowali�my zamiast kopii obiektu bezpo�rednio przekazywanej funkcji - referencj� do obiektu klasy CData - CData&. Klasa zawiera: * prywatne dane; * dwa konstruktory; * własn metod� - funkcj� operatorow operator+(); * deklaracj� zaprzyja�nionej z klas funkcji kategorii friend (cho� jest to funkcja o tej samej nazwie, jej status i uprawnienia s nieco inne). [!!!] NIE WSZYSTKO, CO WEWNTRZ JEST METOD. ________________________________________________________________ Nawet, je�li wewn trz definicji klasy zdefiniujemy w pełni funkcj� (nadaj c jej status inline), nie stanie si� ona metod ! Słowo kluczowe friend okre�la status funkcji jednoznacznie, bez wzgl�du na to, w którym miejscu w tek�cie programu umie�cimy definicj� ciała funkcji. ________________________________________________________________ W zasadzie ciało funkcji jest na tyle proste (wymagamy od niej tylko zwrotu obiektu ze zmodyfikowanym polem danych), �e mo�emy skorzysta� z rozbudowanego wcze�niej operatora + i całe ciało zdefiniowa� tak: class CData { int dz, mc, rok;

Page 104: C++ - lekcje

104

public: ... CData operator+(int); friend CData operator+(int n, CData& x) { return (x + n); } }; Je�li w operacji dodawania argumenty zastosujemy we wcze�niejszej kolejno�ci: return (obiekt + liczba); to zostanie tu wykorzystany operator + rozbudowany poprzednio przez metod� CData::operator+(int). Program w cało�ci mo�e zatem wygl da� tak: [P121.CPP] # include "iostream.h" class CData { int dz, mc, rok; public: CData() {} CData(int d, int m, int y) { mc = m; dz = d; rok = y; } void Pokazuj() { cout << dz << '.' << mc << '.' << rok; } CData operator+(int); friend CData operator+(int n, CData& x) { return (x + n); } }; static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31}; CData CData::operator+(int n) { CData kopia_obiektu = *this; n += kopia_obiektu.dz; while (n > TAB[kopia_obiektu.mc-1]) { n -= TAB[kopia_obiektu.mc-1]; if (++kopia_obiektu.mc == 13) { kopia_obiektu.mc = 1; kopia_obiektu.rok++; } } kopia_obiektu.dz = n; return (kopia_obiektu); } main() { CData staradata(31, 1, 94); //Kostruktor z argumentami CData nowadata, jeszczejednadata; cout << "\n Stara data: "; staradata.Pokazuj(); cout << "\n Podaj ile minelo dni --> "; int n; cin >> n; nowadata = staradata + n; cout << "\n Jest zatem --> "; nowadata.Pokazuj(); cout << "\n Testuje nowy operator: "; jeszczejednadata = (1+n) + staradata; jeszczejednadata.Pokazuj(); return 0; } Operator + w obu sytuacjach działa poprawnie. By� mo�e wpadłe� na pomysł, �e operator - (minus) te� mamy ju� z głowy. Niby tak, ale tylko w takim zakresie, w jakim nasza funkcja operatorowa poprawnie b�dzie obsługiwa� ujemne liczby dni. Je�li zechcesz poda� ujemn liczb� dni (zmuszaj c funkcj� do odejmowania zamiast dodawania), twój dialog z programem b�dzie wygl dał np. tak: C:\>program Stara data: 31.1.94 Podaj ile minelo dni --> -10 Jest zatem --> 21.1.94 Testuje nowy operator: 22.1.94 lub tak: C:\>program Stara data: 31.1.94

Podaj ile minelo dni --> -150 Jest zatem --> -119.1.94 Testuje nowy operator: -118.1.94 Funkcja operatorowa została napisana w taki sposób, �e po przekroczeniu warto�ci -31 program b�dzie wypisywał bzdury. Jako zadanie domowe - spróbuj zmodyfikowa� algorytm w taki sposób, by rozszerzy� zakres poprawnych warto�ci. [!!!] Mo�esz dodawa� obiekty minusem. ________________________________________________________________ * Nale�y tu zwróci� uwag�, �e dodawanie obiektów mo�e wykonywa� nie tylko i nie koniecznie operator + . Je�li zechcesz, mo�esz do tego celu zastosowa� dowolnie wybrany operator (np. -, * itp.). W celu ułatwienia zrozumienia zapisu (i tylko dlatego) wi�kszo�� programistów rozbudowuje działanie operatorów zgodnie z ich pierwotnym zastosowaniem. * DOWOLNO��, ALE NIE PEŁNA! O tyle, o ile działanie operatora mo�e by� zmienione, to ilo�� argumentów potrzebnych operatorowi pozostaje w C++ "sztywna" (patrz przykład z n!). ________________________________________________________________ W bardzo podobny sposób mo�esz rozbudowywa� inne arytmetyczne operatory dwuargumentowe (*, /, -, itp.) w stosunku tak�e do innych klas. OVERLOADING OPERATORÓW JEDNOARGUMENTOWYCH ++ I -- . Typowe operatory jednoargumentowe to ++ i --. Jako przykładem posłu�ymy si� problemem zlicznia znaków pobieranych ze strumienia wej�ciowego. Zaczniemy od redefinicji postinkrementacji licznika. Musimy zastosowa� funkcj� operatorow . Funkcja, chc c operowa� na obiektach musi w stosunku do tych obiektów posiada� status friend, lub by� metod . Prototyp funkcji operatorowej potrzebnej do wykonania overloadingu operatora jednoargumentowego ++ wygl da w postaci ogólnej tak: typ_zwracany nazwa_klasy::operator++(lista argumentów); Funkcje operatorowe zwracaj zwykle warto�� zgodn co do typu z typem obiektów z którymi współpracuj . Je�li identyfikatory b, c i d reprezentuj obiekty, nic nie stoi na przeszkodzie, by stał si� mo�liwy zapis: class Klasa { ... } x, y, z; ... z = x + y; Dodajemy dwa obiekty x i y tego samego typu (tej samej klasy), a wynik przypisujemy obiektowi z, który tak�e jest obiektem tego samego typu. Je�li mo�naby jeszcze zastosowa� operator przypisania tak: z = q = x + y; operator przypisania = zwracałby nam w efekcie obiekt tego samego typu. Funkcje operatorowe musz przestrzega� tych samych zasad, które obowi zuj wyra�enia: typ argumentów x, y, z, q, ... powinien by� zgodny, rezultat operacji (x + y) powinien by� obiektem tego samego typu, co obiekty x, y, z, q. Dokonuj c overloadingu operatorów powinni�my precyzyjnie okre�li� typ warto�ci zwracanej w wyniku działania operatora. Stosowan poprzednio do inkrementacji liczników metod� Skok_licznika() zast pimy w definicji klasy funkcj operatorow : class Licznik { public: char moja_litera; int ile;

Page 105: C++ - lekcje

105

Licznik(char); Licznik operator++(); }; Powinni�my teraz zdefiniowa� funkcj� operatorow . Poniewa� pole obiektu, które zamierzamy inkrementowa� nazywa si�: obiekt.ile // Licznik::ile; funkcja powinna zadziała� tak: Licznik Licznik::operator++(void) { this->ile++; return (*this); } Przetłumaczmy t� notacj� na "ludzki j�zyk". Funkcja operatorowa: * nie pobiera �adnych jawnych argumentów (void); * jest metod , zatem w momencie wywołania otrzymuje w niejawny sposób wska�nik *this do "własnego" obiektu; * posługuj c si� wsak�nikiem this inkrementuje zawarto�� pola int ile własnego obiektu; * zwraca obiekt (zmodyfikowany) klasy Licznik (tj. dokładniej - zwraca wska�nik this do własnego-zmodyfikowanego obiektu. Poniewa� funkcja operatorowa jest metod zadeklarowan wewn trz klasy, bez problemu uzyska dost�p do wewn�trznych pól obiektów tej klasy i wykona inkrementacj� licznika. Mo�emy zatem zastosowa� wyra�enie typu: Licznik licznik_a; licznik_a++; Funkcja jest metod wraz ze wszystkimi wła�ciwymi metodom przywilejami. Zapis mo�emy zatem upro�ci� do postaci: Licznik Licznik::operator++(void) { ile++; return (*this); } a tak skrócone ciało funkcji umie�ci� w definicji klasy obok definicji konstruktora: class Licznik { public: char moja_litera; int ile; Licznik(char z) { ile = 0; moja_litera = z; } Licznik operator++() { ile++; return (this); } }; Aby nie zaciemnia� obrazu, przy pomocy licznika b�dziemy tym razem zlicza� wszystkie znaki za wyj tkiem kropki. Poniewa� licznik nie b�dzie miał swojej ulubionej litery, mo�emy zastosowa� pusty konstruktor. [P121.CPP] /* --------------------- POST - inkrementacja ----------- */ # include "iostream.h" class Licznik { public: int ile; Licznik() { ile = 0;} Licznik operator++() { ile++; return (*this); } } obiekt; void main() { cout << "\n Wpisz kilka znakow: "; char znak; for(;;) { cin >> znak; if(znak == '.') break; obiekt++; }

cout << "\n Wpisales " << obiekt.ile << " znakow"; } Podobnie jak wcze�niej, preinkrementacja i postinkrementacja wymagaj odr�bnego overloadingu. Dokładnie rzecz ujmuj c, zgodnie ze standardem ANSI C, odr�bny overloading nie jest ju� niezb�dny, wykorzystamy to jednak jako pretekst do wykonania go dwiema ró�nymi technikami. Poniewa� logika jest bardzo podobna, pomijamy tu (chyba ju� zb�dny) komemtarz. Dla ułatwienia Ci porównania, zestawili�my obok siebie ró�ne funkcje operatorowe napisane ró�nymi technikami (notacja wska�nikowa i referencyjna). [P122.CPP] /* -------- PRE - inkrementacja ------------------------- */ # include "iostream.h" class Licznik { public: int ile; Licznik() { ile = 0;} Licznik operator+(int n = 1) { this->ile += n; return (*this); } Licznik friend operator++(Licznik& x) { x + 1; return (x); } } obiekt; void main() { cout << "\n Wpisz kilka znakow: "; char znak; for(;;) { cin >> znak; if(znak == '.') break; ++obiekt; } cout << "\n Wpisales " << obiekt.ile << " znakow"; cout << "\n I dodamy jeszcze sto! --> "; obiekt + 100; cout << obiekt.ile; } Poni�ej inny przykład tego samego overloadingu odno�nie tej samej klasy Licznik (w troch� inny sposób). [P123.CPP] # include "conio.h" # include "iostream.h" class Licznik { public: char moja_litera; int ile; Licznik() { ile = 0; } //Pusty konstruktor Licznik(char); Licznik operator++(); //Funkcja pre/post-inkrementacji Licznik operator--(); //Funkcja pre/post-dekrementacji }; Licznik::Licznik(char z) { moja_litera = z; ile = 10; } Licznik Licznik::operator++(void) { ile++; return *this; } Licznik Licznik::operator--(void) { ile--; return *this; } void main() { Licznik obiekt1('A'), obiekt2; //obiekt2 - "pusty" cout << "\n Wpisz napis z max. 10 literami [A]: \n "; for(;;) { char litera = getch(); cout << litera; if(obiekt1.ile == 0) break; if(litera == obiekt1.moja_litera) obiekt1--;

Page 106: C++ - lekcje

106

++obiekt2; //Ten zlicza wszystkie znaki //metoda PRE - inkrementacji if(obiekt2.ile > 30) cout << "\n NIE PRZESADZAJ \n"; } cout << "\n Koniec: " << obiekt1.ile; cout << " liter " << obiekt1.moja_litera; cout << "\n Wszystkich znakow bylo: " << obiekt2.ile; } Overloading "siostrzanych" operatorów ++ i -- jest bli�niaczo podobny. OVERLOADING OPERATORA ! Z matematyki jeste�my przyzwyczajenu do zapisu silni n! i wydawałoby si�, �e maj c w C++ do dyspozycji operator ! nie powinni�my mie� z tym zadaniem najmniejszego kłopotu. Operuj c znan Ci klas Liczba i wyposa�aj c program w funkcj� operatorow mo�emy załatwi� ten problem np. tak: [P124.CPP] # include <iostream.h> class Liczba { public: long wartosc; Liczba(int x) { wartosc = (long) x; } friend void operator!(Liczba&); }; void operator!(Liczba& obiekt) { long wynik = 1; for(int i = 1; i <= obiekt.wartosc; i++) { wynik *= i; } cout << '\n' << wynik; } int x; main() { for(int k = 0; k < 5; k++) { cout << "\n Podaj liczbe --> "; cin >> x; Liczba a(x); cout << "\n Silnia wynosi: "; !a; } return 0; } Program działa, wyniki kolejnych kilku silni s poprawne. Gdy jednak spróbujemy zastosowa� operator ! zgodnie z tradycyjnym matematycznym zapisem: a!; oka�e si�, �e C++ zacznie mie� w tpliwo�ci. Komunikaty o bł�dzie spowoduj w tpliwo�ci kompilatora, czy chodzi nam o operator "!=", w którym zapomni�li�my znaku "=". Je�li w funkcji operatorowej spróbujemy zmieni� operator ! na != , a zapis w programie: z !a; na a!=a; C++ zarz da dwuargumentowej funkcji operatorowej (bo taki operator jest tradycyjnie dwuargumentowy). Mo�emy oczywi�cie próbowa� oszuka� C++ przy pomocy argumentu pozornego. Je�li podamy w funkcji operatorowej dwa argumenty void operator!=(Liczba& obiekt1, Liczba& obiekt2) { long wynik = 1; for(int i = 1; i <= obiekt.wartosc; i++) { wynik *= i; } cout << '\n' << wynik; }

program uda si� skompilowa� i kod wynikowy b�dzie działał poprawnie, C++ zaprotestuje jedynie przy pomocy ostrze�enia Warning: obiekt2 is never used... Chc c unikn � ostrze�e� nale�y u�y� argument pozorny w dowolny sposób. Zwracamy na to uwag�, poniewa� C++ jest pedantem i: [!!!] DZIAŁANIE OPERATORÓW MOE BY� DALECE DOWOLNE, ALE LICZBA ARGUMENTÓW MUSI POZOSTA� ZGODNA Z "TRADYCJAMI" C++. Stosowanie podczas overloadingu operatorów argumentów pozornych jest technik cz�sto stosowan przez programistów. Aby wykaza�, �e korzystanie z gotowych "fabrycznych" zasobów ułatwia �ycie programi�cie czasami w zaskakuj co skuteczny sposób, przytocz� przykładowy program, który posługuj c si� "fabryczn " klas ofstream (obiekty - strumie� danych do pliku wyj�ciowego - Output File STREAM): * zakłada w bie� cym katalogu plik dyskowy DANE.TST; * otwiera plik dla zapisu; * zapisuje do pliku tekst "to jest zawartosc pliku"; * zamyka plik; [P125.CPP] # include "fstream.h" void main() { ofstream plik("dane.tst"); plik << "To jest zawartosc pliku"; } I ju�. O wszystkie szczegóły techniczne tych (wcale przecie� nie najprostszych) operacji zadbał producent w bibliotekach klas Wej�cia/Wyj�cia. Je�li zechcemy do pliku dopisa� co� jeszcze, wystarczy doda�: [P126.CPP] # include "fstream.h" void main() { ofstream plik("dane.tst"); plik << "To jest zawartosc pliku" << " i jeszcze cosik."; } Urzekaj ca prostota, nieprawda�? I to wszystko załatwia poddany overloadingowi operator << . Niedowiarek mógłby w tym momencie zapyta� "a je�li plik ju� istnieje, to chyba nie jest takie proste?". Rzeczywi�cie, nale�ałoby tu rozbudowa� program w C++ do postaci: # include "fstream.h" void main() { ofstream plik("dane.tst", ios::app); plik << " Dopiszemy do pliku jeszcze i to..."; } Korzystamy tu dodatkowo z globalnej zmiennej ios::app (ang. append - doł cz) okre�laj cej inny ni� typowy tryb dost�pu do pliku dyskowego i w dalszym ci gu z operatora << . Tworzenie obiektu - pliku dyskowego jest takie proste, dzi�ki istnieniu konstruktora, który jest tu automatycznie wywoływany po deklaracji: ofstream plik( ... ); [Z] ________________________________________________________________ 1. Wykonaj samodzielnie overloading dowolnego operatora. ________________________________________________________________ LEKCJA 35: O ZASTOSOWANIU DZIEDZICZENIA. ________________________________________________________________ Z tej lekcji dowiesz si�, do czego w praktyce programowania

Page 107: C++ - lekcje

107

szczególnie przydaje si� dziedziczenie. ________________________________________________________________ Dzi�ki dziedziczeniu programista mo�e w pełni wykorzysta� gotowe biblioteki klas, tworz c własne klasy i obiekty, jako klasy pochodne wazgl�dem "fabrycznych" klas bazowych. Je�li bazowy zestw danych i funkcji nie jest adekwatny do potrzeb, mo�na np. przesłoni�, rozbudowa�, b d� przebudowa� bazow metod� dzi�ki elastyczno�ci C++. Zdecydowana wi�kszo�� standardowych klas bazowych wyposa�ana jest w konstruktory. Tworz c klas� pochodn powinni�my pami�ta� o istnieniu konstruktorów i rozumie� sposoby przekazywania argumentów obowi zuj ce konstruktory w przypadku bardziej zło�onej struktury klas bazowych-pochodnych. PRZEKAZANIE PARAMETRÓW DO WIELU KONSTRUKTORÓW. Klasy bazowe mog by� wyposa�one w kilka wersji konstruktora. Dopóki nie przeka�emy konstruktorowi klasy bazowej �adnych argumentów - zostanie wywołany (domy�lny) pusty konstruktor i klasa bazowa b�dzie utworzona z parametrami domy�lnymi. Nie zawsze jest to dla nas najwygodniejsza sytuacja. Je�eli wszystkie, b d� cho�by niektóre z parametrów, które przekazujemy konstruktorowi obiektu klasy pochodnej powinny zosta� przekazane tak�e konstruktorowi (konstruktorom) klas bazowych, powinni�my wytłumaczy� to C++. Z tego te� powodu, je�li konstruktor jakiej� klasy ma jeden, b d� wi�cej parametrów, to wszystkie klasy pochodne wzgl�dem tej klasy bazowej musz posiada� konstruktory. Dla przykładu dodajmy konstruktor do naszej klasy pochodnej Cpochodna: class CBazowa1 { public: CBazowa1(...); //Konstruktor }; class CBazowa2 { public: CBazowa2(...); //Konstruktor }; class Cpochodna : public CBazowa1, CBazowa2 //Lista klas { public: Cpochodna(...); //Konstruktor }; main() { Cpochodna Obiekt(...); //Wywolanie konstruktora ... W momencie wywołania kostruktora obiektu klasy pochodnej Cpochodna() przekazujemy kostruktorowi argumenty. Mo�emy (je�li chcemy, nie koniecznie) przekaza� te argumenty konstruktorom "wcze�niejszym" - konstruktorom klas bazowych. Ta mo�liwo�� okazuje si� bardzo przydatna (niezb�dna) w �rodowisku obiektowym - np. OWL i TVL. Oto prosty przykład definiowania konstruktora w przypadku dziedziczenia. Rola konstruktorów b�dzie polega� na trywialnej operacji przekazania pojedynczego znaku. class CBazowa1 { public: CBazowa1(char znak) { cout << znak; } }; class CBazowa2 { public: CBazowa2(char znak) { cout << znak; } }; class Cpochodna : public CBazowa1, CBazowa2 { public:

Cpochodna(char c1, char c2, char c3); }; Cpochodna::Cpochodna(char c1,char c2,char c3) : CBazowa1(c2), CBazowa2(c3) { cout << c1; } Konstruktor klasy pochodnej pobiera trzy argumenty i dwa z nich: c2 --> przekazuje do konstruktora klasy CBazowa1 c3 --> przekazuje do konstruktora klasy CBazowa2 Sposób zapisu w C++ wygl da tak: Cpochodna::Cpochodna(char c1,char c2,char c3) : CBazowa1(c2), CBazowa2(c3) Mo�emy zatem przekaza� parametry "w tył" do konstruktorów klas bazowych w taki sposób: kl_pochodna::kl_pochodna(lista):baza1(lista), baza2(lista), ... gdzie: lista - oznacza list� parametrów odpowiedniego konstruktora. W takiej sytuacji na li�cie argumentów konstruktorów klas bazowych mog znajdowa� si� tak�e wyra�enia, przy zało�eniu, �e elementy tych wyra�e� s widoczne i dost�pne (np. globalne stałe, globalne zmienne, dynamicznie inicjowane zmienne globalne itp.). Konstruktory b�d wykonywane w kolejno�ci: CBazowa1 --> CBazowa2 --> Cpochodna Dzi�ki tym mechanizmom mo�emy łatwo przekazywa� argumenty "wstecz" od konstruktorów klas pochodnych do konstruktorów klas bazowych. FUNKCJE WIRTUALNE. Działanie funkcji wirtualnych przypomina rozbudow� funkcji dzi�ki mechanizmowi overloadingu. Je�li, zdefiniowali�my w klasie bazowej funkcj� wirtualn , to w klasie pochodnej mo�emy definicj� tej funkcji zast pi� now definicj . Przekonajmy si� o tym na przykładzie. Zacznijmy od zadeklarowania funkcji wirtualnej (przy pomocy słowa kluczowego virtual) w klasie bazowej. Zadeklarujemy jako funkcj� wirtualn funkcj� oddychaj() w klasie CZwierzak: class CZwierzak { public: void Jedz(); virtual void Oddychaj(); }; Wyobra�my sobie, �e chcemy zdefiniowa� klas� pochodn CRybka Rybki nie oddychaj w taki sam sposób, jak inne obiekty klasy CZwierzak. Funkcj� Oddychaj() trzeba zatem b�dzie napisa� w dwu ró�nych wariantach. Obiekt Ciapek mo�e t� funkcj� odziedziczy� bez zmian i sapa� spokojnie, z Sardynk gorzej: class CZwierzak { public: void Jedz(); virtual void Oddychaj() { cout << "Sapie..."; } }; class CPiesek : public CZwierzak { char imie[30]; } Ciapek; class CRybka char imie[30]; public: void Oddychaj() { cout << "Nie moge sapac..."; } } Sardynka;

Page 108: C++ - lekcje

108

Zwró� uwag�, �e w klasie pochodnej w deklaracji funkcji słowo kluczowe virtual ju� nie wyst�puje. W klasie pochodnej funkcja CRybka::Oddychaj() robi wi�cej ni� w przypadku "zwykłego" overloadingu funkcji. Funkcja CZwierzak::Oddychaj() zostaje "przesłoni�ta" (ang. overwrite), mimo, �e ilo�� i typ argumentów. pozostaje bez zmian. Taki proces - bardziej drastyczny, ni� overloading nazywany jest przesłanianiem lub nadpisywaniem funkcji (ang. function overriding). W programie przykładowym Ciapek b�dzie oddychał a Sardynka nie. [P127.CPP] # include <iostream.h> class CZwierzak { public: void Jedz(); virtual void Oddychaj() {cout << "\nSapie...";} }; class CPiesek : public CZwierzak { char imie[30]; } Ciapek; class CRybka char imie[30]; public: void Oddychaj() {cout << "\nSardynka: A ja nie oddycham.";} } Sardynka; void main() { Ciapek.Oddychaj(); Sardynka.Oddychaj(); } Funkcja CZwierzak::Oddychaj() została w obiekcie Sardynka przesłoni�ta przez funkcj� CRybka::Oddychaj() - nowsz wersj� funkcji-metody pochodz c z klasy pochodnej. Overloading funkcji zasadzał si� na "typologicznym pedanty�mie" C++ i na dodatkowych informacjach, które C++ doł cza przy kompilacji do funkcji, a które dotycz licznby i typów argumentów danej wersji funkcji. W przypadku funkcji wirtualnych jest inaczej. Aby wykona� przesłanianie kolejnych wersji funkcji wirtualnej w taki sposób, funkcja we wszystkich "pokoleniach" musi mie� taki sam prototyp, tj. pobiera� tak sam liczb� parametrów tych samych typów oraz zwraca� warto�� tego samego typu. Je�li tak si� nie stanie, C++ potraktuje ró�ne prototypy tej samej funkcji w kolejnych pokoleniach zgodnie z zasadami overloadingu funkcji. Zwró�my tu uwag�, �e w przypadku funkcji wirtualnych o wyborze wersji funkcji decyduje to, wobec którego obiektu (której klasy) funkcja została wywołana. Je�li wywołamy funkcj� dla obiektu Ciapek, C++ wybierze wersj� CZwierzak::Oddychaj(), natomiast wobec obiektu Sardynka zostanie zastosowana wersja CRybka::Oddychaj(). W C++ wska�nik do klasy bazowej mo�e tak�e wskazywa� na klasy pochodne, wi�c zastosowanie funkcji wirtualnych mo�e da� pewne ciekawe efekty "uboczne". Je�li zadeklarujemy wska�nik *p do obiektów klasy bazowej CZwierzak *p; a nast�pnie zastosujemy ten sam wska�nik do wskazania na obiekt klasy pochodnej: p = &Ciapek; p->Oddychaj(); ... p = &Sardynka; p->Oddychaj(); zarz damy w taki sposób od C++ rozpoznania wła�ciwej wersji wirtualnej metody Oddychaj() i jej wywołania we wła�ciwym momencie. C++ mo�e rozpozna�, któr wersj� funkcji nale�ałoby zastosowa� tylko na podstawie typu obiektu, wobec którego funkcja została wywołana. I tu pojawia si� pewien problem. Kompilator wykonuj c kompilcj� programu nie wie, co b�dzie wskazywał pointer. Ustawienie pointera na konkretny adres nast pi dopiero w czasie wykonania programu (run-time). Kompilator "wie" zatem tylko tyle:

p->Oddychaj()(); //która wersja Oddychaj() ??? Aby mie� pewno��, co w tym momencie b�dzie wskazywał pointer, kompilator musiałby wiedzie� w jaki sposób b�dzie przebiega� wykonanie programu. Takie wyra�enie mo�e zosta� wykonane "w ruchu programu" dwojako: raz, gdy pointer b�dzie wskazywał Ciapka (inaczej), a drugi raz - Sardynk� (inaczej): CZwierzak *p; ... for(p = &Ciapek, int i = 0; i < 2; i++) { p->Oddychaj(); p = &Sardynka; } lub inaczej: if(p == &Ciapek) CZwierzak::Oddychaj(); else CRybka::Oddychaj(); Taki efekt nazywa si� polimorfizmem uruchomieniowym (ang. run-time polymorphism). Overloading funkcji i operatorów daje efekt tzw. polimorfizmu kompilacji (ang. compile-time), to funkcje wirtualne daj efekt polimorfizmu uruchomieniowego (run-time). Poniewa� wszystkie wersje funkcji wirtualnej maj taki sam prototyp, nie ma innej metody stwierdzenia, któr wersj� funkcji nale�y zastosowa�. Wybór wła�ciwej wersji funkcji mo�e by� dokonany tylko na podstawie typu obiektu, do którego nale�y wersja funkcji-metody. Ró�nica pomi�dzy polimorfizmem przejawiaj cym si� na etapie kompilacji i poliformizmem przejawiaj cym si� na etapie uruchomienia programu jest nazywana równie� wszesnym albo pó�nym polimorfizmem (ang. early/late binding). W przypadku wyst pienia wczesnego polimorfizmu (compile-time, early binding) C++ wybiera wersj� funkcji (poddanej overloadingowi) do zastosowania ju� tworz c plik .OBJ. W przypadku pó�nego polimorfizmu (run-time, late binding) C++ wybiera wersj� funkcji (poddanej przesłanianiu - overriding) do zastosowania po sprawdzeniu bie� cego kontekstu i zgodnie z bie� cym wskazaniem pointera. Przyjrzyjmy si� dokładniej zastosowaniu wska�ników do obiektów w przykładowym programie. Utworzymy hierarchi� zło�on z klasy bazowej i pochodnej w taki sposób, by klasa pochodna zawierała jaki� unikalny element - np. nie wyst�puj c w klasie bazowej funkcj�. class CZwierzak { public: void Jedz(); virtual void Oddychaj() {cout << "\nSapie...";} }; class CPiesek : public CZwierzak { char imie[20]; void Szczekaj() { cout << "Szczekam !!!"; } } Ciapek; Je�li teraz zadeklarujemy wska�nik do obiektów klasy bazowej: CZwierzak *p; to przy pomocy tego wska�nika mo�emy odwoła� si� tak�e do obiektów klasy pochodnej oraz do elementów obiektu klasy pochodnej - np. do funkcji p->Oddychaj(). Ale pojawia si� tu pewien problem. Je�li zechceliby�my wskaza� przy pomocy pointera taki element klasy pochodnej, który nie został odziedziczony i którego nie ma w klasie bazowej? Rozwi zanie jest proste - wystarczy zarz da� od C++, by chwilowo zmienił typ wska�nika z obiektów klasy bazowej na obiekty klasy pochodnej. W przypadku funkcji Szczekaj() w naszym programie wygl dałoby to tak:

Page 109: C++ - lekcje

109

CZwierzak *p; ... p->Oddychaj(); p->Szczekaj(); //�LE ! (CPiesek*)p->Szczekaj(); //Poprawnie ... Dzi�ki funkcjom wirtualnym tworz c klasy bazowe pozwalamy pó�niejszym u�ytkownikom na rozbudow� funkcji-metod w najwła�ciwszy ich zdaniem sposób. Dzi�ki tej "nieokre�lono�ci" dziedzicz c mo�emy przejmowa� z klasy bazowej tylko to, co nam odpowiada. Funkcje w C++ mog by� jeszcze bardziej "nieokre�lone" i rozbudowywalne. Nazywaj si� wtedy funkcjami w pełni wirtualnymi. LEKCJA 36: FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, co mawia �ona programisty, gdy nie chce by� obiektem klasy abstrakcyjnej. ________________________________________________________________ FUNKCJE W PEŁNI WIRTUALNE (PURE VIRTUAL). W skrajnych przypadkach wolno nam umie�ci� funkcj� wirtualn w klasie bazowej nie definiuj c jej wcale. W klasie bazowej umieszczamy wtedy tylko deklaracj�-prototyp funkcji. W nast�pnych pokoleniach klas pochodnych mamy wtedy pełn swobod� i mo�emy zdefiniowa� funkcj� wirtualn w dowolny sposób - adekwatny dla potrzeb danej klasy pochodnej. Mo�emy np. do klasy bazowej (ang. generic class) doda� prototyp funkcji wirtualnej funkcja_eksperymentalna() nie definiuj c jej w (ani wobec) klasie bazowej. Sens umieszczenia takiej funkcji w klasie bazowej polege na uzyskaniu pewno�ci, i� wszystkie klasy pochodne odziedzicz funkcj� funkcja_eksperymentalna(), ale ka�da z klas pochodnych wyposa�y t� funkcj� we własn definicj�. Takie post�powanie mo�e okaza� si� szczególnie uzasadnione przy tworzeniu biblioteki klas (class library) przeznaczonej dla innych u�ytkowników. C++ w wersji instalacyjnej posiada ju� kilka gotowych bibliotek klas. Funkcje wirtuale, które nie zostaj zdefiniowane - nie posiadaj zatem ciała funkcji - nazywane s funkcjami w pełni wirtualnymi (ang. pure virtual function). O KLASACH ABSTRAKCYJNYCH. Je�li zadeklarujemy funkcj� CZwierzak::Oddychaj() jako funkcj� w pełni wirtualn , oprócz słowa kluczowego virtual, trzeba t� informacj� w jaki� sposób przekaza� kompilatorowi C++. Aby C++ wiedział, �e nasz intencj jest funkcja w pełni wirtalna, nie mo�emy zadeklarowa� jej tak: class CZwierzak { ... public: virtual void Oddychaj(); ... }; a nast�pnie pomin � definicj� (ciało) funkcji. Takie post�powanie C++ uznałby za bł d, a funkcj� - za zwykł funkcj� wirtualn , tyle, �e "niedorobion " przez programist�. Nasz intencj� musimy zaznaczy� ju� w definicji klasy w taki sposób: class CZwierzak { ... public: virtual void Oddychaj() = 0; ... }; Informacj dla kompilatora, �e chodzi nam o funkcj� w pełni wirtualn , jest dodanie po prototypie funkcji "= 0". Definiuj c klas� pochodn mo�emy rozbudowa� funkcj� wirtualn np.: class CZwierzak { ...

public: virtual void Oddychaj() = 0; ... }; class CPiesek : public CZwierzak { ... public: void Oddychaj() { cout << "Oddycham..."; } ... }; Przykładem takiej funkcji jest funkcja Mów() z przedstawionego poni�ej programu. Zostawiamy j w pełni wirtualn , poniewa� ró�ne obiekty klasy CZLOWIEK i klas pochodnych class CZLOWIEK { public: void Jedz(void); virtual void Mow(void) = 0; //funkcja WIRTUALNA }; class NIEMOWLE : public CZLOWIEK { public: void Mow(void); // Tym razem BEZ slowa virtual }; /* Tu definiujemy metod� wirtualn : -------------------- */ void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; }; mog mówi� na ró�ne sposoby... Obiekt Niemowle, dla przykładu, nie chce mówi� wcale, ale z innymi obiektami mo�e by� inaczej. Wyobra� sobie np. obiekt klasy ona (�ona to przecie� te� człowiek !). class Zona : public CZLOWIEK { public: void Mow(void); } W tym pokoleniu definicja wirtualnej metody Mow() mogłaby wygl da� np. tak: void Zona::Mow(void) { cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!! "; cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY ?!!!"; //... itd., itd., itd... } [P128.CPP] #include "iostream.h" class CZLOWIEK { public: void Jedz(void); virtual void Mow(void) = 0; }; void CZLOWIEK::Jedz(void) { cout << "MNIAM, MNIAM..."; }; class Zona : public CZLOWIEK { public: void Mow(void); //Zona mowi swoje }; //bez wzgledu na argumenty (typ void) void Zona::Mow(void) { cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!!"; cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY ?!!!"; }

Page 110: C++ - lekcje

110

class NIEMOWLE : public CZLOWIEK { public: void Mow(void); }; void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; }; main() { NIEMOWLE Dziecko; Zona Moja_Zona; Dziecko.Jedz(); Dziecko.Mow(); Moja_Zona.Mow() return 0; } Przykładowa klasa CZŁOWIEK jest klas ABSTRAKCYJN. Je�li spróbujesz doda� do powy�szego programu np.: CZLOWIEK Facet; Facet.Jedz(); uzyskasz komunikat o bł�dzie: Cannot create a variable for abstract class "CZLOWIEK" (Nie mog� utworzy� zmiennych dla klasy abstrakcyjnej "CZLOWIEK" [???] KLASY ABSTRAKCYJNE. ________________________________________________________________ * Po klasach abstrakcyjnych MONA dziedziczy�! * Obiektów klas abstrakcyjnych NIE MONA stosowa� bezpo�rednio! ________________________________________________________________ Poniewa� wyja�nili�my, dlaczego klasy s nowymi typami danych, wi�c logika (i sens) innej rozpowszechnionej nazwy klas abstrakcyjnych - ADT - Abstract Data Type (Abstrakcyjne Typy Danych) jest chyba zrozumiała i oczywista. ZAGNIEDANIE KLAS I OBIEKTÓW. Mo�e si� np. zdarzy�, �e klasa stanie si� wewn�trznym elementem (ang. member) innej klasy i odpowiednio - obiekt - elementem innego obiektu. Nazywa si� to fachowo "zagnie�d�aniem" (ang. nesting). Je�li, dla przykładu klasa CB b�dzie zawiera� obiekt klasy CA: class CA { int liczba; public: CA() { liczba = 0; } //Konstruktor domyslny CA(int x) { liczba = x; } void operator=(int n) { liczba = n } }; class CB { CA obiekt; public: CB() { obiekt = 1; } }; Nasze klasy wyposa�yli�my w konstruktory i od razu poddali�my overloadingowi operator przypisania = . Aby prze�ledzi� kolejno�� wywoływania funkcji i sposób przekazywania parametrów pomi�dzy tak powi zanymi obiektami rozbudujemy ka�d funkcj� o zgłoszenie na ekranie. class CA { int liczba; public: CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; } CA(int x) { liczba = x; cout << "->CA(int) "; } void operator=(int n) { liczba = n; cout << "->operator "; } }; class CB {

CA obiekt; public: CB() { obiekt = 1; cout << "->Konstruktor CB() "; } }; Mo�emy teraz sprawdzi�, co stanie si� w programie po zadeklarowaniu obiektu klasy CB: [P129.CPP] # include "iostream.h" class CA { int liczba; public: CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; } CA(int x) { liczba = x; cout << "->CA(int) "; } void operator=(int n) { liczba = n; cout << "->operator "; } }; class CB { CA obiekt; public: CB() { obiekt = 1; cout << "->Konstruktor CB() "; } }; main() { CB Obiekt; return 0; } Po uruchomieniu programu mo�esz przekona� si�, �e kolejno�� działa� b�dzie nast�puj ca: C:\>program -> CA(), CA_O::liczba = 0 ->operator ->Konstruktor CB() Skoro oprócz zainicjowania obiektu klasy pochodnej nie robimy w programie dokładnie nic, nie dziwmy si� ostrze�eniu Warning: Obiekt is never used... Jest to sytuacja troch� podobna do komunikacji pomi�dzy konstruktorami klas bazowych i pochodnych. Je�li zaprojektujemy prost struktur� klas: class CBazowa { private: int liczba; public: CBazowa() { liczba = 0} CBazowa(int n) { liczba = n; } }; class CPochodna : public CBazowa { public: CPochodna() { liczba = 0; } CPochodna(int x) { liczba = x; } }; problem przekazywania parametrów mi�dzy konstruktorami klas mo�emy w C++ rozstrzygn � i tak: class CPochodna : public CBazowa { public: CPochodna() : CBazowa(0) { liczba = 0; } CPochodna(int x) { liczba = x; } }; B�dzie to w praktyce oznacza� wywołanie konstruktora klasy bazowej z przekazanym mu argumentem 0. Podobnie mo�emy post pi� w stosunku do klas zagnie�d�onych: [P130.CPP] #include "iostream.h"

Page 111: C++ - lekcje

111

class CA { int liczba; public: CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; } CA(int x) { liczba = x; cout << "->CA(int) "; } void operator=(int n) { liczba = n; cout << "->operator "; } }; class CB { CA obiekt; public: CB() : CA(1) {} }; main() { CB Obiekt; return 0; } Eksperymentuj c z dwoma powy�szymi programami mo�esz przekona� si�, jak przebiega przekazywanie parametrów pomi�dzy konstruktorami i obiektami klas bazowych i pochodnych. JESZCZE RAZ O WSKA�NIKU *this. Szczególnie wa�nym wska�nikiem przy tworzeniu klas pochodnych i funkcji operatorowych mo�e okaza� si� pointer *this. Oto przykład listy. [P131.CPP] # include "string.h" # include "iostream.h" class CLista { private: char *poz_listy; CLista *poprzednia; public: CLista(char*); CLista* Poprzednia() { return (poprzednia); }; void Pokazuj() { cout << '\n' << poz_listy; } void Dodaj(CLista&); ~CLista() { delete poz_listy; } }; CLista::CLista(char *s) { poz_listy = new char[strlen(s)+1]; strcpy(poz_listy, s); poprzednia = NULL; } void CLista::Dodaj(CLista& obiekt) { obiekt.poprzednia = this; } main() { CLista *ostatni = NULL; cout << '\n' << "Wpisanie kropki [.]+[Enter] = Quit \n"; for(;;) { cout << "\n Wpisz nazwe (bez spacji): "; char TAB[70]; cin >> TAB; if (strncmp(TAB, ".", 1) == 0) break; CLista *lista = new CLista(TAB); if (ostatni != NULL) ostatni->Dodaj(*lista); ostatni = lista; } for(; ostatni != NULL;) { ostatni->Pokazuj(); CLista *temp = ostatni; ostatni = ostatni->Poprzednia();

delete (temp); } return 0; } Z reguły to kompilator nadaje warto�� wska�nikowi this i to on automatycznie dba o przyporz dkowanie pami�ci obiektom. Pointer this jest zwykle inicjowany w trakcie działania konstruktora obiektu. LEKCJA 37: KA�DY DYSK JEST ZA MAŁY, A KA�DY PROCESOR ZBYT WOLNY... ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak komputer dysponuje swoimi zasobami w �rodowisku tekstowym (DOS). ________________________________________________________________ Truizmy u�yte w tytule maj znaczy�, �e "zasoby najlepszego nawet komputera s ograniczone" i zwykle okazuj si� wystarczaj ce tylko do pewnego momentu. Najbardziej newralgiczne zasoby to: * czas mikroprocesora i * miejsce w pami�ci operacyjnej. Tworzone przez nas programy powinny wystrzega� si� zatem najci��szych grzechów: * nie pozwala� mikroprocesorowi na słodkie nieróbstwo; Rzadko uzmysławiamy sobie, �e oczekiwanie na naci�ni�cie klawisza przez u�ytkownika (czasem po przeczytaniu napisu na ekranie) trwa sekundy (1, 2, .... czasem 20), a ka�da sekunda lenistwa PC to stracone miliony cykli mikroprocesora. * oszcz�dnie korzysta� z pami�ci dyskowej, a szczególnie oszcz�dnie z pami�ci operacyjnej RAM. MODELE PAMI�CI IBM PC. Jak zapewne wiesz, Twój PC mo�e mie�: * pami�� ROM (tylko do odczytu), * konwencjonaln pami�� RAM (640 KB), * pami�� rozszerzon EMS i XMS, * pami�� karty sterownika graficznego ekranu (np. SVGA-RAM), * pami�� Cache dla buforowania operacji dyskowych. Najcz��ciej stosowane modele pami�ci to: * Small - mały, * Medium - �redni, * Compact - niewielki (tu mam w tpliwo��, mo�e "taki sobie" ?), * Large - du�y, * Huge - jeszcze wi�kszy, odległy. Dodatkowo mo�e wyst pi� * Tiny - najmniejszy. Taki podział został spowodowany segmentacj pami�ci komputera przez procesory Intel 8086 i podziałem pami�ci na bloki o wielko�ci 64 KB. Model Small (Tiny, je�li jest) jest najszybszy, ale najmniej pojemny. Model Huge - odwrotnie - najpojemniejszy, za to najwolniejszy. Model Tiny powoduje ustawienia wszystkich rejestrów segmentowych mikroprocesora na t� sam warto�� (pocz tek tej samej stronicy pami�ci) i umieszczenie wszystkich zasobów programu wewn trz wspólnego obszaru pami�ci o wielko�ci nie przekraczaj cej 64 KB. Wszystkie skoki s wtedy "krótkie", a wszystkie pointery (adresy) 16-bitowe. Kompilacja z zastosowaniem modelu Tiny pozwala uzyska� program wykonywalny w wersji *.COM (a nie *.EXE). Ale niestety nie wszystkie programy mieszcz si� w 64 KB. W modelu Small segment kodu jest jeden (kod max. 64 K) i segment danych te� tylko jeden (dane max. 64 K), ale s to ju� dwa ró�ne segmenty. Zestawienia

Page 112: C++ - lekcje

112

najwa�niejszych parametrów poszczególnych modeli pami�ci przedstawia tabelka poni�ej: Modele pami�ci komputera IBM PC. ________________________________________________________________ Model Segment kodu Segment danych *dp *cp ________________________________________________________________ Tiny 1 1 (CS = DS) 16 bit 16 bit Small 1 1 16 bit 16 bit Medium wiele 1 16 bit 32 bit Compact 1 wiele 32 bit 16 bit Large wiele wiele 32 bit 32 bit Huge wiele wiele 32 bit 32 bit ________________________________________________________________ *dp - data pointer - wska�nik do danych (near/far) *cp - code pointer - wska�nik do kodu. Large - kod + dane = max. 1 MB. Huge - kod = max. 1 MB, wiele segmentów danych po 64 K ka�dy. Wynikaj ce z takich modeli pami�ci kwalifikatory near, far, huge dotycz ce pointerów w C++ nie s akceptowane przez standard ANSI C (poniewa� odnosz si� tylko do IBM PC i nie maj charakteru uniwersalnego). Trzeba tu zaznaczy�, �e typ wska�nika jest przez kompilator przyjmowany domy�lnie (automatycznie) zgodnie z wybranym do kompilacji modelem pami�ci. Je�li poruszamy si� wewn trz niewielkiego obszaru pami�ci, mo�esz "forsowa�" bli�szy typ pointera, przyspieszaj c tym samym działanie programów: huge *p; ... near *ptr; //Bliski pointer ... near int Funkcja(...) //Bliska funkcja { ... } #define ILE (1024*640) near unsigned int Funkcja(void) { huge char *ptr; // tu długi pointer jest niezb�dny long suma = 0; for (p = 0; p < ILE; p++) suma += *p; return (suma); } Zarówno zadeklarowanie funkcji jako bliskiej (near), jak i jako statycznej (static) powoduje wygenerowanie uproszczonej sekwencji wywołania funkcji przez kompilator. Daje to w efekcie mniejszy i szybszy kod wynikowy. IDENTYFIKACJA KLAWISZY. Znane Ci z pliku <stdio.h> i <conio.h> "klasyczne" funkcje obsługi konsoli maj pewne zalety. Korzystanie z klasycznych, nieobiektowych mechanizmów powoduje z reguły wygenerowanie znacznie krótszego kodu wynikowego. Funkcje scanf() i gets() wymagaj wci�ni�cia klawisza [Enter]. Dla szybkiego dialogu z komputerem znacznie bardziej nadaj si� szybsze getch() i kbhit(). Poniewa� klawiatura zawiera tak�e klawisze specjalne (F1 ... F10, [Shift], [Del], itp.), pełn informacj� o stanie klawiatury mo�na uzyska� za po�rednictwem funkcji bioskey(), korzystaj cej z przerywania BIOS Nr 16. Oto krótki przykład zastosowania funkcji bioskey(): #include "bios.h" #include "ctype.h" #include "stdio.h" #include "conio.h" # define CTRL 0x04 # define ALT 0x08 # define RIGHT 0x01 # define LEFT 0x02 int klawisz, modyfikatory;

void main() { clrscr(); printf("Funkcja zwraca : %d", bioskey(1)); printf("\n Nacisnij klawisz ! \n"); while (!bioskey(1)); printf("Funkcja zwrocila: %c", bioskey(1)); printf("\nKod: %d", (char)bioskey(1)); ... A to jeszcze inny sposób korzystania z tej bardzo przydatnej funkcji, tym razem z innymi parametrami: /* Funkcja z parametrem (0) zwraca kod klawisza: ------ */ klawisz = bioskey(0); /* Funkcja sprawdza stan klawiszy specjalnych --------- */ modyfikatory = bioskey(2); if (modyfikatory) { printf("\n"); if (modyfikatory & RIGHT) printf("RIGHT"); if (modyfikatory & LEFT) printf("LEFT"); if (modyfikatory & CTRL) printf("CTRL"); if (modyfikatory & ALT) printf("ALT"); printf("\n"); } /* drukujemy pobrany klawisz */ if (isalnum(klawisz & 0xFF)) printf("'%c'\n", klawisz); else printf("%#02x\n", klawisz); } Nale�y tu zwróci� uwag�, �e funkcje kbhit() i bioskey() nie dokonuj czyszczenia bufora klawiatury. Identyfikuj znak (znaki) w buforze, ale pozostawiaj bufor w stanie niezmienionym do dalszej obróbki. Zwró� uwag�, �e funkcja getch() mo�e oczekiwa� na klawisz w niesko�czono��. Sprawdzi� szybciej, czy u�ytkownik nacisn ł ju� cokolwiek mo�esz np. tak: if (kbhit()) ...; if (!kbhit()) ...; while (!bioskey(1)) ... if (bioskey(1)) ...; Inn wielce przydatn "szybk " funkcj jest getch(). Oto praktyczny przykład pobierania i testowania naci�ni�tych klawiszy klawiatury. [P131.CPP] # include "stdio.h" # include "conio.h" char z1, z2; void Odczyt(void) { z2 = '\0'; z1 = getch(); if (z1 == '\0') z2 = getch(); } main() { clrscr(); printf("\nKropka [.] = Quit"); printf("\nRozpoznaje klawisze [F1] ... [F3] \n\n"); for (;;) { while(!kbhit()); Odczyt(); if (z1 == '.') break; if (z1 != '\0') printf("\nZnak: %c", z1); else switch (z2) { case ';' : printf("\n F1"); break; case '<' : printf("\n F2"); break;

Page 113: C++ - lekcje

113

case '=' : printf("\n F3"); break; default : printf("\n Inny klawisz specjalny!"); } } return 0; } Klawisze specjalne powoduj wygenerowanie dwubajtowego kodu (widzianego w powy�szym przykładowym programie jako dwa jednobajtowe znaki z1 i z2). Funkcja getch() pobiera te bajty z bufora klawiatury kolejno jednocze�nie czyszcz c bufor. W przypadku klawiszy specjalnych pierwszy bajt jest zerowy (NULL, '\0', 00h), co jest sprawdzane w programie. A oto tabela kodów poszczególnych klawiszy: Kody klawiszy klawiatury IBM PC. ________________________________________________________________ Klawisze Kody ASCII (dec) ________________________________________________________________ Home G 71 (00:47h) '\0', 'G' End O 79 (00:4Fh) '\0', 'O' PgUp I 73 PgDn Q 81 Ins R 82 Del S 83 F1 ; 59 F2 ... F10 <, ... D 60, ... 68 Shift + F1 T 84 ... Shift + F10 ] 93 Ctrl + F1 ^ 94 ... Ctrl + F10 f 103 Alt + F1...F10 h, ... q 104, ... 113 Alt + 1...9 x, ... (?) 120, ... 128 Alt + 0 � (?) 129 Strzałki kursora: LeftArrow K 75 RightArrow M 77 UpArrow H 72 DownArrow P 80 Ctrl + PgDn v 118 Ctrl + PgUp � (?) 132 Ctrl + Home w 119 Ctrl + End u 117 ________________________________________________________________ Wyprowadzanie znaków na ekran mo�na przeprowadzi� szybciej posługuj c si� przerywaniem DOS INT 29H. Drukowanie na ekranie w trybie tekstowym przebiega wtedy szybciej ni� robi to standardowe funkcje <stdio.h>, <conio.h>, czy <iostream.h>. Poni�ej prosty przykład praktyczny wykorzystania przerywania 29H: [P132.CPP] # include <stdlib.h> # include <conio.h> # pragma inline void SpeedBox(int, int, int, int, char); main() { clrscr(); for (; !kbhit(); ) { int x = rand() % 40; int y = rand() % 12; SpeedBox(x, y, (80 - x), (24 - y), ('�' + x % 50)); } return 0; } void SpeedBox(int x1, int y1, int x2, int y2, char znak) { int k;

for (; y1 < y2; y1++) { gotoxy(x1, y1); for (k = x1; k < x2; k++) { asm MOV AL, znak asm INT 29H } } } [Z] ________________________________________________________________ 1. Opracuj program pozwalaj cy porówna� szybko�� wyprowadzania danych na ekran monitora ró�nymi technikami (cout, puts(), printf(), asm). 2. Porównaj wielko�� plików wynikowych .EXE powstaj cych w ró�nych wariantach z poprzedniego zadania. ________________________________________________________________ LEKCJA 38: O C++, Windows i małym Chi�czyku. czyli: KTO POWIEDZIAŁ, E PROGRAMOWANIE DLA WINDOWS JEST TRUDNE?!!! Jak �wiat �wiatem ludzie przekazuj sobie s dy, opinie, pogl dy... W ci gu naszej nowo�ytnej ery wymy�lono ju� wiele opinii, które kr �yły przez dziesi�cio- i stulecia gwarantuj c jednym komfort psychiczny (- Ja przecie� mam swoje zdanie na ten temat!), innym daj c pozory wiedzy (- Tak, ja co� o tym wiem, słyszałem, �e...). ywotno�� takich �wier�prawd, uproszcze�, uogólnie�, czy wr�cz kompletnie bzdurnych mitów była i jest zadziwiaj ca. Podejm� tu prób� obalenia funkcjonuj cego powszechnie przes du, �e - Programowanie dla Windows jest trudne. (BZDURA!!!) Aby nie zosta� całkowicie pos dzonym o herezj�, przyznaj� na wst�pie dwa bezsporne fakty. Po pierwsze, wielu powszechnie szanowanych ludzi zrobiło wiele, by ju� pierwszymi przykładami (zwykle na co najmniej dwie strony) skutecznie odstraszy� adeptów programowania dla Windows. No bo jak tu nie straci� zapału, gdy program pisz cy tradycyjne "Hello World." w okienku ma 2 - 3 stronice i jeszcze zawiera kilkadziesi t zupełnie nieznanych i niezrozumiałych słów (skrótów? szyfrów?). Po drugie, wszystko jest trudne, gdy brak odpowiednich narz�dzi. Nawet odkr�cenie małej �rubki bywa niezwykle trudne, gdy do dyspozycji mamy tylko młotek... Napisanie aplikacji okienkowej przy pomocy Turbo Pascal 6, Turbo C, Quick C, czy QBASIC rzeczywi�cie BYŁO nadwyraz trudne. I tu wła�nie dochodzimy do sedna sprawy: (!!!) Programowanie dla Windows BYŁO trudne (!!!) UWAGA! Pierwsza typowa aplikacja dla Windows napisana w BORLAND C++ 3/4 mo�e wygl da� np. tak: #include <iostream.h> void main() { cout <<"Pierwsza Aplikacja dla Windows"; } I ju�! Niedowiarek zapyta: - I TAKIE CO� CHODZI POD Windows??? TAK!. W BORLAND C++ 3+ ... 4+ wystarczy dobra� parametry pracy kompilatora i zamiast aplikacji DOS-owskiej otrzymamy program wyposa�ony we własne okienko, paski przewijania w okienku, klawisze, menu, ikonk�, itp., itd.!

Page 114: C++ - lekcje

114

O MAŁYM CHI�CZYKU, czyli - NAJLEPIEJ ZACZ� OD POCZTKU... Istnieje jedyny sprawdzony sposób rozwi zywania zagadnie� takiego typu - tzw. METODA MAŁEGO CHI�CZYKA. WSZYSCY DOSKONALE WIEDZ, �e j�zyk chi�ski jest szalenie trudny. Dlatego te� mimo ogromnego wysiłku prawie NIKOMU nie udaje si� biegle nauczy� chi�skiego - z jednym wyj tkiem - wyj tkiem małego Chi�czyka. Dlaczego? To proste. Mały Chi�czyk po prostu o tym nie wie! I dlatego ju� po kilku latach doskonale swobodnie włada tym bodaj najtrudniejszym j�zykiem �wiata! Je�li zatem komu� udało si� przekona� Ci�, szanowny Czytelniku, �e programowanie dla Windows jest trudne, namawiam Ci� na dokonanie pewnego eksperymentu intelektualnego. Spróbuj zapomnie�, �e masz ju� na ten temat jakie� zdanie i wczuj si� w rol� małego Chi�czyka. Co roku udaje si� to wielu milionom przyszłych ekspertów od wszystkich mo�liwych j�zyków �wiata (C++ jest chyba znacznie łatwiejszy do chi�skiego). BORLAND C++ aby dopomóc programi�cie w jego ci��kiej pracy tworzy (cz�sto automatycznie) wiele plików pomocniczych. Krótkie zestawienie plików pomocniczych zawiera tabela poni�ej. Najwa�niejsze pliki pomocnicze w kompilatorach Borland/Turbo C++. ________________________________________________________________ Rozszerzenie Przeznaczenie Gdzie/Uwagi ________________________________________________________________ .C .CPP Teksty �ródłowe \EXAMPLES \SOURCE (ASCII) (przykłady) (kod �ródł.) .H .HPP .CAS Pliki nagłówkowe \INCLUDE (ASCII) .PRJ .DPR .IDE Projekty \EXAMPLES \SOURCE .TAH .TCH .TDH Help .TFH .HLP .HPJ .RTF .DSK .TC .CFG Konfiguracyjne .DSW .BCW .DEF .RC .RES Zasoby i definicje .RH .ICO .BMP .BGI .CHR .RTF Grafika DOS, fonty .MAK .NMK .GEN Pliki instrukta�owe dla MAKEFILE MAKE.EXE .ASM .INC .ASI Do asemblacji (ASCII) .RSP Instrukta�owy dla TLINK .LIB .DLL Biblioteki .TOK Lista słów zastrze�onych (reserved words) (ASCII) .DRV Sterowniki (drivery) .OVL Nakładki (overlay) .SYM Plik ze skompilowanymi (Pre - compiled) plikami nagłówkowymi. ________________________________________________________________ �wiadome i umiej�tne wykorzystanie tych plików mo�e znacznie ułatwi� i przyspieszy� prac�. Po wprowadzeniu na rynek polskiej wersji Windows 3.1 okienka zacz�ły coraz cz��ciej pojawia� si� w biurach i domach, i stanowi coraz cz��ciej naturalne (wła�nie tak, jak chi�ski dla Chi�czyków) �rodowisko pracy dla polskich u�ytkowników PC. Nie pozostaje nam nic innego, jak po prostu zauwa�y� i uzna� ten fakt. Po uruchomieniu Borland C++ (2 * klik myszk , lub rozkaz Uruchom

z menu Plik) zobaczymy tradycyjny pulpit (desktop) zintegrowanego �rodowiska IDE - podobny do Turbo Pascala, z tradycyjnym układem głównego menu i okien roboczych. Skoro mamy zaj � si� tworzeniem aplikacji dla Windows- zaczynamy od rozwini�cia menu Options i wybieramy z menu rozkaz Application... . Rozwinie si� okienko dialogowe. Przy pomocy klawiszy mo�emy wybra� sposób generowania aplikacji - dla DOS, dla Windows lub tworzenie bibliotek statycznych .LIB, czy te� dynamicznych .DLL. Wybieramy oczywi�cie wariant [Windows EXE]. [!!!]UWAGA! ________________________________________________________________ Struktura podkatalogów i wewn�trzna organizacja pakietów 3.0, 3.1, 4 i 4.5 ZNACZNIE SI� RÓNI. ________________________________________________________________ Skoro ustawili�my ju� poprawnie najwa�niejsze dla nas parametry konfiguracyjne - mo�emy przyst pi� do uruchomienia pierwszej aplikacji dla Windows. PIERWSZA APLIKACJA "specjalnie dla Windows". Tryb post�powania z kompilatorem BORLAND C++ 3.0/3.1 b�dzie w tym przypadku dokładnie taki sam, jak np. z Turbo Pascalem. Wszystkich niezb�dnych zmian w konfiguracji kompilatora ju� dokonali�my. Kompilator "wie" ju�, �e chcemy uzyska� w efekcie aplikacj� dla Windows w postaci programu .EXE. Mo�emy zatem * Wyda� rozkaz File | New Pojawi si� nowe okienko robocze. Zwró� uwag�, �e domy�lne rozszerzenie jest .CPP, co powoduje domy�lne zastosowanie kompilatora C++ (a nie kompilatora C - jak w przypadku plików z rozszerzeniem .C). Mo�esz to oczywi�cie zmieni�, je�li zechcesz, posługuj c si� menu Options | Compiler | C++ options... (Opcje | Kompilator | Kompilator C albo C++). W tym okienku dialogowym masz sekcj�: Use C++ Compiler: Zastosuj Kompilator C++ (zamiast kompilatora C) (.) CPP extention - tylko dla rozszerzenia .CPP ( ) C++ always - zawsze * Wybierz rozkaz Save as... z menu File Pojawi si� okienko dialogowe "Save File As" (zapis pliku pod wybran nazw i w wybranym miejscu). * Do okienka edycyjnego wpisz nazw� pliku i pełn �cie�k� dost�pu - np. A:\WIN1.CPP lub C:\C-BELFER\WIN1.CPP Zmieni si� tytuł roboczego okna z NONAME00 na wybran nazw� Mo�emy wpisa� tekst pierwszego programu: [P133.CPP] #include <iostream.h> void main() { cout << " Pierwsza Aplikacja " << " Dla MS Windows "; } Po wpisaniu tekstu dokonujemy kompilacji. * Wybierz rozkaz Compile to OBJ z menu Compile. * Wybierz rozkaz Link lub Make z menu Compile. W okienku komunikatów (Messages) powinien pojawi� si� w trakcie konsolidacji komunikat ostrzegawczy: *Linker Warning: No module definition file specified: using defaults Oznacza to: Konsolidator ostrzega, �e brak specjalnego stowarzyszonego z plikiem .CPP tzw. pliku definicji sposobu

Page 115: C++ - lekcje

115

wykorzystania zasobów Windows - .DEF. Program linkuj cy zastosuje warto�ci domy�lne. Je�li w IDE wersji kompilatora przeznaczonej dla �rodowiska DOS spróbujesz uruchomi� program WIN1.EXE w tradycyjny sposób - rozkazem Run z menu Run - na ekranie pojawi si� okienko z komunikatem o bł�dzie (Error message box): Can't run a Windows EXE file D:\WIN1.EXE [ OK ] czyli: "Nie mog� uruchomi� pliku EXE dla Windows". Jak ju� napisałem wcze�niej, kompilatory C++ w pakietach 3.0/3.1 maj swoje ulubione specjalno�ci: Borland C++ - jest zorientowany na współprac� z DOS Turbo C++ - jest zorientowany na współprac� z Windows w wersji 3.1: BCW - dla Windows BC - dla DOS nie oznacza to jednak, �e b�d kłopoty z prac naszego programu! Wyjd� z IDE BC/BCW. Z poziomu Mened�era Programów mo�esz uruchomi� swój program rozkazem Plik | Uruchom. Do okienka musisz oczywi�cie wpisa� poprawn �cie�k� do pliku WIN1.EXE (czyli katalog wyj�ciowy kompilatora Borland C++). *** Wybierz z menu głównego Mened�era Programów (pasek w górnej cz��ci ekranu) rozkaz Plik. Rozwinie si� menu Plik. *** Wybierz z menu Plik rozkaz Uruchom. Pojawi si� okienko dialogowe uruchamiania programów. Wpisz pełn �cie�k� dost�pu do programu - np.: D:\KATALOG\WIN1.EXE i "kliknij" myszk na klawiszu [OK] w okienku. Na ekranie pojawi si� okno naszej aplikacji. Okno jest wyposa�one w: - Pasek z tytułem (Caption) - np.: A:\WIN1.EXE ; - Klawisz zamykania okna i rozwini�cia standardowego menu (tzw. menu systemowego Windows) - [-] ; - Paski przewijania poziomego i pionowego; - Klawisze MINIMIZE i MAXIMIZE (zmniejsz do ikonki | powi�ksz na cały ekran) w prawym górnym naro�niku okna; Program znajduje si� w wersji .EXE na dyskietce doł czonej do ksi �ki. Mo�esz uruchomi� go z poziomu Mened�era Plików (Windows File Manager), Mened�era Programów (Windows Program Manager) lub z DOS-owskiego wiersza rozkazów (DOS Command Line): C\>WIN A:\WIN1.EXE[Enter] Co mo�e nasza pierwsza aplikacja? - Typow dla Windows technik drag and drop - poci gnij i upu�� mo�esz przy pomocy myszki przesuwa� okno naszej pierwszej aplikacji po ekranie ("ci gn c" okno za pasek tytułowy). - Ci gn c ramki b d� naro�niki mo�esz zmienia� wymiary okna w sposób dowolny. - Posługuj c si� paskami przewijania mo�esz przewija� tekst w oknie w pionie i w poziomie. - Mi�esz zredukowa� okno do ikonki. - Mo�esz uruchomi� nasz aplikacj� wielokrotnie i mie� na ekranie kilka okien programu WIN1.EXE.

- Nasza aplikacja wyposa�ona jest w menu systemowe. Mo�esz rozwin � menu i wybra� z menu jeden z kilku rozkazów. Je�li nie pisałe� jeszcze programów dla Windows - mo�esz by� troch� zaskoczony. Gdzie w naszym programie jest napisane np. - co powinno znale�� si� w menu??? Odpowied� jest prosta - nigdzie. Podobnie jak programy tworzone dla DOS korzystaj w niejawny sposób z zasobów systemu - standardowych funkcji DOS, standardowych funkcji BIOS, przerywa�, itp - tak programy tworzone dla Windows mog w niejawny sposób korzysta� z zasobów �rodowiska Windows - standardowego menu, standardowych okien, standardowych klawiszy, itp.. Takie zasoby udost�pniane przez �rodowisko programom aplikacyjnym nazywaj si� interfejsem API (Application Program Interface). Poznałe� ju� API DOS'a - jego przerywania i funkcje. Interfejs Windows nazywa si� "Windows API" i to z jego gotowych funkcji wła�nie korzystamy. Uruchom program wielokrotnie (min. 4 razy). Wykonaj 4 - 6 razy czynno�ci oznaczone powy�ej trzema gwiazdkami *** . Poniewa� nie za� dali�my, by okno programu było zawsze "na wierzchu" (on top) - po ka�dym kolejnym uruchomieniu (nie musisz nic robi� - nast pi to automatycznie - zadba o to Mened�er Windows) poprzednie okno programu zniknie. Je�li po czwartym (pi tym) uruchomieniu programu zredukujesz okno Mened�era Programów do ikony (np. [-] i "do ikony" z menu systemowego) - oka�e si�, �e "pod spodem" stale wida� kaskad� okien naszej aplikacji WIN1.EXE (patrz rys. poni�ej). Na rysunkach poni�ej przedstawiono kolejne stadia pracy z nasz PIERWSZ APLIKACJ. Aplikacja WIN1.EXE została wyposa�ona w ikonk� systemow (znane Ci okienko). Ikonka jest transparentna (półprzezroczysta) i mo�emy j tak�e metod drag and drop przenie�� w dowolne miejsce - np. do roboczego okna naszej aplikacji. Zwró� uwag� tak�e na towarzysz cy nazwie programu napis "inactive" (nieaktywna). Chodzi o to, �e program zrobił ju� wszystko, co miał do zrobienia i zako�czył si�. DOS doło�yłby standardowo funkcj� zwolnienia pami�ci i zako�czył program. W Windows niestety okienko nie zamknie si� samo w sposób standardowy. W Windows, jak wiesz, mo�emy mie� otwarte jednocze�nie wiele okien programów a aktywne jest (tylko jedno) zawsze to okno, do którego przeka�emy tzw. focus. Okno to mo�na rozpozna� po ciemnym pasku tytułowym. Wła�nie z przyj�cia takiego sposobu podziału zasobów Windows pomi�dzy aplikacje wynika skutek praktyczny - okno nie zamknie si� automatycznie po zako�czeniu programu - lecz wył cznie na wyra�ne �yczenie u�ytkownika. API Windows zawiera wiele gotowych funkcji (np. CloseWindow() - zamknij okno, DestroyWindow() - skasuj okno i in.), z których mo�e skorzysta� programista pisz c aplikacj�. Nie jeste�my wi�c całkiem bezradni. Spróbuj skompilowa� w opisany wy�ej sposób i uruchomi� pierwsz aplikacj� w takiej wersji: #include <stdio.h> void main() { printf(" Pierwsza Aplikacja \n Dla MS Windows "); } Jak łatwo si� przekona�, całkowicie klasyczny, w pełni nieobiektowy program WIN1.C b�dzie w Windows działa� dokładnie tak samo. Nasze aplikacje nie musz bynajmniej by� całkowicie obiektowe, chocia� zastosowanie obiektowej techniki programowania pozwala zmusi� nasz komputer do zdecydowanie wydajniejszego działania. PODSUMUJMY: * Je�li korzystamy wył cznie ze standardowych zasobów �rodowiska Windows, tworzenie aplikacji dla Windows nie musi by� wcale trudniejsze od tworzenia aplikacji DOS'owskich. * Czy aplikacja ma by� przeznaczona dla DOS, czy dla Windows mo�emy zdecydowa� "w ostatniej chwili" ustawiaj c odpowiednio robocze parametry kompilatora C++: Options | Applications... | DOS standard albo

Page 116: C++ - lekcje

116

Options | Applications... | Windows EXE * Aplikacje skompilowane do wersji DOS'owskiej mo�emy uruchamia� wewn trz kompilatora DOS'owskiego rozkazem Run | Run. * Aplikacje skompilowane (�ci�lej - skonsolidowane) do wersji okienkowej mo�emy uruchamia� wewn trz Windows z poziomu Mened�era Plików b d� Mened�era Programów rozkazem Uruchom z menu Plik. * Dodatkowe pliki nagłówkowe .H i biblioteki .LIB .DLL znajduj si� w katalogach \BORLANDC\INCLUDE \BORLANDC\OWL\INCLUDE \BORLANDC\LIB \BORLANDC\OWL\LIB �cie�ki dost�pu do tych katalogów nale�y doda� do roboczych katalogów kompilatora w okienku Options | Directories... * Aplikacje nie korzystaj ce z funkcji Windows API nie musz doł cza� okienkowych plików nagłówkowych. Je�li jednak zechcemy zastosowa� funkcje i dane (stałe, struktury, obiekty, itp.) wchodz ce w skład: - Windows API - Windows Stock Objects - obiekty "ze składu Windows" - biblioteki klas Object Windows Library nale�y doł czy� odpowiedni plik nagłówkowy: #include <windows.h> #include <windowsx.h> #include <owl.h> TYPOWE BŁ�DY I KŁOPOTLIWE SYTUACJE: * Nale�y pami�ta� o ustawieniu wła�ciwych katalogów roboczych kompilatora Options | Directories... * Przy bardziej skomplikowanych aplikacjach mo�e wyst pi� potrzeba dobrania innego (zwykle wi�kszego) modelu pami�ci. Modelem domy�lnym jest model Small. Inne parametry pracy kompilatora ustawia si� podobnie za pomoc menu Options. LEKCJA 39: KORZYSTAMY ZE STANDARDOWYCH ZASOBÓW Windows. ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak korzysta� z zasobów Windows bez potrzeby wnikania w wiele szczególów technicznych interfejsu aplikacji - Windows API. ________________________________________________________________ Poniewa� nasze programy mog korzysta� ze standardowych zasobów Windows, na pocz tku b�dziemy posługiwa� si� okienkami standardowymi. Pocz wszy od aplikacji WIN3.EXE "rozszerzymy ofert�" do dwu podstawowych typów: * Standardowe główne okno programu (Default main window). To takie wła�nie okno, jakie dostały nasze pierwsze aplikacje WIN1.EXE. * Okienkiem dialogowym (Dialog box), a dokładniej najprostszym rodzajem okienek dialogowych - tzw. okienkami komunikatów - Message Box. Zastosowanie okienka dialogowego pozwoli nam na wprowadzenie do akcji klawiszy (buttons). ________________________________________________________________ UWAGA: Niestety nie wszystkie tradycyjne funkcje typu printf(), scanf(), gets() itp. zostały zaimplementowane dla Windows! Pisz c własne programy mo�esz przekona� si� o tym dzi�ki opisowi funkcji w Help. Funkcj� nale�y odszuka� w Help | Index. Oprócz przykładu zastosowania znajdziesz tam tabelk� typu: DOS Unix Windows ANSI C C++ Only cscanf Yes fscanf Yes Yes Yes Yes scanf Yes Yes Yes sscanf Yes Yes Yes Yes [Yes] oznacza "zaimplementowana". Dlatego wła�nie w dalszych programach przykładowych dla wersji 3.0 nale�y np. stosowa� np. makro getchar() zamiast tradycyjnego getch() zaimplementowane dla Windows ju� w wersji BC++ 3.0.

________________________________________________________________ Dla przykładu spróbujmy skompilowa� i uruchomi� w �rodowisku Windows jeden z wcze�niejszych programów - tabliczk� mno�enia. Zwró� uwag� na doł czony dodatkowy plik WINDOWS.H i nowy typ wska�nika. Zamiast zwykłego char *p ... LPSTR p ... LPSTR - to Long Pointer to STRing - daleki wska�nik do ła�cucha tekstowego. Jest to jeden z "ulubionych" typów Windows. /* WIN2.CPP: */ /* - Tablica dwuwymiarowa - Wskazniki do elementów tablicy */ #include <windows.h> #include <iostream.h> #include <stdio.h> int T[10][10], *pT, i, j, k; char spacja = ' '; LPSTR p1 = " TABLICZKA MNOZENIA (ineksy)\n"; LPSTR p2 = " Inicjujemy i INKREMENTUJEMY wskaznik:\n"; LPSTR p3 = "... nacisnij cokolwiek (koniec)..."; void main() { printf(p1); for (i = 0; i < 10; i++) { for (j = 0; j < 10; j++) { T[i][j] = (i + 1)*(j + 1); if (T[i][j] < 10) cout << T[i][j] << spacja << spacja; else cout << T[i][j] << spacja; } cout << '\n'; } printf(p2); pT = &T[0][0]; for (k = 0; k < 10*10; k++) { if (*(pT+k) < 10) cout << *(pT + k) << spacja << spacja; else cout << *(pT + k) << spacja; if ((k + 1)%10 == 0) cout << '\n'; } printf(p3); getchar(); } Wybrali�my dla aplikacji standardowe główne okno (Main Window), poniewa� istnieje potrzeba pionowego przewijania okna w celu przejrzenia pełnego wydruku obu tablic. [???] Dlaczego ten tekst jest nierówny??? ________________________________________________________________ Niestety, znaki w trybie graficznym Windows nie maj stałej szeroko�ci (jak było to w trybie tekstowym DOS). Niektóre aplikacje przeniesione ze �rodowiska DOS b�d sprawia� kłopoty. ________________________________________________________________ APLIKACJE DWUPOZIOMOWE. Zastosujemy teraz najprostszy typ okienka dialogowego - okienko kamunikatów (Message Box), nasze nast�pne aplikacje mog by� ju� nie jedno- a dwupoziomowe. Typowe post�powanie okienkowych aplikacji bywa takie: * program wy�wietla w głównym oknie to, co ma do powiedzenia; * aby zadawa� pytania stosuje okienka dialogowe, b d� okienka komunikatów; * funkcja okienkowa (u nas MessageBox()) zwraca do programu decyzj� u�ytkownika; * program główny analizuje odpowied� i podejmuje w głównym oknie stosowne działania.

Page 117: C++ - lekcje

117

Prze�led�my ewolucj� powstaj cej w taki sposób aplikacji. STADIUM 1. Tekst w głównym oknie. Zaczniemy tworzenie naszej aplikacji tak: /* WINR1.CPP: */ /* Stadium 1: Dwa okienka w jednym programie */ # include <stdio.h> # include <windows.h> char *p1 = "Teraz dziala \n funkcja \n MessageBox()"; char *p2 = "START"; int wynik; void main() { printf(" Start: Piszemy w glownym oknie \n"); printf(" ...nacisnij cosik..."); getchar(); MessageBox(0, p1, p2, 0); printf("\n\n\n Hello World dla WINDOWS!"); printf("\n\t...dowolny klawisz... "); getchar(); } Mogliby�my zrezygnowa� z metod typowych dla aplikacji DOSowskich i zatrzymania (i zapytania) makrem getchar() (odpowiednik getch() dla Windows). To działanie mo�emy z powodzeniem powierzy� funkcji okienkowej MessageBox(). Funkcja MessageBox() pobiera cztery parametry: int Message Box(hwndParent, lpszText, lpszTitle, Style) HWND hwndParent - identyfikator macie�ystego okna (głównego okna aplikacji). Poniewa� nie wiemy póki co pod jakim numerem (identyfikatorem) Windows zarejestruj nasz aplikacj� - wpisujemy 0 LPCSTR lpszText - daleki wska�nik do ła�cucha tekstowego wewn trz okienka. LPCSTR lpszTitle - daleki wska�nik do ła�cucha tekstowego - tytułu okienka komunikatu. UINT Style - UINT = unsigned int; numer okre�laj cy zawarto�� okienka. int Return Value - identyfikator klawisza, który wybrał u�ytkownik w okienku komunikatu. [!!!] UWAGA ________________________________________________________________ Deklaracje wska�ników do tekstów powinny wygl da� tak: LPCSTR p1 = "Napis1", p2 = "Tekst2"; ale C++ mo�e samodzielnie dokona� forsowania typów i zamieni� typ char* na typ LPCSTR (lub LPSTR). ________________________________________________________________ /* WINR2.CPP: */ /* Stadium 2: Dwa okienka ze zmienn zawaro�ci */ # include <windows.h> # include <stdio.h> char *p2, *p1 = "Dopisywanie:"; char napisy[4][20] = { "Borland ", "C++ ", "dla ", "Windows" }; void main() { printf("\n\n\n Hello World dla WINDOWS!"); printf("\n AUTOR: ..................."); for( int i = 0; i < 4; i++) { p2 = &napisy[i][0]; MessageBox(0, p2, p1, MB_OK); printf("\n %s", napisy[i]); } MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK); } W tym stadium stosujemy:

- główne okno aplikacji - dwa okienka komunikatów (Dopisywanie i KONIEC) - jeden klawisz - [OK] Ła�cuchy tekstowe przeznaczone do pola tekstowego okienka pobierane s z tablicy napisy[4][20] (cztery napisy po max. 20 znaków) przy pomocy wska�nika p2. MB_OK to predefiniowana stała (Message Box OK - key identifier - identyfikator klawisza [OK] dla okienek komunikatów). /* WINR3.CPP: */ /* Stadium 3: Dwa okienka steruj p�tl */ # include <windows.h> # include <stdio.h> char *p2, *p1 = "Dopisywanie:"; char napisy[4][20] = { "Borland ", "C++ ", "dla ", "Windows" }; void main() { printf("\n\n\n Hello World dla WINDOWS!"); printf("\n AUTOR: ..................."); for( int i = 0; i < 4; i++) { p2 = &napisy[i][0]; if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK) printf("\n %s", napisy[i]); else printf("\n ...?"); } MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK); } W tym stadium stosujemy: - główne okno aplikacji - dwa okienka komunikatów (Dopisywanie i KONIEC) - dwa klawisze - [OK] i [Anuluj] (OK/Cancel) - jedn ikon� [STOP] Zwró� uwag�, �e tym razem sprawdzamy, który klawisz wybrał u�ytkownik w okienku. Odbywa si� to tak: if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK) IDOK jest predefiniowan stał - kodem klawisza [OK] (ang. OK-key IDentifier - identyfikator klawisza OK). Identyfikatory ró�nych zasobów Windows s liczbami całkowitymi. Je�li jeste� dociekliwy Czytelniku, mo�esz sprawdzi� - jaki numer ma klawisz [OK] rozbudowuj c tekst aplikacji np. tak: int Numer; ... Numer = MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL); printf("\nKlawisz [OK] ma numer: %d", Numer); if(Numer == IDOK) ... Zwró� uwag� na sposób wykorzystania zasobów w funkcji MessageBox(). Identyfikatory zasobów, które chcemy umie�ci� w okienku s wpisywane jako ostatni czwarty argument funkcji i mog by� sumowane przy pomocy znaku | (ang. ORing), np.: MessageBox(0,..,.., MB_ICONSTOP | MB_OKCANCEL); oznacza umieszczenie ikony STOP i klawiszy [OK] i [Anuluj]. Kod zwracany przez funkcj� mo�e by� wykorzystywany we wszelkich konstrukcjach warunkowych (switch, case, for, while, if-else, itp.). /* WINR4.CPP: */ /* Stadium 4: Okienka steruj 2 p�tlami, przybywa zasobów. */ # include <windows.h> # include <stdio.h> char *p2, *p1 = "Dopisywanie:"; char *p3 = "I to by bylo na tyle...\n Konczymy ???"; char *p4 = "UWAGA: KONIEC ?"; char napisy[5][20] = { "Borland ", "C++ ", "dla ", "Microsoft", "Windows" };

Page 118: C++ - lekcje

118

main() { printf("\n\n\n Grafoman dla WINDOWS!"); printf("\n AUTOR: (jak wyzej)"); puts("_____________________________\n"); do { for( int i = 0; i < 5; i++) { p2 = &napisy[i][0]; if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK) printf("\n %s", napisy[i]); else printf("\n ...?"); } } while (MessageBox(0,p3,p4,MB_ICONQUESTION | MB_OKCANCEL)==IDCANCEL); return 0; } W tym stadium stosujemy: - główne okno aplikacji - dwa okienka komunikatów (Dopisywanie i KONIEC) - dwa klawisze - [OK] i [Anuluj] (OK/Cancel) - dwie ikonki [STOP] i [PYTAJNIK] Tekst jest przewijany w głównym oknie programu i po zako�czeniu roboczej cz��ci programu i przej�ciu w stan nieaktywny (inactive) mo�esz przy pomocy paska przewijania pionowego obejrze� napisy - histori� Twoich zmaga� z programem. Zwró� uwag�, �e pojemno�� głównego okna jest ograniczona. Je�li napisów b�dzie zbyt du�o, tekst przewini�ty poza okno mo�e ulega� obci�ciu (ang clip on). Zwró� równie� uwag� na naprzemienne przekazywanie aktywno�ci (focus) pomi�dzy oknami aplikacji: MainWindow <-----> MessageBox Warto w tym momencie zwróci� uwag� na kilka typowych dla okienkowych aplikacji mechanizmów. * Je�li naci�niemy klawisz na klawiaturze, b d� klawisz myszki, obsługa takiego zdarzenia mo�e nast�powa� na dwa sposoby. Najpierw Windows pobieraj kod klawisza i dokonuj "kolejkowania" (podobnie jak DOS-owski bufor klawiatury). Nast�pnie przekazuj kod klawisza aplikacji do obsługi. Je�li aplikacja czeka na klawisz i potrafi obsłu�y� takie zdarzenie (np. funkcja MessageBox(), b d� makro getchar(), czy operator cin >> w programie głównym), obsługa zdarzenia zostaje zako�czona. Je�li aplikacja nie potrafi obsłu�y� zdarzenia - obsługa przekazywaba jest stadardowym funkcjom obsługi (Event Handler) Windows. * Kiedy na ekranie pojawia si� okienko dialogowe (tu: komunikatów) zostaje mu przekazany tzw. focus - czyli aktywno��. Naci�ni�cie [Entera] spowoduje zadziałanie tego klawisza w okienku, który wła�nie ten focus otrzymał (tu zwykle pierwszego z lewej). * je�li naci�niemy klawisz specjalny, którego obsług� w sposób standardowy powinny załatwia� Windows - obsługa takiego zdarzenia zostaje przekazana domy�lnej funkcji Windows (ang. Default Event Handler). Tak jest w przypadku klawiszy ze strzałkami (przewijanie w oknie), [Tab], [Alt]+[F4], itp. /* WINR5.CPP: */ /* Stadium 5: Zmiana wielko�ci i nazwy okienka. */ # include <windows.h> # include <iostream.h> # include <string.h> char tytul[80] = "Dopisywanie: "; char *p0, *p2; char *p1 = "UWAGA: Ponawianie proby \n oznacza: WYDRUKUJE I ZAPYTAM"; char *p3 = "I to by bylo na tyle...\n Konczymy ???"; char *p4 = "UWAGA: KONIEC ?"; char napisy[5][20] = { "Borland ", "C++ ", "dla ", "Microsoft",

"Windows" }; main() { cout << "\n\n\n Grafoman dla WINDOWS!"; cout << "\n AUTOR: (jak wyzej)"; cout << "\n_____________________________\n"; p0 = &tytul[0]; do { for( int i = 0; i < 5; i++) { p2 = &napisy[i][0]; strcat(p0, p2); int decyzja = MessageBox(0, p1, p0, MB_ICONHAND | MB_ABORTRETRYIGNORE); if (decyzja == IDABORT) break; else if (decyzja == IDRETRY) { cout << "\n " << napisy[i]; i--; } else if (decyzja == IDIGNORE) { cout << "\n ...?"; continue; } } } while (MessageBox(0, p3, p4, MB_ICONQUESTION | MB_OKCANCEL) == IDCANCEL); return 0; } W Stadium 5 zmienia si� (ro�nie) nagłówek okienka komunikatów. UWAGA: Po wyj�ciu za ekran nast pi załamanie programu. Program nie zawiera handlera obsługuj cego przekroczenia dopuszczalnej długo�ci. Rysunek poni�ej przedstawia ró�ne stadia działania opisanych powy�ej aplikacji. Je�li postanowisz napisa� praktyczn aplikacj� dla Windows, jest to zwykle program znacznie dłu�szy, w którym trzeba przemy�le� sposób organizacji p�tli, wyra�e� warunkowych i sposoby wykorzystania zasobów. [!!!]UWAGA ________________________________________________________________ Okienka mog by� "modalne" i "nie-modlane". Okienko "modalne" to takie okienko, które do momentu jego zamkni�cia uniemo�liwia u�ytkownikowi działania w innych oknach (tej samej aplikacji, b d� innych aplikacji) znajduj cych si� na ekranie. W ramach parametru Styl mo�esz stosowa� predefiniowane stałe MB_APPMODAL MB_TASKMODAL itp. okre�laj ce stopie� "modalno�ci" okienka (na poziomie zadania - TASK, aplikacji - APP, itp.). ________________________________________________________________ LEKCJA 40: STRUKTURA PROGRAMU PROCEDURALNO - ZDARZENIOWEGO PRZEZNACZONEGO DLA WINDOWS. ________________________________________________________________ W trakcie tej lekcji poznasz ogóln budow� interfejsu API Windows i dowiesz si�, co z tego wynika dla nas - autorów programów przeznaczonych dla Windows. ________________________________________________________________ W przeciwie�stwie do długich liniowych programów przeznaczonych dla DOS, w naszych programach dla Windows b�dziemy pisa� co� na kształt krótkich odcinków programu i przekazywa� sterowanie Windows. Jest to bardzo wa�na cecha - kod programu jest zwykle silnie zwi zany z Windows w taki sposób, �e u�ytkownik mo�e w du�ym stopniu decydowa� o sposobie (kolejno�ci) wykonywania

Page 119: C++ - lekcje

119

programu. Praktycznie robi to poprzez wybór opcji-klawiszy w dowolnej kolejno�ci. Przy takiej filozofii w dowolnym momencie powinni�my mie� mo�liwo�� przeł czenia si� do innego programu (innego okna) i nasz program powinien (bez zauwa�alnej zwłoki) przekaza� sterowanie, nie zagarniaj c i nie marnuj c czasu CPU. Z tego powodu kod programu powinien by� bardzo "zmodularyzowany". Ka�da sekcja kodu powinna by� odseparowana i ka�da, po wykonaniu powinna przekazywa� sterowanie do Windows. NOTACJA W�GIERSKA I NOWE TYPY DANYCH. Tworzenie zdarzeniowych programów dla Windows wymaga kilku wst�pnych uwag na temat nowych typów danych. Okienkowe typy s definiowane w plikach nagłówkowych (WINDOWS.H, WINDOWSX.H, OWL.H itp) i maj posta� najcz��ciej struktury, b d� klasy. Typowe sposoby deklaracji w programach okienkowych s nast�puj ce: HWND hWnd - WiNDow Handle - identyfikator okna HWND hWnd - typ (predefiniowany), hWnd - zmienna HINSTANCE hInstance - Instance Handle - identyfikator danego wyst pienia (uruchomienia) programu PAINTSTRUCT - struktura graficzna typu PAINTSTRUCT ps - nasza robocza struktura (zmienna) WNDCLASS - struktura (a nie klasa wbrew myl cej nazwie) POINT - struktura (współrz�dne punktu - piksela na ekranie) RECT - struktura (współrz�dne prostok ta) BOOL - typ signed int wykorzystywany jako flaga (TRUE/FALSE) WORD - unsigned int DWORD - unsigned long int LONG - long int HANDLE, HWND, HINSTANCE - unsigned int (jako nr - identyfikator) UINT - j. w. - unsigned int. W programach okienkowych stosuje si� wiele predefiniowanych stałych, których znaczenie sugeruje przedrostek i nazwa, np: WM_CREATE - Windows Message: Create! - Komunikat Windows: Utworzy�! (np. okno) WS_VISIBLE - Window Style: Visible - Styl Okna: Widoczne ID_... - IDentifier - IDentyfikator MB_... - Message Box - elementy okienka komunikatów W �rodowisku Windows stosuje si� specjaln notacj� nazwan od narodowo�ci swojego wynalazcy Karoja Szimoni - notacj w�giersk . Sens notacji w�gierskiej polega na dodaniu do nazwy zmiennej okre�lonych liter jako przedrostka (prefix). Litery-przedrostki stosowane w notacji w�gierskiej zebrano w Tabeli poni�ej. Pomi�dzy nazewnictwem Microsofta a Borlanda istniej wprawdzie drobne rozbie�no�ci, ale ogólne zasady mo�na odnie�� zarówno do BORLAND C++ 3+...4+, jak i Microsoft C++ 6...7, czy Visual C++. Notacja w�gierska ________________________________________________________________ Prefix Skrót ang. Znaczenie ________________________________________________________________ a array tablica b bool zmienna logiczna (0 lub 1) by unsigned char znak (bajt) c char znak cb count of bytes liczba bajtów cr color reference value okre�lenie koloru cx, cy short (count x, y len.) x-ilo��, y-długo�� (short) dw unsigned long liczba długa bez znaku double word podwójne słowo fn function funkcja pfn pointer to function wsk. do funkcji h handle "uchwyt" - identyfikator i integer całkowity id identifier identyfikator n short or int krótki lub całkowity np near pointer wska�nik bliski p pointer wska�nik l long długi lp long pointer wska�nik typu long int lpfn l. p. to function daleki wska�n. do funkcji s string ła�cuch znaków sz string terminated '\0' ła�cuch ASCIIZ tm text metric miara tekstowa

w unsigned int (word) słowo x,y short x,y coordinate współrz�dne x,y (typ: short) ________________________________________________________________ O PROGRAMOWANIU PROCEDURALNO - ZDARZENIOWYM DLA WINDOWS. W proceduralno-sekwencyjnych programach DOS'owskich sterowanie jest przekazywane mniej lub bardziej kolejno kolejnym instrukcjom w taki sposób, jak �yczył sobie tego programista. W Windows program-aplikacja prezentuje u�ytkownikowi wszystkie dost�pne opcje w formie widocznych na ekranie obiektów (visual objects) do wyboru przez u�ytkownika. Program funkcjonuje zatem według zupełnie innej koncepcji nazywanej "programowaniem zdarzeniowym" (ang. event-driven programming). Mo�na powiedzie�, �e za przebieg wykonania programu nie jest odpowiedzialny tylko programista lecz cz��� tej odpowiedzialno�ci przejmuje u�ytkownik i to on decyduje w jaki sposób przebiega wykonanie programu. U�ytkownik mo�e wybra� w dowolnym momencie dowoln spo�ród wszystkich oferowanych mu opcji a program powinien zawsze zareagowa� poprawnie i równie szybko. Jest oczywiste, �e pisz c program nie mo�emy przewidzie� w jakiej kolejno�ci u�ytkownik b�dzie wybierał opcje/rozkazy z menu. Przeciwnie powini�my napisa� program w taki sposób by dla ka�dego rozkazu istniał oddzielny kod. Jest to ogólna koncepcja, na której opiera si� programowanie zdarzeniowe. W przeciwie�stwie do programów proceduralno - sekwencyjnych, które nale�y czyta� od pocz tku do ko�ca, programy dla Windows musz zosta� poci�te na na mniejsze fragmenty - sekcje - na zasadzie jedna sekcja - obsługa jednego zdarzenia. Je�li zechcesz wy�wietli� napis "Hello, World", sekcja zdarzeniowego programu obsługuj ca takie zdarzenie mo�e wygl da� np. tak: Funkcja_Obsługi_Komunikatów_o_Zdarzeniach(komunikat) { switch (komunikat_od_Windows) { case WM_CREATE: ... TextOut(0, 0, "Napis: np. Hello world.", dlugosc_tekstu); break; ... case WM_CLOSE: // CLOSE - zamkn � okno .... break; ..................... itd. } a w przypadku obiektowego stylu programowania - metoda obsługuj ca to zdarzenie (nale� ca np. do obiektu Obiekt_Główne_Okno - TMainWindow) mo�e wygl da� np. tak: void TMainWindow::RysujOkno() { TString Obiekt_napis = "Hello, World"; int dlugosc_tekstu = sizeof(Obiekt_napis); TextOut(DC, 10, 10, Obiekt-napis, dlugosc_tekstu); } Taki fragment kodu programu jest specjalnie przeznaczony do obsługi jednego zdarzenia (ewent-ualno�ci). W okienku wykonuje si� operacja PAINT (maluj). "Malowanie" okna mo�e si� odbywa� albo po raz pierwszy, albo na skutek przesuni�cia. Programy zdarzeniowe tworzone w C++ dla Windows b�d zbiorem podobnych "kawałków" nast�puj cych w tek�cie programu sekcja za sekcj . Oto jak działa program zdarzeniowy: kod programu, podzielony na sekcje obsługuj ce poszczególne zdarzenia koncentruje si� wokół interfejsu. FUNKCJE WinMain() i WindowProc(). W programach pisanych w standardowym C dla Windows u�ywane s dwie najwa�niejsze funkcje: WinMain() i WindowProc(). ________________________________________________________________ UWAGA: Funkcji WindowProc() mo�na nada� dowoln nazw�, ale WinMain() musi si� zawsze nazywa� WinMain(). Jest to nazwa zastrze�ona podobnie jak main() dla aplikacji DOSowskich. ________________________________________________________________

Page 120: C++ - lekcje

120

Funkcja WinMain() powoduje utworzenie okna programu umo�liwiaj c zdefiniowanie i zarejestrowanie struktury "okno" (struct WNDCLASS) a nast�pnie powoduje wy�wietlenie okna na ekranie. Od tego momentu zarz dzanie przejmuje funkcja WindowProc(). W typowej proceduralno - zdarzeniowej aplikacji dla Windows to wła�nie funkcja WindowProc() obsługuje pobieranie informacji od u�ytkownika (np. naci�ni�cie klawisza lub wybór z menu). Funkcja WindowProc() robi to dzi�ki otrzymywaniu tzw. komunikatów (ang. Windows message). W Windows zawsze po wyst pieniu jakiego� zdarzenia (event) nast�puje przesłanie komunikatu (message) o tym zdarzeniu do bie� cego aktywnego w danym momencie programu w celu poinformowania go, co si� stało. Je�li został naci�ni�ty klawisz, komunikat o tym zdarzeniu zostanie przesłany do funkcji WindowProc(). Tak funkcjonuje interfejs pomi�dzy aplikacj a Windows. W programach tworzonych w C prototyp funkcji WindowProc() wygl da nast�puj co: LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam); Słowa FAR i PASCAL oznaczaj , �e: FAR - kod funkcji znajduje si� w innym segmencie ni� kod programu; PASCAL - kolejno�� odkładania argumentów na stos - odwrotna (jak w Pascalu). ________________________________________________________________ UWAGA: Prototyp funkcji mo�e zosta� podany równie� tak: LONG FAR PASCAL WndProc(HWND, unsigned, WORD, LONG); ________________________________________________________________ Pierwszy parametr hWnd jest to tzw. identyfikator okna (ang. window handle). Ten parametr zawiera informacj�, dla którego okna przeznaczony jest komunikat. Zastosowanie takiego identyfikatora jest celowe, poniewa� funkcje typu WindowProc() mog obsługiwa� przesyłanie komunikatów do wielu okien. Je�li okien jest wiele, okno jest identyfikowane przy pomocy tego wła�nie identyfikatora (numeru). Nast�pny parametr to sam komunikat o długo�ci jednego słowa (word). Ten parametr przechowuje warto�� z zakresu zdefiniowanego w pliku nagłówkowym WINDOWS.H. W zale�no�ci od tego co si� zdarzyło, Windows mog nam przekaza� ok. 150 ró�nych komunikatów a w tym np.: WM_CREATE Utworzono okno WM_KEYDOWN Naci�ni�to klawisz WM_SIZE Zostały zmienione wymiary okna WM_MOVE Okno zostało przesuni�te WM_PAINT Okno nale�y narysowa� (powtórnie) - (re)draw WM_QUIT Koniec pracy aplikacji itp. Przedrostek WM_ to skrót od Windows Message - komunikat Windows. Wymiana komunikatów w �rodowisku Windows mo�e przebiega� w ró�ny sposób - zale�nie od �ródła wywołuj cego generacj� komunikatu i od charakteru zdarzenia. Ze wzgl�du na �ródło mo�na komuniakty umownie podzieli� na nast�puj ce grupy: 1. Działanie u�ytkownika (np. naci�ni�cie klawisza) powoduje wygenerowanie komunikatu. 2. Program - aplikacja wywołuje funkcj� Windows i powoduje przesłanie komunikatu do aplikacji. 3. �rodowisko Windows przesyła komunikat do programu. 4. Dwie aplikacje zwi zane mechanizmem dynamicznej wymiany danych (Dinamic Data Exchange - DDE) wymieniaj komunikaty.

Komunikaty Windows mo�na tak�e podzieli� umownie na nast�puj ce kategorie: 1. Komunikaty dotycz ce zarz dzania oknami (Windows Managenent Msg.): WM_ACTIVATE (zaktywizuj lub zdezaktywizuj okno), WM_PAINT, WM_MOVE, WM_SIZE, WM_CLOSE, WM_QUIT. Bardzo istotnym szczegółem technicznym jest problem przekazywania aktywno�ci pomi�dzy oknami. Szczególnie cz�sto wyst�puje potrzeba przekazania aktywno�ci do elementu steruj cego. Je�li hEditWnd b�dzie identyfikatorem (window handle) okienka edycyjnego: case WM_SETFOCUS: SetFocus(hEditWnd); break; funkcja SetFocus() spowoduje, �e wszystkie komunikaty dotycz ce zdarze� klawiatury b�d kierowane do okna steruj cego, je�eli okno macie�yste jest aktywne. Poniewa� zmiana rozmiaru okna głównego nie poci ga za sob automatycznej zmiany rozmiaru okna steruj cego, potrzebna jest dodatkowo obsługa wiadomo�ci WM_SIZE wobec okna elementu steruj cego. 2. Komunikaty inicjacyjne dotycz ce konstrukcji np. menu aplikacji: WM_INITMENU - zainicjuj menu (wysyłany przed zainicjowaniem), WM_INITDIALOG - zainicjuj okienko dialogowe. 3. Komunikaty generowane przez Windows w odpowiedzi na wybór rozkazu z menu, zegar, b d� naci�ni�cie klawisza: WM_COMMAND - wybrano rozkaz z menu, WM_KEYDOWN - naci�ni�to klawisz, WM_MOUSEMOVE - przesuni�to myszk�, WM_TIMER - czas min ł. 4. Komunikaty systemowe. Aplikacja nie musi odpowiada� na rozkazy obsługiwane przez domy�ln procedur� Windows - DefWindowProc(). Szczególnie dotyczy to rozkazów nie odnosz cych si� do roboczego obszaru okna - Non Client Area Messages. 5. Komunikaty schowka (Clipborad Messages). Sens działania funkcji WindowProc() w C/C++ polega na przeprowadzeniu analizy, co si� stało i podj�ciu stosownej akcji. Mo�na to realizowa� przy pomocy drabinki if-else-if, ale najwygodniejsze jest stosowanie instrukcji switch. LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam) { switch (Message) { case WM_CREATE: ..... break; /* Koniec obsługi komunikatu WM_CREATE */ case WM_MOVE: .... /* Kod obsługi komunikatu WM_MOVE */ break; /* Koniec obsługi WM_MOVE. */ case WM_SIZE: .... /* Kod obsługi sytuacji WM_SIZE */ break; /* Koniec obsługi WM_SIZE */ .......... /* Inne, pozostałe mo�liwe sytuacje */ case WM_CLOSE: /* Zamkni�cie okna */ .... break; default: /* wariant domy�lny: standardowa obsługa .... przez standardow funkcj� Windows */ } } ________________________________________________________________ UWAGA: Poniewa� komunikatów "interesuj cych" dan aplikacj� mo�e by� ponad 100 a sposobów reakcji u�ytkownika jeszcze wi�cej, w "powa�nych" aplikacjach tworzone s cz�sto struktury decyzyjne o wi�kszym stopniu zło�ono�ci. Je�li istnieje potrzeba optymalizacji działania programów stosuje si� struktury dwu typów:

Page 121: C++ - lekcje

121

* hierarchia warto�ci (Value Tree) i * drzewo analizy zdarze� (Event Tree). Utworzone w taki sposób tzw. "Drzewo decyzyjne" nazywane tak�e "Drzewem analizy zdarze�" mo�e by� wielopoziomowe. Widoczny powy�ej pierwszy poziom drzewa (pierwszy przesiew) realizowany jest zwykle przy pomocy instrukcji switch a nast�pne przy pomocy drabinek typu if-else-if-break. Schemat if-else-if-break cz�sto bywa zast�powany okienkami dialogowymi. ________________________________________________________________ Parametry wParam i lParam przechowuj parametry istotne dla danego komunikatu. wParam ma długo�� pojedynczego słowa (word) a lParam ma długo�� podwójnego słowa (long). Je�li, dla przykładu, okno zostało przesuni�te, te parametry zawieraj nowe współrz�dne okna. Je�eli program ma by� programem zdarzeniowym, powinni�my przed podj�ciem jakiejkolwiek akcji zaczeka� a� Windows przy�l nam komunikat o tym, jakie zdarzenie nast piło. Wewn trz Windows tworzona jest dla komunikatów kolejka (ang message queue). Dzi�ki istnieniu kolejkowania otrzymujemy komunikaty pobierane z kolejki pojedynczo. Je�li u�ytkownik przesunie okno a nast�pnie przyci�nie klawisz, to Windows wywołaj funkcj� WindowProc() najpierw z parametrem WM_MOVE a nast�pnie z parametrem WM_KEYDOWN. Jednym z najwa�niejszych zada� funkcji WinMain() jest utworzenie kolejki dla komunikatów i poinformowanie Windows, �e komunikaty do naszego programu nale�y kierowa� pod adresem funkcji WindowProc(). W tym celu stosuje si� daleki wska�nik do procedury okienkowej lpfn (Long Pointer to Function). Poza tym funkcja WinMain() tworzy okno (okna) i wy�wietla je na ekranie w pozycji pocz tkowej. Kiedy program zostaje po raz pierwszy załadowany i uruchomiony - Windows najpierw wywołuj funkcj� WinMain(). Windows manipuluj komunikatami posługuj c si� struktur MSG (od messages - komunikaty). Struktura MSG jest zdefiniowana w pliku WINDOWS.H w nast�puj cy sposób: typedef struct tagMSG { HWND hwnd; WORD message; WORD wParam; LONG lParam; DWORD time; POINT pt; } MSG; Na pierwszym polu tej struktury znajduje si� "identyfikator" (kod) okna, dla którego przeznaczony jest komunikat (ka�dy komunikat mo�e by� przesłany tylko do jednego okna). Na drugim polu struktury przechowywany jest sam komunikat. Komunikat jest zakodowany przy pomocy predefiniowanych stałych w rodzaju WM_SIZE, WM_PAINT czy WM_MOUSEMOVE. Kolejne dwa pola słu� do przechowania danych-parametrów towarzysz cych ka�demu komunikatowi: wParam i lParam. Na nast�pnym polu przechowywany jest w zakodowanej postaci czas - moment, w którym wyst piło zdarzenie. Na polu pt przechowywane s współrz�dne kursora myszki na ekranie w momencie w którym został wygenerowany komunikat o wyst pieniu zdarzenia. Nale�y zwróci� tu uwag�, �e typ POINT oznacza struktur�. Struktura POINT (punkt) w Windows wygl da tak: typedef struct tagPOINT { int x; int y; } POINT; Aby mie� pewno��, �e otrzymali�my wszystkie komunikaty, które zostały do nas skierowane, w programie wykonywana jest p�tla pobierania komunikatów (message loop) wewn trz funkcji WinMain(). Na pocz tek wywoływana jest zwykle okienkowa (czyli

nale� c do Windows API) funkcja GetMessage(). Ta funkcja wypełnia struktur� komunikatów i zwraca warto��. Zwracana przez funkcj� warto�� jest ró�na od zera, je�eli otrzymany wła�nie komunikat był czymkolwiek za wyj tkiem WM_QUIT. Komunikat WM_QUIT jest komunikatem ko�cz cym prac� ka�dej aplikacji dla Windows. Je�li otrzymamy komunikat WM_QUIT powinni�my przerwa� p�tl� pobierania komunikatów i zako�czy� prac� funkcji WinMain(). Taka sytuacja oznacza, �e wi�cej komunikatów nie b�dzie. Po uwzgl�dnieniu tych warunków p�tla mo�e wygl da� tak: int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \ LPSTR lpszCmdLine, int nCmdShow) .... while(GetMessage(&msg,NULL,0,0)) //Poki nie otrzymamy WM_QUIT { .... } Po naci�ni�ciu przez u�ytkownika klawisza generowany jest komunikat WM_KEYDOWN. Jednak�e z faktu otrzymania komunikatu WM_KEYDOWN nie wynika, który klawisz został przyci�ni�ty, czy była to du�a, czy mała litera. Funkcj� TranslateMessage() (PrzetłumaczKomunikat) stosuje si� do przetłumaczenia komunikatu WM_KEYDOWN na komunikat WM_CHAR. Komunikat WM_CHAR przekazuje przy pomocy parametru wParam kod ASCII naci�ni�tego klawisza. Funkcj� TranslateMessage() stosujemy w p�tli pobierania komunikatów tak: int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \ LPSTR lpszCmdLine, int nCmdShow) .... while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); .... } W tym stadium program jest gotów do przesłania komunikatu do funkcji - procedury okienkowej WindowProc(). Posłu�ymy si� w tym celu funkcj DispatchMessage() (ang. dispatch - odpraw, przeka�, DispatchMessage = OtprawKomunikat). Funkcja WinMain() poinformowała wcze�niej Windows, �e odprawiane komunikaty powinny trafi� wła�nie do WindowProc(). while(GetMessage(&msg, NULL, NULL, NULL)) { TranslateMessage(&msg); DispatchMessage(&msg); } Tak funkcjonuje p�tla pobieraj ca komunikaty od Windows i przekazuj ca je funkcji WindowProc(). P�tla działa do momentu pobrania komunikatu WM_QUIT (Koniec!). Otrzymanie komunikatu WM_QUIT powoduje przerwanie p�tli i zako�czenie pracy programu. Komunikaty systemowe (system messages), które s kierowane do Windows tak�e trafiaj do tej p�tli i s przekazywane do WindowProc(), ale ich obsług powinna si� zaj � specjalna funkcja DefWindowProc() - Default Window Procedure, umieszczona na ko�cu (wariant default). Jest to standardowa dla aplikacji okienkowych posta� p�tli pobierania komunikatów. Jak wida�, wymiana informacji pomi�dzy u�ytkownikiem, �rodowiskiem a aplikacj przebiega tu troch� inaczej ni� w DOS. Program pracuj cy w �rodowisku tekstowym DOS nie musi np. rysowa� własnego okna. [Z] ________________________________________________________________ 1. Uruchom Windows i popatrz �wiadomym, fachowym okiem, jak przebiega przekazywanie aktywno�ci (focus) mi�dzy okienkami aplikacji. ________________________________________________________________ LEKCJA 41: JAK TWORZY SI� APLIKACJ� DLA Windows? ________________________________________________________________ W trakcie tej lekcji dowiesz si�, jak "poskłada�" aplikacj� dla

Page 122: C++ - lekcje

122

Windows z podstawowych funkcji interfejsu API i jakie komunikaty s najwa�niejsze dla naszych aplikacji. ________________________________________________________________ Przy tworzeniu programu zwró�my szczególn uwag� na to, co dzieje si� w programie po otrzymaniu komunikatu WM_PAINT (nale�y narysowa� okno). Jest to � danie ze strony Windows, by program narysował obszar roboczy (client area) swojego okna. Program otrzyma komunikat WM_PAINT zawsze na pocz tku, kiedy powinien narysowa� swoje okno po raz pierwszy i pó�niej powtórnie, za ka�dym razem, gdy trzeba b�dzie odtworzy� okno na ekranie. Je�li inne okno przesuwane po ekranie przysłoni okno naszego programu, po odsłoni�ciu naszego okna Windows prze�l do programu komunikat WM_PAINT - odtwórz swoje okno - narysuj go powtórnie (redraw, repaint). Je�li zechcemy wyprowadzi� na ekran napis "Hello World" tak�e b�dziemy musieli narysowa� okno od nowa. Nie zawsze "od�wie�enia" wymaga całe okno. W ka�dej z sytuacji: - całe okno zostało przysłoni�te i odsłoni�te - cz��� okna wymaga od�wie�enia - okno jest rysowane po raz pierwszy Windows prze�l do programu ten sam komunikat - WM_PAINT. Je�li odtworzenia wymaga tylko cz��� okna, taka cz��� okna nazywa si� niewa�n -nieaktualn (ang. invalid). W Windows takie nieaktualne fragmenty okna zawsze maj kształt prostok tów. Wyobra�my sobie, �e jakie� inne okno przesłoniło naro�nik okna naszego programu. Je�li u�ytkownik usunie to przesłaniaj ce okno, odsłoni�ty obszar b�dzie potraktowany przez Windows jako nieaktualny. Windows prze�l do aplikacji komunikat WM_PAINT � daj cy odtworzenia okna. daj c odtworzenia okna Windows powinny nas poinformowa� która cz��� naszego okna została na ekranie "zepsuta". Współrz�dne prostok ta na ekranie Windows przeka� przy pomocy specjalnej struktury nazywanej struktur rysunku (ang. paint structure - PAINTSTRUCT). Struktur� rysunku mo�emy nazwa� w programie np.: PAINSTRUCT ps; W funkcji WindowProc() obsługa komunikatu WM_PAINT rozpoczyna si� od wyczyszczenia pól struktury rysunku ps. Struktura predefiniowanego typu PAINTSTRUCT (w WINDOWS.H) zawiera informacje o rysunku. PAINTSTRUCT ps; { switch (Message) { case WM_CREATE: ..... break; case WM_MOVE: .... break; case WM_SIZE: .... break; case WM_PAINT: /* Obsługa rysowania okna */ memset(&ps, 0x00, sizeof(PAINTSTRUCT); .... break; //Koniec obsługi WM_PAINT case WM_CLOSE: .... break; default: ..... } } Nast�pnie pola struktury rysunku zostaj wypełnione poprzez okienkow funkcj BeginPaint() - RozpocznijRysowanie. Zwró� uwag�, �e do poprawnego działania funkcji potrzebne s informacje o tym, które okno trzeba od�wie�y� (Windows powinny wiedzie� wobec którego okna � damy informacji o "zepsutym" prostok cie) i adres naszej struktury rysunku. Aby przekaza� te informacje post�pujemy tak: case WM_PAINT: memset(&ps, 0x00, sizeof(PAINTSTRUCT)); hDC = BeginPaint(hWnd, &ps); .... Teraz funkcja BeginPaint() mo�e wypełni� nasz struktur� rysunku

ps danymi. Pola struktury typu PAINTSTRUCT wygl daj nast�puj co: typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BYTE rgbReserved[16]; } PAINTSTRUCT; Przy pomocy pola typu RECT (ang. rectangle - prostok t) Windows przekazuj do programu współrz�dne wymiary (ang. dimensions) "zepsutego" na ekranie prostok ta. Typ RECT oznacza nast�puj c struktur�: typedef struct tagRECT { int left; //współrz�dna lewa - x int top; //współrz�dna górna - y int right; //współrz�dna prawa - x int bottom; //współrz�dna dolna - y } RECT; Górny lewy róg nieaktualnego prostok ta (invalid rectangle) ma dwie współrz�dne (left, top) a dolny prawy róg prostok ta ma współrz�dne (right, bottom). Te współrz�dne ekranowe mierzone s w pikselach i s to współrz�dne wzgl�dne - wzgl�dem lewego górnego naro�nika okna aplikacji. Lewy górny naro�nik okna aplikacji ma wi�c współrz�dne (0,0). Zwró�my uwag� na warto�� zwracan przez funkcj� BeginPaint() - zmienn hDC: case WM_PAINT: memset(&ps, 0x00, sizeof(PAINTSTRUCT)); hDC = BeginPaint(hWnd, &ps); .... Wszystnie operacje graficzne b�d wymaga� nie kodu okna hWnd a wła�nie kodu-identyfikatora kontekstowego hDC. Na pocz tku pracy programu, gdy okno jest rysowane po raz pierwszy, Windows generuj komunikat WM_PAINT i cały obszar roboczy okna jest uznawany za nieaktualny. Kiedy program otrzyma ten pierwszy komunikat, mo�emy wykorzysta� to do umieszczenia w oknie np. napisu. Je�li tekst ma rozpoczyna� si� od lewego górnego naro�nika okna aplikacji, funkcja TextOut() u�ywana w Windows do wykre�lania tekstu (w trybie graficznym) powinna rozpoczyna� wyprowadzanie tekstu od punktu o (pikselowych) współrz�dnych (0,0). case WM_PAINT: ... TextOut(hDC, 0, 0, (LPSTR) "Tekst", strlen("Tekst")); EndPaint(hWnd, &ps); break; Funkcja TextOut() (wyprowad� tekst) pobiera pi�� parametrów: hDC - identyfikator-kod prostok ta, który nale�y narysowa� x - współrz�dna pozioma (w pikselach) y - współrz�dna pionowa pocz tku naszego napisu W tym przypadku współrz�dne wynosz (0,0). LPSTR - wska�nik do ła�cucha znaków "Hello world." LPSTR = long pointer to string (wska�nik typu far). Wska�nk ten przekazujemy do funkcji poprzez forsowanie typu: ... (LPSTR) "Tekst"; Zgodnie z definicj typu w pliku WINDOWS.H spowoduje to zamian� wska�nika do ła�cucha typu near char* (bliski) na wska�nik typu far (daleki). Ostatni parametr funkcji to długo�� wyprowadzanego tekstu - tu obliczana przez funkcj� strlen(). Prze�led�my etapy powstawania aplikacji. Funkcja MainWin() rejestruje i tworzy główne okno programu oraz inicjuje globalne zmienne i struktury. Funkcja WinMain() zawiera

Page 123: C++ - lekcje

123

p�tl� pobierania komunikatów. Ka�dy komunikat przeznaczony dla głównego okna (lub ewentualnych nastepnych okien potomnych) jest pobierany, ewentualnie poddawany translacji i przekazywany do funkcji obsługuj cej dialog z Windows. Przed zako�czeniem programu funkcja WinMain() kasuje utworzone wcze�niej obiekty, zwalnia pami�� i pozostałe zasoby. UWAGA: "Obiekty" nie s tu u�yte w sensie stosowanym w OOP. "Obiekt" oznacza tu np. struktur�. int PASCAL WinMain(HANDLE hInstance, hPrevInstance, LPSTR lpszCmLine, int nCmdShow) { ... HANDLE hInstance - identyfikator bie� cego pojawienia si� danej aplikacji. Poniewa� w Windows program mo�e by� uruchamiany wielokrotnie, stosuje sie pojecie tzw. "Instancji" - wyst pienia - uruchomienia programu. HANDLE hPrevInstance - identyfikator poprzedniego wyst pienia danej aplikacji LPSTR lpszCmdLine - daleki wska�nik do parametrów wywołania programu z linii rozkazu int nCmdShow - sposób pocz tkowego wy�wietlenia okna (pełne okno, b d� ikona) Deklaracja struktury typu MSG (Message) do przechowywania komunikatów. MSG msg; Nadanie nazwy aplikacji: strcpy(szAppName, "Nazwa Aplikacji"); Rejestrujemy struktury okien je�li jest to pierwsze uruchomienie danej aplikacji i sprawdzamy, czy rejestracja powiodła si�: if(!PrevInstance) { if((int nRc = RegisterClass() ... Utworzenie głównego okna programu (mo�e si� nie uda�): hWndMain = CreateWindow(....); if(hWndMain == NULL) { MessageBox(0, "Klops", "Koniec", MB_OK); return (FALSE); } Wy�wietlenie głównego okna na ekranie: ShowWindow(hWndMain, nCmdShow); P�tla komunikatów wykrywaj ca komunikat WM_QUIT: while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } Główna procedura obsługi okna WindowProc(). Instrukcja switch przeł cza do odpowiedniego wariantu działania - obsługi odpowiedniego komunikatu. Musz tu znajdowa� sie procedury obsługi wszystkich interesujacych nas działa� uzytkownika i ogólnych komunikatow Windows (np. WM_CLOSE). Je�li wyst pi taki komunikat, którego obsługa nie została przewidziana, obsługa jest przekazywana, do funkcji okienkowej DefWindowProc() - obsług� przejmuj Windows. Komunikaty inicjowane przez u�ytkownika s rozpatrywane zasadniczo jako WM_COMMAND. Rozkaz wybrany z menu lub odpowiadaj ca mu kombinacja klawiszy jest przekazywana przy pomocy pierwszego parametru komunikatu - wParam. Kod odpowiadaj cy rozkazowi z menu nazywa sie "control menu ID", a

identyfikator kombinacji klawiszy - "accelerator ID". Procedura obsługi komunikatów powinna zawiera� case (WM_COMMAND): ..... break; Wewn trz przy pomocy instrukcji switch{...} nale�ałoby rozpatrywa� kolejne warianty, wykorzystuj c identyfikator wybranego z menu rozkazu - ID. Obsługa komunikatow �wiadcz cych o wyborze przez u�ytkownika rozkazu z menu stanowi zwykle główn robocz cze�� programu. LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message, WORD wParam, LONG lParam) { HMENU hMenu=0; /* Identyfikator menu */ HBITMAP hBitmap=0; /* Identyfikator mapy bitowej */ HDC hDC; /* Identyfikator kontekstowy */ PAINSTRUCT ps; /* Struktura rysunku */ int nRc=0; /* Zwrot kodu przez funkcje */ switch (message) { case WM_CREATE: Gdy okno jest tworzone Windows przesyłaj jeden raz komunikat WM_CREATE do okna. Procedura obsługi nowego okna (new window procedure) otrzymuje ten komunikat po utworzeniu okna, ale jeszcze zanim okno pojawi sie na ekranie. lParam - Wska�nik do struktury CREATESTRUCT o postaci: typedef struct { LPSTR lpCreateParams; HANDLE hInst; HANDLE hMenu; HWND hwndParent; int cy; int cx; int y; int x; LONG style; LPSTR lpszName; LPSTR lpszClass; DWORD dwExStyle; } CREATESTRUCT; */ Kod obsługi powiekszania/zmniejszania case WM_SIZE. wParam zawiera kod operacji - zmniejsz/powi�ksz lParam zawiera now wysoko�� i szeroko�� okna case WM_PAINT: Pobranie kontekstowego identyfikatora urz dzenia. Funkcja BeginPaint() spowoduje w razie potrzeby wysłanie komunikatu WM_ERASEBKGND (Erase Background - skasuj tło). memset(&ps, 0x00, sizeof(PAINTSTRUCT)); hDC = BeginPaint(hWnd, &ps); Set Background Mode - ustaw rodzaj tła (tu: przezroczyste): SetBkMode(hDC, TRANSPARENT); Aplikacja powinna wykre�li� obszar roboczy okna posługuj c sie grafik GDI i (Graficzny Interfejs Urz dzenia - analogia do graficznego standardu BGI w �rodowisku DOS). Struktura ps typu PAINSTRUCT zwrócona przez BeginPaint() wskazuje prostok t do zamalowania. Wypisanie tekstu w głównym oknie aplikacji: TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello, world.")); Funkcja TextOut() pracuje w trybie graficznym, wi�c (podobnie jak inne funkcje graficzne Windows API) otrzymuje jako argument tzw. "kontekst urz dzenia" - hDC.

Page 124: C++ - lekcje

124

Zamykanie okna: case WM_CLOSE: DestroyWindow(hWnd); if (hWnd == hWndMain) PostQuitMessage(0); Je�li zamkni�te zostało główne okno aplikacji, funkcja PostQuitMessage() wysyła do Windows komunikat, �e aplikacja zako�czyła działanie i okno aplikacji zostało usuni�te. W tym stadium stosuje si� funkcje PostQuitMessage() i PostAppMessage(). Pozostale przypadki s obsługiwane przez wariant domy�lny - default. Przekazanie komunikatu do obsługi przez Windows. default: return (DefWindowProc(hWnd, Message, wParam, lParam)); Funkcja rejestruj ca wszystkie klasy wszystkich okien zwi zanych z bie� c aplikacja (nazwiemy j roboczo FRegisterClasses()). Jesli operacja sie powiodła - funkcja zwraca kod bł�du. int FRegisterClasses(void) { WNDCLASS wndclass; /* Struktura do definiowania klas okien. */ memset(&wndclass, 0x00, sizeof(WNDCLASS)); Ustawienie parametrów okna w strukturze: wndclass.style = CS_HRDRAW | CS_VRDRAW; wndclass.lpfnWindowProc = WindowProc; Dodatkowa pami�� dla klasy Window i obiektów klasy Window. Doł czanie innych zasobów odbywa si� przy pomocy funkcji: LoadBitmap() - załaduj map� bitow LoadIcon() - załaduj ikonk� LoadCurcor(), LoadMenu(), itp. ... wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInst; wndclass.hIcon = LoadIcon(NULL, ID_ICON); wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); Utworzenie p�dzla (brush) dla tła: wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wndclass.lpszMenuName = szAppName; wndclass.lpszClassName = szAppName; if (!RegisterClass(&wndclass)) return -1; } Typowe obiekty ze składu Windows to HBRUSH P�dzel; i HPEN Ołówek; Nale�y tu zwróci� uwag� jeszcze na dwa szczegóły techniczne. DC i GDI - Device Context, Graphics Device Interface - to tzw. kontekst urz dzenia i graficzny interfejs urz dzenia. Pozwala to Windows działa� skutecznie w trybie "Device Independent" (niezale�nym od sprz�tu). LEKCJA 42: KOMPILATORY "SPECJALNIE DLA Windows". ________________________________________________________________ Z tej lekcji dowiesz si�, czym ró�ni si� kompilatory przeznaczone dla pracy w �rodowisku Windows. ________________________________________________________________ W IDE i w sposobie zachowania zaszły istotne zmiany. Posługuj c si� Turbo C++ z pakietu BORLAND C++ 3.0 lub BCW z pakietu 3.1 mo�emy korzysta� z uroków i usług Windows szerzej ni� do tej pory. Mo�emy otwiera� wiele okien i uruchamia� bezpo�rednio z poziomu IDE okienkowe aplikacje. W głównym menu kompilatora zaszły pewne zmiany (sygnalizuj ce obiektowo- i okienkowo - zorientowan ewolucj� pakietów Borlanda), na które warto zwróci� uwag�. Znikn�ło menu Debug (co wcale nie oznacza, �e nie mo�emy

korzysta� z Debuggera), pojawiło si� natomiast nowe menu Browse (przegl danie). Rozkazy, których tradycyjnie szukali�my w menu Debug zostały rozrzucone do innych menu. I tak: Menu Compile zawiera: Compile (kompilacja do *.OBJ), Make (kompilacja i konsolidacja do *.EXE), Link (konsolidacja bez powtórnej kompilacji), Build all (konsolidacja wszystkich modułów), Information... (informacja o przebiegu kompilacji), Remove messages (usuwanie komunikatów z pliku wynikowego) Menu Run zawiera: Run (uruchomienie i ewentualna rekompilcja), Arguments... (argumenty uruchomieniowe z wiersza rozkazu), Debugger (zamiast w Debug - TU!) Debugger arguments... (argumenty dla Debuggera) Menu Project zawiera: Open project - otwórz (nowy lub istniej cy) plik projektu, Close project - zamknij projekt, Add item... - dodaj element (plik) do projektu, Delete item - usu� element (plik) z projektu, Include ˙˙files... ˙˙- ˙˙podaj ˙katalog ˙zawieraj cy ˙dodatkowe doł czane do programu pliki nagłówkowe *.H W menu Options (zestaw znany ju� z Borland C++) warto zwróci� uwag� na pewn dodatkow mo�liwo��. Jak wiemy z do�wiadczenia, uruchamiaj c program cz�sto dokonujemy zmian i korekt w pliku �ródłowym *.C, czy *.CPP. Znacznie rzadziej jednak zmieniamy zestaw doł czanych do programu plików nagłówkowych *.H. Wiemy równie�, �e kompilacja tych wła�nie plików nagłówkowych zajmuje cz�sto lwi cz��� czasu całej kompilacji i konsolidacji programu. Borland zauwa�ył to i w okienku dialogowym: Options | Compiler | Code generation --> Code Generation Options umie�cił opcj� Pre-compiled headers (pliki nagłówkowe wst�pnie skompilowane wcze�niej - i tylko jeden raz). Szczególnie w przypadku aplikacji okienkowych mo�e to znacznie przyspieszy� proces uruchamiania i "szlifowania" naszych programów. Nie ma jednak nic za darmo. Borland/Turbo C++ po skompilowaniu plików nagłówkowych tworzy na dysku roboczy plik *.SYM nadaj c mu nazw� zgodn z nazw bie� cego projektu (jest to zwykle nazwa głównego modułu *.CPP) i do poprawnego działania wymaga kilkadziesi t lub nawet kilkaset kilobajtów dodatkowej przestrzeni na dysku. [!!!]UWAGA ________________________________________________________________ Je�li przenosisz projekt na dyskietk� i tam kontynuujesz prac� nad projektem, pami�taj, �e mo�e zabrakn � miejsca na prekompilowany plik .SYM. ________________________________________________________________ Czytelnik zechce sam sprawdzi� w jakim stopniu przyspieszy to kompilacj� naszego własnego programu proceduralno - zdarzeniowego WINPZ1.CPP: WINZ1.CPP. Jednomodułowa aplikacja proceduralno - zdarzeniowa dla Windows. ________________________________________________________________ #include <windows.h> #pragma argused long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) { WNDCLASS Okno1; MSG komunikaty; HWND NrOkna;

Page 125: C++ - lekcje

125

LPSTR LongPtr1 = "Okno 1"; LPSTR lp2 = "AM: BC++ 3..4/Reczne sterowanie (1)"; if (hPrevInstance == 0) { Okno1.style= CS_HREDRAW | CS_VREDRAW ; Okno1.lpfnWndProc= WndProc; Okno1.cbClsExtra = 0; Okno1.cbWndExtra= 0; Okno1.hInstance = hInstance; Okno1.hCursor = LoadCursor(0, IDC_CROSS ); Okno1.hbrBackground= GetStockObject(WHITE_BRUSH ); Okno1.lpszMenuName= 0; Okno1.lpszClassName= LongPtr1; if (!RegisterClass(&Okno1)) return 0; } NrOkna = CreateWindow(LongPtr1, lp2, WS_VISIBLE | WS_SYSMENU | WS_MINIMIZEBOX | WS_VSCROLL | WS_MAXIMIZEBOX, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0); ShowWindow(NrOkna, nCmdShow); UpdateWindow(NrOkna); while (GetMessage(&komunikaty, 0, 0, 0)) { TranslateMessage(&komunikaty ); DispatchMessage(&komunikaty ); } return 0; } long FAR PASCAL WndProc (HWND NrOkna, unsigned KomunikatWindows, WORD wParam, LONG lParam) { HDC NrKontekstu; PAINTSTRUCT struktura_graficzna; RECT prostokat; switch(KomunikatWindows) { case WM_PAINT: { NrKontekstu = BeginPaint(NrOkna, &struktura_graficzna); GetClientRect(NrOkna, &prostokat); TextOut(NrKontekstu,80,50, ": Reczne sterowanie:", 20 ); TextOut(NrKontekstu, 5,70, "Tu -->", 6); TextOut(NrKontekstu, 5, 85, "Blad:", 5); TextOut(NrKontekstu,75,70, "-----------------------------", 40); TextOut(NrKontekstu,30,110, "Programowanie proceduralno - zdarzeniowe.", 41 ); TextOut(NrKontekstu,30,135, "Szablon moze zostac rozbudowany o inne funkcje.", 47 ); TextOut(NrKontekstu,30,180, "RECZNIE panujemy np. nad:", 25 ); TextOut(NrKontekstu,20,220, "paskiem tytulowym okna, tytulem ikonki...", 41); TextOut(NrKontekstu, 100, 250, "!KONIEC - [Alt]+[F4]", 20); EndPaint(NrOkna,&struktura_graficzna); break; } case WM_DESTROY: { PostQuitMessage(0); break; } default: return DefWindowProc(NrOkna,KomunikatWindows,wParam,lParam); } return 0; }

Program demonstruje opisane wy�ej mechanizmy, mo�e by� uruchamiany wielokrotnie i sprowadzony do ikony. Z uwagi na brak zdefiniowanych dodatkowych zasobów (brak w projekcie plików: .RC - resources - zasoby .ICO - ikona .DEF - definicji .PRJ lub .IDE - projektu .DSK - konfiguracyjnego itp.) podczas kompilacji programu wyst pi dwa komunikaty ostrzegawcze. Komunikaty te mo�na zignorowa�. A oto druga przykładowa aplikacja w tym samym stylu. Tym razem funkcja okienkowa reaguje na naci�ni�cie lewego klawisza myszki, co powoduje wygenerowanie komunikatu WM_LEFTBUTTONDOWN. Program WINZ-2.CPP ________________________________________________________________ #include <windows.h> #include <string.h> #pragma argused char napis[10]; int X, Y; LONG FAR PASCAL WndProc (HWND, WORD, WORD, LONG); int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) { WNDCLASSwndClass; MSGmsg; HWNDhWnd; LPSTR Lp1 = "Mysza1"; LPSTR lp2 = "WINPZ2: Wykrywanie Lewego Klawisza Myszki"; if (!hPrevInstance) { wndClass.style= CS_HREDRAW | CS_VREDRAW ; wndClass.lpfnWndProc= WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra= 0; wndClass.hInstance = hInstance; wndClass.hIcon = 0; wndClass.hCursor= LoadCursor(0, IDC_ARROW ); wndClass.hbrBackground= GetStockObject(WHITE_BRUSH ); wndClass.lpszMenuName= 0; wndClass.lpszClassName= Lp1; if (!RegisterClass(&wndClass)) exit(1); } hWnd = CreateWindow(Lp1, lp2, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg ); DispatchMessage(&msg ); } return 0; } LONG FAR PASCAL WndProc (HWND hWnd, WORD Message, WORD wParam, LONG lParam) { HDC hDC; PAINTSTRUCT ps; RECT rect; switch(Message) { case WM_SIZE: hDC = GetDC( hWnd );

Page 126: C++ - lekcje

126

TextOut(hDC, 50, 100, "Wykrywanie nacisniecia", 22); TextOut(hDC, 50, 120, "lewego klawisza myszki.", 23); TextOut(hDC, 20, 140, "Komunikat o zdarzeniu: ", 22); TextOut(hDC, 20, 156, "Left Button Down - LBUTTONDOWN", 31); TextOut(hDC, 50, 170, "Po wcisnieciu klawisza,", 23); TextOut(hDC, 50, 190,"w biezacej pozycji kursora, pojawi sie napis <-- Tu!.", 52); ReleaseDC(hWnd, hDC); break; case WM_PAINT: hDC = BeginPaint(hWnd, &ps); TextOut(hDC, X,Y, napis, strlen(napis)); EndPaint(hWnd, &ps); break; case WM_LBUTTONDOWN: strcpy(napis,"<-- Tu !"); X = LOWORD(lParam); Y = HIWORD(lParam); InvalidateRect(hWnd, 0, TRUE); UpdateWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, Message, wParam, lParam); } return 0; } Plik nagłówkowy STRING.H pojawia si� ze wzgl�du na obecno�� funkcji strlen() wyznaczaj cej długo�� napisu. Zmienne X i Y to bie� ce (wzgl�dne) współrz�dne kursora myszki w momencie naci�ni�cia klawisza. Program demonstruje nast�puj ce efekty: X = LOWORD(lParam); - przekazanie współrz�dnej X przy pomocy parametru lParam (LOWORD to LOw WORD of the double word - młodsze słowo podójnego słowa). Y = HIWORD(lParam); Analogicznie - przekazanie współrz�dnej Y (HIgh WORD of the double word). Funkcja InvalidateRect() powoduje uznanie prostok nego pola za nieaktualne. Funkcja UpdateWindow() "od�wie�a" okno. Dzi�ki temu tandemowi napis znika i pojawia si� w nowym miejscu. PROJEKT. Aby skompilowa� powy�sze programy przykładowe nale�y: 1. Uruchomi� kompilator C++. 2. Załadowa� do okienka edycyjnego (File | Open) plik z tekstem �ródłowym programu. 3. Wybra� rozkaz Compile z menu Compile. Przed kompilacj i konsolidacj (je�li był inny) ustawi� sposób tworzenia kodu wynikowego [Windows EXE]. Kompilacja przebiegnie poprawnie (pami�taj o Opcjach i Katalogach), mimo to pojawi si� jednak dwa komunikaty ostrzegawcze. W okienku "Compile Status" (stan/przebieg kompilacji) pojawi si� zawarto��: Lines 3832 (znakomita wi�kszo�� to WINDOWS.H, prekompilacja byłaby celowa) Warnings: 1 Errors: 0 Je�li wybierzesz klawisz [OK] w okienku "focus" (aktywno��) zostanie przekazana do okienka komunikatów "Message" a tam pojawi si� napis: Warning: Parameter 'lspzCmdLine' is never used.

Wska�nik do parametrów uruchomieniowych programu (Arguments) pobieranych z wiersza rozkazu nie został ani raz u�yty w programie. Na to nic nie mo�emy poradzi�. Po prostu argumenty uruchomieniowe nie s nam potrzebne. Wykonujemy wi�c "klik" (przekazanie "focusa") w okienku edycyjnym i mo�emy przej�� do nast�pnej czynno�ci: 4. Konsolidacja: Compile | Link. W okienku "Message" znów pojawi si� ostrze�enie: Linker Warning: No module definition file specified: using defaults. (brak wyspecyfikowanego pliku definicji .DEF; stosuj� warto�ci domy�lne) I tu ju� mo�emy co� zaradzi�. Mo�emy zatem pokusi� si� o stworzenie naszego pierwszego pliku definicji (nazwa jest troch� myl ca - chodzi o zdefiniowanie sposobu wykorzystania zasobów �rodowiska Windows). Aby utworzy� plik .DEF (jest to plik ASCII) nale�y: 1. Otworzy� nowe okienko edycyjne (nie wychodz c z IDE): File | New Otworzy si� okienko NONAMExx.CPP. Ta nazwa nie jest oczywi�cie najodpowiedniejsza, wi�c umieszczamy plik we wła�ciwym katalogu (tym samym, co główny program *.CPP) przy pomocy rozkazu File | Save as... i nadajemy plikowi stosown nazw� i rozszerzenie *.DEF. Okieno pozostaje puste, ma jednak "focus" i now nazw�, np. C:\..\PR.DEF. 3. Redagujemy nasz pierwszy plik definicji, np. tak: NAME JAKAKOLWIEK // <-- nazwa aplikacji DESCRIPTION 'Opis: A. MAJCZAK, BC C++ 3...4' EXETYPE WINDOWS // <-- EXE dla Windows CODE PRELOAD MOVEABLE DISCARDABLE DATA PRELOAD MOVEABLE MULTIPLE HEAPSIZE 4096 // <-- sterta 4 KB STACKSIZE 5120 // <-- stos 5 KB _______________________________________________________________ UWAGA: W przypadku tworzenia bibliotek .DLL dane musz mie� status SINGLE (pojedyncze) zamiast MULTIPLE (wielokrotne). U�ycie tu słowa MULTIPLE pozwoli nam na wielokrotne uruchamianie aplikacji. ________________________________________________________________ Mo�naby tu zapyta� - po co to robi�, skoro u�ywamy standardowych warto�ci i obecno�� tego pliku nie wnosi nic nowego do sposobu działania naszego programu? Odpowied� jest prosta. Maj c taki plik b�dziemy mogli prze�ledzi� stadia tworzenia tzw. projektu (w BC++ 4 bez tego ani rusz). Zapisujemy zatem plik na dysk: 4. File | Save. (plik .DEF zostaje zapisany na dysku). Poniewa� pracujemy w �rodowisku Windows, okno edycji pliku *.DEF mo�emy traktowa� podobnie jak ka�de inne okno. Najwygodniej zatem przej�� do okna edycji głównego pliku �ródłowego *.CPP przy pomocy własnego menu systemowego tego� okna. 5. Menu Systemowe [-] | Zamknij. I mo�emy przyst pi� do tworzenia projektu składaj cego si� z dwu plików: *.CPP i *.DEF. Je�li, dla przykładu, przyjmiemy w tym miejscu, �e nasze dwa moduły nazywaj si�: WINZ2.CPP i WINZ2.DEF i s przechowywane w katalogu głównym dysku C:\ , kolejno�� czynno�ci powinna by� nast�puj ca: 1. Rozwijamy menu Project ([Alt]+[P] lub myszk ). 2. Wybieramy z menu rozkaz Open Project... (Utwórz projekt). Pojawia si� okienko dialogowe Open Project File z domy�lnym rozszerzeniem *.PRJ (w BC 4+ - *.IDE). 3. Do okienka File Name: wpisujemy nazw� pliku z opisem projektu: np. WINZ2.PRJ. W dolnej cz��ci ekranu otwiera si� okienko

Page 127: C++ - lekcje

127

Project: WINZ2 4. Wybieramy z menu Project rozkaz Add item... (dodaj element do projektu). Pojawia si� okienko dialogowe "Add to Project List" (dodawanie do listy elementów projektu). 5. Do okienka File Name: wpisujemy nazw� głównego pliku projektu: WINZ2.CPP (*.cpp jest domy�lnym rozszerzeniem). Plik mo�emy wybra� tak�e z listy w okienku Files: . 6. Wybieramy w okienku dialogowym klawisz [+Add] (dodaj do projektu). 7. Wpisujemy nazw� kolejnego pliku wchodz cego w skład projektu (w tym przypadku WINZ2.DEF). 8. Wybieramy klawisz [+Add] w okienku. UWAGA: Czynno�ci 7) i 8) w przypadku bardziej zło�onych projektów b�d powtarzane wielokrotnie. 9. Wybieramy klawisz [Done] w okienku (zrobione/gotowe). Konfigurowanie projektu zostało zako�czone. 10. Przy pomocy rozkazów Compile, Link, Make, Build all, Run mo�emy teraz skompilowa�, skonsolidowa� i uruchomi� nasz program w postaci projektu. Ostrze�enie Linkera zniknie. [!!!]UWAGA ________________________________________________________________ W dolnej cz��ci ekranu w stadium tworzenia projektów ( i pó�niej po załadowaniu pliku projektu [Open Project] pojawi si� lista plików. Do trybu edycji pliku mo�esz przj�� poprzez dwukrotne klini�cie pliku na tej li�cie. Zwró� uwag�, �e pliki projektów .PRJ ( w Borland 4+ .IDE) przechowuj równie� informacje o konfiguracji. Najwa�niejsza z nich to informacja o katalogach, z których korzysta kompilator: Options | Directories... | Include Options | Directories... | Library Options | Directories... | Output ________________________________________________________________ Najwygodniej przechowywa� wszystkie pliki wchodz ce w skład jednego projektu w odr�bnym katalogu dyskowym. Dla wprawy załó� odr�bny katalog i zapisz tam pliki: *.CPP *.DEF *.PRJ (lub *.IDE) dla swoich pierwszych dwóch projektów, które wła�nie powstały. [!!!] UWAGA ________________________________________________________________ Ten sam plik definicji mo�esz wykorzystywa� do tworzenia nast�pnych przykładowych aplikacji typu Windows EXE. ________________________________________________________________ LEKCJA 43: Elementy steruj ce i zarz dzanie programem. ________________________________________________________________ Jak sterowa� prac aplikacji. Jak umieszcza� elementy graficzne-steruj ce w oknie aplikacji. Najcz��ciej stosowane funkcje API Windows. ________________________________________________________________ Elementy steruj ce prac aplikacji w Windows (ang. controls) s równie� swoistymi okienkami (tyle, �e potomnymi - Child Window wobec głównego okna aplikacji - Parent Window). Do utworzenia takiego specjalnego okna równie� mo�na u�y� funkcji CreateWindow(). Je�li okno ma sta� si� nie głównym oknem aplikacji, lecz oknem steruj cym przebiegiem programu, funkcja wymaga podania nast�puj cych argumentów: - rodzaj klasy steruj cej (ang. control class) - rodzaj elementu steruj cego (ang. control style) Typowe rodzaje elementów (obiektów) staruj cych w �rodowisku Windows: BUTTON - klawisz rozkazu, prostok tne okno typu Child, reprezentuj ce przycisk, który u�ytkownik mo�e wł czy�; przycisk mo�e by� opatrzony etykiet (text label).

COMBOBOX - okienko dialogowe kombinowane. Jest zło�eniem klasy EDIT i LISTBOX; LISTBOX - oknienko z list (zwykle element składowy okienka dialogowego typu Combo Box. STATIC - pole statyczne (bez prawa edycji). Niewielkie okno zawieraj ce tekst lub grafik�; słu�y z reguły do oznaczania innych okien steruj cych. SCROLLBAR - pasek przewijania (pionowy - Vertical Scroll Bar; poziomy - Horizontal Scroll Bar). Style klawiszy steruj cych (Button Styles): BS_PUSHBUTTON - Klawisz. Okno steruj ce wysyła, po ka�dym wyborze klawisza (klikni�cie), wiadomo�� do okna macie�ystego (Parent Window). BS_RADIOBUTTON - Okr gły przeł cznik działaj cy zwykle na zasadzie @tylko jeden z grupy". BS_CHECKBOX - - prostok tny przeł cznik [X] wł czaj cy (aktywna) lub wył czaj cy (nieaktywna) opcj�. Działa niezale�nie od pozostałych. Inne style okre�laj np. sposób edycji tekstu (ES_LEFT, ES_MULTILINE, itp.) Szczegóły - patrz system Help - Windows API. Oto przykład utworzenia okna elementu steruj cego typu "Klawisz" (BUTTON to nazwa typu): hControlWnd = CreateWindow ("BUTTON", " Napis_na_Klawiszu ", BS_PUSHBUTTON |WS_CHILD | WS_VISIBLE, 10, 20, 30, 40, hWnd, ID_Elem, hInstance, 0); Identyfikator ID_Elem jest potrzebny, gdy w jednym oknie znajduje si� kilka elementów steruj cych - pozwala na ich rozpoznawanie w programie. Sposób przekazywania informacji o klikni�ciu klawisza przypomn� na przykładzie okienka komunikatów: if(IDOK==MessageBox(0, "", "", MB_OK)) ... IDOK to predefiniowany w Windows identyfikator klawisza [OK]. Oto krótkie wyja�nienie pozostałych elementów: 10, 10, 30, 20, - współrz�dne. x, y, szeroko��, wysoko�� hWnd, - oznacznik okna macie�ystego Przesuwanie i zmiana wielko�ci elementu steruj cego. Funkcja MoveWindow() przesuwa okno we wskazane miejsce: MoveWindow(hKlawisz, 10, 10, 20, 30, TRUE); Poniewa� okno elementu steruj cego ma zadane wzgl�dne współrz�dne w oknie macie�ystym, gdy okno macierzyste zostanie przesuni�te - element steruj cy b�dzie przesuni�ty automatycznie. Równie� po zmianie rozmiarów okna macie�ystego okno elementu steruj cego zmienia poło�enie, zawsze jednakowe wzgl�dem lewego górnego rogu. Usuwanie okna steruj cego Okienko elementu steruj cego mo�emy usun � (jak i ka�de inne okna) przy pomocy funkcji: DestroyWindow(hKlawisz);

Page 128: C++ - lekcje

128

Przekazywanie informacji do- i z- okna elementu steruj cego Zdarzenie w oknie elementu steruj cego - np. klikni�cie klawisza - powoduje wygenerowanie komunikatu WM_COMMAND. Towarzysz ce komunikatowi parametry przenosz istotne informacje: wParam - identyfikator elementu steruj cego, lParam - dla wci�ni�tego klawisza b�dzie to BN_CLICKED. Niektóre komunikaty Windows mog by� kierowane do okna elementu steruj cego i wymusza� pewne operacje. Dla przykładu komunikat WM_GETTEXTLENGTH przesłany do okienka edycyjnego typu Text Edit Box (element steruj cy klasy EDIT) jest � daniem podania długo�ci tekstu wpisanego wła�nie do okienka. Aby Windows wygenerowały komunikat i przesłały go do naszego elementu steruj cego - musimy "poprosi�" przy pomocy funkcji SendMessage() (Wy�lijKomunikat): DlugTekstu = SendMessage(hEditWnd, WM_GETTEXTLENGHT, 0, 0); gdzie: hEditWnd jest identyfikatorem elementu - okienka edycyjnego [???]Robi na "szaro'? ________________________________________________________________ Podobnie jak opcje w menu - klawisze tak�e mog zosta� udost�pnione (ang. enable), b d� zablokowane (ang. disable). Je�li hKlawisz b�dzie identyfikatorem elementu steruj cego, mo�na go udost�pni� (1), b d� zablokowa� (0) przy pomocy funkcji: EnableWindow(hKlawisz, 0); EnableWindow(hKlawisz, 1); ________________________________________________________________ Typowy projekt dla �rodowiska Windows składa si� z kilku (czasem kilkunastu) plików: .H, .MNU, .DLG, .RC, .DEF, .PRJ, .ICO, .BMP, itp. Kompilator zasobów generuje na podstawie tego "składu" ko�cowy plik aplikacji. ------------------Plik MEDYT-01.H------------------------------- #define szAppName "MEDYT-01" #define ID_EDIT 200 ------------------Plik główny: MEDYT-01.CPP--------------------- #include <windows.h> #include "EDIT.H" #pragma argused HWND hEditWnd; long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { WNDCLASS wndClass; MSG msg; HWND hWnd; RECT rect; if ( !hPrevInstance ) { wndClass.style= CS_HREDRAW | CS_VREDRAW ; wndClass.lpfnWndProc= WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra= 0; wndClass.hInstance = hInstance; wndClass.hIcon = LoadIcon(NULL, szAppName); wndClass.hCursor= LoadCursor(NULL, IDC_CROSS); wndClass.hbrBackground= GetStockObject(WHITE_BRUSH ); wndClass.lpszMenuName= NULL; wndClass.lpszClassName= szAppName; if (!RegisterClass(&wndClass)) return 0; } hWnd = CreateWindow(szAppName, "MEDYT-01", WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0); GetClientRect(hWnd, (LPRECT) &rect); hEditWnd = CreateWindow ("Edit",NULL, WS_CHILD | WS_VISIBLE | ES_MULTILINE | WS_VSCROLL | WS_HSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0, 0,(rect. right - rect. left), (rect. bottom - rect. top),hWnd,IDC_EDIT, hIstance,NULL); if( ! hEditWnd ) { DestroyWindow(hWnd); return 0; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg ); DispatchMessage(&msg ); } return 0; } long FAR PASCAL WndProc (HWND hWnd, unsigned Message, WORD wParam, LONG lParam) { switch(Message) { case ID_EDIT: if(HIWORD(lParam)==EN_ERRSPACE) /* starsze słowo lParam zawiera wła�ciwe dla okna edycyjnego wiadomo�ci, je�eli jest to EN_ERRSPACE - okno steruj ce nie mo�e alokowa� dodatkowego obszaru pami�ci */ { MessageBox (GetFocus(), "Brak pamieci", "MEDYT-01", MB_ICONSTOP | MB_OK); } break; case WM_SETFOCUS: SetFocus(hEditWnd); break; /* Pierwsze dwa parametry funkcji MoveWindow s ustawione na zero, dzi�ki temu po zastosowaniu tej funkcji nie zmieni si� wzajemne poło�enie obu okien, a jedynie uaktualnianiu ulegnie okno steruj ce. */ case WM_SIZE: MoveWindows(hEditWnd, 0, 0, LOWORD(lParam)); HIWORD(lParam), TRUE); break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd,Message,wParam,lParam)); } return 0; } Jak sterowa� prac aplikacji. Jak umieszcza� elementy graficzne-steruj ce w oknie aplikacji. Najcz��ciej stosowane funkcje API Windows. ________________________________________________________________ Elementy steruj ce prac aplikacji w Windows (ang. controls) s równie� swoistymi okienkami (tyle, �e potomnymi - Child Window wobec głównego okna aplikacji - Parent Window). Do utworzenia takiego specjalnego okna równie� mo�na u�y� funkcji CreateWindow(). Je�li okno ma sta� si� nie głównym oknem aplikacji, lecz oknem steruj cym przebiegiem programu, funkcja wymaga podania nast�puj cych argumentów: - rodzaj klasy steruj cej (ang. control class) - rodzaj elementu steruj cego (ang. control style)

Page 129: C++ - lekcje

129

Typowe rodzaje elementów (obiektów) staruj cych w �rodowisku Windows: BUTTON - klawisz rozkazu, prostok tne okno typu Child, reprezentuj ce przycisk, który u�ytkownik mo�e wł czy�; przycisk mo�e by� opatrzony etykiet (text label). COMBOBOX - okienko dialogowe kombinowane. Jest zło�eniem klasy EDIT i LISTBOX; LISTBOX - oknienko z list (zwykle element składowy okienka dialogowego typu Combo Box. STATIC - pole statyczne (bez prawa edycji). Niewielkie okno zawieraj ce tekst lub grafik�; słu�y z reguły do oznaczania innych okien steruj cych. SCROLLBAR - pasek przewijania (pionowy - Vertical Scroll Bar; poziomy - Horizontal Scroll Bar). Style klawiszy steruj cych (Button Styles): BS_PUSHBUTTON - Klawisz. Okno steruj ce wysyła, po ka�dym wyborze klawisza (klikni�cie), wiadomo�� do okna macie�ystego (Parent Window). BS_RADIOBUTTON - Okr gły przeł cznik działaj cy zwykle na zasadzie @tylko jeden z grupy". BS_CHECKBOX - - prostok tny przeł cznik [X] wł czaj cy (aktywna) lub wył czaj cy (nieaktywna) opcj�. Działa niezale�nie od pozostałych. Inne style okre�laj np. sposób edycji tekstu (ES_LEFT, ES_MULTILINE, itp.) Szczegóły - patrz system Help - Windows API. Oto przykład utworzenia okna elementu steruj cego typu "Klawisz" (BUTTON to nazwa typu): hControlWnd = CreateWindow ("BUTTON", " Napis_na_Klawiszu ", BS_PUSHBUTTON |WS_CHILD | WS_VISIBLE, 10, 20, 30, 40, hWnd, ID_Elem, hInstance, 0); Identyfikator ID_Elem jest potrzebny, gdy w jednym oknie znajduje si� kilka elementów steruj cych - pozwala na ich rozpoznawanie w programie. Sposób przekazywania informacji o klikni�ciu klawisza przypomn� na przykładzie okienka komunikatów: if(IDOK==MessageBox(0, "", "", MB_OK)) ... IDOK to predefiniowany w Windows identyfikator klawisza [OK]. Oto krótkie wyja�nienie pozostałych elementów: 10, 10, 30, 20, - współrz�dne. x, y, szeroko��, wysoko�� hWnd, - oznacznik okna macie�ystego Przesuwanie i zmiana wielko�ci elementu steruj cego. Funkcja MoveWindow() przesuwa okno we wskazane miejsce: MoveWindow(hKlawisz, 10, 10, 20, 30, TRUE); Poniewa� okno elementu steruj cego ma zadane wzgl�dne współrz�dne w oknie macie�ystym, gdy okno macierzyste zostanie przesuni�te - element steruj cy b�dzie przesuni�ty automatycznie. Równie� po zmianie rozmiarów okna macie�ystego okno elementu steruj cego zmienia poło�enie, zawsze jednakowe wzgl�dem lewego górnego rogu.

Usuwanie okna steruj cego Okienko elementu steruj cego mo�emy usun � (jak i ka�de inne okna) przy pomocy funkcji: DestroyWindow(hKlawisz); Przekazywanie informacji do- i z- okna elementu steruj cego Zdarzenie w oknie elementu steruj cego - np. klikni�cie klawisza - powoduje wygenerowanie komunikatu WM_COMMAND. Towarzysz ce komunikatowi parametry przenosz istotne informacje: wParam - identyfikator elementu steruj cego, lParam - dla wci�ni�tego klawisza b�dzie to BN_CLICKED. Niektóre komunikaty Windows mog by� kierowane do okna elementu steruj cego i wymusza� pewne operacje. Dla przykładu komunikat WM_GETTEXTLENGTH przesłany do okienka edycyjnego typu Text Edit Box (element steruj cy klasy EDIT) jest � daniem podania długo�ci tekstu wpisanego wła�nie do okienka. Aby Windows wygenerowały komunikat i przesłały go do naszego elementu steruj cego - musimy "poprosi�" przy pomocy funkcji SendMessage() (Wy�lijKomunikat): DlugTekstu = SendMessage(hEditWnd, WM_GETTEXTLENGHT, 0, 0); gdzie: hEditWnd jest identyfikatorem elementu - okienka edycyjnego [???]Robi na "szaro'? ________________________________________________________________ Podobnie jak opcje w menu - klawisze tak�e mog zosta� udost�pnione (ang. enable), b d� zablokowane (ang. disable). Je�li hKlawisz b�dzie identyfikatorem elementu steruj cego, mo�na go udost�pni� (1), b d� zablokowa� (0) przy pomocy funkcji: EnableWindow(hKlawisz, 0); EnableWindow(hKlawisz, 1); ________________________________________________________________ Typowy projekt dla �rodowiska Windows składa si� z kilku (czasem kilkunastu) plików: .H, .MNU, .DLG, .RC, .DEF, .PRJ, .ICO, .BMP, itp. Kompilator zasobów generuje na podstawie tego "składu" ko�cowy plik aplikacji. ------------------Plik MEDYT-01.H------------------------------- #define szAppName "MEDYT-01" #define ID_EDIT 200 ------------------Plik główny: MEDYT-01.CPP--------------------- #include <windows.h> #include "EDIT.H" #pragma argused HWND hEditWnd; long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { WNDCLASS wndClass; MSG msg; HWND hWnd; RECT rect; if ( !hPrevInstance ) { wndClass.style= CS_HREDRAW | CS_VREDRAW ; wndClass.lpfnWndProc= WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra= 0; wndClass.hInstance = hInstance; wndClass.hIcon = LoadIcon(NULL, szAppName); wndClass.hCursor= LoadCursor(NULL, IDC_CROSS); wndClass.hbrBackground= GetStockObject(WHITE_BRUSH ); wndClass.lpszMenuName= NULL;

Page 130: C++ - lekcje

130

wndClass.lpszClassName= szAppName; if (!RegisterClass(&wndClass)) return 0; } hWnd = CreateWindow(szAppName, "MEDYT-01", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0); GetClientRect(hWnd, (LPRECT) &rect); hEditWnd = CreateWindow ("Edit",NULL, WS_CHILD | WS_VISIBLE | ES_MULTILINE | WS_VSCROLL | WS_HSCROLL | ES_AUTOHSCROLL | ES_AUTOVSCROLL, 0, 0,(rect. right - rect. left), (rect. bottom - rect. top),hWnd,IDC_EDIT, hIstance,NULL); if( ! hEditWnd ) { DestroyWindow(hWnd); return 0; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg ); DispatchMessage(&msg ); } return 0; } long FAR PASCAL WndProc (HWND hWnd, unsigned Message, WORD wParam, LONG lParam) { switch(Message) { case ID_EDIT: if(HIWORD(lParam)==EN_ERRSPACE) /* starsze słowo lParam zawiera wła�ciwe dla okna edycyjnego wiadomo�ci, je�eli jest to EN_ERRSPACE - okno steruj ce nie mo�e alokowa� dodatkowego obszaru pami�ci */ { MessageBox (GetFocus(), "Brak pamieci", "MEDYT-01", MB_ICONSTOP | MB_OK); } break; case WM_SETFOCUS: SetFocus(hEditWnd); break; /* Pierwsze dwa parametry funkcji MoveWindow s ustawione na zero, dzi�ki temu po zastosowaniu tej funkcji nie zmieni si� wzajemne poło�enie obu okien, a jedynie uaktualnianiu ulegnie okno steruj ce. */ case WM_SIZE: MoveWindows(hEditWnd, 0, 0, LOWORD(lParam)); HIWORD(lParam), TRUE); break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd,Message,wParam,lParam)); } return 0; } LEKCJA 44: O Okienkach dialogowych. ________________________________________________________________ O tym, jak konstruuje si� okienka dialogowe. ________________________________________________________________ Do wy�wietlania okienek dialogowych w Windows API słu�y funkcja

DialogBox(), a do zako�czenia ich "�ycia na ekranie" - EndDialog(). Podobnie jak ka�de okno, równie� okno dialogowe musi mie� swoj funkcj�, obsługi komunikatów Windows. Zamiast WindowProc() nazywa si� j tradycyjnie DlgProc(): BOOL FAR PASCAL DlgProc(HWND hDLG, unsigned Message, WORD wParam, LONG lParam); { switch (message) { ... default: return (0); } } Za wyj tkiem braku domy�lnego handlera Windows - DefWindowProc(), który jest zb�dny, w zwi zku z wewn�trznie przyjmowanymi warto�ciami domy�lnymi, funkcja podobna jest bardzo w swojej konstrukcji do WindowProc(). Funkcja zwraca warto�� FALSE (czyli 0), je�li przesłany komunikat nie został obsłu�ony. Typowymi komunikatami, które rozpatruje wi�kszo�� okienek dialogowych, s WM_INITDIALOG oraz WM_COMMAND. Przykład okienka dialogowego: ------------------Plik: DLGBOX1.H------------------------------- #define szAppName "DLGBOX1" #define IDM_DLG1 100 ------------------Plik zasobów: DLGBOX1.RC---------------------- #include "DLGBOX1.H" #include <windows.h> IDI_ICON ICON CONTROL.ICO DLGBOX1 MENU BEGIN MENUITEM "&O DlgBox" IDM_DLG1 /* to menu pojawi si� w oknie macie�ystym */ END DLGBOX1 DIALOG 30,30,200,100 /* Pierwsze liczby to współrz�dne lewego-górnego rogu okna, dwie nast�pne - to szeroko�� i długo��. Współrz�dne s wzgl�dne. Punkt (0,0) to naro�nik okna macie�ystego */ STYLE WS_POPUP | WS_DLGFRAME BEGIN LTEXT "Przyklad" -1, 0, 12, 160, 8 CTEXT "DLGBOX1 - Przyklad" -1, 0, 36, 160, 8 DEFPUSHBUTTON "OK" IDOK, 64, 60, 32,14, WS_GROUP END ---------------------------------------------------------------- Pomi�dzy par słów kluczowych BEGIN-END mo�na umieszcza� ró�ne instrukcje steruj ce. Definiuj one, jaki rodzaj okna steruj cego uka�e si� w okienku dialogowym. Instrukcje te mo�na stosowa� w nast�puj cym formacie: typ_okna "tekst" ID, x, y, szeroko��, wysoko�� [styl] Parametr styl jest opcjonalny. Styl okna okre�laj identyfikatory predefiniowane w API Windows (WS_...). Parametr ID jest odpowiednikiem identyfikatora dla okien potomnych typu Child Window; dla okien steruj cych, które nie zwracaj komunikatów do okna macierzystego, ma warto�� -1. IDOK wykorzystali�my jako identyfikator dla okna steruj cego typu BUTTON. Zostanie on wysłany do funkcji okienkowej jako warto�� parametru wParam, gdy u�ytkownik kliknie klawisz. ------------------Plik główny: DLGBOX1.CPP---------------------- #include <windows.h> #include <stdio.h> #include <string.h> #include "DLGBOX1.H" #pragma argused

Page 131: C++ - lekcje

131

HANDLE hInst; long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ; BOOL FAR PASCAL ControlProc (HWND, unsigned, WORD, LONG) ; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow ) { WNDCLASS wndClass; MSG msg; HWND hWnd; if ( !hPrevInstance ) { wndClass.style= CS_HREDRAW | CS_VREDRAW ; wndClass.lpfnWndProc= WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra= 0; wndClass.hInstance = hInstance; wndClass.hIcon = LoadIcon(NULL, szAppName); wndClass.hCursor= LoadCursor(NULL, IDC_ARROW ); wndClass.hbrBackground= GetStockObject(WHITE_BRUSH ); wndClass.lpszMenuName= szAppName; wndClass.lpszClassName= szAppName; if (!RegisterClass(&wndClass)) return 0; } hInst = hInstance; hWnd = CreateWindow(szAppName, "DLGBOX1", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg ); DispatchMessage(&msg ); } return 0; } BOOL FAR PASCAL ControlProc (HWND hDlg, unsigned Message, WORD wParam, LONG lParam) { switch(msg) { case WM_INITDIALOG: return TRUE; break; case WM_COMMAND: switch(wParam) { case IDOK: case IDCANCEL: EndDialog(hDlg,0); return TRUE; } break; } return (0); } long FAR PASCAL WndProc (HWND hWnd, unsigned msg, WORD wParam, LONG lParam) { FARPROC lpControlProc; switch(Message) { case WM_COMMAND: switch(wParam) { case IDM_ABOUT: lpControlProc = MakeProcInstance((FARPROC) ControlProc, hInst); DialogBox(hInst, "DLGBOX1", hWnd, lpControlProc); return 0; } break; case WM_DESTROY:

hDC = BeginPaint(hWnd , &ps); TextOut(hDC, 30, 50,"Demo okienka dialogowego", 25); TextOut(hDC, 30, 70,"Zastosuj menu...", 17); EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd,Message,wParam,lParam)); } return 0; } Stosuj c okienko edycyjne mo�emy u�y� nast�puj cych predefiniowanych parametrów: CONTROL - okre�la okno elementu steruj cego zdefiniowane przez u�ytkownika. CONTROL "tekst", klasa, styl, x, y, szeroko��, wysoko�� LTEXT - element steruj cy: okienko tekstowe Wyrównywanie tesktu: do lewej. RTEXT - j. w. Wyrównywanie tesktu: do prawej CTEXT - j. w. Wyrównywanie tesktu: centrowanie w okienku CHECKBOX - pole tekstowe po prawej stronie przeł cznika typu Check Box. PUSHBUTTON - Klawisz z napisem. LISTBOX - okienko z list GROUPBOX - grupa elementów steruj cych typu BUTTON; zgrupowanie kilku elementów steruj cych i otoczenie ramk . Tekst zostanie umieszczony w lewym górnym rogu. DEFPUSHBUTTON - Klawisz domy�lny w stylu BS_DEFPUSHBUTTON. RADIOBUTTON - analogicznie jak dla stylu BS_RADIOBUTTON. EDITTEXT - tworzy okno oparte na klasie EDIT. COMBOBOX - tworz okno oparte na klasie COMBOBOX. ICON - definiuje ikon� opart na klasie STATIC; w okienku dialogowym. SCROLLBAR - tworzy okno oparte na klasie SCROLLBAR. [!!!]UWAGA ________________________________________________________________ W niektórych przypadkach okienko dialogowe mo�e by� głównym oknem aplikacji. ________________________________________________________________ LEKCJA 45: Doł czanie zasobów - menu i okienka dialogowe. ________________________________________________________________ Jak doda� menu i okienka dialogowe do aplikacji. ________________________________________________________________ Aby doda� do aplikacji menu nale�y utworzy� plik (ASCII) zasobów *.RC, który zostanie u�yty w projekcie. Pierwsz instrukcj jest MENU, "NazwaMenu", MENU i para słów kluczowych (znanych z Pascala) BEGIN oraz END, mi�dzy którymi znajdzie si� kombinacja instrukcji MENUITEM oraz POPUP. MENUITEM definiuje pozycj� na głównym pasku menu - okre�la - jak b�dzie wygl da� i jaki identyfikator b�dzie j reprezentował. Instrukcja POPUP pozwala, rozwin � list� pozycji wchodz cych w skład danego menu. Nazwa menu mo�e by� u�yta podczas rejestracji klasy danego okna jako wpisana w odpowiednie pole struktury na której oparte jest okno. W ten sposób uzyskamy menu dla wszystkich okien danej klasy. BEGIN POPUP "Rozkaz" BEGIN MENUITEM "Rozkaz 1", IDM_R1 MENUITEM "Rozkaz 2", IDM_R2 MENUITEM "Rozkaz 3", IDM_R3 END POPUP "Kolor" BEGIN MENUITEM "Czarny", IDM_BLACK

Page 132: C++ - lekcje

132

MENUITEM "Niebieski", IDM_BLUE MENUITEM "Zielony", IDM_GREEN END MENUITEM "Koniec", IDM_EXIT END Ka�da pozycja ma własny identyfikator, np. IDM_EXIT, IDM_BLUE, który Windows przekazuj do aplikacji, gdy zostaje ona wybrana przez u�ytkownika z systemu menu. Poniewa� ka�dy identyfikator powinien by� unikalny, najlepiej jest go zdefiniowa� w pliku zasobów .RC lub własnym pliku nagłówkowym .H: #define IDM_EXIT 100 #define IDM_BLUE 101 #define IDM_R1 102 ... Mamy ju� zdefiniowane menu w pliku zasobów, nale�y je teraz doł czy� do aplikacji na jeden z dwóch sposobów: - Mo�na okre�li� menu jako menu danej klasy okien, gdy klasa ta jest rejestrowana. W ten sposób doł czymy menu do ka�dego okna opartego na tej klasie. Aby to wykona�, wystarczy przypisa� odpowiedniemu polu struktury nazw� naszego menu. Je�eli obiekt klasy WNDCLASS nazwiemy Window1, to: Window1.lpszMenuName = "NazwaMenu"; Gdy klasa zostanie zarejestrowana, ka�de okno tej klasy b�dzie miało to samo menu, chyba �e dostarczymy odpowiedni identyfikator menu w momencie tworzenia okna funkcj CreateWindow(). - Drugim sposobem jest doł czenie menu w momencie tworzenia okna, wtedy tylko tworzone okno b�dzie miało dane menu. Nale�y najpierw załadowa� menu przy u�yciu funkcji LoadMenu(), która zwraca jego identyfikator: HMENU hMenu = LoadMenu(hInstance, "NazwaMenu"); hWnd = CreateWindow(szAppName, "Nazwa Aplikacji", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, <-- tu hIstance, NULL ); Typow praktyk jest doł czenie pozycji menu do instrukcji switch w funkcji okienkowej. Poniewa� Windows wysyła komunikat WM_COMMAND do odpowiedniej funkcji okienkowej w odpowiedzi na wybór pozycji przez u�ytkownika, a parametr wParam zawiera identyfikator tej�e pozycji - mo�na napisa� tak: case WM_COMMAND: switch (wParam) { case IDM_R1: ... obsługa ...; break; case IDM_R2: ... obsługa ...; break case IDM_QUIT: ...DestroyWindow(...); } Jak rozbudowuje si� menu. API Windows zawiera funkcje, umo�liwiaj ce rozbudow� menu nawet w ruchu aplikacji (run-time). Rozbudowa menu w konkretnym oknie nie poci ga za sob zmian w innych, opartych na tej samej klasie oknach. Jest to mo�liwe, poniewa� w chwili tworzenia okna otrzymuje ono swoj kopi� menu (tradycyjne w C/C++ przekazywanie kopii zmiennej do funkcji). Nie wszystkie pozycje w menu s w danym stadium pracy aplikacji

sensowne (mo�liwe do wykonania). Zaraz przekonasz si�, jak to si� dzieje, �e niektóre pozycje "robi si� na szaro". W API Windows słu�y do tego funkcja: EnableMenuItem (hMenu, IDM_R1, MF_DISABLED); EnableMenuItem (hMenu, IDM_R1, MF_GRAYED); EnableMenuItem (hMenu, IDM_R1, MF_ENABLED); Rozkaz R1 skojarzony z identyfikatorem IDM_R1 i znajduj cy si� w systemie menu o oznaczniku hMenu stanie si� kolejno zablokowany, widoczny-lecz-niedost�pny, dost�pny. Dodawanie i usuwanie pozycji w menu Dodawanie pozycji do menu mo�e by� wykonane dwoma sposobami: przez wstawienie pomi�dzy istniej ce pozycje lub na ko�cu listy. W pierwszym przypadku nale�y u�y� funkcji InsertMenu(). Funkcja ta pozwala jednocze�nie okre�li� status pozycji, mi�dzy innymi czy b�dzie umieszczone nowe pole mo�na okre�li� dwoma sposobami: przez identyfikator pozycji maj cej by� przed now lub przez numeracj� poszczególnych, licz c id lewej skrajnej pozycji (C++ tradycyjnie liczy od zera). Sposób "odliczania" pozycji w systemie menu okre�la tryb (BYCOMMAND lub BYPOSITION - rozkaz, b d� pozycja): InsertMenu(hMenu, IDM_R1, MF_BYCOMMAND |MF_DISABLED, IDM_R5, "Rozkaz 5"); InsertMenu(hMenu, 1, MF_ENABLED, IDM_R5, "Rozkaz 5"); Funkcja wstawi za pozycj "Rozkaz 1" now pozycj� "Rozkaz 5", jednocze�nie ustawia jej status. Drug funkcj dodaj c pozycj� do utworzonego systemu menu jest: AppendMenu(hMenu, MF_ENABLED, IDM_R4, "Rozkaz 4"); Poni�ej przykład zdefiniowania menu aplikacji w taki wła�nie sposób: case WM_CREATE: hMenu = CreateMenu(); //Utworzenie menu AppendMenu(hMenu, MF_ENABLED, IDM_R1, "Rozkaz 1"); AppendMenu(hMenu, MF_ENABLED, IDM_R2, "Rozkaz 2"); AppendMenu(hMenu, MF_ENABLED, IDM_R3, "Rozkaz 3"); SetMenu(hWnd, hMenu); //Wy�wietlenie menu ... break; Usuwanie pozycji z menu mo�na przeprowadzi� dwoma sposobami: - poprzez wskazanie numeru pozycji w systemie menu: DeleteMenu(hMenu, 1, MF_BYPOSITION); //usuni�ta zostanie druga //pozycja z systemu menu - przez wyszczególnienie identyfikatorem pozycji DeleteMenu(hMenu, IDM_R3, MF_BYCOMMAND); Po usuni�ciu pozycji z menu Window usunie równie� wszystkie zwi zane z ni submenu. Zaznaczanie pozycji w menu (mark). Obok pozycji w menu mo�na umie�ci� znak markuj cy ("ptaszek"). Znak markuj cy mo�na zainicjowa� w pliku zasobów .RC. Dzi�ki temu, u�ytkownik w momencie otwarcia okna dowie si� z wygl du menu o pocz tkowym ustawieniu opcji. MENUITEM "Rozkaz 2", IDM_R2, CHECKED W trakcie pracy aplikacji nale�y posłu�y� si� funkcj CheckMenuItem(). Zwykle najpierw kasujemy "ptaszka" przy poprzedniej pozycji: CheckMenuItem( hMenu, IDM_R2, MF_UNCHECKED); CheckMenuItem(hMenu, IDM_R3, MF_CHECKED); Zmiany pozycji menu

Page 133: C++ - lekcje

133

Funkcja ModyfyMenu() pozwala na zmian� nazwy pozycji i jej atrybutów. Oto przykłady u�ycia tej funkcji: ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND, IDM_R2, "Polecenie 2"); Identyfikator pozycji nie ulegnie zmianie, jedynie nazwa pola z "Rozkaz 2" na "Polecenie 2". Mo�emy zmieni� jednocze�nie i identyfikator, by nie pomyli� si� w programie: ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND, IDM_P2, "Polecenie 2"); Dodatkowo mo�na ustawi� za jednym zamachem i atrybuty: ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND | MF_CHECKED | MF_GRAYED, IDM_R2, "Polecenie 2"); U�ycie grafiki w systemie menu. W systemie menu aplikacji mo�emy zamiast ła�cucha znaków "Rozkaz 2" umie�ci� element graficzny - np. w postaci mapy bitowej. Zamiast pola o nazwie "Pole", wprowadza map� bitow : HMENU hMenu = GetMenu(hWnd); HBITMAP hBitmap = LoadBitmap (hIstance, "Pole"); ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND | MF_BITMAP, IDM_R2, (LPSTR) MAKELONG (hBitmap, 0)); GetMenu() zwraca oznacznik aktualnego menu, potrzebny jako pierwszy parametr funkcji ModifyMenu(). Drugim parametrem tej funkcji jest identyfikator pozycji, któr chcemy zmieni�. Trzecia okre�la, �e zmiana ma by� wykonana przez wyszukanie pozycji za po�rednictwem jej identyfikatora oraz �e now pozycj� ma reprezentowa� mapa bitowa. Czwarty parametr okre�la identyfikator nowej pozycji. Poniewa� ostatnim parametrem nie jest ju� wska�nik do ła�cucha znakowego, nale�y przesła� oznacznik mapy bitowej jako mniej znacz ce słowo tego parametru. W tym celu 16-bitowy oznacznik jest ł czony z 16-bitow stał , a nast�pnie poddawany konwersji do typu Long Pointer to STRing. Zmiana menu aplikacji na kolejne. Aplikacja w ró�nych stadiach pracy mo�e mie� na ekranie ró�ne (kilka czasem kilkana�cie) menu. Wymiany menu w oknie aplikacji mo�na dokona�, załadowuj c nowe menu funkcj LoadMenu() i ustawiaj c je jako aktualne funkcj SetMenu(): ... hMenu2 = LoadMenu (hIstance, "Menu2"); SetMenu (hWnd, hMenu2); DrawMenuBar(...); ... Menu i Menu2 powinny by� zdefiniowane w pliku zasobów *.RC. Po ka�dej zmianie menu nale�y u�y� funkcji DrawMenuBar(), aby wprowadzone zmiany pojawiły si� na ekranie. Oto przykład stosownego pliku zasobów: Menu1 MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New" , IDM_NEW MENUITEM "&Save", IDM_SAVE MENUITEM "E&xit", IDM_EXIT END POPUP "&Options" BEGIN MENUITEM "Menu&1", IDM_M1,CHECKED MENUITEM "Menu&2" , IDM_M2 END END Menu2 MENU

BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_OPEN MENUITEM "&New" , IDM_NEW MENUITEM "&Save", IDM_SAVE MENUITEM "Save &As", IDM_SAVEAS MENUITEM "&DOS shell", IDM_DOSSHELL MENUITEM "E&xit", IDM_EXIT END POPUP "&Options" BEGIN MENUITEM "Menu&1", IDM_M1, MENUITEM "Menu&2" , IDM_M2, CHECKED END END ZASTOSOWANIE Resource Worshop Takie pliki zasobów w Borland C++ mało kto tworzy dzi� "na piechot�". BORLAND C++ oferuje do tego celu dwa narz�dzia: Edytor zasobów - Resource Workshop Automatyczny generator - DialogExpert (wersje 4+) Najwygodniejszym sposobem jest zastosowanie edytora zasobów Resource Workshop. Jest to tym wygodniejsze, �e Resource Workshop pozwala jednocze�nie obserwowa� i �ródłowy plik *.RC (ASCII) i efekt - menu w ruchu. W �rodowisku Borland C++ okienka dialogowe tworzy si� tak�e zwykle przy pomocy Resource Worshop. Tworzenie okienek dialogowych przy pomocy Resource Workshop przypomina składanie budowli z gotowych klocków. Kolejne elementy steruj ce mo�emy umieszcza� w okienku dialogowym poprzez wybranie ich z palety narz�dzi i przeniesienie do projektowanego okienka technik "poci gnij i upu��" (drag & drop). Po skróconym omówieniu najwa�niejszych funkcji z API Windows przejd�my to niemniej krótkiej prezentacji zasad tworzenia aplikacji przy pomocy biblioteki obiektów OWL. [Z] ________________________________________________________________ 1. Przeanalizuj program w pełnej wersji (na dyskietce). 2. Zmodyfikuj dowoln aplikacj� przykładow tak, by doł czy� do niej inn ikon�. 3. Opracuj własne menu i własn ikon� przy pomocy Resource Workshop. ________________________________________________________________ Krótka instrukcja do Resource Workshop. ________________________________________________________________ 1. Uruchomienie: Ikonka Worshop w oknie grupowym Borland C++. 2. Pocz tek pracy: File | New Project... 3. Rodzaje zasobów do wyboru w okienku dialogowym "New project": [ ] RC - plik zasobów [ ] CUR - kursor [ ] BMP - mapa bitowa [ ] RES - plik zasobów w formie skompilowanej [ ] ICO - ikonka [ ] FNT - czcionki (Fonts) Wybieramy odpowiednio: RC 4. Zmieni si� menu w głównym oknie Resource Workshop. Z menu wybieramy Resource | New W okienku dialogowym z listy Resource Type (rodzaj zasobów): ACCELERATORS, BITMAP, CURSOR, DIALOG, FONT, ICON, MENU, RCDATA, STRINGTABLE, VERSINFO wybieramy odpowiednio MENU lub DILOG. Kolejny raz zmieni si� menu. W przypadku menu wybieramy: Menu: New pop-up - nowa pozycja POPUP New menu item - nowa pozycja MENUITEM Zwró� uwag�, �e typowe menu File, Edit, Help jesy ju� gotowe do wstawienia (ukryte pod pozycjami New file pop-up, New edit pop-up...).

Page 134: C++ - lekcje

134

W przypadku okienka dialogowego najwa�niejsze jest menu Control. S tam wszyskie rodzaje podstawowych elementów steruj cych (Push button, Radio button, scroll bar, List box, Combo box, Edit box, itd.). Projektuj c okienko mo�esz równie� wy�wietli� siatk� (Grid). Przy pomocy Resource Workshop mo�esz poddawa� edycji i modyfikowa� pliki zasobów zarówno nale� ce do programów przykładowych zawartoch na dyskietce, jak i zasoby "firmowych" przykładów Borlanda. W katalogach \SOURCE (kody �ródłowe .CPP) i \EXAMPLES (przykłady - projekty) znajdziesz wiele rozmaitych przykładów. Mo�esz tak�e poddawa� edycji pliki .BMP, .ICO i inne niekoniecznie nale� ce do pakietu Borland C++. ________________________________________________________________ LEKCJA 46: O PROGRAMACH OBIEKTOWO - ZDARZENIOWYCH. ________________________________________________________________ Po aplikacjach sekwencyjnych, proceduralno-zdarzeniowych, jedno- i dwupoziomowych, pora rozwa�y� dokładniej stosowanie technik obiektowych. ________________________________________________________________ Programy pracuj ce w �rodowisku Windows tworzone s w oparciu o tzw. model trójwarstwowy. Pierwsza warstwa to warstwa wizualizacji, druga - interfejs, a trzecia - to wła�ciwa maszyneria programu. W tej lekcji zajmiemy si� "anatomi " aplikacji wielowarstwowych a nast�pnie sposobami wykorzystania bogatego instrumentarium oferowanego przez Borlanda wraz z kompilatorami BC++ 3+...4+. Biblioteka OWL w wersjach BORLAND C++ 3, 3.1, 4 i 4.5 zawiera definicje klas potrzebnych do tworzenia aplikacji dla Windows. Fundamentalne znaczenie dla wi�kszo�ci typowych aplikacji maj nast�puj ce klasy: TModule (moduł - program lub biblioteka DLL) TApplication (program - aplikacja) TWindow (Okno) Rozpoczn� od krótkiego opisu dwu podstawowych klas. KLASA TApplication. Tworz c obiekt klasy TNaszProgram b�dziemy wykorzystywa� dziedziczenie od tej wła�nie klasy bazowej: class TNaszProgram : public TApplication Podstawowym celem zastosowania tej wła�nie klasy bazowej jest odziedziczenie gotowej funkcji - metody virtual InitMainWindow() (zainicjuj główne okno programu). Utworzenie obiektu klasy TNaszProgram nast�puje zwykle w czterech etapach: * Windows uruchamiaj program wywołuj c główn funkcj� WinMain() lub OwlMain() wchodz c w skład ka�dej aplikacji. * Funkcja WinMain() tworzy przy pomocy operatora new nowy obiekt - aplikacj�. * Obiekt - aplikacja zaczyna funkcjonowa�. Konstruktor obiektu (własny, b d� odziedziczony po klasie TApplication) wywołuje funkcj� - wirtualn metod� InitMainWindow(). * Funkcja przy pomocy operatora new tworzy obiekt - okno aplikacji. Wska�nik do utworzonego obiektu zwraca funkcja GetApplication(). Dla zobrazowania mechanizmów poni�ej przedstawiamy uproszczony "wyci g" z dwu opisywanych klas. Nie jest to dokładna kopia kodu �ródłowego Borlanda, lecz skrót tego kodu pozwalaj cy na zrozumienie metod implementacji okienkowych mechanizmów wewn trz klas biblioteki OWL i tym samym wewn trz obiektów obiektowo -

zdarzeniowych aplikacji. A oto najwa�niejsze elementy implementacji klasy TApplication: - Konstruktor obiektu "Aplikacja": TApplication::TApplication(const char far* name, HINSTANCE Instance, HINSTANCE prevInstance, const char far* CmdLine, int CmdShow, TModule*& gModule) { hPrevInstance = prevInstance; nCmdShow = CmdShow; MainWindow = 0; HAccTable = 0; //Accelerator Keys Table Handle BreakMessageLoop = FALSE; AddApplicationObject(this); //this to wska�nik do własnego gModule = this; //obiektu, czyli do bie�. aplikacji } Funkcja - metoda "Zainicjuj Instancj�": void TApplication::InitInstance() { InitMainWindow(); if (MainWindow) { MainWindow->SetFlag(wfMainWindow); MainWindow->Create(); MainWindow->Show(nCmdShow); } Metoda "Zainicjuj główne okno aplikacji": void TApplication::InitMainWindow() { SetMainWindow(new TFrameWindow(0, GetName())); } Metoda Run() - "Uruchom program": int TApplication::Run() { int status; { if (!hPrevInstance) InitApplication(); InitInstance(); status = MessageLoop(); } A oto p�tla pobierania komunikatów w uproszczeniu. "Pump" to po prostu "pompowanie" komunikatów (message) oczekuj cych (waiting) w kolejce. PeekMessage() to sprawdzenie, czy w kolejce oczekuje komunikat. PM_REMOWE to "brak komunikatu". BOOL TApplication::PumpWaitingMessages() { MSG msg; BOOL foundOne = FALSE; while (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { foundOne = TRUE; if (msg.message == WM_QUIT) { BreakMessageLoop = TRUE; MessageLoopResult = msg.wParam; ::PostQuitMessage(msg.wParam); break; } if (!ProcessAppMsg(msg)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); } } return foundOne; } int TApplication::MessageLoop()

Page 135: C++ - lekcje

135

{ long idleCount = 0; MessageLoopResult = 0; while (!BreakMessageLoop) { TRY { if (!IdleAction(idleCount++)) ::WaitMessage(); if (PumpWaitingMessages()) idleCount = 0; } if (MessageLoopResult != 0) { ::PostQuitMessage(MessageLoopResult); break; } }) } BreakMessageLoop = FALSE; return MessageLoopResult; } else if (::IsWindowEnabled(wnd)) { *(info->Wnds++) = wnd; ::EnableWindow(wnd, FALSE); } } return TRUE; } KLASA TWindow. Klasa TWindow (Okno) zawiera implementacj� wielu przydatnych przy tworzeniu aplikacji "cegiełek". Poni�ej przedstawiono fragment pliku �ródłowego (patrz \SOURCE\OWL\WINDOW.CPP). Łatwo mo�na rozpozna� pewne znane ju� elementy. ... extern LRESULT FAR PASCAL _export InitWndProc(HWND, UINT, WPARAM, LPARAM); ... struct TCurrentEvent //Struktura Bie� ceZdarzenie { TWindow* win; //Wska�nik do okna UINT message; //Komunikat WPARAM wParam; LPARAM lParam; }; ... DEFINE_RESPONSE_TABLE(TWindow) //Makro: Zdefiniuj tablic� odpowiedzi na zdarzenia //EV_WM_SIZE - Zdarzenie (EVent)-nadszedł komunikat WM_SIZE ... EV_WM_SETCURSOR, EV_WM_SIZE, EV_WM_MOVE, EV_WM_PAINT, EV_WM_LBUTTONDOWN, EV_WM_KILLFOCUS, EV_WM_CREATE, EV_WM_CLOSE, EV_WM_DESTROY, EV_COMMAND(CM_EXIT, CmExit), ... END_RESPONSE_TABLE; Funkcje - metody obsługuj ce komunikaty zaimplementowane zostały wewn trz klasy TWindow tak: TWindow::EvCreate(CREATESTRUCT far&) { SetupWindow(); return (int)DefaultProcessing(); } void TWindow::EvSize(UINT sizeType, TSize&) { if (Scroller && sizeType != SIZE_MINIMIZED) { Scroller->SetPageSize(); Scroller->SetSBarRange(); } }

Metoda GetWindowClass() bardzo przypomina klasyczne zainicjowanie zanej ju� struktury WNDCLASS: void TWindow::GetWindowClass(WNDCLASS& wndClass) { wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = *GetModule(); wndClass.hIcon = 0; wndClass.hCursor = ::LoadCursor(0, IDC_ARROW); wndClass.hbrBackground = HBRUSH(COLOR_WINDOW + 1); wndClass.lpszMenuName = 0; wndClass.lpszClassName = GetClassName(); wndClass.style = CS_DBLCLKS; wndClass.lpfnWndProc = InitWndProc; } Skoro te wszystkie "klocki" zostały ju� zaimplementowane wewn trz definicji klas, nasze programy powinny tylko umiej�tnie z nich korzysta� a teksty �ródłowe programów powinny ulec skróceniu i uproszczeniu. STADIA TWORZENIA OBIEKTOWEJ APLIKACJI. Poniewa� znakomita wi�kszo�� dzisiejszych u�ytkowników pracuje z Windows 3.1, 3.11, i NT - zaczniemy tworzenie aplikacji od umieszczenia na pocz tku informacji dla OWL, �e nasz docelowy program ma by� przeznaczony wła�nie dla tego �rodowiska: #define WIN31 Jak ju� wiemy dzi�ki krótkiemu przegl dowi struktury bazowych klas przeprowadzonemu powy�ej - funkcje API Windows s w istocie klasycznymi funkcjami posługuj cymi si� mechanizmami j�zyka C. C++ jest "pedantem typologicznym" i przeprowadza dodatkowe testowanie typów parametrów przekazywanych do funkcji (patrz "Technika programowania w C++"). Aby ułatwi� współprac�, zwi�kszy� poziom bezpiecze�stwa i "uregulowa�" potencjalne konflikty - dodamy do programu: #define STRICT Chc c korzysta� z biblioteki OWL wypada doł czy� wła�ciwy plik nagłówkowy: #include <owl.h> Plik OWL.H zawiera ju� wewn trz doł czony WINDOWS.H, który wyst�pował we wcze�niejszych aplikacjach proceduralno - zdarzeniowych i jeszcze par� innych plików. Poniewa� chcemy skorzysta� z gotowych zasobów - odziedziczymy pewne cechy po klasie bazowej TApplication. Zgodnie z zasadami programowania obiektowego chc c utworzy� obiekt musimy najpierw zdefiniowa� klas�: class TOkno ... i wskaza� po której klasie bazowej chcemy dziedziczy�: class TOkno : public TApplication { ... Konstruktor obiektu klasy TOkno powinien tylko przekaza� parametry konstruktorowi klasy bazowej - i ju�. class TOkno : public TApplication { public: TOkno(LPSTR name, HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nShow) : TApplication(name, hInstance, hPrevInstance, lpCmdLine, nShow) { return; } virtual void InitMainWindow(); }; Umie�cili�my w definicji klasy jeszcze jedn funkcj� inicjuj c główne okno aplikacji. Mo�emy j zdefiniowa� np. tak:

Page 136: C++ - lekcje

136

void TOkno::InitMainWindow(void) { MainWindow = new (TWindow(0, "Napis - Tytul Okna")); } Działanie funkcji polega na utworzeniu nowego obiektu (operator new) klasy bazowej TWindow. Główne okno stanie si� zatem obiektem klasy TWindow (Niektóre specyficzne aplikacje posługuj si� okienkiem dialogowym jako głównym oknem programu. W takiej sytuacji dziedziczenie powinno nast�powa� po klasie TDialog). Konstruktorowi tego obiektu przekazujemy jako parametr napis, który zostanie umieszczony w nagłówku głównego okna aplikacji. Pierwszy argument (tu ZERO) to wska�nik do macie�ystego okna, poniewa� w bardziej zło�onych aplikacjach wyst�puj okna macie�yste (parent) i okna potomne (child). Okno macie�yste to zwykle obiekt klasy "główne okno" a okno potomne to najcz��ciej okienko dialogowe, b d� okienko komunikatów. W tym przypadku wpisujemy zero, poniewa� program nie posiada w tym stadium wcze�niejszego okna macie�ystego. Pozostało nam jeszcze doda� funkcj� WinMain() i pierwszy program obiektowy w wersji "Maszyna do robienia nic" jest gotów. Listing . Obiektowa "Maszyna do robienia nic" ________________________________________________________________ #define STRICT #define WIN31 #include <owl.h> class TOkno : public TApplication { public: TOkno(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) : TApplication(AName, hInstance, hPrevInstance, lpCmdLine, nCmdShow) {}; void InitMainWindow(){MainWindow = new TWindow(NULL, Name);}; }; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { TOkno OBIEKT("Windows - Program PW1", hInstance, hPrevInstance, lpCmdLine, nCmdShow); OBIEKT.Run(); return 0; } ________________________________________________________________ Wykonanie takiej aplikacji przebiega nast�puj co. Windows wywołuj główn funkcj� WinMain(), która przekazuje swoje parametry do konstruktora klasy TOkno::TOkno(). Konstruktor przekazuje parametry do konstruktora klasy bazowej TApplication(). Po skonstruowaniu obiektu w pami�ci funkcja wywołuje odziedziczon metod� Run(). Funkcja Run() wywołuje metody InitApplication() (zainicjuj aplikacj�) i InitInstance() (zainicjuj dane wyst pienie programu). Metoda InitInstance() wywołuje funkcj� InitMainWindow(), która buduje główne okno aplikacji na ekranie. Po pojawieniu si� okna rozpoczyna działanie p�tla pobierania komunikatów (message loop). P�tla komunikatów działa a� do otrzymania komunikatu WM_QUIT. Rozbudujmy aplikacj� o okienko komunikatów. Zastosujemy do tego funkcj� MessageBox(). Funkcja zostanie u�yta nie jako metoda (składnik obiektu), lecz jako "wolny strzelec" (stand alone function). Listing B. Maszyna rozszerzona o okienka komunikatów. ________________________________________________________________ #define WIN31 #define STRICT #include <owl.h> class TOkno : public TApplication { public: TOkno(LPSTR Nazwa, HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) : TApplication(Nazwa, hInstance, hPrevInstance, lpCmdLine, nCmdShow) {};

void InitMainWindow(){MainWindow = new TWindow(NULL, "Okno PW2" );}; }; int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { TOkno OBIEKT("Okno PW2", hInstance, hPrevInstance, lpCmdLine, nCmdShow); LPSTR p1 = "Jesli wybierzesz [Anuluj]\n- aplikacja nie ruszy!"; LPSTR p2 = "START"; if (MessageBox(NULL, p1, p2, MB_OKCANCEL) == IDCANCEL) MessageBox(NULL, "I juz..." , "KONIEC" , MB_OK); else OBIEKT.Run(); return 0; } ________________________________________________________________ Uwagi techniczne. �cie�ki do katalogów: ..\INCLUDE;..\CLASSLIB\INCLUDE;..\OWL\INCLUDE; ..\LIB;..\CLASSLIB\LIB;..\OWL\LIB; Konsolidacja: Options | Linker | Settings: Windows EXE (typ aplikacji) Options | Linker | Libraries: - Container class Libraries: Static (bibl. klas CLASSLIB) - OWL: Static (bibl. OWL statycze .LIB) - Standard Run-time Lib: Static (bibl. uruchomieniowe .LIB) (.) None - oznacza �adne (nie zostan doł czone); (.) Static - oznacza statyczne .LIB (.) Dinamic - oznacza dynamiczne .DLL ________________________________________________________________ JAK ROZBUDOWYWA� OBIEKTOWE APLIKACJE? Mimo całego uroku obiektowych aplikacji pojawia si� tu wszak�e drobny problem. Skoro komunikacja klawiatura/myszka -> program -> ekran nie odbywa si� wprost, lecz przy pomocy wymiany danych pomi�dzy obiektami ró�nych warstw - w jaki sposób (w którym miejscu programu) umie�ci� "zwyczajne" funkcje i procedury i jak zorganizowa� wymian� informacji. "Zwyczajne" funkcje b�d przecie� wchodzi� w skład roboczych cz��ci naszych programów (Engine). Rozwa�my to na przykładzie aplikacji reaguj cej na naci�ni�cie klawisza myszki. Najbardziej istotny - "newralgiczny" punkt programu został zaznaczony w tek�cie "<-- TU". Od Windows przejmiemy obsług� komunikatów WM_LBUTTONDOWN, WM_RBUTTONDOWN. Aby wiedzie�, w którym miejscu ekranu jest kursor myszki, wykorzystamy informacje przenoszone przez parametr lParam. Rozpoczniemy tworzenie programu od zdefiniowania klasy. #define WIN31 #define STRICT #include <stdio.h> #include <string.h> #include <owl.h> class TNAplikacja : public TApplication { public: TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) : TApplication(AName, hInstance, hPrevInstance, lpCmdLine, nCmdShow) {}; virtual void InitMainWindow(); }; Wykorzystamy okienko komunikatu do �wiadomego zako�czenia pracy aplikacji. Klasa TApplication jest wyposa�ona w metod� CanClose() (czy mo�na zamkn �?) słu� c do zamykania głównego okna aplikacji. Metoda została zaimplementowana tak: BOOL TApplication::CanClose() { if (MainWindow) return (MainWindow->CanClose()); else

Page 137: C++ - lekcje

137

return (TRUE); } B�dzie nam wi�c potrzebna własna wersja metody CanClose() i wska�nik do obiektu MainWindow. Wska�nik (typu far utworzony przez składowe makro _FAR) wygenerujemy przy pomocy makra _CLASSDEF(nazwa_klasy): _CLASSDEF(TGOkno) Implementujemy teraz klas� główne okno aplikacji. Jako klas� bazow stosujemy TWindow. class TGOkno : public TWindow { public: TGOkno(PTWindowsObject AParent, LPSTR ATitle) : TWindow(AParent, ATitle) {}; Konstruktor tradycyjnie wykorzystujemy do przekazania parametrów do konstruktora klasy bazowej. PTWindowsObject AParent to wska�nik (PoinTer) do obiektu "okno" a ATitle to string - tytuł. Obsługa komunikatów kierowanych do tego okna mo�e by� realizowana przy pomocy metod zaimplementowanych jako elemeny składowe klasy Główne Okno - TGOkno. Program graficzny powinien reagowa� raczej na myszk� ni� na klawiatur�. Windows rozpoznaj zdarzenia zwi zane z myszk i generuj komunikaty o tych zdarzeniach. Zdarzenia myszki (mouse events). ________________________________________________________________ Komunikat Zdarzenie ________________________________________________________________ WM_MOUSEMOWE - przesuni�to myszk� (wewn trz obszaru roboczego - inside the client area - ICA) WM_LBUTTONDOWN - naci�ni�to LEWY klawisz myszki (ICA) WM_LBUTTONDBLCLK - naci�ni�to dwukrotnie LEWY klaw. (ICA) WM_LBUTTONUP - puszczono LEWY klawisz (ICA) WM_RBUTTONDOWN - naci�ni�to PRAWY klawisz myszki (ICA) WM_RBUTTONDBLCLK - naci�ni�to dwukrotnie PRAWY klaw. (ICA) WM_RBUTTONUP - puszczono PRAWY klawisz (ICA) WM_MBUTTONDOWN - naci�ni�to �RODK. klawisz myszki (ICA) WM_MBUTTONDBLCLK - naci�ni�to dwukrotnie �ROD. klaw. (ICA) WM_MBUTTONUP - puszczono �RODKOWY klawisz (ICA) WM_NCMOUSEMOVE - ruch myszki poza client area (NCA) WM_NLBUTTONDOWN - naci�ni�to LEWY klawisz myszki poza obszarem roboczym - non-client area (NCA) WM_NCLBUTTONDBLCLK - naci�ni�to dwukrotnie LEWY klaw. (NCA) WM_NCLBUTTONUP - puszczono LEWY klawisz (NCA) WM_NCRBUTTONDOWN - naci�ni�to PRAWY klawisz myszki (NCA) WM_NCRBUTTONDBLCLK - naci�ni�to dwukrotnie PRAWY klaw. (NCA) WM_NCRBUTTONUP - puszczono PRAWY klawisz (NCA) WM_NCMBUTTONDOWN - naci�ni�to �R. klawisz myszki (NCA) WM_NCMBUTTONDBLCLK - naci�ni�to dwukrotnie �RODK. klaw. (NCA) WM_LBUTTONUP - puszczono �RODKOWY klawisz (NCA) ________________________________________________________________ Nast�pna tabelka zawiera (znacznie skromniejszy) zestaw komunikatów generowanych pod wpływem zdarze� zwi zanych z klawiatur . Cho�by z wizualnego porównaia wielko�ci tych tabel wyra�nie wida�, �e Windows znacznie bardziej "lubi " współprac� z myszk . Komunikaty Windows w odpowiedzi na zdarzenia zwi zane z klawiatur . _______________________________________________________________ Komunikat Zdarzenie _______________________________________________________________ WM_KEYDOWN Naci�ni�to (jaki�) klawisz. WM_KEYUP Puszczono klawisz. WM_SYSKEYDOWN Naci�ni�to klawisz "systemowy". WM_SYSKEYUP Puszczono klawisz "systemowy". WM_CHAR Kod ASCII klawisza. ________________________________________________________________ Klawisz systemowy to np. [Alt]+[Esc], [Alt]+[F4] itp.

________________________________________________________________ Komunikaty Windows mo�emy wykorzysta� w programie. ... BOOL CanClose(); void WMLButtonDown(RTMessage Msg)= [WM_FIRST + WM_LBUTTONDOWN]; void WMRButtonDown(RTMessage Msg)= [WM_FIRST + WM_RBUTTONDOWN]; }; Nasze Główne_Okno potrafi obsługiwa� nast�puj ce zdarzenia: * Funkcja CanClose() zwróciła wynik TRUE/FALSE, * Naci�ni�to lewy/prawy klawisz myszki. Komunikat Msg zadeklarowany jako zmienna typu RTMessage jest w klasie macie�ystej TWindow wykorzystywany tak: _CLASSDEF(TWindow) class _EXPORT TWindow : public TWindowsObject { ... protected: virtual LPSTR GetClassName() { return "OWLWindow"; } virtual void GetWindowClass(WNDCLASS _FAR & AWndClass); virtual void SetupWindow(); virtual void WMCreate(RTMessage Msg) = [WM_FIRST + WM_CREATE]; virtual void WMMDIActivate(RTMessage Msg) = [WM_FIRST + WM_MDIACTIVATE]; ... virtual void WMSize(RTMessage Msg) = [WM_FIRST + WM_SIZE]; virtual void WMMove(RTMessage Msg) = [WM_FIRST + WM_MOVE]; virtual void WMLButtonDown(RTMessage Msg) = [WM_FIRST + WM_LBUTTONDOWN]; Zwró� uwag� na notacj�. Zamiast WM_CREATE pojawiło si� [WM_FIRST + WM_CREATE]. Komunikat WM_FIRST jest predefiniowany w OWLDEF.H i musi wyst pi� w obiektowych aplikacjach w dowolnej klasie okienkowej, b d� steruj cej (window class/controll class), która winna odpowiada� na okre�lony komunikat. Oto fragment pliku OWLDEF.H zawieraj cy definicje stałych tej grupy: #define WM_FIRST 0x0000 /* 0x0000- 0x7FFF window messages */ #define WM_INTERNAL 0x7F00 /* 0x7F00- 0x7FFF reserved for internal use */ #define ID_FIRST 0x8000 /* 0x8000- 0x8FFF child id messages */ #define NF_FIRST 0x9000 /* 0x9000- 0x9FFF notification messages */ #define CM_FIRST 0xA000 /* 0xA000- 0xFFFF command messages */ #define WM_RESERVED WM_INTERNAL - WM_FIRST #define ID_RESERVED ID_INTERNAL - ID_FIRST #define ID_FIRSTMDICHILD ID_RESERVED + 1 #define ID_MDICLIENT ID_RESERVED + 2 #define CM_RESERVED CM_INTERNAL - CM_FIRST W tym momencie zwró�my jeszcze uwag�, �e funkcje z grupy MessageHandlers s typu void i zwykle s metodami wirtualnymi - przeznaczonymi "z definicji" do nadpisywania przez programistów w klasach potomnych. Wszystkie te metody maj zawsze jedyny argument - referencj� do struktury TMessage zdefiniowanej nast�puj co: struct TMessage { HWND Receiver; //Identyfikator okna - odbiorcy WORD Message; //sam komunikat union { WORD WParam; //Parametr WParam stowarzyszony z //komunikatem; ALBO (dlatego unia!) struct tagWP {

Page 138: C++ - lekcje

138

BYTE Lo; BYTE Hi; } WP; union { DWORD lParam; struct tagLP { WORD Lo; WORD Hi; } LP; }; long Result; }; Po tych wyja�nieniach mo�emy zaimplementowa� poszczególne funkcje. void TAplikacja::InitMainWindow() { MainWindow = new (0, Name); } Je�li wybrano klawisz [Yes] funkcja zwróci IDYES. Je�li funkcja zwróciła IDYES - operator porównania zwróci TRUE (prawda) i ta te� warto�� zostanie zwrócona przez metod� CanClose: BOOL TMyWindow::CanClose() { return (MessageBox(HWindow, "Wychodzimy?", "Koniec", MB_YESNO | MB_ICONQUESTION) == IDYES); } Stosunkowo najciekawsza kombinacja odbywa si� wewn trz handlera komunikatu WM_LBUTTONDOWN. Ze struktury komunikatów pobierana jest zawarto�� młodszego słowa parametru lParam - Msg.LP.Lo i starszego słowa Msg.LP.Hi. S to wzgl�dne współrz�dne graficzne kursora myszki (wzgl�dem naro�nika okna) w momencie naci�ni�cia lewego klawisza myszki. Funkcja sprintf() zapisuje je w postaci dwu liczb dziesi�tnych %d, %d do bufora znakowego char string[20]. Funkcja GetDC() (Get Device Context) okre�la kontekst urz dzenia (warstwa sterownika urz dzenia) i dalej obiekt mo�e ju� stosuj c funkcj� kontekstow "czu� si�" niezale�ny od sprz�tu. Dane te w postaci znakowej s pobierane przez funkcj� kontekstow OutText() jako string a równocze�nie pobierane s w formie liczbowej: Msg.LP.Hi. Msg.LP.Lo, aby wyznaczy� współrz�dne tekstu na ekranie. Funkcja strlen() oblicza długo�� ła�cucha znakowego - i to ju� ostatni potrzebny nam parametr. void TMyWindow::WMLButtonDown(RTMessage Msg) { HDC DC; char string[20]; sprintf(string, "(%d, %d)", Msg.LP.Lo, Msg.LP.Hi); <-- TU DC = GetDC(HWindow); TextOut(DC, Msg.LP.Lo, Msg.LP.Hi, string, strlen(string)); /* Mo�na zwolni� kontekst */ ReleaseDC(HWindow, DC); } Ewentualna metoda uniewa�niaj ca prostok t (invalid rectangle) i kasuj ca w ten sposób zawarto�� okna w odpowiedzi na WM_RBUTTONDOWN mo�e zosta� zaimplementowana np. tak: void TMyWindow::WMRButtonDown(RTMessage) { InvalidateRect(HWindow, 0, 1); } Główny program to ju� tylko wywołanie metody Run() wobec obiektu. int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { TNAplikacja OBIEKT("Wspolrzedne w oknie", hInstance, hPrevInstance, lpCmdLine, nCmdShow); OBIEKT.Run(); return (OBIEKT.Status); }

Wy�wietlanie współrz�dnych jakkolwiek warto�ciowe z dydaktycznego punktu widzenia jest mało interesuj ce. Pokusimy si� o obiektow aplikacj� umo�liwiaj c odr�czne rysowanie w oknie (freehand drawing). [!!!]UWAGA ________________________________________________________________ Pakiety Borland C++ 3..4.5 zawieraj wiele gotowych "klocków" do wykorzystania. Oto przykład wykorzystania w pliku zasobów .RC standardowego okienka wej�ciowego (Input Dialog Box) i standardowego okienka typu Plik (File Dialog Box): #include <windows.h> #include <owlrc.h> rcinclude INPUTDIA.DLG rcinclude FILEDIAL.DLG ROZKAZY MENU LOADONCALL MOVEABLE PURE DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New" CM_FILENEW MENUITEM "&Open" CM_FILEOPEN MENUITEM "&Save" CM_FILESAVE END END Takie menu mo�na zastosowa� w programie obiektowym umieszcaj c je w konstruktorze i dokonuj c nadpisania metody AssignMenu() (przypisz menu): TGOkno::TGOkno(PTWindowsObject AParent, LPSTR ATitle) : TWindow(AParent, ATitle) { AssignMenu("ROZKAZY"); ... } [S] rcinclude - doł cz zasoby LOADONCALL - załaduj po wywołaniu owlrc - zasoby biblioteki klas OWL Gotowe "klocki" mo�na wykorzysta� nawet wtedy, gdy nie pasuj w 100%. Inne ni� typowe odpowiedzi na wybór rozkazu implementujemy w programie głównym poprzez nadpisanie wirtualnej metody virtual void CMFileOpen(RTMessage msg) = [CM_FIRST + CM_FILEOPEN] TGOkno GOkno; void TGOkno::CMFileOpen(RTMessage) { ... obsługa zdarzenia ... } ________________________________________________________________ [Z] ________________________________________________________________ 1. Przeanalizuj gotowe zasoby doł czone do Twojej wersji Borland C++. 2. Uruchom kilka projektów "firmowych" doł czonych w katalogu \EXAMPLES. Zwró� szczególn uwag� na projekty STEPS (kolejne kroki w tworzeniu aplikacji obiektowej). ________________________________________________________________ LEKCJA 47: APLIKACJA OBIEKTOWA - RYSOWANIE W OKNIE. ________________________________________________________________ W trakcie tej lekcji opracujemy obiektow aplikacj� psoługuj c si� bibliotek klas Object Windows Library. ________________________________________________________________ Zaczniemy oczywi�cie od standardowych "klocków". Definicja klasy

Page 139: C++ - lekcje

139

Nasza_Aplikacja i moduł prezentacyjno - uruchomieniowy b�d wygl da� standardowo, nie musimy im zatem po�wi�ca� zbytniej uwagi. Przytoczymy je jedynie. Pointer do napisu inicjujemy po to, by okienko komunikatu zawierało jak � bardziej konkretn informacj� dla u�ytkownika. Rysunki z wn�trza tej aplikacji mo�na przy pomocy Schowka przenie�� jako pliki .CLP, b d� za pomoc PAINTBRUSH - jako .BMP, .PCX i drukowa�. #include <owl.h> LPSTR Ptr = "Jesli chcesz zapamietac rysunek, \ powinienes przeniesc go do Clipboard'u \ klawiszami [Print Screen] \ lub [Alt]+[PrtScr]."; class TNAplikacja : public TApplication { public: TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) : TApplication(AName, hInstance, hPrevInstance, lpCmdLine, nCmdShow) {}; virtual void InitMainWindow(); }; ... int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { TNAplikacja OBIEKT("Rysownik. Prawy klawisz umozliwia wyjscie.", hInstance, hPrevInstance, lpCmdLine, nCmdShow); OBIEKT.Run(); return (OBIEKT.Status); } Nic specjalnie ciekawego nie dzieje si� w funkcji inicjuj cej główne okno, ani w funkcji zamykaj cej aplikacj�. Zmieniły si� tylko napisy w okienku komunikatów. void TNAplikacja::InitMainWindow() { MainWindow = new TGOkno(0, Name); } BOOL TGOkno::CanClose() { return (MessageBox(HWindow, Ptr, "KONIEC", MB_YESNO | MB_ICONQUESTION) == IDYES); } Zajmiemy si� teraz główn "maszyneri " programu. Rozbudujemy obsług� komunikatów przez handlery zaimplenmentowane w klasie Główne_Okno. _CLASSDEF(TGOkno) class TGOkno : public TWindow { public: HDC dc; BOOL ButtonDown; BOOL Flaga_Start; TGOkno(PTWindowsObject AParent, LPSTR ATitle); //Konstruktor virtual void WMLButtonDown(RTMessage Msg) = [WM_FIRST + WM_LBUTTONDOWN]; virtual void WMLButtonUp(RTMessage Msg) = [WM_FIRST + WM_LBUTTONUP]; virtual void WMMouseMove(RTMessage Msg) = [WM_FIRST + WM_MOUSEMOVE]; virtual void WMRButtonDown(RTMessage Msg) = [WM_FIRST + WM_RBUTTONDOWN]; virtual BOOL CanClose(); }; Konstruktor przekazuje parametry do konstruktora klasy bazowej i zeruje flag� ButtonDown - lewy klawisz myszki przyci�ni�ty.

TGOkno::TGOkno(PTWindowsObject AParent, LPSTR ATitle) : TWindow(AParent, ATitle) { ButtonDown = FALSE; } Funkcja obsługuj ca zdarzenie WM_LBUTTONDOWN jeden raz inicjuje obsług� myszki i ustawia flag�. Funkcje SetCapture() i GetDC() załatwij problem relacji kontekstowych i okre�laj obszar roboczy (client area). Je�li umie�cimy te funkcje w konstruktorze za obszar client area uznany zostanie cały ekran. Po zadziałaniu tych funkcji komunikaty od myszki b�d dotyczy� wył cznie obszaru roboczego. Do naci�ni�cia prawego klawisza nie b�dzie dost�pu do "ramki" okna. void TGOkno::WMLButtonDown(RTMessage Msg) { if (!Flaga_Start) { Flaga_Start = TRUE; //UWAGA: SetCapture(HWindow); //Jesli zainicjujemy SetCapture() dc = GetDC(HWindow); //w konstruktorze - mamy caly ekran } MoveTo(dc, Msg.LP.Lo, Msg.LP.Hi); ButtonDown = TRUE; } Funkcja MoweTo() powoduje przesuni�cie kursora graficznego do aktualnej pozycji myszki (ju� wzgl�dnej - z uwzgl�dnieniem dc) bez rysowania linii. Flaga ButtnDown została ustawiona. Rysowanie scedujemy na metod� obsługuj c WM_MOUSEMOVE - przesuni�cie myszki. void TGOkno::WMMouseMove(RTMessage Msg) { if (ButtonDown) LineTo(dc, Msg.LP.Lo, Msg.LP.Hi); } Je�li lewy klawisz jest naci�ni�ty - funkcja LineTo() b�dzie kre�li� lini� do kolejnych punktów "�ledz c" ruch myszki. Je�li u�ytkownik pu�ci lewy klawisz - zerujemy flag� stanu klawisza ButtonDown <== FALSE. void TGOkno::WMLButtonUp(RTMessage) { if (ButtonDown) ButtonDown = FALSE; } Jak ju� nabazgrzemy po ekranie, prawy klawisz umo�liwi nam skasowanie zawarto�ci przy pomocy InvalidateRect(). void TGOkno::WMRButtonDown(RTMessage) { InvalidateRect(HWindow, 0, 1); ReleaseCapture(); ReleaseDC(HWindow, dc); Flaga_Start = FALSE; } Para funkcji ReleaseDC() i ReleaseCapture() pozwala przekaza� komunikaty od myszki do "ramki okna". Dzi�ki temu mo�na po skasowaniu ekranu np. rozwin � menu systemowe i zako�czy� aplikacj�. A oto program w cało�ci. Listing. Odr�czne rysowanie. ________________________________________________________________ #define STRICT #define WIN31 #include <owl.h> LPSTR Ptr = "Jesli chcesz zapamietac rysunek, \ powinienes przeniesc go do Clipboard'u \ klawiszami [Print Screen] \ lub [Alt]+[PrtScr]."; class TNAplikacja : public TApplication { public: TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,

Page 140: C++ - lekcje

140

LPSTR lpCmdLine, int nCmdShow) : TApplication(AName, hInstance, hPrevInstance, lpCmdLine, nCmdShow) {}; virtual void InitMainWindow(); }; _CLASSDEF(TMyWindow) class TMyWindow : public TWindow { public: HDC dc; BOOL ButtonDown; BOOL Flaga_Start; TMyWindow(PTWindowsObject AParent, LPSTR ATitle); //Konstruktor virtual void WMLButtonDown(RTMessage Msg) = [WM_FIRST + WM_LBUTTONDOWN]; virtual void WMLButtonUp(RTMessage Msg) = [WM_FIRST + WM_LBUTTONUP]; virtual void WMMouseMove(RTMessage Msg) = [WM_FIRST + WM_MOUSEMOVE]; virtual void WMRButtonDown(RTMessage Msg) = [WM_FIRST + WM_RBUTTONDOWN]; virtual BOOL CanClose(); }; TMyWindow::TMyWindow(PTWindowsObject AParent, LPSTR ATitle) : TWindow(AParent, ATitle) { ButtonDown = FALSE; } void TMyWindow::WMLButtonDown(RTMessage Msg) { if ( !Flaga_Start ) { Flaga_Start = TRUE; //UWAGA: SetCapture(HWindow); //Jesli zainicjujemy SetCapture() dc = GetDC(HWindow); //w konstruktorze - mamy caly ekran } MoveTo(dc, Msg.LP.Lo, Msg.LP.Hi); ButtonDown = TRUE; } void TMyWindow::WMMouseMove(RTMessage Msg) { if ( ButtonDown ) LineTo(dc, Msg.LP.Lo, Msg.LP.Hi); } void TMyWindow::WMLButtonUp(RTMessage) { if (ButtonDown) ButtonDown = FALSE; } void TMyWindow::WMRButtonDown(RTMessage) { InvalidateRect(HWindow, NULL, TRUE); ReleaseCapture(); ReleaseDC(HWindow, dc); Flaga_Start = FALSE; } void TNAplikacja::InitMainWindow() { MainWindow = new TMyWindow(0, Name); } BOOL TMyWindow::CanClose() { return (MessageBox(HWindow, Ptr, "KONIEC", MB_YESNO | MB_ICONQUESTION) == IDYES); } int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { TNAplikacja OBIEKT("Rysownik. Prawy klawisz umozliwia wyjscie.", hInstance, hPrevInstance, lpCmdLine, nCmdShow); OBIEKT.Run();

return (OBIEKT.Status); } ________________________________________________________________ LEKCJA 48: O PAKIETACH BORLAND C++ 4/4.5. ________________________________________________________________ Z tej lekcji dowiesz si�, czy warto kupi� nowsz wersj� Borland C++ 4/4.5 i jakie niespodzianki czekaj Ci� po zamianie kompilatora na nowszy. ________________________________________________________________ Czy warto sprawi� sobie BORLAND C++ 4/4.5 ? Kilka słów o tym, co oferuje Borland w pakietach "Borland C++ 4/4.5" i jakie niespodzianki czekaj nowych u�ytkowników przy instalacji i uruchamianiu. Wymagania sprz�towe i instalacja Aby instalacja i u�ytkowanie pakietu przebiegało poprawnie, zaleca si� nast�puj c konfiguracj� sprz�tu: Wymagania sprz�towe Borland C++ 4. ________________________________________________________________ Parametr minimum zalecane (pełna konfig.) ________________________________________________________________ * procesor 80386/33 MHZ 486 DX (lub 386 + 387) * miejsce na dysku 8 MB 80 MB (bez kompresji) * pami�� RAM 4 MB 8 MB i wi�cej * system DOS 4.01 DOS 6.0...6.22 * MS Windows 3.1 Windows NT ________________________________________________________________ Cz��ciowa instalacja Borland C++ 4. ________________________________________________________________ Konfiguracja Dysk ________________________________________________________________ 1. Kompilator BCC 16 bitowy (D+W) 9 MB 2. Kompilator BCC 32 bitowy (D+W) 13 MB 3. �rodowisko IDE 16 bitowe 26 MB 4. �rodowisko IDE 32 bitowe 30 MB 5. Tylko dla DOS (minimum) 8 MB ________________________________________________________________ * D+W - dla DOS i Windows Mo�na próbowa� zainstalowa� Borland C++ 4 na małym dysku, mo�na tak�e ograniczy� si� do 4 MB RAM, ale generowanie 32-bitowych aplikacji b�dzie wtedy znacznie utrudnione a praca kompilatora wolniejsza. W przypadku stosowania kompresorów (np. SUPERSTOR, DOUBLE SPACE) nale�y pami�ta�, �e wtórna kompresja plików jest mało skuteczna i dysk zgłaszany jako 80 MB mo�e okaza� si� "ciasny". Borland C++ 4 mo�na instalowa� z dyskietek, b d� z CD-ROM. Poniewa� pakiet BC++ 4 jest "okienkowo - zorientowany", nawet program instalacyjny wymaga obecno�ci Windows. Uruchomienie programu instalacyjnego nast�puje zatem z poziomu Mened�era programów rozkazem File | Run... (w spolszczonej wersji Windows - Plik | Uruchom...) lub z DOS-owskiego wiersza rozkazu: C:\>WIN X:INSTALL Opcji jest troch� wi�cej - o najciekawszych z nich - kilka słów poni�ej. Warto zwróci� uwag� na tzw. "rozszerzenie dla Windows" (extention to MS Windows) - Win32s. W programie INSTALL.EXE do zainstalowania tego pakietu (pakiet jest oryginalnym produktem Microsofta i wymaga 8 MB przestrzeni dyskowej) słu�y opcja [Install Win32s]. Najwa�niejszy plik-driver instaluje si� w pliku SYSTEM.INI: device=X:\WINDOWS\SYSTEM\WIN32S\W32S.386 Pozwala to na uruchamianie 32 - bitowych aplikacji pod Windows 3.1. Je�li masz Windows NT - jest to zb�dne - o ten "drobiazg" zadbał ju� Microsoft.

Page 141: C++ - lekcje

141

W przypadku instalacji w sieci, gdzie Windows zainstalowane s na serwerze nale�y pami�ta�, �e BC++ 4 w trakcie instalacji modyfikuje nie tylko klasyczne parametry systemu: FILES=40 BUFFERS=40 PATH=...;X:\BC4\BIN; ale tak�e pliki konfiguracyjne i inicjacyjne w katalogu WINDOWS: WIN.INI, PROGMAN.INI, SYSTEM.INI, oraz tworzy nowe własne pliki, które b�dzie próbował zapisa� w katalogach \WINDOWS i \WINDOWS\SYSTEM, np. BCW.INI, TDW.INI, HELP.ICO, OWL.INI, BWCC.DLL, itp. (ł cznie 18 nowych plików). Brak prawa zapisu na dysk serwera mo�e uniemo�liwi� poprawn instalacj� i skonfigurowanie BC++ 4/4.5 w sieci. Borland wraz z wersjami bibliotek dostarcza komplet kodów �ródłowych. Je�li chcesz - mo�esz sam wygenerowa� sobie cał bibliotek�, je�li chcesz - mo�esz na własne oczy przekona� si� jak to wszystko działa i jak jest zbudowane. Oprócz teoretycznych mo�liwo�ci poznawczych daje to praktyczn mo�liwo�� dostosowania bibliotek do nowej wersji kompilatora, co w przypadku "czwórki" mo�e okaza� si� dla wielu u�ytkowników bardzo przydatne (o czy dokładniej za chwil�). Oprócz klasycznego paska głównego menu zintegrowane �rodowisko (IDE) zostało wyposa�one w rozbudowan listw� narz�dziow . W skład pakietu wchodz mi�dzy innymi: * BCW - zintegrowane �rodowisko (IDE) dla �rodowiska Windows * TDW - Turbo Debugger for Windows * BCC - kompilator uruchamiany z DOS'owskiego wiersza rozkazu * BCC32 - kompilator 32 - bitowy (odpowiednik BCC) * BRCC - kompilator zasobów do kompilacji plików *.RC z zasobami do postaci binarnej *.RES * RLINK - konsolidator słu� cy do doł czania plików z zasobami przy tworzeniu plików wykonywalnych *.EXE * TLINK - "zwykły" konsolidator * MAKE - program narz�dziowy do automatyzacji kompilacji i konsolidacji, korzystaj cy z tzw. plików instrukta�owych (emuluj cy NMAKE Microsofta) * WINSIGHT - przegl danie informacji o okienkach (dla Windows) i komunikatach * TDUMP - bezpo�rednie przegl danie informacji zawartych w plikach *.EXE i *.OBJ * TDSTRIP - narz�dzie do usuwania tablicy symboli z plików wykonywalnych * IMPLIB - importer bibliotek z DLL * TDMEM - wy�wietlanie informacji o zaj�to�ci pami�ci * MAKESWAP - zarz dzanie swapowaniem (tworzenie plików tymczasowych EDPMI.SWP o zadanej wielko�ci) i jeszcze par� narz�dzi (np. tradycyjny bibliotekarz TLIB, TOUCH, GREP, itp.), o których tu nie wspominam. Czego robi� nie nale�y? Przede wszystkim nie nale�y traktowa� Borland C++ 4/4.5 jako "upgrade" do wcze�niejszych wersji (3, czy 3.1). W kompilatorze dokonano sporych zmian (np. inaczej działa operator new). Nie wolno zatem "nadpisa�" zawarto�ci poprzednich katalogów i plików o tych samych nazwach. Szczególnie dotyczy to plików konfiguracyjnych BCCONFIG.BCW i TDCONFIG.TDW. Je�li stare wersje tych plików nie zostan przemianowane, b d� usuni�te z pola widzenia (PATH) - pojawi si� konflikty przy uruchamianiu BC++. Ze wzgl�du na wprowadzone zmiany pliki .OBJ tworzone przez wcze�niejsze kompilatory C b�d w zasadzie przeno�ne, natomiast pliki .OBJ i biblioteki utworzone przez wcze�niejsze wersje kompilatorów C++ (szczególnie Borland C++ 3.1) b�d sprawia� kłopoty (nie b�d np. poprawnie wywoływane destruktory). Przy konsolidacji "starych" plików mo�na stosowa� opcj� -K2 konsolidatora, co pozwoli zmniejszy� do minimum ryzyko

konfliktów. Je�li jest ju� Borland Pascal 7... Je�li masz ju� zainstalowany Borland Pascal 7 nale�y pami�ta�, �e poprawna praca obu kompilatorów w jednym systemie wymaga "uregulowania stosunków": 1. Ka�dy kompilator musi mie� własn kopi� debuggera TDW. Aby unikn � konfliktu pascalowski debugger mo�na przemianowa� np.: TDW.EXE --> PASTDW.EXE 2. Nale�y usun � stare pliki inicjuj ce TDW.INI. Mo�na tu posłu�y� si� narz�dziem TDWINI.EXE. 3. Nale�y sprawdzi� poprawno�� instalacji driverów w pliku SYSTEM.INI: DEVICE=X:\BC4\BIN\WINDPMI.386 DEVICE=X:\BC4\BIN\TDDEBUG.386 <-- tu mo�liwy konflikt z BP 7 Nale�y usun � dubluj ce si� instalacje pozostawiaj c tylko te z BC++ 4 oraz usun � pascalowskie TDDEBUG.386 (pas) i TDWIN.DLL by uniemo�liwi� omyłkowe zainstalowanie. Przy poprawnym skonfigurowaniu systemu pozostałe zasoby ( w tym np. Resource Workshop 4) b�d poprawnie współpracowa� z BP 7. Stare zasoby C++ Zapewne wi�kszo�� u�ytkowników Borland C++ 4 "przesi dzie si�" z BC++ 3/3.1 lub Turbo C++. I tu tak�e czychaj pewne niebezpiecze�stwa. Stare projekty - tradycyjnie .PRJ w BC++ 4 zyskuj nowe domy�lne rozszerzenie .IDE. W okienku dialogowym zarz dzania projektem: Project | Open... przy pomocy opcji [3.1 Project Files (*.prj)] mo�na dokona� automatycznej konwersji do formatu .IDE, przy czym stara wersja pliku *.PRJ pozostanie bez zmian. Niektóre stare kody �ródłowe b�d wymaga� drobnych modyfikacji. Szczególnie nale�y zwró�i� uwag� na: - nakładki (overlay support) - zarz dzanie pami�ci (new - delete) - informacje diagnostyczne w plikach (debug info) - zmianne pseudorejestrowe (dost�pne teraz tylko w niektórych trybach) O bibliotece Turbo Vision. Biblioteka Turbo Vision Library - TV.LIB współpracuj ca poprawnie z BC++ 3.0/3.1 powinna zosta� powtórnie skompilowana, poniewa� BC++ 4 stosuje inny format: - informacji diagnostycznych (debug info format) - inn długo�� identyfikatorów (symbol length) - inn bibliotek� Runtime Library Kod �ródłowy biblioteki znajduje si� w katalogu: \BIN\TVISION\SOURCE Po (Uwaga!) wprowadzeniu kilku niewielkich zmian - do plików �ródłowych .CPP - do pliku instrukta�owego MAKEFILE oraz po skompilowaniu przy pomocy BCC 4 w DWU WERSJACH: TVO.LIB (z nakładk - Overlay) i TVNO.LIB (bez nakładki - No Overlay) biblioteka TVL mo�e by� nadal z powodzeniem stosowana z Borland C++ 4. Podobnie rekompilacji wymaga bibiloteka klas doł czona w wersji �ródłowej w katalogu X:\BC4\SOURCE\CLASSLIB. O AUTOMATYZACJI - CASE. Prócz znanego ju� od do�� dawna (w komputerologii kilka lat to cała epoka) tradycyjnego narz�dzia Resource Worshop, w wersji BC4 wyst�puj jeszcze inne narz�dzia CASE kategorii "wizard" (kreator aplikacji):

Page 142: C++ - lekcje

142

- ClassExpert - ApplicationExpert - DialogExpert - TargetExpert Nazwa TargetExpert pochodzi od ang. "Target platform" - docelowa platforma pracy aplikacji (DOS, Win16, Win32). Biblioteka OWL 2.0 została wzbogacona o dodatkowe klasy VBX umo�liwiaj ce współprac� z Visual Basic i wykorzystanie elementów utworzonych przy pomocy VB. Wspomaganie tworzenie programu przy pomocy tych narz�dzi (AppExpert podobnie jak inne narz�dzie typu Wizard jest automatycznym generatorem aplikacji) wymaga od u�ytkownika wyboru z listy "zagadnienia" a z okienek docelowych cech programu. Przytocz� tu dla przykładu list� opcji z pojedynczego okienka AppExperta z krótkim wyja�nieniem: ________________________________________________________________ Topics: (okienko z list : Zagadnienia) Application (program docelowy) -- Basic Opttions (wybór opcji podstawowych) -- Advanced Options (opcje zaawansowane) -- Code Gen Control (sposób generacji kodu) -- Admin Options (opcje "administracyjne") Main Window (główne okno programu) -- Basic Options (podstawowe opcje) -- SDI Client (interf. jednego dokumentu) -- MDI Client (interf. wielu dokumentów) MDI Child/View (okna potomne, widok/edycja) -- Basic Options (opcje podstawowe) Model: (Szkielet programu) [X] Multiple document interface - interfejs MDI [ ] Single document interface - interfejs SDI Features: (cechy) [.] SpeedBar (ma pasek narz�dzi) [.] Status line (ma wiersz statusowy) [.] Drag/drop (obsługuje ci gnij/upu��) [.] Printing (obsługuje drukark�) ________________________________________________________________ Po wybraniu w okienku klawisza [Generate] (wygeneruj) AppExpert generuje szkielet programu aplikacji o podanych własno�ciach. Wygenerowane zostaje od sze�ciu do dziewi�ciu (zale�nie od ustawienia opcji i Twoich �ycze�) plików projektu: *.IDE - plik projektu (lub .PRJ) *.APX - plik roboczy AppExpert'a (odpowiednik .PRJ) *.RC - plik zasobów *.RH - plik nagłówkowy zasobów *.H - plik nagłówkowy, �ródłowy *.CPP - moduł główny �ródłowy *.HPJ - plik pomocy *.RTF - �ródłowy pomocy kontekstowej *.ICO - ikonka projektu Przy pomocy rozkazu Generate makefile mo�na równie� automatycznie utworzy� plik instrukta�owy MAKEFILE dla generatora MAKE.EXE. Uzyskany plik szkieletowy *.CPP nale�y tylko uzupełni� o obsług� interesuj cych nas zdarze�/komunikatów. Przyspiesza to znacznie tworzenie typowych aplikacji. Programi�ci wszystkich krajów... BC++ 4 zawiera bibliotek� LOCALE.DLL umo�liwiaj c obsług� angielsko- francusko- i niemiecko- j�zyczn . Borland zapowiada, �e nast�pne wersje b�d coraz bogatsze. Doczekali�my si� spolszczenia Windows - mo�e i Borland C++ po polsku ju� tu� tu�? Póki co, najwygodniej podmieni� czcionki. ________________________________________________________________ ZAKO�CZENIE. I to ju� niestety koniec. Po przeanalizowaniu historii: programowania sekwencyjnego i strukturalnego

oraz nowoczesnych styli programowania: obiektowego i zdarzeniowego pozostał Ci ju� tylko wykonanie trzech rzeczy. Powiniene� teraz: 1. Pisa� własne aplikacje 2. Pisa� własne aplikacje 3. Pisa� własne aplikacje Tak naprawd� - jest to jedyny sposób, by zosta� dobrym programist . Przez pewien czas oka�e Ci si� zapewne przydatna dyskietka doł czona do ksi �ki. Znajdziesz tam jeszcze sporo programów przykładowych, które nie zmie�ciły si� w ksi �ce. Przyjemnej pracy z programem MEDYT.