16.05.2015 Views

Programowanie w C++ Borland Builder - Wyższa Szkoła Informatyki ...

Programowanie w C++ Borland Builder - Wyższa Szkoła Informatyki ...

Programowanie w C++ Borland Builder - Wyższa Szkoła Informatyki ...

SHOW MORE
SHOW LESS

Create successful ePaper yourself

Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.

<strong>Programowanie</strong><br />

w C i <strong>C++</strong><br />

BORLAND <strong>C++</strong><br />

- 1-


Spis Treści<br />

Lekcja 1. Co o C i <strong>C++</strong> każdy wiedzieć powinien. 3<br />

Lekcja 2. Jak korzystać z kompilatora <strong>Borland</strong> <strong>C++</strong>? 8<br />

Lekcja 3. Główne menu i inne elementy IDE. 11<br />

Lekcja 4. Jeszcze o IDE <strong>C++</strong> . 18<br />

Lekcja 5. Działania przy pomocy myszki i błędy w programie. 24<br />

Lekcja 6. Następny program - komputerowa arytmetyka. 29<br />

Lekcja 7. Z czego składa się program. 33<br />

Lekcja 8. Jakich słów kluczowych używa <strong>C++</strong>. 42<br />

Lekcja 9. O sposobach odwoływania się do danych. 51<br />

Lekcja 10. Jakie operatory stosuje <strong>C++</strong>. 59<br />

Lekcja 11. Jak deklarować zmienne. Co to jest wskaźnik. 65<br />

Lekcja 12. Wskaźniki i tablice w C i <strong>C++</strong>. 74<br />

Lekcja 13. Jak tworzyć w programie pętle i rozgałęzienia. 82<br />

Lekcja 14. Jak tworzyć i stosować struktury. 90<br />

Lekcja 15. Jak posługiwać się funkcjami. 97<br />

Lekcja 16. Asembler tasm i basm. 108<br />

Lekcja 17. Trochę szczególów technicznych. 115<br />

Lekcja 18. O łańcuchach tekstowych 123<br />

Lekcja 19. Kilka innych przydatnych funkcji. 131<br />

Lekcja 20. Jeśli program powinien uruchomić inny program... 135<br />

Lekcja 21. Kilka procesów jednocześnie. 141<br />

Lekcja 22. Na zdrowy chłopski rozum programisty. 148<br />

Lekcja 23. Co nowego w <strong>C++</strong>? 153<br />

Lekcja 24. Skąd wzięły się klasy i obiekty w c++. 164<br />

Lekcja 25. Przykład obiektu. 171<br />

Lekcja 26. Co to jest konstruktor. 176<br />

Lekcja 27. O dziedziczeniu. 185<br />

Lekcja 28. Dziedziczenie złożone. 188<br />

Lekcja 29. Funkcje i overloading. 193<br />

Lekcja 30. Wymiana danych między obiektami. 197<br />

Lekcja 31. Przekazanie obiektów jako argumentów do funkcji. 205<br />

Lekcja 32. Wskaźniki do obiektów. 210<br />

Lekcja 33. Overloading operatorów. 212<br />

Lekcja 34. O zastosowaniu dziedziczenia. 222<br />

Lekcja 35. Funkcje wirtualne i klasy abstrakcyjne. 227<br />

Lekcja 36. Kaźdy dysk jest za mały, a kaźdy procesor zbyt wolny. 232<br />

Lekcja 37. O <strong>C++</strong>, Windows i małym Chińczyku. 236<br />

Lekcja 38. Korzystamy ze standardowych zasobów Windows. 242<br />

Lekcja 39. Struktura programu proceduralno - zdarzeniowego 248<br />

Lekcja 40. Jak tworzy się aplikację dla Windows? 254<br />

Lekcja 41. Kompilatory "specjalnie dla Windows". 259<br />

Lekcja 42. Elementy sterujące i zarządzanie programem. 265<br />

Lekcja 43. O okienkach dialogowych. 272<br />

Lekcja 44. Dołączanie zasobów - menu i okienka dialogowe. 275<br />

Lekcja 45. O programach obiektowo - zdarzeniowych. 280<br />

Lekcja 46. Aplikacja obiektowa - rysowanie w oknie. 290<br />

Lekcja 47. O pakietach <strong>Borland</strong> <strong>C++</strong> 4/4.5. 294<br />

Plik zostal sciagniety ze strony BINBOY'a<br />

http://www.binboy.w.pl<br />

mail: karol@binboy.w<br />

http://binboy.koti.com.pl<br />

binboy@binboy.koti.com<br />

- 2-


Lekcja 1. Co o C i <strong>C++</strong> każdy wiedzieć powinien.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, dlaczego pora na <strong>C++</strong>.<br />

________________________________________________________________<br />

Język <strong>C++</strong> jest uniwersalnym, nowoczesnym językiem programowania.<br />

Stosowane przez USA i inne kraje wobec Polski wieloletnie<br />

embargo COCOM'u (przeszkody w dostępie do nowoczesnej<br />

technologii) sprawiły m. in., że popularność OS2, UNIXa i C/<strong>C++</strong><br />

jest w Polsce do dziś nieproporcjonalnie mała, a Basica, Pascala<br />

i DOSa nieproporcjonalnie duża. W USA <strong>C++</strong> już od kilku lat<br />

stanowi podstawowe narzędzie programistów.<br />

Już słyszę oburzenie (A co mnie obchodzi historia<br />

"komputerologii" i koligacyjki!). Otóż obchodzi, bo wynikają z<br />

niej pewne "grzechy pierworodne" języka C/<strong>C++</strong>, a dla Ciebie,<br />

szanowny Czytelniku - pewne wnioski praktyczne.<br />

Grzech Pierwszy:<br />

* Kompilator języka C/<strong>C++</strong> jest standardowym wyposażeniem systemu<br />

operacyjnego UNIX.<br />

Skutki praktyczne:<br />

Każdy PC jest w momencie zakupu (co często wchodzi w cenę zakupu<br />

komputera) wyposażany w system operacyjny DOS - np. DR DOS, PC<br />

DOS, PTS DOS lub MS DOS. Standardowo w zestaw systemu MS DOS<br />

wchodzi interpreter języka BASIC (w MS-DOS - QBasic.EXE). Możesz<br />

więc być pewien, że jeśli jest DOS, to musi być i BASIC.<br />

Podobnie rzecz ma się z C/<strong>C++</strong>. Jeśli jest na komputerze system<br />

UNIX (za wyjątkiem najuboższych wersji systemu XENIX), masz tam<br />

do dyspozycji kompilator C/<strong>C++</strong>, za to BASICA ani Pascala prawie<br />

na pewno tam nie ma. Podobnie coraz popularniejszy OS/2<br />

wyposażony jest w kompilator (całkiem niezły) <strong>C++</strong> i dodatkowo<br />

jeszcze w pewne gotowe-firmowe biblioteki.<br />

Grzech drugi:<br />

* Język C/<strong>C++</strong> powstał jeszcze zanim wymyślono PC, DOS, GUI<br />

(Graficzny Interfejs Użytkownika), Windows i inne tym podobne.<br />

Dwa najważniejsze skutki praktyczne:<br />

I. W założeniach twórców język <strong>C++</strong> miał być szybki (i jest) i<br />

zajmować mało miejsca w pamięci (bo ówczesne komputery miały jej<br />

bardzo mało!). Zawiera więc różne, niezrozumiałe dla nas z<br />

dzisiejszego punktu widzenia skróty. Np. to co w Pascalu czy<br />

Basicu wygląda zrozumiale:<br />

i:=i+1;<br />

(Pascal)<br />

10 I=I+1 lub inaczej NEXT I (Basic)<br />

- 3-


to w języku <strong>C++</strong> wygląda dziwacznie:<br />

i++; albo jeszcze dziwniej ++i;<br />

Tym niemniej zwróć uwagę, że w Pascalu zajmuje to 7 znaków, w<br />

Basicu - 8 znaków (spacja to też znak!), a w <strong>C++</strong> tylko 4.<br />

Inny przykład:<br />

X=X+5<br />

X:=X+5<br />

X+=5<br />

(Basic, 5 znaków),<br />

(Pascal, 6 znaków),<br />

(<strong>C++</strong>, tylko 4 znaki).<br />

Z takiej właśnie filozofii wynika i sama nazwa - najkrótsza z<br />

możliwych. Jeśli bowiem i++ miało znaczyć mniej więcej tyle samo<br />

co NEXT I (następne I) to <strong>C++</strong> znaczy mniej więcej tyle samo co<br />

"NASTĘPNA WERSJA C".<br />

II. Nie ma nic za darmo. W języku C/<strong>C++</strong>, podobnie jak w<br />

samochodzie wyścigowym formuły I, za szybkość i skuteczność<br />

płaci się komfortem. Konstrukcje stosowane w języku C/<strong>C++</strong> są<br />

maksymalnie dostosowane do "wygody" komputera. Pozwala to na<br />

uzyskiwanie ˙niezwykle szybkich "maszynowo-zorientowanych" kodów<br />

wykonywalnych programu, ale od programisty wymaga<br />

przyzwyczajenia się do "komputerowo-zorientowanego sposobu<br />

myślenia".<br />

Grzech Trzeci (i chyba najcięższy):<br />

* Jest najlepszy. Ostrożniej - jest najchętniej stosowanym<br />

narzędziem profesjonalnych programistów.<br />

Ma oczywiście konkurentów. Visual Basic (do małych aplikacji<br />

okienkowych), Turbo Pascal (do nauki podstaw - elementów<br />

programowania sekwencyjnego, proceduralno-strukturalnego),<br />

QuickBasic (programowanie strukturalne w środowisku DOS),<br />

Fortran 90, ADA, SmallTalk, itp, itd.<br />

Sam wielki Peter Norton przyznaje, że początkowe wersje swojego<br />

słynnego pakietu Norton Utilities pisał w Pascalu, ale dopiero<br />

przesiadka na C/<strong>C++</strong> pozwoliła mu doprowadzić NU do dzisiejszej<br />

doskonałości. Jakie są programy Petera Nortona - każdy widzi...<br />

Zapewne masz na swoim komputerze kilka różnych aplikacji (np.<br />

TAG, QR-Tekst, Word, itp.) - jeśli zajrzysz do nich do środka<br />

(View), możesz sam przekonać się, że większość z nich została<br />

napisana właśnie w <strong>C++</strong> (Kompilatory <strong>C++</strong> pozostawiają w kodzie<br />

wynikowym .EXE swoją wizytówkę zwykle czytelną przy pomocy<br />

przeglądarki; przekonasz się o tym także zaglądając przez [View]<br />

do własnych programów); stosowane narzędzia możesz rozpoznać<br />

także po obecności dynamicznych bibliotek - np. BWCC.DLL -<br />

biblioteka elementów sterujących - klawiszy, itp - <strong>Borland</strong><br />

Custom Controls for Windows).<br />

Skutki praktyczne:<br />

- 4-


Nauczywszy się języka C/<strong>C++</strong> możesz nie bać się ani systemu<br />

UNIX/XENIX a ich środowiska okienkowego - X Windows, ani OS2,<br />

ani Windows 95 (dotychczasowe testy starych 16-bitowych<br />

aplikacji wykazały b. wysoki stopień kompatibilności), ani<br />

stacji roboczych, ani dużych komputerów klasy mainframe. Język<br />

C/<strong>C++</strong> dosłużył się bowiem ogromnej ilości tzw. implementacji<br />

czyli swoich odmian, przeznaczonych dla różnych komputerów i dla<br />

różnych systemów operacyjnych. Windows NT i Windows 95 również<br />

zostały napisane w <strong>C++</strong>.<br />

Czytając prasę (np. Computer World, PC-Kurier i in.) zwróć<br />

uwagę, że najwięcej ofert pracy jest właśnie dla programistów<br />

posługujących się <strong>C++</strong> (i tak zapewne będzie jeszcze przez kilka<br />

lat, póki nie wymyślą czegoś lepszego - np. jakiegoś <strong>C++</strong>+).<br />

Z Grzechu Trzeciego (choć nie tylko) wynika także pośrednio<br />

Grzech Czwarty.<br />

Języka <strong>C++</strong> Grzech Czwarty - ANSI C, <strong>C++</strong>, czy Turbo <strong>C++</strong>, Visual<br />

<strong>C++</strong>, czyli mała wieża BABEL.<br />

Nie jestem pewien, czy "wieża BABEL" jest określeniem<br />

trafniejszym niż "kamień filozoficzny", bądź "perpetuum mobile".<br />

To co w ciągu ostatnich lat stało się z językiem <strong>C++</strong> ma coś<br />

wspólnego z każdym z tych utopijnych symboli. A w dużym<br />

uproszczeniu było to tak.<br />

Podobnie, jak mechanikom od zarania dziejów marzyło się<br />

perpetuum mobile, tak informatykom zawsze marzyło się stworzenie<br />

jednego SUPER-UNIWERSALNEGO języka programowania. Takiego, który<br />

byłby zupełnie niezależny od sprzętu tzn., aby program napisany<br />

w takim języku mógł być przeniesiony BEZ ŻADNYCH ZMIAN na<br />

dowolny komputer I DZIAŁAŁ. Do takiej roli pretendowały kolejno<br />

FORTRAN, Algol a potem przyszła pora na C/<strong>C++</strong>. Gdyby informatycy<br />

nie okazali się zbyt zachłanni, może coś by z tego wyszło. Ale,<br />

jak to w życiu, programiści (podobnie jak żona rybaka z bajki "O<br />

rybaku i złotej rybce") chcieli wszystkiego naraz:<br />

* żeby program dał się przenieść na komputer innego typu i<br />

działał,<br />

* żeby działał szybko i optymalnie wykorzystywał sprzęt,<br />

* żeby umiał wszystko, co w informatyce tylko wymyślono (tj. i<br />

grafika, i obiekty, i obsługa peryferii i...).<br />

I stało się. W pomyślanym jako uniwersalny języku zaczęły<br />

powstawać odmiany, dialekty, mutacje, wersje itp. itd.<br />

Jeśli <strong>C++</strong> nie jest Twoim pierwszym językiem, z pewnością<br />

zauważyłeś Czytelniku, że pomiędzy GW Basic a Quick Basic są<br />

pewne drobne różnice. Podobnie Turbo Pascal 7.0 trochę różni się<br />

od Turbo Pascala 5.0. Mimo to przykład poniżej pewnie Cię trochę<br />

zaskoczy. Dla zilustrowania skali problemu przedstawiam poniżej<br />

dwie wersje TEGO SAMEGO PROGRAMU napisanego w dwu różnych<br />

wersjach TEGO SAMEGO JĘZYKA <strong>C++</strong>. . Obydwa programy robią<br />

dokładnie to samo. Mają za zadanie wypisać na ekranie napis<br />

"Hello World" (czyli "Cześć świecie!").<br />

- 5-


Program (1)<br />

main()<br />

{<br />

printf("Hello World\n");<br />

}<br />

Program (2)<br />

#include <br />

#include <br />

LPSTR p = "Hello World\n";<br />

main(void)<br />

{<br />

cout


upodobania)<br />

* nazwy stałych<br />

(gdyby chodziło tylko o PI i e - wszystko byłoby proste)<br />

* nazy zasobów (FILE, PRN, CONSOLE, SCREEN itp. itd)<br />

(tu jest lepiej, ale też rozbieżności są zauważalne)<br />

Autor programu może jeszcze nadawać zmiennym (liczbom, zmiennym<br />

napisom, obiektom, itp.) własne nazwy, więc czasem nawet<br />

wytrawny programista ma kłopoty ze zrozumieniem tekstu<br />

żródłowego programu...<br />

W języku C a następnie <strong>C++</strong> przyjęto pewne maniery nadawania nazw<br />

- identyfikatorów ułatwiające rozpoznawanie tych grup słów:<br />

* nazwa() - funkcja<br />

* słowa kluczowe i nazwy zmiennych - małymi literami<br />

* STAŁE - nazwy stałych najczęściej dużymi literami<br />

* long/LONG - typy danych podstawowe/predefiniowane dla Windows<br />

_NAZWA - nazwy stałych predefiniowanych przez producenta<br />

__nazwa lub __nazwa__ - identyfikatory charakterystyczne dla<br />

danej wersji kompilatora<br />

itp., których to zwyczajów i ja postaram się przestrzegać w<br />

tekście książki.<br />

Amerykański Instytut Standardów ANSI od lat prowadzi walkę z<br />

wiatrakami. Stoi na straży jednolitego standardu języka, który<br />

nazywa się standardem ANSI C i ANSI <strong>C++</strong>. Wielcy producenci od<br />

czasu do czasu organizują konferencje i spotkania gdzieś w<br />

ciepłych krajach i uzgadniają niektóre standardy - czyli wspólne<br />

dla nich i zalecane dla innych normy, ale niektórzy bywają<br />

zazdrośni o własne tajemnice i nie publikują wszystkich<br />

informacji o swoich produktach. Dlatego wszelkie "słuszne i<br />

uniwersalne" standardy typu ODBC, Latin 2, Mazovia, LIM, OLE,<br />

DDE, BGI, itp., itd. mają niestety do dziś ograniczony zakres<br />

stosowalności a wszelkie zapewnienia producentów o całkowitej<br />

zgodności ich produktu z... (tu wpisać odpowiednie) należy<br />

niestety nadal traktować z pewną rezerwą.<br />

W niniejszej książce zajmiemy się kompilatorem <strong>Borland</strong> <strong>C++</strong> w<br />

jego wersjach 3.0 do 4.5, jest to bowiem najpopularniejszy w<br />

Polsce kompilator języka C/<strong>C++</strong> przeznaczony dla komputerów IBM<br />

PC. Nie bez znaczenia dla tej decyzji był także fakt, że <strong>Borland</strong><br />

<strong>C++</strong> i Turbo <strong>C++</strong> bez konfliktów współpracuje z:<br />

* Turbo Pascal i <strong>Borland</strong> Pascal;<br />

* Assemblerami: TASM, BASM i MASM;<br />

* Turbo Debuggerem i Turbo Profilerem;<br />

* bibliotekami Turbo Vision, ObjectVision, Object Windows<br />

Library, Database Tools, itp.<br />

* pakietami innych producentów - np. Win/Sys Library, Object<br />

- 7-


Professional, CA-Visual Objects, Clipper, itp.<br />

i in. produktami "ze stajni" <strong>Borland</strong>a popularnymi wśród<br />

programistów. Programy TASM/BASM, Debugger, Profiler a także<br />

niektóre biblioteki (np. Object Windows Library, Turbo Vision<br />

Library, itp. wchodzą w skład pakietów instalacyjnych BORLANDA,<br />

ale UWAGA - niestety nie wszystkich). <strong>Borland</strong> <strong>C++</strong> 4+ pozwala,<br />

dzięki obecności specjalnych klas VBX w bibliotece klas i<br />

obiektów Object Windows Library na wykorzystanie programów i<br />

zasobów tworzonych w środowisku Visual Basic'a. Podobnie<br />

kompilatory <strong>C++</strong> firmy Microsoft (szczególnie Visual <strong>C++</strong>)<br />

bezkonfliktowo współpracują z zasobami innych aplikacji - np.<br />

Access, Excel, itp..<br />

Warto tu zwrócić uwagę na jeszcze jeden czynnik, który może stać<br />

się Twoim, Czytelniku atutem. Jeśli znasz już kompilatory Turbo<br />

Pascal, bądź <strong>Borland</strong> Pascal, zwróć uwagę, że wiele funkcji<br />

zaimplementowanych w Turbo Pascal 6.0. czy 7.0 ma swoje<br />

odpowiedniki w BORLAND <strong>C++</strong> i Turbo <strong>C++</strong>. Odpowiedniki te zwykle<br />

działają dokładnie tak samo, a różnią się najczęściej<br />

nieznacznie pisownią nazwy funkcji. Wynika to z błogosławieństwa<br />

"lenistwa" (ponoć homo sapiens najwięcej wynalazków popełniał<br />

właśnie ze strachu, bądź z lenistwa...). Firmie <strong>Borland</strong> "nie<br />

chciało się" wymyślać od nowa tego, co już sprawdziło się<br />

wcześniej i do czego przyzwyczaili się klienci! I odwrotnie.<br />

Poznawszy <strong>Borland</strong>/Turbo <strong>C++</strong> z łatwością zauważysz te same<br />

funkcje w <strong>Borland</strong>/Turbo Pascalu.<br />

[!!!]UWAGA!<br />

________________________________________________________________<br />

O Kompilatorach BORLAND <strong>C++</strong> 4 i 4.5 napiszę nieco póżniej,<br />

ponieważ są bardziej skomplikowane i wymagają trochę większej<br />

znajomości zasad tworzenia i uruchamiania programów (projekty).<br />

To prawda, że zawierają narzędzia klasy CASE do automatycznego<br />

generowania aplikacji i jeszcze kilka innych ułatwień, ale miej<br />

trochę cierpliwości...<br />

________________________________________________________________<br />

[???] C.A.S.E.<br />

________________________________________________________________<br />

CASE - Computer Aided Software Engineering - inżynieria<br />

programowa wspomagana komputerowo. Najnowsze kompilatory <strong>C++</strong><br />

wyposażone są w narzędzia nowej generacji. W różnych wersjach<br />

nazywają się one AppExpert, ClassExpert, AppWizard, VBX<br />

Generator, itp. itd, które pozwalają w dużym stopniu<br />

zautomatyzować proces tworzenia aplikacji. Nie można jednak<br />

zaczynać kursu pilotażu od programowania autopilota - a kursu<br />

programowania od automatycznych generatorów aplikacji dla<br />

Windows...<br />

________________________________________________________________<br />

- 8-


Zaczynamy zatem od rzeczy najprostszych, mając jedynie tę<br />

krzepiącą świadomość, że gdy już przystąpimy do pisania<br />

aplikacji konkurencyjnej wobec Worda, QR-Tekst'a, czy Power<br />

Point'a - może nas wspomagać system wspomaganina CASE dołączony<br />

do najnowszych wersji BORLAND <strong>C++</strong> 4 i 4.5. Jeśli mamy już gotowe<br />

aplikacje w Visual Basic'u - <strong>Borland</strong> <strong>C++</strong> 4+ pozwoli nam<br />

skorzystać z elementów tych programów (ale pracować te aplikacje<br />

po przetransponowaniu do <strong>C++</strong> będą od kilku do kilkuset razy<br />

szybciej).<br />

_______________________________________________________________<br />

- 9-


LEKCJA 2. Jak korzystać z kompilatora BORLAND <strong>C++</strong>?<br />

________________________________________________________________<br />

W trakcie tej lekcji poznasz sposoby rozwiązania typowych<br />

problemów występujących przy uruchomieniu kompilatora <strong>Borland</strong><br />

<strong>C++</strong>.<br />

________________________________________________________________<br />

UWAGA:<br />

Z A N I M rozpoczniesz pracę z dyskietką dołączoną do niniejszej<br />

książki radzimy Ci SPORZĄDZIĆ ZAPASOWĄ KOPIĘ DYSKIETKI przy<br />

pomocy rozkazu DISKCOPY, np.<br />

DISKCOPY A: A: lub DISKCOPY B: B:<br />

Unikniesz dzięki temu być może wielu kłopotów, których może Ci<br />

narobić np. przypadkowy wirus lub kropelka kawy.<br />

INSTALACJA DYSKIETKI.<br />

Na dyskietce dołączonej do niniejszej książki, którą najlepiej<br />

zainstalować na dysku stałym (z dyskiem pracuje się znacznie<br />

szybciej, a prócz tego jest tam znacznie więcej miejsca), w jej<br />

katalogu głównym znajduje się programik instalacyjny o nazwie:<br />

INSTALUJ.BAT<br />

napisany jako krótki plik wsadowy w języku BPL (Batch<br />

Programming Language - język programowania wsadowego). Aby<br />

zainstalować programy z dyskietki na własnym dysku powinieneś:<br />

* sprawdzić, czy na dysku (C:, D:, H: lub innym) jest co<br />

najmniej 2 MB wolnego miejsca,<br />

* włożyć dyskietkę do napędu i wydać rozkaz:<br />


Program instalacyjny utworzy na wskazanym dysku katalog<br />

\C-BELFER<br />

i tam skopiuje całą zawartość dyskietki oraz dokona dekompresji<br />

(rozpakowania) plików. Jeśli chcesz skopiwać zawartość dyskietki<br />

do własnego katalogu roboczego, wystarczy "wskazać" programowi<br />

instalacyjnemu właściwy adres:<br />


komputerze; najlepiej zasięgnij rady lokalnego eksperta).<br />

[???] NIE CHCE USTAWIĆ ŚCIEŻKI ?<br />

________________________________________________________________<br />

Tak czasem się zdarza - zwykle wtedy, gdy pracujesz w DOS-ie z<br />

programem Norton Commander. Musisz pozbyć się "na chwilę"<br />

programu NC. Naciśnij [F10] - Quit i potwierdź przez [Y] lub<br />

[Enter]. Po ustawieniu ścieżek możesz powtórnie uruchomić NC.<br />

________________________________________________________________<br />

Albo<br />

3. Dodać do pliku AUTOEXEC.BAT dodatkową ścieżkę. Jest to<br />

wyjście najlepsze. Na końcu linii ustawiającej ścieżki - np.:<br />

PATH C:\; C:\DOS; C:\NC; C:\WINDOWS<br />

dodaj ścieżkę do kompilatora <strong>C++</strong>, np.:<br />

PATH C:\; C:\DOS; C:\NC; D:\BORLANDC\BIN;<br />

Załatwi to problem "raz na zawsze". Po uruchomieniu komputera<br />

ścieżka będzie odtąd zawsze ustawiana automatycznie.<br />

Ponieważ kompilator <strong>C++</strong> wymaga w trakcie pracy otwierania i<br />

łączenia wielu plików, różne wersje (program instalacyjny<br />

INSTALL.EXE podaje tę informację w okienku pod koniec<br />

instalacji) wymagają dodania do pliku konfiguracyjnego<br />

CONFIG.SYS wiersza:<br />

FILES = 20<br />

(dla różnych wersji wartość ta wacha się w granicach od 20 do<br />

50). Najbezpieczniej, jeśli nie masz pewności dodać 50. Jeśli<br />

wybrałeś wariant trzeci i ewentualnie zmodyfikowałeś swój<br />

CONFIG.SYS, wykonaj przeładowanie systemu [Ctrl]-[Alt]-[Del].<br />

Teraz możesz wydać rozkaz<br />

BC[Enter]<br />

Mam nadzieję, że tym razem się udało i oto jesteśmy w IDE<br />

<strong>Borland</strong> <strong>C++</strong>. Jeśli nie jesteś jedynym użytkownikiem, na ekranie<br />

rozwinie się cała kaskada okienek roboczych. Skonsultuj z<br />

właścicielem, które z nich można pozamykać a które pliki można<br />

skasować lub przenieść. Pamiętaj "primo non nocere" - przede<br />

wszystkim nie szkodzić!<br />

€[S!] IDE = Integrated Development Environment,<br />

IDE, czyli Zintegrowane Środowisko Uruchomieniowe. Bardziej<br />

prozaicznie - połączony EDYTOR i KOMPILATOR. Zapewne znasz już<br />

coś podobnego z Pascala lub Quick Basica. Od dziś będzie to<br />

Twoje środowisko pracy, w którym będziesz pisać, uruchamiać i<br />

modyfikować swoje programy.<br />

€[???] DISK FULL!<br />

- 12-


________________________________________________________________<br />

Co robić, jeśli przy próbie uruchomienia kompilator <strong>C++</strong><br />

odpowiedział Ci:<br />

Disk full! Not enough swap space.<br />

Program BC.EXE (TC.EXE) jest bardzo długi. Jeśli wydasz rozkaz<br />

(wariant 1: Turbo <strong>C++</strong> 1.0, niżej BORLAND <strong>C++</strong> 3.1):<br />

DIR TC.EXE<br />

uzyskasz odpowiedź, jak poniżej:<br />

C:>DIR TC.EXE<br />

Directory of D:\TC\BIN<br />

TC EXE 876480 05-04-90 1:00a<br />

1 file(s) 876480 bytes<br />

17658880 bytes free<br />

C:>DIR BC.EXE<br />

Directory of C:\BORLANDC\BIN<br />

BC EXE 1410992 06-10-92 3:10a<br />

1 file(s) 1410992 bytes<br />

18926976 bytes free<br />

Ponieważ plik kompilatora nie mieści się w 640 K pamięci musi<br />

dokonywać tzw. SWAPOWANIA i tworzy na dysku dodatkowy plik<br />

tymczasowy (ang. swap file). Na dysku roboczym powinno<br />

pozostawać najmniej 500 KB wolnego miejsca. Jeśli możesz,<br />

pozostaw na tym dysku wolne nie mniej niż 1 MB. Ułatwi to i<br />

przyspieszy pracę.<br />

________________________________________________________________<br />

Tworzony tymczasowo plik roboczy wygląda tak:<br />

Volume in drive D has no label<br />

Directory of D:\SIERRA<br />

TC000A SWP 262144 12-13-94 5:42p<br />

1 file(s) 262144 bytes<br />

11696320 bytes free<br />

(13-XII to dziś!)<br />

€[!!!] UWAGA:<br />

Kompilator <strong>C++</strong> będzie próbował tworzyć plik tymczasowy zawsze w<br />

bieżącym katalogu, tzn. tym, z którego wydałeś rozkaz<br />

TC lub BC.<br />

II. WNIOSKI PRAKTYCZNE.<br />

* Lepiej nie uruchamiać <strong>C++</strong> "siedząc" na dyskietce, ponieważ<br />

może mu tam zabraknąć miejsca na plik tymczasowy.<br />

* Dla użytkowników Novella: Uruchamiajcie kompilator <strong>C++</strong> tylko<br />

- 13-


we własnych katalogach - do innych możecie nie mieć praw zapisu.<br />

Plik .SWP jest tworzony tylko podczas sesji z kompilatorem <strong>C++</strong> i<br />

usuwany natychmiast po jej zakończeniu. Możesz go zobaczyć tylko<br />

wychodząc "na chwilę" do systemu DOS przy pomocy rozkazu DOS<br />

Shell (menu File).<br />

€[S!] SWAP - Zamiana.<br />

________________________________________________________________<br />

Jeśli wszystkie dane, potrzebne do pracy programu nie mieszczą<br />

się jednocześnie w pamięci operacyjnej komputera, to program -<br />

"właściciel", (lub system operacyjny - DOS, OS2, Windows) może<br />

dokonać tzw. SWAPOWANIA. Polega to na usunięciu z pamięci<br />

operacyjnej i zapisaniu na dysk zbędnej w tym momencie części<br />

danych, a na ich miejsce wpisaniu odczytanej z dysku innej<br />

części danych, zwykle takich, które są programowi pilnie<br />

potrzebne do pracy właśnie teraz.<br />

________________________________________________________________<br />

€[Z] - Propozycje zadań do samodzielnego wykonania.<br />

----------------------------------------------------------------<br />

1.1 Sprawdź ile bajtów ma plik .EXE w tej wersji kompilatora<br />

<strong>C++</strong>, której używasz.<br />

1.2. Posługując się rozkazem DOS Shell z menu File sprawdź gdzie<br />

znajduje się i jakiej jest wielkości plik tymczasowy .SWP. Ile<br />

masz wolnego miejsca na dysku ?<br />

________________________________________________________________<br />

EOF<br />

- 14-


LEKCJA 3. Główne menu i inne elementy IDE.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się jak poruszać się w<br />

zintegrowanym środowisku (IDE) Turbo <strong>C++</strong>.<br />

________________________________________________________________<br />

Najważniejszą rzeczą w środowisku IDE jest GŁÓWNE MENU (ang.<br />

MENU BAR), czyli pasek, który widzisz w górnej części ekranu.<br />

Działa to podobnie, jak główne menu w programie Norton Commander<br />

(dostępne tam przez klawisz [F9]).<br />

KRÓTKI PRZEGLĄD GŁÓWNEGO MENU.<br />

Przyciśnij klawisz [F10].<br />

Główne menu stało się aktywne. Teraz przy pomocy klawiszy<br />

kursora (ze strzałkami []) możesz poruszać się po menu i<br />

wybrać tę grupę poleceń, która jest Ci potrzebna. A oto nazwy<br />

poszczególnych grup:<br />

[S!]€GRUPY POLECEŃ - NAZWY POSZCZEGÓLNYCH "ROZWIJANYCH" MENU.<br />

= Bez nazwy (menu systemowe).<br />

File Operacje na plikach.<br />

Edit Edycja plików z tekstami źródłowymi programów.<br />

Search Przeszukiwanie.<br />

Run Uruchomienie programu.<br />

Compile Kompilacja programu.<br />

Debug "Odpluskwianie", czyli wyszukiwanie błędów w<br />

programie.<br />

Project Tworzenie dużych, wielomodułowych programów.<br />

Options Opcje, warianty IDE i kompilatora.<br />

Window Okna (te na ekranie).<br />

Help Pomoc, niestety po angielsku.<br />

UWAGA:<br />

__________________________________________________________<br />

W niektórych wersjach kompilatora na pasku głównego menu pojawi<br />

się jeszcze Browse - przeglądanie (funkcji, struktury klas i<br />

obiektów). Zwróć uwagę, że w okienkowych wersjach niektóre<br />

rozkazy "zmieniają" menu i trafiają do<br />

Browse, Debug, Project.<br />

W B<strong>C++</strong> 4 menu Run brak (!). Tworzenie aplikacji sprowadza się<br />

tam do następujących kroków:<br />

Project | Open project lub | AppExpert<br />

Debug | Run<br />

ROZWIJAMY MENU.<br />

Z takiego kręcenia się w kółko po pasku (a propos, czy<br />

zauważyłeś, że pasek podświetlenia może być "przewijany w<br />

kółko"?) jeszcze niewiele wynika. Robimy więc następny krok.<br />

- 15-


Wskaż w menu głównym nazwę "File" i naciśnij [Enter].<br />

Rozwinęło się menu File zawierające listę rozkazów dotyczących<br />

operacji na plikach. Po tym menu też możesz się poruszać przy<br />

pomocy klawiszy kursora ze strzałkami górę lub w dół. Masz do<br />

wyboru dwie grupy rozkazów rozdzielone poziomą linią:<br />

€[S!]<br />

______________________________________________________________<br />

Open - Otwórz istniejący już plik z programem (np. w celu<br />

dopisania czegoś nowego).<br />

New<br />

Save<br />

- Utwórz nowy plik (zaczynamy tworzyć nowy program).<br />

- Zapisz bieżący program na dysk. Pamiętaj: Pliki z<br />

dysku nie znikają po wyłączeniu komputera. Zawsze<br />

lepiej mieć o jedną kopię za dużo niż o jedną za mało.<br />

oraz<br />

Print - Wydrukuj program.<br />

Get Info€€ - Wyświetl informacje o stanie IDE.<br />

Dos Shell - Wyjście "na chwilę" do systemu DOS z możliwością<br />

powrotu do IDE przez rozkaz EXIT.<br />

Quit - Wyjście z IDE Turbo <strong>C++</strong> i powrót do DOSa. Inaczej -<br />

KONIEC PRACY.<br />

_______________________________________________________________<br />

Skoro już wiemy jak rozpocząć pracę nad nowym programem,<br />

zacznijmy przygotowanie do uruchomienia naszego pierwszego<br />

programu.<br />

Wybierz z menu File rozkaz Open... (otwórz plik). Ponieważ<br />

rozkaz taki jest niejednoznaczny, wymaga przed wykonaniem<br />

podania dodatkowych informacji. Gdyby Twój komputer mówił,<br />

zapytałby w tym momencie "który plik mam otworzyć?". Pytanie<br />

zadać musi, będzie więc prowadził dialog z Tobą przy pomocy<br />

OKIENEK DIALOGOWYCH. Jeśli wybrałeś z menu rozkaz OPEN i<br />

nacisnąłeś [Enter], to masz właśnie na ekranie takie okienko<br />

dialogowe. Okienko składa się z kilku charakterystycznych<br />

elementów:<br />

OKIENKO TEKSTOWE - (ang. Text Box lub Input Box) w którym możesz<br />

pisać (klawisz BackSpace [


[Shift]-[Tab] (spróbuj!).<br />

Możesz także posługiwać się myszką.<br />

Więcej o okienkach i menu dowiesz się z następnych lekcji, a na<br />

razie wróćmy do naszego podstawowego zadania - tworzenia<br />

pierwszego programu.<br />

Zanim zaczniemy tworzyć program włóż do kieszeni napędu A: (lub<br />

B:) dyskietkę dołączoną do niniejszej książki. Może ona stać się<br />

Twoją dyskietką roboczą i pomocniczą zarazem na okres tego<br />

kursu.<br />

Jeżeli zainstalowałeś zawartość dyskietki na dysku - przejdź do<br />

stosownego katalogu - C:\C-BELFER (D:\C-BELFER) i odszukaj tam<br />

programy przykładowe. Jeśli nie - możesz nadal korzystać z<br />

dyskietki (jest na niej trochę miejsca).<br />

Wpisz do okienka tekstowego nazwę A:\PIERWSZY (lub odpowiednio<br />

np. C:\C-BELFER\PIERWSZY). Rozszerzeniem możesz się nie<br />

przejmować - zostanie nadane automatycznie. Plik roboczy z Twoim<br />

programem zostanie utworzony na dyskietce w napędzie A:.<br />

Wskaż klawisz [Open] w okienku dialogowym i naciśnij [Enter] na<br />

klawiaturze.<br />

UWAGA!<br />

_________________________________________________________________<br />

Dopóki manipulujesz okienkiem tekstowym i okienkiem z listą<br />

klawisz polecenia [Open] jest wyróżniony (podświetlony) i<br />

traktowany jako tzw. OPCJA DOMYŚLNA (ang. default). W tym<br />

stadium aby wybrać [Open] WYSTARCZY NACISNĄĆ [Enter].<br />

__________________________________________________________________<br />

Wróciliśmy do IDE. zmieniło się tyle, że w nagłówku okna edytora<br />

zamiast napisu<br />

"NONAME00.CPP"<br />

(ang. no mame - bez nazwy)<br />

jest teraz nazwa Twojego programu - PIERWSZY.CPP. Kursor miga w lewym<br />

górnym rogu okna edytora. Możemy zaczynać.<br />

Pierwsze podejście do programu zrobimy trochę "intuicyjnie".<br />

Zamiast wyjaśniać wszystkie szczegóły posłużymy się analogią do<br />

konstrukcji w Pascalu i Basicu (zakładam, że napisałeś już<br />

choćby jeden program w którymś z tych języków). Szczegóły te<br />

wyjaśnię dokładniej począwszy od następnej lekcji.<br />

WPISUJEMY PROGRAM "PIERWSZY.CPP".<br />

Wpisz następujący tekst programu:<br />

/* Program przykładowy - [P-1] */<br />

- 17-


#include <br />

main()<br />

{<br />

printf("Autor: ..........."); /*tu wpisz imie Twoje!*/<br />

printf(" TO JA, TWOJ PROGRAM - PIERWSZY.CPP");<br />

printf("...achoj !!!");<br />

}<br />

I już. Jak widzisz nie jest to aż takie straszne. Gdyby nie to,<br />

że zamiast znajomego PRINT"TO JA...", albo writeln(".."); jest<br />

printf("...");, byłoby prawie całkiem zrozumiałe. Podobny<br />

program w Pascalu mógłby wyglądać np. tak:<br />

# include uses Crt;<br />

main() /* początek */ program AHOJ; {początek}<br />

{ Begin<br />

printf("Autor");<br />

write('Autor');<br />

printf("TO JA");<br />

write('TO JA');<br />

printf("ahoj");<br />

write('ahoj');<br />

} end.<br />

a w BASICU:<br />

10 PRINT "Autor" : REM Początek<br />

20 PRINT "TO JA"<br />

30 PRINT "ahoj"<br />

40 END<br />

€[!!!]UWAGA<br />

______________________________________________________________<br />

Zwróć uwagę, że działanie funkcji:<br />

PRINT (Basic),<br />

printf() (<strong>C++</strong>),<br />

Write i Writeln (Pascal)<br />

nie jest identyczne, a TYLKO PODOBNE.<br />

________________________________________________________________<br />

Sprawdzimy, czy program działa. Tam, gdzie są kropki wpisz Twoje<br />

imię - np. Ewa, Marian, Marcin. Pamiętaj o postawieniu na końcu<br />

znaków cudzysłowu ("), zamknięciu nawiasu i średniku (;) na<br />

końcu linii (wiersza).<br />

Naciśnij kombinację klawiszy [Alt]-[R]. Jest to inny, niż<br />

opisano poprzednio sposób dostępu do menu. Kombinacja klawiszy<br />

[Alt]-[Litera] powoduje uaktywnienie tego menu, którego nazwa<br />

zaczyna się na podaną literę. Przy takiej konwencji litera nie<br />

musi być zawsze pierwszą literą nazwy opcji. Może to być także<br />

litera wyróżniona w nazwie przez podkreślenie lub wyświetlenie<br />

np. w innym kolorze. I tak:<br />

[Alt]+[F] menu File (Plik)<br />

[Alt]+[C] menu Compile (Kompilacja<br />

- 18-


[Alt]+[R] menu Run (Uruchamianie)<br />

[Alt]+[W] menu Window (Okna)<br />

itd., itd..<br />

Kombinacja [Alt]+[R] wybiera więc menu Run (uruchomienie<br />

programu). Menu Run daje Ci do wyboru następujące polecenia:<br />

[S!]<br />

________________________________________________________________<br />

Run - Uruchomienie programu (Utwórz plik .EXE i Wykonaj).<br />

Program Reset - "Wyzerowanie" zmiennych programu.<br />

Go to Cursor - Wykonanie programu do miejsca wskazanego kursorem<br />

w tekście.<br />

Trace Into - Uruchom śledzenie programu.<br />

Step Over - Śledzenie programu z możliwością pominięcia funkcji.<br />

(dosł. tzw. "Przekraczanie" funkcji).<br />

Arguments - Uruchom program z zadanymi argumentami.<br />

________________________________________________________________<br />

Wybierz "Run". Jeśli nie zrobiłeś żadnego błędu, program<br />

powinien się skompilować z komentarzem "Success" i wykonać<br />

(kompilacja zakończona sukcesem; napis mignie tak szybko, że<br />

możesz tego nie zauważyć). Jeśli chcesz spokojnie obejrzeć<br />

wyniki działania swojego programu powinieneś wykonać<br />

następujące czynności:<br />

1. Rozwiń menu Window naciskając klawisze [Alt]-[W].<br />

2. Wybierz z menu rozkaz User screen (ekran użytkownika).<br />

Możesz wykonać to samo bez rozwijania menu naciskając kombinację<br />

klawiszy [Alt]-[F5].<br />

3. Po przejrzeniu wydruku naciśnij [Enter]. Wrócisz do okna<br />

edytora.<br />

Jeśli zrobiłeś błędy - kompilacja się nie uda i program nie<br />

zostanie wykonany, w okienku natomiast pojawi się napis "Errors"<br />

(czyli "Błędy"). Jeśli tak się stało naciśnij [Enter]<br />

dwukrotnie. Popraw ewentualne niezgodności i spróbuj jeszcze<br />

raz.<br />

Błędów zwykle bywa nie więcej niż dwa. Najczęściej jest to brak<br />

lub przekłamanie którejś litery (w słowie main lub printf) i<br />

brak średnika na końcu linii. W okienku komunikatów (Message)<br />

mogą pojawić się napisy - np.:<br />

Error: Statement missing ;<br />

(Błąd: Zgubiony znak ;)<br />

€[S] Error Messages - Komunikaty o błędach.<br />

________________________________________________________________<br />

Najczęściej w komunikatach o błędach będą na początku pojawiać<br />

się następujące słowa:<br />

- 19-


Error - błąd<br />

Warning - ostrzeżenie<br />

Syntax - składnia (składniowy)<br />

Expression - wyrażenie<br />

never used - nie użyte (nie zastosowane)<br />

assign - przypisywać, nadawać wartość/znaczenie<br />

value - wartość<br />

statement - operator, operacja, wyrażenie<br />

________________________________________________________________<br />

€[???] Co z tym średnikiem?<br />

________________________________________________________________<br />

Zwróć uwagę, że po pdświetleniu komunikatu o błędzie (pasek<br />

wyróżnienia podświetlenia możesz przesuwać po liście przy pomocy<br />

klawiszy ze strzałkami w górę i w dół) i po naciśnięciu [Entera]<br />

kompilator pokaże ten wiersz programu, w którym jego zdaniem<br />

jest coś nie w porządku. Brak średnika zauważa zwykle dopiero po<br />

przejściu do następnego wiersza (i tenże wiersz pokaże), co bywa<br />

na początku trochę mylące.<br />

________________________________________________________________<br />

[???] CZEGO ON JESZCZE CHCE ?<br />

________________________________________________________________<br />

Nawet po usunięciu wszystkich błędów <strong>C++</strong> nie "uspokoi się"<br />

całkiem i będzie wyświetlał ciągle komunikat ostrzegawczy:<br />

* w OKIENKU KOMPILACJI: (bardzo typowa sytuacja)<br />

Errors: 0 (Błędy: 0)<br />

Warnings: 1 (Ostrzeżenia: 1)<br />

* W OKIENKU KOMUNIKATÓW - (Messages - tym w dolnej części<br />

ekranu):<br />

*WARNING A:\PIERWSZY.C 4: Function should return a value in<br />

function main<br />

(Uwaga: Funkcja main powinna zwrócić wartość.)<br />

Na razie zadowolimy się spostrzeżeniem, że:<br />

* Błędy UNIEMOŻLIWIAJĄ KOMPILACJĘ i powodują komunikat ERRORS.<br />

* Ostrzeżenia NIE WSTRZYMUJĄ KOMPILACJI i powodują komunikat<br />

WARNINGS.<br />

Jaki jest sens powyższego ostrzeżenia i jak go uniknąć dowiesz<br />

się z następnych lekcji.<br />

________________________________________________________________<br />

Pozostaje nam w ramach tej lekcji:<br />

* Zapisać Twój pierwszy program na dysku i<br />

* Wyjść z IDE <strong>C++</strong>.<br />

- 20-


JAK STĄD WYJŚĆ ?<br />

Aby zapisać plik PIERWSZY.CPP z Twoim programem (końcową<br />

ostateczną wersją) na dysk należy wykonać następujące czynności:<br />

1. Naciśnij klawisz [F10].<br />

W głównym menu pojawi się pasek wyróżnienia sygnalizując, że<br />

menu stało się aktywne.<br />

2. Naciśnij klawisz [F].<br />

Pasek wyróżnienia przesunie się podświetlając menu File<br />

(operacje na plikach). Rozwinie się menu File.<br />

3. Naciśnij klawisz [S] - wybierz polecenie Save (jeśli chcesz<br />

zapisać program w bieżącym katalogu i pod bieżącą nazwą) lub<br />

rozkaz Save As... (zapisz jako...), podaj nowy dysk/katalog i<br />

nową nazwę pliku.<br />

Tekst Twojego programu został zapisany na dysku/dyskietce. Teraz<br />

możemy wyjść z <strong>C++</strong>.<br />

Aby to zrobić, wykonaj następujące czynności:<br />

1. Naciśnij klawisz [F10]. Uaktywni się główne menu.<br />

2. Rozwiń menu File naciskając klawisz [F].<br />

3. Wybierz z menu polecenie "Exit/Quit" i naciśnij [Enter].<br />

€[!!!] SAVE szybciej.<br />

________________________________________________________________<br />

Zwróc uwagę, że zamiast rozwijać kolejne menu, możesz korzystać<br />

z kombinacji klawiszy, które pozwalają Ci wydać rozkaz bez<br />

rozwijania menu. Takie kombinacje klawiszy (ang. hot keys lub<br />

shortcut keys) znajdziesz w menu obok rozkazu, np.:<br />

[Alt]-[X] - Quit/Exit<br />

[F2] - Save<br />

[F3] - Open<br />

[Alt]-[F5] - User screen (Podglądanie działania programu) itp.<br />

________________________________________________________________<br />

€[Z]<br />

________________________________________________________________<br />

1. Spróbuj napisać i uruchomić kilka własnych programów<br />

wypisujących różne napisy. W swoich programach zastosuj funkcję<br />

printf() według następującego wzoru:<br />

printf("....tu wpisz napis do wydrukowania...");<br />

zastosuj znaki przejścia do nowego wiersza według wzoru:<br />

printf("...napis...\n");<br />

porównaj działanie.<br />

- 21-


Swoim programom staraj się nadawać łatwe do rozpoznania nazwy<br />

typu PIERWSZY, DRUGI, ADAM1, PRZYKLAD itp.<br />

€[???] NIE CHCE DZIAŁAĆ ?<br />

________________________________________________________________<br />

Pamiętaj, że dla języka C i <strong>C++</strong> (w przeciwieństwie np. do<br />

Basica) PRINTF i printf to nie to samo! Słowa kluczowe i nazwy<br />

standardowych funkcji<br />

MUSZĄ BYĆ PISANE MAŁYMI LITERAMI !!!<br />

________________________________________________________________<br />

€[???] GDZIE MOJE PROGRAMY ?<br />

________________________________________________________________<br />

Bądź spokojny. Zapisz wersje źródłowe programów na dyskietkę<br />

(dysk). Swoje programy skompilowane do wykonywalnej wersji *.EXE<br />

znajdziesz najprawdopodobniej w katalogu głównym tego dysku, na<br />

którym zainstalowany został <strong>C++</strong> lub w katalogu<br />

\BORLANDC\BIN\.... Jeśli ich tam nie ma, zachowaj zimną krew i<br />

przeczytaj uważnie kilka następnych stron.<br />

________________________________________________________________<br />

€ PAMIĘTAJ:<br />

________________________________________________________________<br />

Jeśli masz oryginalny tekst programu, nazywany WERSJĄ ŹRÓDŁOWĄ<br />

PROGRAMU, zawsze możesz uzyskać ten program w wersji "roboczej",<br />

tzn. skompilować go na plik wykonywalny typu *.EXE (ang.<br />

EXEcutable - wykonywalny).<br />

________________________________________________________________<br />

€[S!] printf() - PRINTing Function - Funkcja DRUKująca<br />

________________________________________________________________<br />

na ekranie (dokładniej - na standardowym urządzeniu wyjścia).<br />

Odpowiednik PRINT w Basicu lub write w Pascalu. Dla ułatwienia<br />

rozpoznawania nazw funkcji w tekście większość autorów pisząca o<br />

języku <strong>C++</strong> umieszcza zawsze po nazwie funkcji parę nawiasów (tak<br />

też musi ją stosować programista w programach w <strong>C++</strong>). Ja także<br />

będę stosować dalej tę zasadę.<br />

________________________________________________________________<br />

€[???] A JEŚLI NIE MA <strong>C++</strong> ???<br />

________________________________________________________________<br />

W przeciwieństwie do INTERPRETERÓW (np. QBasic), które muszą być<br />

obecne, by program zadziałał, KOMPILATORY tworzą wersje<br />

wykonywalne programów, które mogą pracować niezależnie. W<br />

katalogu głównym tego dysku, na którym jest zainstalowany Twój<br />

BORLAND/Turbo <strong>C++</strong> znajdziesz swoje programy PIERWSZY.EXE,<br />

DRUGI.EXE itp. Aby te programy uruchomić nie musisz uruchamiać<br />

kompilatora <strong>C++</strong>. Wystarczy:<br />

1. Przejść na odpowiedni dysk przy pomocy polecenia:<br />

D: (E: lub F:)<br />

- 22-


2. Przejść do odpowiedniego katalogu - np. głównego:<br />

CD \<br />

3. Wydać polecenie:<br />

PIERWSZY[Enter]<br />

________________________________________________________________<br />

€[!!!]UWAGA:<br />

________________________________________________________________<br />

Jeśli nie jesteś jedynym użytkownikiem kompilatora <strong>C++</strong> i na tym<br />

samym komputerze pracuje jeszcze ktoś inny, sprawdź, czy inny<br />

użytkownik nie ustawił inaczej katalogu wyjściowego (Options |<br />

Directories | Output Directory). Katalog wyjściowy (ang. output<br />

directory) to ten katalog, w którym <strong>C++</strong> zapisuje pliki *.EXE po<br />

wykonaniu kompilacji. Jeśli jesteś skazany na własne siły -<br />

patrz - następne lekcje.<br />

________________________________________________________________<br />

SPECJALNE KLAWISZE, które warto poznać.<br />

Oto skrócona tabela z najważniejszymi kombinacjami klawiszy<br />

służącymi do "nawigacji" (czyli prościej - poruszania się) w<br />

środowisku IDE kompilatorów BORLAND <strong>C++</strong> i Turbo <strong>C++</strong>.<br />

Przydatne w <strong>Borland</strong> <strong>C++</strong> i Turbo <strong>C++</strong> kombinacje klawiszy.<br />

________________________________________________________________<br />

Wybór rozkazów z menu:<br />

Alt+F Rozwinięcie menu File (operacje na plikach)<br />

Alt+E Rozwinięcie menu Edit (edycja tekstu programu)<br />

Alt+S Rozwinięcie menu Search (przeszukiwanie)<br />

Alt+R Rozwinięcie menu Run (uruchamianie programu)<br />

Alt+C Rozwinięcie menu Compile (kompilacja)<br />

Alt+D Rozwinięcie menu Debug (diagnostyka i błędy)<br />

Alt+P Rozwinięcie menu Project (program wielomodułowy)<br />

Alt+O Rozwinięcie menu Option (opcje, konfiguracja)<br />

Alt+W Rozwinięcie menu Window (zarządzanie oknami)<br />

Alt+H Rozwinięcie menu Help (pomoc)<br />

Alt+B Rozwinięcie menu przeglądarki - Browse (Win)<br />

Alt+X Wyjście z kompilatora DOS'owskiego - Exit<br />

Alt+F4 Wyjście z kompilatora dla Windows<br />

________________________________________________________________<br />

Rozkazy w trybie edycji tekstu programu:<br />

________________________________________________________________<br />

Shift+Delete Wytnij wybrany blok tekstu (Cut) i umieść w<br />

przechowalni (Clipboard)<br />

Shift+Insert Wstaw blok tekstu z przechowalni (Paste)<br />

Ctrl+Insert Skopiuj zaznaczony blok tekstu do przechowalni<br />

(Copy)<br />

Ctrl+Y Skasuj wiersz tekstu (Delete a line)<br />

Ctrl+Delete Skasuj zaznaczony blok tekstu<br />

- 23-


Shift+[-->] Zaznaczanie bloku tekstu w prawo<br />

Shift+[


LEKCJA 4. Jeszcze o IDE <strong>C++</strong> .<br />

_______________________________________________________________<br />

W trakcie tej lekcji:<br />

1. Dowiesz się więcej o menu i okienkach w środowisku IDE.<br />

2. Poznasz trochę technik "myszologicznych".<br />

3. Napiszesz i uruchomisz swój drugi program.<br />

________________________________________________________________<br />

W dolnej części ekranu jest podobny pasek do paska menu,<br />

niemniej ważny, choć o innym przeznaczeniu. Pasek ten jest to<br />

tzw. WIERSZ STATUSOWY (ang. Status Line). Jak wynika z nazwy w<br />

tym wierszu wyświetlane są informacje dotyczące bieżącego stanu<br />

(i bieżących możliwości) środowiska IDE. Zaryzykuję tezę, że<br />

często jeden prosty, własny eksperyment może być więcej wart niż<br />

wiele stron opisów. Poeksperymentujmy zatem chwilę z wierszem<br />

statusowym.<br />

[???] NIE CHCE SIĘ URUCHOMIĆ ???<br />

________________________________________________________________<br />

Jeśli przy starcie kompilatora <strong>C++</strong> nastąpi komunikat:<br />

System Message<br />

Disk is not ready in drive A<br />

[Retry] [Cancel]<br />

(Komunikat systemu <strong>C++</strong>: Dyskietka w napędzie A nie gotowa do<br />

odczytu; Jeszcze raz? Zrezygnować?)<br />

to znaczy, że <strong>C++</strong> nie może odtworzyć ostatniego ekranu<br />

roboczego, ponieważ nie udostępniłeś mu dyskietki z programami,<br />

nad którymi ostatnio pracowałeś.<br />

________________________________________________________________<br />

W wierszu statusowym wyjaśnione jest działanie klawiszy<br />

funkcyjnych F1, F2, itd. Mogą tam również pojawiać się<br />

krótkie napisy-wyjaśnienia dotyczące np. rozkazu wyróżnionego<br />

właśnie w menu. Powinien tam być napis:<br />

F1 Help F2 Save F3 Load AltF9 Compile F9 Make F10 Menu<br />

znaczy to:<br />

[F1] - Pomoc<br />

[F2] - Zapamiętanie bieżącego pliku na dysku pod bieżącą nazwą<br />

(nawet jeśli tą nazwą jest NONAME01.CPP, tzn. została nadana<br />

automatycznie i znaczy - o ironio - "BEZNAZWY01.CPP") i w<br />

bieżącym katalogu.<br />

[F3] - Załadowanie do okienka edycyjnego nowego pliku tekstowego<br />

(np. nowego programu).<br />

[Alt]-[F9] - Kompilacja w trybie "Compile".<br />

[F9] - Kompilacja w trybie "Make" (jednoczesnej kompilacji i<br />

- 25-


konsolidacji).<br />

[F10] - Uaktywnienie głównego menu.<br />

JAK ZROBIĆ PORZĄDEK?<br />

W trakcie uruchamiania kompilator korzysta z plików<br />

zewnętrznych. <strong>C++</strong> stara się być USER FRIENDLY (przyjazny wobec<br />

użytkownika) i odtworzyć taki stan ekranu, w jakim ostatnio<br />

przerwałeś pracę, co nie zawsze jednak jest korzystne. W wierszu<br />

statusowym pojawiają się napisy informujące o tym (np:<br />

Loading Desktop File . . .<br />

- ładuję plik zawierający konfigurację ostatniego ekranu<br />

roboczego...). Jeśli chcesz by na początku<br />

sesji z <strong>C++</strong> ekran był "dziewiczo" czysty, powinieneś:<br />

* zmienić nazwę pliku [D:]\BORLANDC\BIN\TCDEF.DSK<br />

na dowolną inną, np. STARY.DSK lub STARY1.DSK, stosując<br />

polecenie systemu DOS RENAME. [D:] oznacza odpowiedni dla<br />

Twojego komputera dysk. <strong>C++</strong> wystartuje wtedy z czystym ekranem i<br />

utworzy nowy plik TCDEF.DSK.<br />

* Plików TCDEF nie należy usuwać. Kiedy nabierzesz trochę wprawy<br />

pliki te znacznie przyspieszą i ułatwią Ci pracę z <strong>C++</strong>.<br />

Aby zamknąć zbędne okna możesz zastosować również rozkaz Close<br />

(ang. Close - zamknij) z menu Window (okna). Zwróć uwagę, że<br />

polecenie Close odnosi się do bieżącego okna wyróżnionego przy<br />

pomocy podwójnej ramki. Aby zamknąć bieżące okno, powinieneś:<br />

1. Nacisnąć klawisze [Alt]-[W]<br />

Rozwinie się menu Windows.<br />

2. Wybrać z menu rozkaz Close - [C].<br />

Może pojawić się okienko z ostrzeżeniem:<br />

WARNING: A:\PIERWSZY.CPP not saved. Save?<br />

(UWAGA: plik A:\PIERWSZY.CPP nie zapisany na dysku. Zapisać ?).<br />

[???] ZNIKNĄŁ PROGRAM ???<br />

________________________________________________________________<br />

<strong>C++</strong> chce Cię uchronić przed utratą programu, ale uważaj! Jeśli<br />

odpowiesz Yes - Tak ([Y] lub [Enter]), to nowa wersja programu<br />

zostanie nadpisana na starą!<br />

________________________________________________________________<br />

[!!!] ZAMYKANIE OKIEN.<br />

________________________________________________________________<br />

Możesz szybciej zamknąć okno naciskając kombinację klawiszy<br />

- 26-


[Alt]-[F3].<br />

________________________________________________________________<br />

[!!!]UWAGA<br />

________________________________________________________________<br />

Bądź ostrożny podejmując decyzję o zapisie wersji programu na<br />

dysk. Okienko z ostrzeżeniem pojawi się za każdym razem przed<br />

zamknięciem okna edycyjnego z tekstem programu. Jeśli przy<br />

zamykaniu okna nie pojawi się ostrzeżenie, to znaczy, że program<br />

w tej wersji, którą widzisz na ekranie został już zapisany na<br />

dysk.<br />

________________________________________________________________<br />

A JEŚLI NIE CHCĘ ZAMYKAĆ OKIEN?<br />

W porządku, nie musisz. W menu Window ([Alt]-[W]) masz do<br />

dyspozycji rozkaz Next (następne okno). Możesz go wybrać albo<br />

naciskając klawisz [N], albo przy pomocy klawiszy kursora. Każde<br />

z okien na Twoim roboczym ekranie ma nazwę - nagłówek - np.<br />

NONAME00.CPP, PIERWSZY.CPP, ale nie tylko. Pierwsze dziesięć<br />

okien ma również swoje numery - podane blisko prawego - górnego<br />

rogu okna w nawiasach kwadratowych - np. [1], [2] itd.<br />

Posługując się tym rozkazem możesz przechodzić od okna do okna<br />

nie zamykając żadnego z okien. Spróbuj!<br />

Jest jeszcze inny sposób przejścia od okna do okna. Jeśli chcesz<br />

przejść do okna o numerze np. [1], [2], [5] itp. powinieneś<br />

nacisnąć kombinację klawiszy [Alt]-[1], [Alt]-[5] itp..<br />

Niestety, tylko pierwsze 9 okien ma swoje numerki.<br />

Możesz korzystać z listy okien (Window | List) lub klawisza<br />

funkcyjnego [F6].<br />

[S] ACTIVE WINDOW - AKTYWNE OKNO.<br />

________________________________________________________________<br />

Na ekranie może się znajdować jednocześnie wiele okien, ale w<br />

danym momencie tylko jedno z nich może być AKTYWNE. Aktywne<br />

okno, to to, w którym miga kursor i w którym aktualnie<br />

pracujesz. Aktywne okno jest dodatkowo wyróżnione podwójną<br />

ramką.<br />

________________________________________________________________<br />

[???] Robi "na szaro"?<br />

________________________________________________________________<br />

Zwróć uwagę, że dopóki bieżącym aktywnym oknem jest okienko<br />

komunikatów (Message - to w dolnej części ekranu), nie możesz<br />

np. powtórzyć kompilacji programu. Rozkazy Compile | Compile i<br />

Run | Run będą "zrobione na szaro" (ang. grayed out) - czyli<br />

nieaktywne. Najpierw trzeba przejść do okna edycji tekstu<br />

programu (np. poprzez kliknięcie myszką).<br />

________________________________________________________________<br />

- 27-


Rozwiń menu Options (opcje).<br />

Możesz to zrobić na wiele sposobów. Najszybciej chyba naciskając:<br />

[Alt]+[O]<br />

Rozwinęło się menu, udostępniając następującą listę poleceń:<br />

FULL MENUs - Pełne Menu ("s" oznacza, że chodzi o "te" menu w<br />

liczbie mnogiej, a nie o pojedyncze menu).<br />

COMPILER - Kompilator.<br />

MAKE... - dosł. "ZRÓB", dotyczy tworzenia "projektów" (zwróć<br />

uwagę na wielokropek [...]).<br />

DIRECTORIES... - KATALOGI (znów wielokropek !).<br />

ENVIRONMENT... - OTOCZENIE lub inaczej ŚRODOWISKO.<br />

SAVE - ZAPAMIĘTAJ (UWAGA: To jest zupełnie inne SAVE niż<br />

w menu File. Nie wolno mylić tych poleceń.<br />

Pomyłka grozi utratą tekstu programu!).<br />

Popatrz na linię statusową. Jeśli będziesz poruszać się po menu<br />

Option, podświetlając kolejne rozkazy, w wierszu statusowym<br />

będzie wyświetlany krótki opis działania wskazanego rozkazu. I<br />

tak, powinieneś zobaczyć kolejno następujące napisy:<br />

Full Menus [Off/On]- Use or don't use full set of menu commands.<br />

(Stosuj lub nie stosuj pełnego zestawu rozkazów w menu -<br />

domyślnie przyjmowane jest Off/Nie).<br />

Compiler - Set compiler defaults for code generation, error<br />

messages and names.<br />

(Ustaw domyślne parametry pracy kompilatora dotyczące<br />

generowania kodu programu, komunikatów o błędach i nazw).<br />

Make... - Set condition for project-makes.<br />

(Ustawianie warunków do tworzenia projektu).<br />

Directories... - Set path for compile, link and executable<br />

files.<br />

(Wybierz katalogi i ustaw ścieżki dostępu dla kompilacji,<br />

konsolidacji i WSKAŻ MIEJSCE - GDZIE ZAPISAĆ PLIK TYPU *.EXE po<br />

kompilacji).<br />

Environment... - Make environment wide settings (eg, mouse<br />

settings).<br />

(Ustawienie parametrów rozszerzonego otoczenia, np. parametrów<br />

pracy myszki).<br />

Save - Save all the settings you've made in the Options menu.<br />

(Powoduje zapamiętanie na dysku wszystkich zmian parametrów<br />

roboczych IDE, które ustawiłeś, korzystając z rozkazów<br />

dostępnych za pośrednictwem menu Options.).<br />

- 28-


Ten rozkaz pozwala Ci ustawić konfigurację IDE "raz na zawsze".<br />

Przygotujmy się do powtórzenia kompilacji programu PIERWSZY.CPP.<br />

Jeśli masz na ekranie rozwinięte menu Options, wybierz z menu<br />

polecenie Directories... .<br />

KOMPILACJA ZE WSKAZANIEM ADERSU.<br />

1. Wskaż w menu polecenie Directories i naciśnij [Enter].<br />

Po poleceniu umieszczony jest wielokropek. Znaczy to, że rozkaz<br />

nie zostanie wykonany, zanim komputer nie uzyska od Ciebie<br />

pewnych dodatkowych informacji. Wiesz już, że praktycznie<br />

oznacza to dla Ciebie konieczność "wypełnienia" okienka<br />

dialogowego. Po wybraniu polecenia Directories ukazało się<br />

okienko dialogowe już "wstępnie wypełnione". Takie "wstępne<br />

wypełnienie" okienka daje Ci pewne dodatkowe informacje. Wynika<br />

z niego mianowicie JAKIE PARAMETRY SĄ PRZYJMOWANE DOMYŚLNIE<br />

(default).<br />

W okienku dialogowym masz trzy okienka tekstowe:<br />

* Include Directories (Katalog zawierający pliki nagłówkowe, np.<br />

STDIO.H, CONIO.H, GRAPHICS.H itp. dołączane do programów).<br />

* Library Directories (Katalog zawierający gotowe biblioteki,<br />

zawarte w plikach typu *.LIB,).<br />

* Output Directory (Katalog wyjściowy, w którym po kompilacji<br />

będą umieszczane Twoje programy w wersji *.EXE).<br />

Pierwsze dwa zostawimy w spokoju.<br />

2. Naciśnij dwukrotnie klawisz [Tab]. Kursor wskazuje teraz<br />

okienko tekstowe Output Directory.<br />

3. Wpisz do okienka tekstowego Output Directory:<br />

A:\ lub C:\C-BELFER<br />

znaczy to, że od teraz po wykonaniu kompilacji i utworzeniu<br />

pliku wykonywalnego typu *.EXE, plik taki zostanie zapisany we<br />

wskazanym katalogu i na wskazanym dysku/dyskietce.<br />

UWAGA:<br />

________________________________________________________________<br />

* Jeśli zainstalowałeś zawartość dyskietki na dysku i wolisz<br />

posługiwać się własnym katalogiem roboczym - wpisz tam<br />

odpowiednią ścieżkę dostępu - np. C:\C-BELFER. Jeśli Twój<br />

katalog zagnieżdżony jest głębiej (np. w przypadku użytkowników<br />

sieci Novell) - podaj pełną ścieżkę dostępu - np.:<br />

F:\USERS\ADAM\C-BELFER<br />

* Wszędzie, gdzie w treści książki odwołuję się do dyskietki A:<br />

możesz konsekwentnie po zainstalowaniu stosować odpowiedni<br />

- 29-


katalog na dysku stałym, bądź na dysku sieciowym.<br />

________________________________________________________________<br />

4. Naciśnij [Enter].<br />

Spróbuj teraz, znaną z poprzedniej lekcji metodą, wczytać do<br />

okienka edytora Twój pierwszy program. Musisz wykonać<br />

następujące czynności:<br />

1. Włóż do napędu A: dyskietkę z programem PIERWSZY.CPP (jeśli<br />

jeszcze jej tam nie ma).<br />

2. Rozwiń menu File, naciskając kombinację klawiszy [Alt]-[F].<br />

3. Wybierz z menu rozkaz Open, naciskając klawisz [O].<br />

Pojawi się znane Ci okienko dialogowe. Zwróć uwagę na wiersz<br />

statusowy. Napis:<br />

Enter directory path and file mask<br />

znaczy:<br />

Wpisz ścieżkę dostępu do katalogu i "wzorzec" nazwy pliku.<br />

Użyte słowo "wzorzec" oznacza, że wolno Ci wpisać do okienka<br />

tekstowego także nazwy wieloznaczne, zawierające znaki "*" i<br />

"?", np.:<br />

*.C<br />

A:\???.C<br />

D:\BORLANDC\SOURCE\P*.*<br />

itp. (Spróbuj!, zawsze możesz się wycofać lub zmienić zdanie,<br />

posługując się klawiszami [BackSpace], [Shift], [Tab] i [Esc].).<br />

Klawisz [Tab] umożliwia Ci skok od okienka do okienka "do<br />

przodu", a [Shift]-[Tab] - "do tyłu". Zgodnie z nazwą (ang.<br />

ESCape - uciekać), klawisz [Esc] pozwala Ci wycofać się z<br />

niewygodnych sytuacji - np. zamknąć okienko dialogowe lub zwinąć<br />

rozwinięte menu bez żadnej akcji.<br />

Jeśli wpiszesz wzorzec nazwy, to w okienku z listą zobaczysz<br />

wszystkie pliki wybrane z podanego dysku i z podanego katalogu<br />

według zadanego wzorca. Aby wybrać plik z listy należy klawiszem<br />

[Tab] przejść do okienka z listą, klawiszami kursora wskazać<br />

potrzebny plik i nacisnąć [Enter].<br />

4. Wpisz do okienka tekstowego<br />

A:\PIERWSZY.CPP<br />

5. Naciśnij [Enter].<br />

[!!!]FAST START - SZYBKI START.<br />

________________________________________________________________<br />

Jeśli chcesz by <strong>C++</strong> automatycznie wczytał Twój program do<br />

okienka edytora, to możesz zadać nazwę pliku z tekstem programu<br />

jako parametr w wierszu polecenia, uruchamiając <strong>C++</strong> np. tak:<br />

BC A:\PIERWSZY.CPP<br />

- 30-


Jeśli korzystasz z programu Norton Commander, to możesz dodać do<br />

pliku NC.EXT następujący wiersz:<br />

C: TC !.!<br />

cpp: bc !.!<br />

wówczas wystarczy tylko wskazać odpowiedni plik typu *.C lub<br />

.CPP z tekstem programu i nacisnąć [Enter].<br />

________________________________________________________________<br />

Kompilatory <strong>Borland</strong>a mogą w różnych wersjach nazywać się różnie:<br />

TC.EXE, BC.EXE, BCW.EXE (dla Windows), itp.. Sprawdź swoją<br />

wersję kompilatora i wpisz właściwe nazwy dodając ewentualnie<br />

ścieżki dostępu - np.:<br />

C: D:\BORLANDC\BIN\BC !.!<br />

CPP: WIN C:\BORLANDC\BIN\BCW !.!<br />

[!!!]UWAGA<br />

________________________________________________________________<br />

Rozkazy uruchamiające kompilator mogą być złożone nawet z 4<br />

parametrów - np.:<br />

WIN /3 C:\BORLANDC\BIN\BCW C:\C-BELFER\PROGRAMY\P027.CPP<br />

spowoduje:<br />

* uruchomienie Windows w trybie rozszerzonym 386<br />

* uruchomienie kompilatora w wersji dla Windows - BCW.EXE<br />

* załadowanie pliku z programem - P27.CPP z wskazanego katalogu<br />

________________________________________________________________<br />

[P002.CPP]<br />

Dokonaj w swoim programie następujących zmian:<br />

________________________________________________________________<br />

#include (stdio.h><br />

#include <br />

main()<br />

{<br />

printf("\n");<br />

printf("Autor: np. Antoni Kowalski\n");<br />

printf("program: PIERWSZY.CPP \n - wersja II \n");<br />

getch();<br />

}<br />

________________________________________________________________<br />

******Uwaga: Jeśli pracujesz w Windows - Z TEGO MIEJSCA********<br />

przy pomocy rozkazów Edit | Copy<br />

możesz przenieść program do okna kompilatora<br />

- 31-


poprzez schowek Windows (Clipboard).<br />

W oknie kompilatora należy:<br />

1. Otworzyć nowe okno edytora tekstowego:<br />

File | New<br />

2. Wstawić plik ze schowka:<br />

Edit | Paste<br />

--- To okno (AM-Edit) i całego BELFRA możesz w tym czasie zredukować<br />

--- Do ikonki.------------------------------------------------------<br />

********************************************************************<br />

Dzięki dodaniu do tekstu programu funkcji getch(), program nie<br />

powinien już tak szybko mignąć na ekranie i zniknąć. Zatrzyma<br />

się teraz i zaczeka na przyciśnięcie klawisza. Funkcja getch(),<br />

działa podobnie do:<br />

10 IF INKEY$="" GOTO 10<br />

w Basicu lub Readln w Pascalu.<br />

Nazwa pochodzi od GET CHaracter (POBIERZ ZNak, z klawiatury).<br />

Skompiluj program PIERWSZY.CPP. Aby to zrobić, powinieneś:<br />

1. Rozwinąć menu Compile - [Alt]-[C].<br />

2. Wybrać z menu rozkaz Compile - [C].<br />

Ostrzeżenie WARNING na razie ignorujemy.<br />

Wykonaj kompilację programu powtórnie przy pomocy rozkazu Run z<br />

menu Run. Naciśnij kolejno klawisze:<br />

[Alt]-[R], [R]<br />

lub<br />

[Alt]-[R], [Enter]<br />

Ten sam efekt uzyskasz naciskając kombinację klawiszy<br />

[Ctrl]-[F9].<br />

Uruchom program powtórnie naciskając kombinację klawiszy<br />

[Alt]-[R], [R]. Zwróć uwagę, że teraz kompilacja nastąpi<br />

znacznie szybciej. Tak naprawdę <strong>C++</strong> stwierdzi tylko, że od<br />

ostatniej kompilacji nie dokonano żadnych zmian w programie i<br />

odstąpi od zbędnej kompilacji. Takie właśnie znaczenie ma<br />

komunikat "Checking dependences" (sprawdzam zależności, który<br />

mignie w okienku kompilacji. Po korekcie programu napisy<br />

wyglądają znacznie przyzwoiciej, prawda? Po obejrzeniu napisów<br />

naciśnij [Enter].<br />

Możemy teraz wyjść z programu <strong>C++</strong>. Rozwiń menu File naciskając<br />

klawisze [Alt]-[F] i wybierz z menu rozkaz Quit. Pojawi się<br />

okienko z ostrzeżeniem:<br />

WARNING: A:\PIERWSZY.CPP not saved. Save?<br />

(UWAGA: plik A:\PIERWSZY.CPP nie zapisany na dysku. Zapisać ?).<br />

- 32-


W ten sposób <strong>C++</strong> ZNOWU chce Cię uchronić przed utratą programu,<br />

ale uważaj! Jeśli odpowiesz Tak ([Y] lub [Enter]), to nowa<br />

wersja programu zostanie nadpisana na starą! Jeśli odpowiesz Nie<br />

[N]<br />

na dysku pozostanie stara wersja programu a nowa<br />

zniknie.<br />

Po wyjściu z <strong>C++</strong> znajdziesz się w jego katalogu roboczym, lub w<br />

tym katalogu bieżącym, z którego wydałeś rozkaz uruchomienia<br />

kompilatora <strong>C++</strong>. Aby uruchomić swój program musisz zatem wydać<br />

następujący rozkaz:<br />

A:\PIERWSZY.EXE<br />

lub krócej<br />

A:\PIERWSZY<br />

a jeśli chcesz się przekonać, czy Twój program jest tam, gdzie<br />

powinien być, możesz go zobaczyć. Napisz rozkaz<br />

DIR A:\<br />

lub<br />

DIR A:\*.EXE<br />

Aby upewnić się całkowicie, że to właśnie ten program, zwróć<br />

uwagę na datę i czas utworzenia pliku. Jeśli masz prawidłowo<br />

ustawiony zegar w swoim komputerze, data powinna być dzisiejsza<br />

a czas - kilka minut temu. Jeśli coś jest nie tak, powinieneś<br />

przy pomocy rozkazów systemu DOS: DATE i TIME zrobić porządek w<br />

swoim systemie. O takich drobiazgach warto pamiętać. Pozwoli Ci<br />

to w przyszłości odróżnić nowsze i starsze wersje programów,<br />

uniknąć pomyłek i zaoszczędzić wiele pracy.<br />

[Z] 1. - Propozycja zadania - ćwiczenia do samodzielnego wykonania.<br />

-------------------------------------------------------------------<br />

Spróbuj odszukać plik żródłowy .CPP i plik wynikowy .EXE<br />

wychodząc "na chwilę" z IDE przy pomocy rozkazu File | DOS<br />

Shell.<br />

-------------------------------------------------------------------<br />

A teraz zajrzyjmy do środka do pliku PIERWSZY.EXE. Jeśli<br />

korzystasz z programu Norton Commander, to masz do dyspozycji<br />

opcje [F3] - View (przeglądanie) i [F4] - Edit (edycja). Jeśli<br />

nie korzystasz z NC, musisz wydać następujący rozkaz:<br />

TYPE A:\PIERWSZY.EXE | C:\DOS\MORE<br />

lub<br />

C:\DOS\EDIT A:\PIERWSZY.EXE<br />

Jak widzisz na ekranie, napisy zawarte w programie pozostały<br />

czytelne, ale to co widać dookoła nie wygląda najlepiej. Na<br />

podstawie tego co widzisz, można (na razie ostrożnie) wysnuć<br />

wniosek, że ani Viewer (przeglądarka), ani Edytor, które<br />

- 33-


doskonale spisują się przy obróbce plików tekstowych, nie nadają<br />

się do analizy i obróbki programów w wersji *.EXE. Narzędziami,<br />

które będziemy musieli stosować, mogą być programy typu<br />

DEBUGGER, PROFILER, LINKER (konsolidator), kompilator i in..<br />

Mam nadzieję, że czujesz się w środowisku IDE już trochę<br />

swobodniej, a więc bierzemy się za drugi program.<br />

__________________________________________________________<br />

EOF<br />

- 34-


LEKCJA 5. DZIAŁANIA PRZY POMOCY MYSZKI I BŁĘDY W PROGRAMIE.<br />

________________________________________________________________<br />

Z tej lekcji dowiesz się,<br />

* Jak posługiwać się myszką w środowisku IDE (DOS)<br />

* O czy należy pamiętać, przy tworzeniu i uruchamianiu<br />

programów.<br />

* Jak poprawiać błędy w programie.<br />

________________________________________________________________<br />

Zanim będzie można kontynuować eksperymenty, trzeba coś zrobić,<br />

by robocze okno edytora było puste. Aby otworzyć takie nowe<br />

puste okno edytora należy:<br />

* Rozwinąć menu File;<br />

* Wybrać z menu rozkaz New (nowy).<br />

Na ekranie monitora otworzy się nowe puste okno zatytułowane<br />

"NONAME00.CPP", "NONAME01.CPP", itp (lub "bez nazwy" i o<br />

kolejnym numerze). Różne edytoro-podobne aplikacje mają zwyczaj<br />

otwierania okna dla nowego pliku tekstowego i nadawanie mu na<br />

początku jednej z dwóch nazw:<br />

[S] SŁOWNICZEK: UFO w trybie Edycji<br />

________________________________________________________________<br />

Untitled - niezatytułowany<br />

Noname - bez nazwy<br />

(Tak na marginesie UFO to skrót od Unidentified Flying Object -<br />

Niezidentyfikowany Obiekt Latający, gdy przejdziemy do<br />

programowania obiektowego, znajomość tego terminu też Ci się<br />

przyda).<br />

________________________________________________________________<br />

Nadanie plikowi dyskowemu z tekstem źródłowym programu jego<br />

właściwej nazwy i zapisanie go na dysku stałym komputera w<br />

określonym miejscu następuje w tym momencie, kiedy po napisaniu<br />

programu zapisujesz go na dysk rozkazem:<br />

File | Save lub File | Save As...<br />

Zapis File | Save oznacza "Rozkaz Save z menu File". Gdy po<br />

opracowaniu programu rozwiniesz menu File i wybierzesz rozkaz<br />

Save as... (zapisz jako...), pojawi się okienko dialogowe "Save<br />

File as" (zapis pliku jako...).<br />

Do okienka edycyjnego "Name" (nazwa) możesz wpisać nazwę, którą<br />

chcesz nadać swojemu nowemu programowi. Zwróć uwagę, że możesz<br />

podać nazwę pliku i jednocześnie wskazać miejsce - np.:<br />

Name:<br />

F:\USERS\ADAM\PROBY\PROGRAM.CPP<br />

Po wpisaniu nazwy naciśnij klawisz [Enter] lub wybierz klawisz<br />

[OK] w okienku dialogowym myszką. Tytuł okna edytora zmieni się<br />

na wybraną nazwę.<br />

- 35-


Możesz również (jeśli odpowiedni katalog już istnieje), wskazać<br />

właściwy katalog w okienku z listą "Files" i dwukrotnie<br />

"kliknąć" lewym klawiszem myszki.<br />

Możesz wskazać myszką okienko edycyjne i nacisnąć lewy klawisz<br />

myszki, bądź naciskać klawisz [Tab] aż do momentu, gdy kursor<br />

zostanie przeniesiony do okienka edycyjnego. Okienko edycyjne to<br />

to okienko, do którego wpisujesz nazwę pliku. W okienku<br />

edycyjnym (Save File As) naciskaj klawisz [BackSpace] aż do<br />

chwili skasowania zbędnej nazwy pliku i pozostawienia tam tylko<br />

ścieżki dostępu - np. A:\PROBY\. Wpisz nazwę programu - np.<br />

PROG1.CPP. Po wpisaniu nazwy możesz nacisnąć [Enter] lub wskazać<br />

myszką klawisz [OK] w okienku i nacisnąć lewy klawisz myszki.<br />

Jeśli tak zrobisz w przypadku pustego okienka NONAME00.CPP -<br />

kompilator utworzy na dysku we wskazanym katalogu plik o zadanej<br />

nazwie - np. A:\PROBY\PROGR1.CPP (na razie pusty). Zmieni się<br />

także nagłówek (nazwa) okienka edycyjnego na ekranie roboczym.<br />

[!!!]UWAGA.<br />

________________________________________________________________<br />

Wszystkie pliki zawierające teksty programów w języku <strong>C++</strong><br />

powinny ˙mieć charakterystyczne rozszerzenie *.CPP (CPP to skrót<br />

od C Plus Plus), lub .C. Po tym rozszerzeniu rozpoznaje te<br />

programy kompilator. Nadanie rozszerzenia .C lub .CPP może<br />

dodatkowo wpływać na sposób kompilacji programu. Zanim wyjaśnimy<br />

te szczegóły, będziemy zawsze stosować rozszerzenie .CPP.<br />

Wszelkie inne rozszerzenia (.BAK, .TXT, .DEF, itp.) nie<br />

przeszkadzają w edycji i kompilacji programu, ale mogą w<br />

niejawny sposób wpłynąć na sposób kompilacji.<br />

________________________________________________________________<br />

Jeśli masz puste robocze okno edytora - możesz wpisać tam<br />

swój własny nowy program. Wpisz:<br />

void main(void)<br />

Każdy program w <strong>C++</strong> składa się z instrukcji. Wiele takich<br />

instrukcji to wywołania funkcji. W <strong>C++</strong> rozkaz wywołania i<br />

wykonania funkcji polega na wpisaniu nazwy funkcji (bez żadnego<br />

dodatkowego słowa typu run, execute, load, itp.). Tych funkcji<br />

może być w programie jedna, bądź więcej. Tworzenie programu w<br />

<strong>C++</strong> z zastosowaniem funkcji (takich jakgdyby mini-programików)<br />

przypomina składanie większej całości z klocków.<br />

Należy podkreślić, że:<br />

każdy program w <strong>C++</strong> musi zawierać funkcję main() (ang. main -<br />

główna).<br />

Wykonanie każdego programu rozpoczyna się właśnie od początku<br />

funkcji main(). Innymi słowy - miejsce zaznaczone w programie<br />

przy pomocy funkcji main() to takie miejsce, w które komputer<br />

zagląda zawsze na początku wykonania programu i od tego właśnie<br />

miejsca rozpoczyna poszukiwanie i wykonywanie rozkazów.<br />

- 36-


[S] Entry Point<br />

___________________________________________________________________<br />

Punkt wejścia do programu nazywa się:<br />

Program Entry Point<br />

Taki właśnie punkt wejścia wskazuje słowo main().<br />

Punk wejścia mogą mieć nie tylko programy .EXE ale także biblioteki<br />

(.DLL - dynamicznie dołączanie biblioteki).<br />

____________________________________________________________________<br />

Każda funkcja powinna mieć początek i koniec. Początek funkcji w<br />

C/<strong>C++</strong> zaznacza się przez otwarcie nawiasów klamrowych { a koniec<br />

funkcji poprzez zamknięcie } . Początek głównej funkcji main()<br />

to zarazem początek całego programu. Zaczynamy zwykle od<br />

umieszczenia w oknie edytora <strong>C++</strong> znaków początku i końca<br />

programu.<br />

main()<br />

{<br />


Podobnie jak wcześniej, kompilator wyświetli na ekranie okienko<br />

zawierające komunikaty o przebiegu kompilacji. Po zakończeniu<br />

kompilacji nastąpi wykonanie programu. Na moment mignie roboczy<br />

ekran użytkownika. Na nieszczęście program nic nie robi, więc<br />

nic się tam nie wydarzy.<br />

Aby przeanalizować, jak kompilator <strong>C++</strong> reaguje na błędy w<br />

programach, zmień tekst w pierwszej linii programu na błędny:<br />

vod main(void)<br />

{<br />

}<br />

Spróbuj powtórnie skompilować i uruchomić program.<br />

Kompilator wyświetli okienko, w którym pojawi się komunikat o<br />

błędach. W taki właśnie sposób kompilator taktownie informuje<br />

programistę, że nie jest aż taki dobry, jak mu się czasami<br />

wydaje. Komputer jest niestety pedantem. Oczekuje (my, ludzie<br />

tego nie wymagamy) absolutnej dokładności i żelaznego<br />

przestrzegania pewnych zasad. "Zjadając" jedną literę naruszyłeś<br />

takie zasady, co zauważył kompilator.<br />

W górnej części ekranu kompilator wyróżnił paskiem podświetlenia<br />

ten wiersz programu, który zawiera błąd. W dolnej części ekranu,<br />

w tzw. okienku komunikatów (ang. Message window) pojawił się<br />

komunikat, jaki rodzaj błędu został wykryty w Twoim programie. W<br />

danym przypadku komunikat brzmi:<br />

Declaration syntax error - Błąd w składni deklaracji<br />

Co to jest deklaracja?<br />

Pierwsza linia (wiersz) funkcji nazywa się deklaracją funkcji.<br />

Taka pierwsza linia zawiera informacje ważne dla kompilatora:<br />

nazwę funkcji oraz tzw. typy wartości używanych przez funkcję.<br />

Komunikat o błędzie oznacza, że nieprawidłowo została napisana<br />

nazwa funkcji lub nazwy typów wartości, którymi posługuje się<br />

funkcja. W naszym przypadku słowo void zostało przekręcone na<br />

"vod", a słowo to ma w <strong>C++</strong> specjalne znaczenie. Słowo "void"<br />

jest częścią języka <strong>C++</strong>, a dokładniej - słowem kluczowym (ang.<br />

keyword).<br />

[S] Function declaration - Deklaracja funkcji.<br />

Keyword - Słowo kluczowe.<br />

________________________________________________________________<br />

Function declaration - Deklaracja funkcji.<br />

Pierwszy wiersz funkcji jest nazywany deklaracją funkcji. Ten<br />

wiersz zawiera informacje dla kompilatora <strong>C++</strong> pozwalające<br />

poprawnie przetłumaczyć funkcję na kod maszynowy.<br />

Keyword - Słowo kluczowe.<br />

- 38-


to specjalne słowo wchodzące w skład języka programowania. Słowa<br />

kluczowe to słowa o zastrzeżonym znaczeniu, które można stosować<br />

w programach wyłącznie w przewidzianym dla nich sensie.<br />

________________________________________________________________<br />

Popraw błąd w tekście. Aby robocze okienko edytora stało się<br />

oknem aktywnym, wskaż kursorem myszki dowolny punkt w oknie<br />

edytora i naciśnij lewy klawisz myszki, albo naciśnij klawisz<br />

[F6]. Zmień słowo "vod" na "void". Przy pomocy klawiszy ze<br />

strzałkami umieść migający kursor po prawej stronie nawiasu {<br />

sygnalizującego ˙początek programu i naciśnij [Enter]. Spowoduje<br />

to wstawienie pomiędzy początek a koniec programu nowej pustej<br />

linii i umieszczenie kursora na początku nowego wiersza. Wpisz<br />

do nowego wiersza instrukcję oczyszczenia ekranu (odpowiednik<br />

instrukcji CLS w Basicu):<br />

clrscr();<br />

W <strong>C++</strong> clrscr() oznacza wywołanie funkcji czyszczącej roboczy<br />

ekran programu (User screen). Nazwa funkcji pochodzi od skrótu:<br />

CLeaR SCReen - czyść ekran.<br />

Że to funkcja - można rozpoznać po dodanej za nazwą parze<br />

nawiasów okrągłych - (). W tym jednak przypadku wiersz:<br />

clrscr();<br />

stanowi nie deklarację funkcji, lecz wywołanie funkcji (ang.<br />

function call). <strong>C++</strong> znalazłszy w programie wywołanie funkcji<br />

wykona wszystkie rozkazy, które zawiera wewnątrz funkcja<br />

clrscr(). Nie musisz przejmować się tym, z jakich rozkazów<br />

składa się funkcja clrscr(). Te rozkazy nie stanowią części<br />

Twojego programu, lecz są zawarte w jednym z "fabrycznych"<br />

plików bibliotecznych zainstalowanych wraz z kompilatorem <strong>C++</strong>.<br />

[S]<br />

Function - Funkcja<br />

Fuction call - Wywołanie funkcji<br />

________________________________________________________________<br />

Funkcja to coś przypominające mini-program. Funkcja zawiera<br />

listę rozkazów służących do wykonania typowych operacji (np.<br />

czyszczenie ekranu, wyświetlanie menu, wydruk, czy sortowanie<br />

listy imion). W programach posługujemy się zwykle wieloma<br />

funkcjami. Poznałeś już najważniejszą funkcję główną - main(). W<br />

C/<strong>C++</strong> możesz posługiwać się gotowymi funkcjami (tzw.<br />

bibliotecznymi) a także tworzyć nowe własne funkcje. Na razie<br />

będziemy posługiwać się gotowymi funkcjami dostarczanymi przez<br />

producenta wraz z kompilatorem <strong>C++</strong>.<br />

________________________________________________________________<br />

- 39-


Włącz kompilację i próbę uruchomienia programu.<br />

Kompilator stwierdzi, że program zawiera błędy.<br />

Naciśnij dowolny klawisz, by zniknęło okienko kompilacji.<br />

Kompilator napisał:<br />

Error: Function 'clrscr' should have a prototype<br />

(Funkcja 'clrscr' powinna mieć prototyp)<br />

[???] O co mu chodzi?<br />

________________________________________________________________<br />

Tzw. PROTOTYP funkcji to coś bardzo podobnego do deklaracji<br />

funkcji. Prototyp służy do przekazania kompilatorowi pewnych<br />

informacji o funkcji jeszcze przed użyciem tej funkcji w<br />

programie. Dla przykładu, gdy pisałeś pierwszą linię programu:<br />

void main(void)<br />

podałeś nie tylko nazwę funkcji - main, lecz także umieściliśmy<br />

tam dwukrotnie słowo void. Dokładnie o znaczeniu tych słów<br />

napiszemy w dalszej części książki. Na razie zwróćmy jedynie<br />

uwagę, że podobnych "dodatkowych" informacji dotyczących funkcji<br />

clrscr() w naszym programie nie ma.<br />

________________________________________________________________<br />

Zwróć uwagę, że zapisy:<br />

main() int main(void) main(void) {<br />

{ { } }<br />

}<br />

są całkowiecie równoważne. Fakt, że słowa kluczowe void (w nawiasie)<br />

i int (przed funkcją i tylko tam!) mogą zostać pominięte wskazuje, że są<br />

to wartości domyślne (default settings) przyjmowane automatycznie.<br />

Funkcja clrscr() została napisana przez programistów z firmy<br />

BORLAND i znajduje się gdzieś w osobnym pliku dołączonym do<br />

kompilatora <strong>C++</strong>. Aby móc spokojnie posługiwać się tą funkcją w<br />

swoich programach, powinieneś dołączyć do swojego programu<br />

informację w jakim pliku dyskowym znajduje się opis funkcji<br />

clrscr(). Taki (dość szczegółowy) opis funkcji nazywa się<br />

właśnie prototypem funkcji. Aby dodać do programu tę (niezbędną)<br />

informację<br />

* naciśnij [F6] by przejść do okna edytora<br />

* ustaw migający kursor na początku tekstu programu<br />

* naciśnij [Enter] dwukrotnie, by dodać dwie nowe puste linie do<br />

tekstu programu<br />

* na samym początku programu wpisz:<br />

- 40-


#include <br />

Takie specjalne linie (zwróć uwagę na podświetlenie)<br />

rozpoczynające się od znaku # (ASCII 35) nie są właściwie<br />

normalną częścią składową programu. Nie stanowią one jednej z<br />

instrukcji programu, mówiącej komputerowi CO NALEŻY ROBIĆ, lecz<br />

stanowią tzw. dyrektywę (rozkaz) dla kompillatora <strong>C++</strong> - W JAKI<br />

SPOSÓB KOMPILOWAĆ PROGRAM. Dyrektywa kompilatora (ang. compiler<br />

directive) powoduje dokonanie określonych działań przez<br />

kompilator na etapie tłumaczenia programu na kod maszynowy. W<br />

danym przypadku dyrektywa<br />

#include ....<br />

(ang. include - włącz, dołącz) powoduje włączenie we wskazane<br />

miejsce zawartości zewnętrznego tekstowego pliku dyskowego - np.:<br />

CONIO.H,<br />

(plik CONIO.H<br />

nazywany ˙także "plikiem nagłówkowym" znajduje się w podkatalogu<br />

\INCLUDE). Kompilator dołącza zawartość pliku CONIO.H jeszcze<br />

przed rozpoczęciem procesu kompilacji programu.<br />

Naciśnij kombinację klawiszy [Ctrl]+[F9]. Spowoduje to<br />

kompilację i uruchomienie programu (Run). Przykładowy program<br />

powinien tym razem przekompilować się bez błędów. Po dokonaniu<br />

kompilacji powinien szybko błysnąć ekran użytkownika. Po tym<br />

błysku powinien nastąpić powrót do roboczego środowiska IDE<br />

kompilatora <strong>C++</strong>. Jeśli nie zdążyłeś się przyjrzeć i chcesz<br />

spokojnie sprawdzić, co zrobił Twój program - naciśnij<br />

kombinację klawiszy [Alt]+[F5].<br />

Dzięki działaniu funkcji clrscr() ekran będzie całkowicie<br />

czysty.<br />

[S] Compiler directive - DYREKTYWA KOMPILATORA<br />

________________________________________________________________<br />

Dyrektywa kompilatora to rozkaz wyjaśniający kompilatorowi <strong>C++</strong> w<br />

jaki sposób dokonywać kompilacji programu. Dyrektywy kompilatora<br />

zawsze rozpoczynają się od znaku # (ang. hash).<br />

Kompilatory <strong>C++</strong> posiadają pewien dodatkowy program nazywany<br />

PREPROCESOREM. Preprocesor dokonuje przetwarzania tekstu<br />

programu jescze przed rozpoczęciem właściwej kompilacji.<br />

Dokładniej rzecz biorąc #include jest właściwie dyrektywą<br />

preprocesora (szczegóły w dalszej części książki).<br />

________________________________________________________________<br />

[Z] - Propozycje zadań do samodzielnego wykonania.<br />

________________________________________________________________<br />

1. Spróbuj poeksperymentować "zjadając" kolejno różne elementy w<br />

poprawnie działającym na początku programie:<br />

- 41-


- litera w nazwie funkcji<br />

- średnik na końcu wiersza<br />

- cudzysłów obejmujący tekst do wydrukowania<br />

- nawias ( lub ) w funkcji printf()<br />

- nawias klamrowy { lub }<br />

- znak dyrektywy #<br />

- całą dyrektywę #include <br />

Porównaj komunikaty o błędach i zgłaszaną przez kompilator<br />

liczbę błędów. Czy po przekłamaniu jednego znaku kompilator<br />

zawsze zgłasza dokładnie jeden błąd?<br />

________________________________________________________________<br />

______________________________________________________________________<br />

EOF<br />

- 42-


LEKCJA 6. NASTĘPNY PROGRAM - KOMPUTEROWA ARYTMETYKA.<br />

________________________________________________________________<br />

W trakcie tej lekcji napiszesz i uruchomisz następny program<br />

wykonujący proste operacje matematyczne.<br />

________________________________________________________________<br />

Aby przystąpić po wyjaśnieniach do pracy nad drugim programem,<br />

powinieneś wykonać następujące czynności:<br />

1. Zrób porządek na ekranie. Zamknij rozkazem Close z menu<br />

Window zbędne okna (możesz posłużyć się kombinacją [Alt]-[F3]).<br />

2. Rozwiń menu File.<br />

3. Wybierz z menu rozkaz Open...<br />

4. Wpisz do okienka tekstowego:<br />

A:\DRUGI.CPP<br />

5. Naciśnij [Enter].<br />

6. Wpisz do okienka edytora tekst programu:<br />

[P003.CPP ]<br />

/* Program przykladowy: _DRUGI.CPP */<br />

# include /* zwróć uwagę, że tu NIE MA [;] ! */<br />

# include /* drugi plik nagłówkowy */<br />

int main() /* tu tez nie ma średnika [;] ! */<br />

{<br />

float x, y;<br />

float wynik;<br />

clrscr();<br />

printf("Zamieniam ulamki zwykle na dziesietne\n");<br />

printf("\nPodaj licznik ulamka: ");<br />

scanf("%f", &x); /* pobiera liczbę z klawiatury */<br />

printf("\nPodaj mianownik ulamka: ");<br />

scanf( "%f", &y);<br />

wynik = x / y; /* tu wykonuje sie dzielenie */<br />

printf("\n %f : %f = %f", x, y, wynik);<br />

printf("\n nacisnij dowolny klawisz...\n");<br />

getch(); /* program czeka na nacisniecie klawisza. */<br />

return 0;<br />

}<br />

//


* Tekst komentarza */<br />

// Tekst komentarza<br />

w drugim przypadku ogranicznikiem pola komentarza jest koniec<br />

wiersza.<br />

* Spacjami i TABami możesz operować dowolnie. Kompilator<br />

ignoruje także puste miejsca w tekście. Nie należy natomiast<br />

stosować spacji w obrębie słów kluczowych i identyfikatorów.<br />

________________________________________________________________<br />

7. Skompiluj program [Alt]-[C], [M] lub [Enter].<br />

8. Popraw ewentualne błędy.<br />

9. Uruchom program rozkazem Run, naciskając [Alt]-[R], [R].<br />

10. Zapisz wersję źródłową programu DRUGI.CPP na dyskietkę A:\<br />

stosując tym razem SHORTCUT KEY - klawisz [F2].<br />

[S!] scanf() - SCANing Function - Funkcja SKANująca.<br />

________________________________________________________________<br />

Funkcja pobiera ze standardowego urządzenia wejścia- zwykle z<br />

klawiatury podaną przez użytkownika liczbę lub inny ciąg znaków.<br />

Działa podobnie do funkcji INPUT w Basicu, czy readln w Pascalu.<br />

* float - do Floating Point - "Pływający" - zmienny przecinek.<br />

Słowo kluczowe służące do tzw. DEKLARACJI TYPU ZMIENNEJ lub<br />

funkcji. Oznacza liczbę rzeczywistą np.: float x = 3.14;<br />

* int - od Integer - całkowity.<br />

Słowo kluczowe służące do deklaracji typu zmiennej lub funkcji.<br />

Oznacza liczbę całkowitą np.: 768.<br />

* #include - Włącz.<br />

Dyrektywa włączająca cały zewnętrzny plik tekstowy. W tym<br />

przypadku włączone zostały dwa tzw. pliki nagłówkowe:<br />

CONIO.H i STDIO.H.<br />

* CONIO.H - CONsole Input/Output.<br />

Plik nagłówkowy zawierający prototypy funkcji potrzebnych do<br />

obsługi standardowego Wejścia/Wyjścia na/z konsoli (CONsole).<br />

Plik zawiera między innymi prototyp funkcji clrscr(), potrzebnej<br />

nam do czyszczenia ekranu.<br />

*STDIO.H - STanDard Input/Output<br />

Plik nagłówkowy zawierający prototypy funkcji potrzebnych do<br />

obsługi standardowego Wejścia/Wyjścia na/z konsoli (Input -<br />

Wejście, Output - Wyjście). Plik zawiera między innymi prototyp<br />

funkcji printf(), potrzebnej nam do drukowania wyników na<br />

ekranie.<br />

- 44-


eturn - słowo kluczowe: Powrót, zwrot.<br />

Po wykonaniu programu liczba 0 (tak kazaliśmy programowi<br />

rozkazem return 0;) jest zwracana do systemu operacyjnego, w<br />

naszym przypadku do DOSa. Zwróć uwagę, że nie pojawiło się tym<br />

razem ostrzeżenie WARNING podczas kompilacji.<br />

________________________________________________________________<br />

OPERATORY ARYTMETYCZNE <strong>C++</strong>.<br />

<strong>C++</strong> potrafi oczywiście nie tylko dzielić i mnożyć. Oto tabela<br />

operatorów arytmetycznych c i <strong>C++</strong>.<br />

OPERATORY ARYTMETYCZNE języka <strong>C++</strong>.<br />

________________________________________________________________<br />

Operator Nazwa Tłumaczenie Działanie<br />

________________________________________________________________<br />

+ ADDition Dodawanie Suma liczb<br />

- SUBstraction Odejmowanie Różnica liczb<br />

* MULtiplication Mnożenie Iloczyn liczb<br />

/ DIVision Dzielenie Iloraz liczb<br />

% MODulus Dziel Modulo Reszta z dzielenia<br />

________________________________________________________________<br />

Przykładowe wyniki niektórych operacji arytmetycznych.<br />

________________________________________________________________<br />

Działanie (zapis w <strong>C++</strong>) Wynik działania<br />

________________________________________________________________<br />

5 + 7 12<br />

12 - 7 5<br />

3 * 8 24<br />

10 / 3 3.333333<br />

10 % 3 1<br />

________________________________________________________________<br />

[???] Czym różni się dzielenie / od % ?<br />

________________________________________________________________<br />

Operator dzielenia modulo % zamiast wyniku dzielenia - daje<br />

rzesztę z dzielenia. Dla przykładu, dzielenie liczby 14 przez<br />

liczbę 4 daje wynik 3, reszta z dzielenia 2. Wynik operacji<br />

14%4<br />

będzie więc wynosić 2. Operator ten jest niezwykle przydatny np.<br />

przy sprawdzaniu podzielności, skalowaniu, określaniu zakresów<br />

liczb przypadkowych, itp..<br />

- 45-


Przykłady generacji liczb pseudolosowych wybiegają nieco w przyszłość,<br />

ale postanowiłem w Samouczku umieścić je razem. Po przestudiowaniu<br />

tworzenia pętli programowych możesz wrócić do tej lekcji i rozważyć<br />

przykłady po raz wtóry.<br />

Przykład 1:<br />

randomize();<br />

int X=ramdom();<br />

X = X % 10;<br />

Przykład 2:<br />

---------------------<br />

#include /* Zwróc uwagę na dołączony plik */<br />

#include <br />

main()<br />

{<br />

int i;<br />

}<br />

printf("Dziesięć liczb pseudo-losowych od 0 do 99\n\n");<br />

for(i=0; i


Zwróć uwagę, że to randomize() uruchamia generator liczb pseudolosowych,<br />

czyli jakgdyby "włącza bęben maszyny losującej".<br />

________________________________________________________________<br />

Wykonaj z programem DRUGI.CPP kilka eksperymentów.<br />

[Z]<br />

________________________________________________________________<br />

1. Zamień operator dzielenia na operator mnożenia [*]:<br />

wynik = x * y; /* tu wykonuje sie mnożenie */<br />

i napis w pierwszej funkcji printf() na np. taki:<br />

printf( "Wykonuje mnozenie liczb" );<br />

Uruchom program. Sprawdź poprawność działania programu w<br />

szerokim zakresie liczb. Przy jakiej wielkości liczb pojawiają<br />

się błędy?<br />

2. Zmień nazwy zmiennych x, y, wynik na inne, np.:<br />

to_jest_liczba_pierwsza,<br />

to_jest_liczba_druga,<br />

itp.<br />

Czy <strong>C++</strong> poprawnie rozpoznaje i rozróżnia takie długie nazwy?<br />

Kiedy zaczynają się kłopoty? Sprawdź, czy można w nazwie<br />

zmiennej użyć spacji? Jaki komunikat wyświetli kompilator?<br />

________________________________________________________________<br />

[???] PRZEPADŁ PROGRAM ???<br />

________________________________________________________________<br />

Nie przejmuj się. Wersja początkowa programu DRUGI.CPP jest na<br />

dyskietce dołączonej do niniejszej książki (tam nazywa się<br />

DRUGI.CPP).<br />

Zwróć uwagę, że kompilator <strong>C++</strong> tworzy automatycznie kopie<br />

zapasowe plików źródłowych z programami i nadaje im standardowe<br />

rozszerzenie *.BAK. Zanim zatem zaczniesz się denerwować,<br />

sprawdź, czy kopia np. DRUGI.BAK nie jest właśnie tą wersją<br />

programu, która Ci "przepadła".<br />

________________________________________________________________<br />

__________________________________________________________________<br />

EOF<br />

- 47-


LEKCJA 7. Z czego składa się program.<br />

_______________________________________________________________<br />

W trakcie tej lekcji:<br />

* Dowiesz się co robić, jeśli tęsknisz za Pascalem.<br />

* Zapoznasz się wstępnie z preprocesorem <strong>C++</strong>.<br />

* Poznasz dokładniej niektóre elementy języka <strong>C++</strong>.<br />

_______________________________________________________________<br />

Zanim zagłębimy się w szczegóły działania preprocesora i<br />

kompilatora, dla zilustrowania mechanizmu działania dyrektyw<br />

popełnimy żart programistyczny. Nie ma nic gorszego niż spalić<br />

dobry żart, upewnijmy się więc najpierw, czy nasza<br />

"czarodziejska kula" jest gotowa do magicznych sztuczek.<br />

Sprawdź, czy na dyskietce znajdują się pliki<br />

A:\PASCAL.H<br />

A:\POLTEKST.H<br />

Jeśli nie, to przed zabawą w magiczne sztuczki programistyczne<br />

musisz odtworzyć te pliki z zapasowej kopii dyskietki, którą<br />

sporządziłeś przed rozpoczęciem LEKCJI 1.<br />

Jeśli masz już oba pliki, to wykonaj następujące czynności:<br />

1. Włóż do napędu A: dyskietkę z plikami PASCAL.H i POLTEKST.H.<br />

2. Uruchom kompilator <strong>C++</strong>.<br />

PROGRAMY HOKUS.EXE i POKUS.EXE - czyli sztuczki z Preprpcesorem<br />

<strong>C++</strong><br />

1. Zrób porządek na ekranie - pozamykaj zbędne okna.<br />

2. Naciśnij klawisz [F3]. Pojawi się znajome okienko dialogowe<br />

"Open".<br />

3. Wpisz do okienka tekstowego nazwę nowego programu:<br />

A:\HOKUS.C<br />

i naciśnij [Enter].<br />

4. Wpisz następujący tekst programu:<br />

[P004.CPP]<br />

#include <br />

Program<br />

Begin<br />

Write("Ten program jest podobny");<br />

Write(" do Turbo Pascala ");<br />

Write(" tak tez mozna pisac w BORLAND <strong>C++</strong> !");<br />

Readln;<br />

End<br />

5. Uruchom program [Ctrl]-[F9]. Jeśli wystąpią błędy, skoryguj<br />

ewentualne niezgodności z oryginałem. Ostrzeżenie "WARNING"<br />

możesz zignorować.<br />

- 48-


UWAGA: MUSI ZOSTAĆ ZACHOWANA IDEALNA ZGODNOŚĆ z tekstem<br />

oryginału!<br />

6. Uruchom program rozkazem Run [Alt]-[R], [Enter]. Zwróć uwagę,<br />

że powtórna kompilacja przebiega szybciej, jeśli w międzyczasie<br />

nie dokonałeś zmian w programie.<br />

7. Zamknij okno edytora rozkazem Close (z menu Window). Zapisz<br />

program HOKUS.CPP w wersji źródłowej na dyskietkę A:.<br />

A teraz następna sztuczka, na którą pozwala <strong>C++</strong>.<br />

Utworzymy następny program POKUS.CPP.<br />

1. Wykonaj czynności z pp. 1 i 2 z poprzedniego przykładu.<br />

2. Otwórz okienko nowego programu - File | Open (np. klawiszem<br />

[F3]) i wpisz nazwę programu. Możesz zastosować również File |<br />

New.<br />

A:\POKUS.CPP<br />

3. Naciśnij [Enter].<br />

4. Wpisz tekst programu:<br />

[P005.CPP]<br />

# include <br />

program<br />

poczatek<br />

czysty_ekran<br />

drukuj ("Ten program - POKUS.CPP ");<br />

drukuj ("Jest napisany po polsku ");<br />

drukuj ("a mimo to Turbo <strong>C++</strong> go rozumie!");<br />

czekaj;<br />

koniec<br />

5. Uruchom program [Alt]-[R], [R]. Jeśli wystąpią błędy,<br />

skoryguj ewentualne niezgodności z oryginałem. Ostrzeżenie<br />

"WARNING" możesz zignorować.<br />

UWAGA: MUSI ZOSTAĆ ZACHOWANA IDEALNA ZGODNOŚĆ!<br />

6. Zamknij okno edytora rozkazem Close (z menu Window). Zapisz<br />

program HOKUS.C w wersji źródłowej na dyskietkę A:.<br />

WYJAŚNIENIE SZTUCZEK - PREPROCESOR <strong>C++</strong> CPP.EXE.<br />

A teraz wyjaśnienie naszych magicznych sztuczek. Jeśli jesteś<br />

niecierpliwy, na pewno już sam zajrzałeś do plików PASCAL.H i<br />

POLTEKST.H, bo jest chyba oczywiste od początku, że to tam<br />

właśnie musi ukrywać się to wszystko, co pozwala nam robić nasze<br />

hokus-pokus. Skorzystaliśmy z pewnej nie występującej ani w<br />

Pascalu, ani w Basicu umiejętności języków C i <strong>C++</strong> - a<br />

mianowicie z PREPROCESORA.<br />

- 49-


Najczęściej stosowanymi dyrektywami preprocesora są:<br />

# include - włącz<br />

i<br />

# define - zdefiniuj<br />

Do rozpoznania dyrektyw preprocesora służy znak (#) - HASH.<br />

Zwróć uwagę, że zapisy<br />

#include<br />

# include<br />

są całkowicie równoważne. Poza tym dyrektywy preprocesora nie<br />

kończą się średnikiem.<br />

Działanie preprocesora (czyli wstępne przetwarzanie tekstu<br />

programu jeszcze przed przystąpieniem do kompilacji) polega na<br />

zastąpieniu w tekście programu jednych łańcuchów znaków przez<br />

inne. Takie pary możemy "zadać" preprocesorowi właśnie dyrektywą<br />

#define. Nasze nagłówki wyglądają następująco:<br />

PASCAL.H:<br />

_______________________________________________________________<br />

# include <br />

# define Program main()<br />

# define Begin {<br />

# define Writeln printf<br />

# define Readln getch()<br />

# define End }<br />

________________________________________________________________<br />

POLTEKST.H:<br />

________________________________________________________________<br />

# include <br />

# define program main()<br />

# define poczatek {<br />

# define koniec }<br />

# define czysty_ekran clrscr();<br />

# define drukuj printf<br />

# define czekaj getch()<br />

________________________________________________________________<br />

Zwróć uwagę, że warunkiem poprawnego zadziałania preprocesora<br />

jest zrezygnowanie ze spacji wewnątrz łańcuchów znakowych,<br />

spacje bowiem w preprocesorze rozdzielają dwa łańcuchy znaków - np.<br />

"drukuj"<br />

- ten ZA KTÓRY CHCEMY COŚ PODSTAWIĆ oraz np.<br />

"printf"<br />

- ten, KTÓRY NALEŻY PODSTAWIAĆ. Często w programach<br />

- 50-


zauważysz łańcuchy znaków pisane w dość specjalny sposób:<br />

napisy_w_których_unika_się_spacji.<br />

ELEMENTY PROGRAMU W JĘZYKU <strong>C++</strong>.<br />

Uogólniając, program w języku <strong>C++</strong> składa się z następujących<br />

elementów:<br />

1. Dyrektyw preprocesora. Przykład:<br />

#define drukuj printf<br />

Działanie: W tekście programu PONIŻEJ niniejszej dyrektywy<br />

zastąp wszystkie łańcuchy znaków "drukuj" łańcuchami znaków<br />

"printf".<br />

#include <br />

Działanie: W to miejsce pliku wstaw zawartość pliku tekstowego<br />

NAZWA.ROZ z katalogu KATALOG na dysku D:.<br />

2. Komentarzy. Przykład:<br />

// Tu obliczamy sumę lub /*To jest komentarz*/<br />

3. Deklaracji. Przykład:<br />

KAŻDY PROGRAM musi zawierać deklarację funkcji main (ang. main -<br />

główna). Funkcja ta często jest bezparametrowa, co można<br />

zaakcentować wpisując w nawiasy słowo kluczowe void:<br />

main(void)<br />

lub pisząc puste nawiasy:<br />

main()<br />

4. Instrukcji.<br />

i++;<br />

Działanie: Dokonaj inkrementacji zmiennej i, tzn. wykonaj<br />

operację i:=i+1<br />

[???] Dla dociekliwych - kilka słów o funkcji main()<br />

________________________________________________________________<br />

Funkcja main() występuje najczęściej w następujących<br />

(równoważnych) postaciach:<br />

main() int main() int main(void)<br />

- program w momencie uruchomienia nie pobiera żadnych argumentów<br />

z wiersza rozkazu --> () lub (void)<br />

- program zwraca po zakończeniu jedną licznę (int = integer -<br />

- 51-


liczba całkowita) do systemu operacyjnego informując go w taki<br />

sposób, czy wykonał się do końca i bezbłędnie i czy można go<br />

usunąć z pamięci (bywają także programy rezydujące w pamięci -<br />

tzw. TSR, o czym system operacyjny powinien "wiedzieć").<br />

void main()<br />

void main(void)<br />

- program nie pobiera i nie zwraca żadnych paramatrów.<br />

Główna funkcja main() może w środowisku okienkowym przeobrazić<br />

się w główną funkcję okienkową:<br />

WinMain(.....)<br />

a w środowisku obiektowym w<br />

OwlMain(....)<br />

OWL - biblioteka obiektów dla Windows - Object Windows Library.<br />

W nawiasach funkcji main(), WinMain() i OwlMain() mogą pojawić<br />

się parametry (argumenty) pobierane przez program w momencie<br />

uruchomienia z wiersza rozkazu lub od środowiska operacyjnego<br />

(szczegóły w dalszej części książki).<br />

Programy w <strong>C++</strong> mogą składać się z wielu plików dyskowych. Typowy<br />

program zawiera. Nazywa się to zwykle projektami wielomodułowymi<br />

- a poszczególne pliki - modułami lub elementami składowymi<br />

projektu:<br />

* plik nagłówkowy - NAZWA.H<br />

* moduł główny - NAZWA.CPP (ten i tylko ten zawiera funkcję<br />

main())<br />

* moduły pomocnicze - NAZWA2.CPP, NAZWA3.CPP, itp<br />

* pliki z zasobami typu menu, okienka dialogowe, itp - NAZWA.RC,<br />

NAZWA.DLG<br />

* wreszcie plik instruktażowy - jak z tego wszystkiego zrobić<br />

końcową aplikację. W zależności od wersji kompilatora pliki<br />

instruktażowe mogą mieć nazwy: NAZWA.PRJ (Project - BORLAND),<br />

NAZWA.IDE, a dla programu MAKE - MAKEFILE, NAZWA.MAK, NAZWA.NMK,<br />

itp.<br />

W środowisku Windows występuje jeszcze zwykle w składzie<br />

projektów aplikacji tzw. plik definicji sposobu wykorzystania<br />

zasobów - NAZWA.DEF.<br />

________________________________________________________________<br />

[S!] void - czyli nijaki.<br />

________________________________________________________________<br />

Słowa kluczowe:<br />

void - pusty, wolny, nieokreślony, avoid - unikać.<br />

main - główny, główna.<br />

return - powrót, zwrot.<br />

Nazwa funkcji:<br />

- 52-


exit() - wyjście.<br />

________________________________________________________________<br />

Po nazwie funkcji main() NIE NALEŻY stawiać średnika (;).<br />

Przy pomocy tej funkcji program kontaktuje się z systemem<br />

operacyjnym. Parametry funkcji main, to te same parametry z<br />

którymi uruchamiamy nasz program w systemie DOS. Np. rozkaz<br />

FORMAT A:<br />

oznacza, że do programu przekazujemy parametr A:.<br />

Ponieważ w każdym programie oprócz nagłówka funkcji:<br />

main(void)<br />

podajemy również tzw. ciało funkcji, np.:<br />

{<br />

printf("wydrukuj cokolwiek");<br />

return 0;<br />

}<br />

jest to jednocześnie DEFINICJA FUNKCJI main().<br />

Zwróć uwagę, że funkcja printf() nie jest w powyższym<br />

przykładzie w żaden sposób ani deklarowana ani definiowana.<br />

Wiersz:<br />

printf("pisz!");<br />

stanowi WYWOŁANIE funkcji printf() z parametrem 'pisz!' -<br />

łańcuchem znaków, który należy wydrukować.<br />

W <strong>C++</strong> nawet jeśli nawiasy przeznaczone w funkcji na przekazanie<br />

jej argumentów są puste - muszą być obecne. Poprawne wywołanie<br />

funkcji w języku <strong>C++</strong> może mieć następującą formę:<br />

nazwa_funkcji();<br />

nazwa_funkcji(par1, par2, par3, .....);<br />

zmienna = nazwa_funkcji(par1, par2, ...);<br />

Funkcja w momencie jej wywołania uzyskuje przekazane jej<br />

parametry. Są to tzw. ARGUMENTY FUNKCJI. Aby to wszystko<br />

bardziej przypominało to, co znasz ze szkoły popatrzmy na<br />

analogię. W zapisie:<br />

y = sin(x) lub y = sin(90)<br />

x - oznacza argument funkcji, który może być zmienną (w szkole<br />

nazywałeś zmienne "niewiadomymi")<br />

y - oznacza wartość zwracaną "po zadziałaniu" funkcji<br />

sin() - oznacza nazwę funkcji. Zastosowanie funkcji będziemy w<br />

programach nazywać "wywołaniem funkcji".<br />

- 53-


Język <strong>C++</strong> operuje wyłącznie pojęciem FUNKCJI. W C ani w <strong>C++</strong> nie<br />

ma podziału na FUNKCJE i PROCEDURY.<br />

Każda funkcja może być w programie wywoływana wielokrotnie.<br />

Każde wywołanie funkcji może następować z innymi argumentami.<br />

Funkcja może w wyniku swojego działania zmieniać wartość jakiejś<br />

zmiennej występującej w programie. Mówimy wtedy, że funkcja<br />

ZWRACA wartość do programu. Funkcja main() jest funkcją<br />

szczególną, która "zwraca" wartość do systemu operacyjnego, w<br />

którym pracuje program. Zapis:<br />

main() lub int main()<br />

{ {<br />

return 5; exit(5);<br />

} }<br />

oznacza:<br />

1. Funkcja main jest bezparametrowa (nie przyjmuje żadnych<br />

argumentów z zewnątrz).<br />

2. Funkcja main zwraca jako wynik swojego działania liczbę<br />

całkowitą typu int (ang. INTeger - całkowita). Zwróć uwagę, że<br />

jest to domyślny sposób działania funkcji main(). Jeśli nie<br />

napiszemy przed funkcją main() słowa "int" - kompilator <strong>C++</strong> doda<br />

je sobie automatycznie. Jeśli świadomie nie zamierzamy zwracać<br />

do systemu operacyjnego żadnych informacji - musimy wyraźnie<br />

napisać tam "void".<br />

3. Funkcja zwróci do systemu DOS wartość 5. Zwróć uwagę na<br />

istotną różnicę formalną, Słowo "return" jest słowem kluczowym<br />

języka C, natomiast słowo "exit" jest nazwą funkcji exit().<br />

Zastosowanie tej funkcji w programie wymaga dołączenia pliku<br />

nagłówkowego z jej prototypem.<br />

Ponieważ nasz kurs języka <strong>C++</strong> rozpoczęliśmy od programu z<br />

funkcją printf() i zapewne będzie nam ona towarzyszyć jeszcze<br />

długo, pora poświęcić jej trochę uwagi.<br />

FUNKCJA printf().<br />

Jest to funkcja FORMATOWANEGO wyjścia na standardowe urządzenie<br />

wyjścia (ang. stdout - STandarD OUTput). Definicja - ściślej<br />

tzw. PROTOTYP tej funkcji znajduje się w pliku nagłówkowym<br />

STDIO.H. Wniosek praktyczny: Każdy program korzystający z<br />

funkcji printf() powinien zawierać dyrektywę preprocesora:<br />

#include <br />

zanim nastąpi wywołanie funkcji printf().<br />

[???] A JEŚLI ZAPOMNIAŁEM O ???<br />

________________________________________________________________<br />

Możesz nadać plikowi z tekstem żródłowym programu rozszerzenie<br />

.C zamiast .CPP. W kompilatorach <strong>Borland</strong>a powoduje to przy<br />

- 54-


domyślnych ustawieniach kompilatora wywołanie kompilatora C<br />

zamiast <strong>C++</strong>. C jest bardziej tolerancyjny i dokona kompilacji<br />

(wyświetli jedynie komunikat ostrzegawczy - Warning). Kompilator<br />

<strong>C++</strong> jest mniej tolerancyjny. Jeśli zapomnisz dołączyć odpowiedni<br />

plik nagłówkowy może pojawić się komunikat:<br />

Error: Function printf() should have a prototype in function<br />

main<br />

(Funkcja printf() powinna mieć prototyp)<br />

Więcej o zawartości i znaczeniu plików nagłówkowych *.h dowiesz<br />

się z następnych lekcji. Na razie postaraj się pomiętać o<br />

dołączeniu wskazanego w przykładzie pliku.<br />

________________________________________________________________<br />

[???] Skąd to wiadomo?<br />

________________________________________________________________<br />

Jeśli masz wątpliwości, jaki plik nagłówkowy należałoby dołączyć<br />

- najprościej zajrzeć do systemu pomocy - Help. Na pasku<br />

głównego menu w IDE masz napis Help. Menu Help możesz rozwinąć<br />

myszką lub naciskając kombinację klawiszy [Alt]+[H]. Jeśli w<br />

menu wybierzesz rozkaz Index (Spis) przeniesiesz się do okienka<br />

z alfabetycznym spisem haseł. Są tam słowa kluczowe, nazwy<br />

funkcji i jeszcze wiele innych interesujących rzeczy. Powinieneś<br />

teraz wykonać następujące czynności:<br />

* posługując się klawiszami kursora (ze strzałkami) odszukać w<br />

spisie nazwę funkcji<br />

albo<br />

* rozpocząć pisanie nazwy funkcji na klawiaturze (system Help<br />

sam wyszuka w spisie wypisaną w ten sposób nazwę)<br />

* nacisnąć [Enter]<br />

Przeniesiesz się do okienka opisu danej funkcji. Na samym<br />

początku w okienku każdej funkcji podana jest nazwa pliku<br />

nagłówkowego, w którym znajduje się prototyp funkcji. Nawet<br />

jeśli nie jesteś biegłym anglistą, łatwo rozpoznasz pliki<br />

nagłówkowe - po charakterystycznych rozszerzeniach .H (rzadziej<br />

.HPP. Charakterystyczne rozszerzenie *.H pochodzi od "plik<br />

nagłówkowy" - ang. Header file).<br />

________________________________________________________________<br />

Funkcja printf() zwraca wartość całkowitą typu int:<br />

* liczbę bajtów przesłanych na standardowe urządzenie wyjścia;<br />

* w przypadku wystąpienia błędu - kod znaku EOF.<br />

[S!]<br />

EOF - End Of File - znak końca pliku.<br />

EOL - End Of Line - znak końca linii.<br />

Indicator - znak, wskaźnik (nie mylić z pointerem !)<br />

- 55-


[???] SKĄD TO WIADOMO ?<br />

________________________________________________________________<br />

Kody EOF, EOL są tzw. predefiniowanymi stałymi. Ich szyfrowanie<br />

(przypisywanie tym identyfikatorom określonej stałej wartości<br />

liczbowej) dokonuje się z zastosowaniem preprocesora <strong>C++</strong>.<br />

To, że nie musisz się zastanawiać ile to właściwie jest EOF<br />

(zero ? czy -1 ?) zawdzięczamy też dołączanym plikom typu *.H, w<br />

których np. przy użyciu dyrektywy #define zostały PREDEFINIOWANE<br />

(zdefiniowane wstępnie) niektóre stałe. Jeśli jesteś bardzo<br />

dociekliwy, zajrzyj do wnętrza pliku STDIO.H (view, edit, type).<br />

Znajdziesz tam między innymi taki wiersz:<br />

#define EOF (-1) //End of file indicator<br />

________________________________________________________________<br />

Składnia prototypu (ang. syntax):<br />

int printf(const char *format [arg1, arg2,.....]);<br />

lub trochę prościej:<br />

printf(format, arg1, arg2,.....argn);<br />

Liczba argumentów może być zmienna.<br />

<strong>C++</strong> oferuje wiele funkcji o podobnym działaniu - np.:<br />

cprintf(), fprintf(), sprintf(), vprintf(), vsprintf(), itp.<br />

Ponieważ FORMAT brzmi może trochę obco, nazwijmy go WZORCEM. Jak<br />

wiesz, wszystkie informacje przechowywane są w pamięci komputera<br />

jako ciągi zer i jedynek. Jest to forma trochę niewygodna dla<br />

człowieka, więc zanim informacja trafi na ekran musi zostać<br />

zamieniona na postać dla nas wygodniejszą - np. na cyfry<br />

dziesiętne, litery itp.. Taki proces nazywany jest KONWERSJĄ, a<br />

podany w funkcji printf() FORMAT - WZORZEC to upraszczając,<br />

rozkaz dokonania takiej właśnie konwersii. Możesz więc zarządać<br />

przedstawienia liczby na ekranie w postaci np. SZESNASTKOWEJ lub<br />

DZIESIĘTNEJ - tak, jak Ci wygodniej. Wzorce konwersji w<br />

najprostszym przypadku mają postać %s, %d, %f, itp.:<br />

I tak:<br />

%s - wyprowadź łańcuch znaków (s - String - łańcuch)<br />

Przykład:<br />

printf("%s","jakis napis");<br />

ale także<br />

printf("Jakis napis");<br />

- 56-


ponieważ format "%s" jest formatem domyślnym dla funkcji<br />

printf().<br />

Przykład:<br />

printf("%39s","jakis napis");<br />

spowoduje uzupełnienie napisu spacjami do zadanej długości 39<br />

znaków (Sprawdź!). Funkcja printf() operuje tzw. POLEM<br />

WYJŚCIOWYM. Długość pola wyjściowego możemy określić przy pomocy<br />

liczb wpisanych pomiędzy znaki % oraz typ - np. s. Możemy także<br />

określić ilość cyfr przed i po przecinku.<br />

%c - wyprowadź pojedynczy znak (c - Character - znak)<br />

Przykład:<br />

printf("%c",'X');<br />

(spowoduje wydrukowanie litery X)<br />

%d - wyprowadź liczbę całkowitą typu int w postaci dziesiętnej<br />

€€€€€(d - Decimal - dziesiętny).<br />

Przykład:<br />

printf("%d", 1994);<br />

%f - wyprowadź liczbę rzeczywistą typu float w postaci<br />

dziesiętnej (f - Floating point - zmienny przecinek).<br />

Przykład:<br />

printf("%f", 3.1416);<br />

printf("%f3.2", 3.14159);<br />

%o - wyprowadź liczbę całkowitą typu int w postaci ósemkowej<br />

(o - Octal - ósemkowa).<br />

Przykład:<br />

printf("%o", 255);<br />

%x - wyprowadź liczbę całkowitą typu int w postaci szesnastkowej<br />

€€€€€(x - heXadecimal - szesnastkowa).<br />

%x lub %X - cyfry szesnastkowe a,b,c,d,e,f lub A,B,C,D,E,F.<br />

%ld - liczba całkowita "długa" - long int.<br />

%Lf - liczba rzeczywista poczwórnej precyzji typu long double<br />

float.<br />

%e - liczba w formacie wykładniczym typu 1.23e-05 (0.0000123)<br />

%g - automatyczny wybór formatu %f albo %e.<br />

- 57-


Po przytoczeniu przykładów uogólnijmy sposób zastosowania wzorca<br />

formatu:<br />

%[przełączniki][szerokość_pola][.precyzja][rozmiar]Typ<br />

Posługując się różnymi sposobami formatowania liczb możemy<br />

zażądać wydrukowania liczb w najwygodniejszej dla nas formie. W<br />

programie przykładowym dokonujemy zamiany liczb dziesiętnych na<br />

szesnastkowe.<br />

[P006.CPP]<br />

// Program przykladowy 10na16.CPP<br />

#include <br />

#include <br />

int liczba;<br />

int main()<br />

{<br />

clrscr();<br />

printf("Podaj liczbe dziesietna calkowita ? \n");<br />

scanf("%d", &liczba);<br />

printf("\nSzesnastkowo to wynosi: ");<br />

printf("%x",liczba);<br />

getch();<br />

return 0;<br />

}<br />

Ten program pozwala zamienić dziesiętne liczby całkowite na<br />

liczby szesnastkowe. Zakres dostępnych liczb wynika z<br />

zadeklarowanego typu int. Więcej na ten temat dowiesz się z<br />

następnych lekcji. Spróbujmy odwrotnie:<br />

[P007.CPP]<br />

// Program przykladowy 16na10.CPP<br />

//UWAGA: Sam dołącz pliki nagłówkowe<br />

int liczba;<br />

int main()<br />

{<br />

clrscr();<br />

printf("Podaj liczbe SZESNASTKOWA-np. AF - DUZE LITERY: \n");<br />

scanf("%X", &liczba);<br />

printf("%s","\nDziesietnie to wynosi: ");<br />

printf("%d",liczba);<br />

getch();<br />

return 0;<br />

}<br />

Myślę, że program 16NA10.CPP można pozostawić bez dodatkowego<br />

- 58-


komentarza. Zwróć uwagę, że funkcja scanf() "formatuje" dane<br />

wejściowe bardzo podobnie do funkcji printf(). Pewnie dziwi Cię<br />

trochę "dualny" zapis:<br />

liczba i &liczba.<br />

Zagadka zostanie niebawem wyjaśniona. W trakcie następnych<br />

Lekcji zajmiemy się dokładniej zmiennymi, i ich rozmieszczeniem<br />

w pamięci a na razie wracamy do funkcji printf().<br />

Jako się rzekło wcześniej - funkcja printf() może mieć wiele<br />

argumentów. Pozwala nam to przy pomocy jednego wywołania funkcji<br />

wyprowadzać złożone napisy.<br />

Przykład:<br />

printf("Iloczyn 3 %c 5 %8s %d", '*', "wynosi ",15);<br />

Działanie:<br />

"Iloczyn_3_ - wyprowadź jako łańcuch znaków.<br />

%c - tu wyprowadź pojedynczy znak - '*'.<br />

_5_ - wyprowadź jako łańcuch znaków.<br />

%8s - wyprowadź łańcuch "wynosi_" uzupełniając go z przodu<br />

spacjami do długości 8 znaków.<br />

%d - wyprowadź 15 jako liczbę dziesiętną.<br />

UWAGA: Znakiem podkreślenia w tekście książki "_" oznaczyłem<br />

spację, spacja to też znak.<br />

Przykład:<br />

printf("Iloczyn 3 %c 5 %9s %f", 'x', "wynosi ", 3*5);<br />

Zwróć uwagę, że tym razem kazaliśmy komputerowi samodzielnie<br />

policzyć ile wynosi nasz iloczyn, tzn. zastosowaliśmy jako<br />

argument funkcji printf() nie stałą, a WYRAŻENIE. Działanie<br />

możesz prześledzić przy pomocy programu przykładowego:<br />

[P008.CPP]<br />

// Program WYRAZ.CPP - Dołącz pliki nagłówkowe<br />

int main()<br />

{<br />

clrscr();<br />

printf("Skomplikowany napis:\n");<br />

printf("Iloczyn 3 %c 5 %8s %d", '*', "wyniosi ", 15);<br />

getch();<br />

printf("\nWyrazenie jako argument:\n");<br />

printf("Iloczyn 3 %c 5 %9s %d", 'x', "wynosi ", 3*5);<br />

printf("\n\n\n");<br />

printf("Przyjrzyj sie i nacisnij klawisz...");<br />

getch();<br />

return 0;<br />

- 59-


}<br />

Wyjaśnijmy jeszcze jedno "dziwactwo" - znaki sterujące<br />

rozmieszczeniem napisów na ekranie. Oto tabelka z najczęściej<br />

używanymi znakami specjalnymi:<br />

________________________________________________________________<br />

Znak Nazwa Działanie<br />

________________________________________________________________<br />

\n New Line Przejście na początek nowego wiersza<br />

\b BackSpace Cofnięcie kursora o jeden znak<br />

\f Form feed O stronicę w dół<br />

\r Carriage return Powrót na początek bież. wiersza<br />

\t Horizontal Tab Tabulacja pozioma<br />

\v Vertical Tab Tabulacja pionowa<br />

\a Sound a beep Pisk głośniczka<br />

\\ Displ. backslash Wyświetl znak \<br />

\' Display ' Wyświetl znak ' (apostrof)<br />

\" Display " Wyświetl znak " (cudzysłów)<br />

________________________________________________________________<br />

UWAGA: Trzy ostatnie "backlash-kody" pozwalają wyprowadzić na<br />

ekran znaki specjalne \ ' i ", co czasami się przydaje.<br />

Szczególnie \\ jest często przydatny.<br />

[Z]<br />

Spróbuj samodzielnie:<br />

1. Napisać i uruchomić program wykonujący konwersję liczb<br />

ósemkowych na dziesiętne i odwrotnie.<br />

2. Przy pomocy pojedynczego wywołania funkcji printf()<br />

wydrukować kilka złożonych napisów typu:<br />

* suma 2+4 to 6<br />

* działanie 5*7*27+6-873 daje wynik...( właśnie, ile?).<br />

3. Sprawdź działanie tabulacji pionowej \v. Ile to wierszy?<br />

[???] DYSKIETKA NIE JEST Z GUMY !!!<br />

________________________________________________________________<br />

Jeśli podczas kompilacji programów w okienku będzie się<br />

uporczywie, bez widocznego powodu pojawiał napis "Errors" -<br />

błędy, a w okienku komunikatów "Message" pojawi się napis:<br />

Fatal A:\PROGRAM.C: Error writing output file<br />

(Fatalny błąd podczas kompilacji pliku A:\PROGRAM.C: Błąd przy<br />

zapisie pliku wyjściowego),<br />

to znak, że na dyskietce zabrakło miejsca. Pora zmienić katalog<br />

wyjściowy kompilatora <strong>C++</strong>. Aby to zrobić należy:<br />

1. Rozwinąć menu Option - [Alt]-[O].<br />

2. Wybrać rozkaz Directories... - [D].<br />

3. Przejść do okienka "Output Directory" - 2 razy [Tab].<br />

4. Wpisać do okienka katalog z dysku stałego, np.: C:\<br />

5. Nacisnąć [Enter].<br />

- 60-


6. Powtórzyć kompilację programu, przy której nastąpiło<br />

przepełnienie dyskietki.<br />

7. Usunąć z dyskietki A: zbędne pliki *.EXE (TYLKO *.EXE !!!).<br />

Oczywiście lepiej posługiwać się własnym katalogiem na dysku<br />

stałym, ale dysk też niestety nie jest z gumy. Złośliwi twierdzą<br />

nawet, że każdy dysk jest za mały a każdy procesor zbyt wolny<br />

(to ponoć tylko kwestia czasu...).<br />

________________________________________________________________<br />

[!!!] Dla dociekliwych - Przykłady programów.<br />

________________________________________________________________<br />

Jeśli zajrzysz już do systemu Help, przwiń cierpliwie tekst<br />

opisu funkcji do końca. W większości funkcji na końcu<br />

umieszczony jest krótki "firmowy" program przykładowy.<br />

Nie musisz go przepisywać!<br />

W menu Edit IDE masz do dyspozycji rozkaz<br />

Edit | Copy Example (Skopiuj przykład)<br />

Przykład zostanie skopiowany do Schowka (Clipboard).<br />

Po wyjściu z systemu pomocy warto rozkazem<br />

File | New<br />

otworzyć nowe okno robocze a następnie rozkazem<br />

Edit | Paste<br />

(Wstaw)<br />

wstawić program przykładowy ze schowka. Możesz go teraz<br />

uruchamiać, modyfikować a nawet wstawić jako fragment do swojego<br />

programu.<br />

Podobnie jak większość edytorów tekstu zintegrowany edytor<br />

środowiska IDE pozwala manipulować fragmentami blokami tekstu i<br />

wykonywać typowe operacje edytorskie zarówno w obrębie<br />

pojedynczego okna, jak i pomiędzy różnymi okienkami. Służą do<br />

tego celu następujące operacje:<br />

* Select/Mark text block - zaznaczenie fragmentu tekstu.<br />

Możesz dokonać tego klawiszami- np.: [Shift]+[-->], bądź<br />

naciskając i przytrzymując lewy klawisz myszki i "przejeżdżając<br />

nad odpowiednim fragmentem tekstu". Wybrany fragment tekstu<br />

zostanie wyróżniony podświetleniem.<br />

* Edit | Cut - wytnij.<br />

Zaznaczony wcześniej fragment tekstu zostanie skopiowany do<br />

Schowka i jednocześnie usunięty z ekranu.<br />

* Edit | Copy - skopiuj.<br />

Zaznaczony wcześniej fragment tekstu zostanie skopiowany do<br />

Schowka i bez usuwania z ekranu.<br />

* Edit | Paste - wstaw.<br />

Zaznaczony wcześniej w Schowku fragment tekstu zostanie<br />

skopiowany na ekran począwszy od miejsca wskazanego w danej<br />

chwili kursorem.<br />

- 61-


LEKCJA 8. Jakich słów kluczowych używa <strong>C++</strong>.<br />

W trakcie tej lekcji dowiesz się:<br />

* Jakie znaczenie mają słowa kluczowe języka <strong>C++</strong>.<br />

* Jakie jeszcze dziwne słowa mogą pojawiać się w programach w<br />

pisanych <strong>C++</strong>.<br />

* Trochę więcej o wczytywaniu i wyprowadzaniu danych.<br />

* Co to jest i do czego służy zmienna.<br />

_______________________________________________________________<br />

Każdy język musi operować tzw. słownikiem - zestawem słów<br />

zrozumiałych w danym języku. Jak wiesz z doświadczenia, komputer<br />

jest pedantem i wymaga dodatkowo (my, ludzie, tego nie<br />

wymagamy), aby znaczenie słów było absolutnie jednoznaczne i<br />

precyzyjne. Aluzje, kalambury i zabawne niedomówienia są na<br />

razie w dialogu z komputerem niedopuszczalne. Pamięci<br />

asocjatywne (oparte na skojarzeniach), sieci neuronowe (neural<br />

networks), tworzone bardzo często właśnie przy pomocy <strong>C++</strong><br />

- systemy expertowe,<br />

- systemy z tolerancją błędów - np. OCR - systemy optycznego<br />

rozpoznawania pisma,<br />

- "rozmyta" arytmetyka i logika (fuzzy math)<br />

- logika większościowa i mniejszościowa<br />

- algorytmy genetyczne (genetic algorithms)<br />

i inne pomysły matematyków oraz informatyków rozpoczęły już<br />

proces "humanizowania" komputerowego myślenia. Powstała nawet<br />

specjalna "mutacja" neural C i neural <strong>C++</strong>, ale to temat na<br />

oddzielną książkę. Na razie traktujemy nasz komputer jako<br />

automat cyfrowy pozbawiony całkowicie wyobraźni i poczucia<br />

humoru, a język <strong>C++</strong>, jako środek porozumiewania się z tym<br />

"ponurakiem".<br />

Podobnie do słów języka naturalnego (rzeczowników, czasowników)<br />

i słowa języka programowania można podzielić na kilka grup<br />

różniących się przeznaczeniem. Takie niby - słowa czasem nazywa<br />

się również tokenami lub JEDNOSTKAMI LEKSYKALNYMI (leksykon -<br />

inaczej słownik) a sposoby tworzenia wyrażeń (expressions)<br />

nazywane są syntaktyką języka (stąd bierze się typowy komunikat<br />

o błędach "Syntax Error" - błąd syntaktyczny, czyli niewłaściwa<br />

składnia). Słownik języka <strong>C++</strong> składa się z:<br />

* Słów kluczowych<br />

* Identyfikatorów<br />

* Stałych liczbowych i znakowych<br />

* Stałych tekstowych (łańcuchów znaków - napisów)<br />

* Operatorów (umownych znaków operacji)<br />

* Znaków interpunkcyjnych<br />

* Odstępów<br />

UWAGA: Zarówno pojedyncza spacja czy ciąg spacji, tabulator<br />

poziomy, znak nowej linii, jak i komentarz dowolnej długości (!)<br />

są traktowane przez kompilator jak pojedyncza spacja.<br />

Od zarania dziejów informatyki twórcy uniwersalnych języków<br />

programowania starali się upodobnić słowa tych języków do<br />

zrozumiałych dla człowieka słów języka naturalnego - niestety -<br />

- 62-


angielskiego (swoją drogą, może to i lepiej, że <strong>C++</strong> nie<br />

wymyślili Japończycy...). Najważniejszą częścią słownika są tzw.<br />

SŁOWA KLUCZOWE (keywords).<br />

SŁOWA KLUCZOWE w <strong>C++</strong>.<br />

Oto pełna lista słów kluczowych Turbo <strong>C++</strong> v 1.0 z krótkim<br />

wyjaśnieniem ich znaczenia. Zaczynam od listy podstawowej wersji<br />

kompilatora, ponieważ rozważania o niuansach dotyczących kilku<br />

specyficznych słów kluczowych (np. friend, template) pozostawiam<br />

sobie na póżniej. Krótkie wyjaśnienie - jak to krótkie<br />

wyjaśnienie - pewnie nie wyjaśni wszystkiego od razu, ale na<br />

pewno pomoże zrozumieć znaczenie większości słów kluczowych.<br />

[S] Keywords - słowa kluczowe.<br />

asm<br />

Pozwala wstawić kod w ASEMBLERZE bezpośrednio do programu<br />

napisanego w C lub <strong>C++</strong>.<br />

auto - zmienna lokalna. Przyjmowane domyślnie.<br />

break - przerwij.<br />

case - w przypadku.<br />

cdecl - spec. konwencja nazewnictwa/przekazania parametrów<br />

zgodna ze standardem jęz. C.<br />

char - znak, typ zmiennej - pojedynczy bajt.<br />

class - klasa.<br />

const - stała, konstanta.<br />

continue - kontynuuj.<br />

default - przyjmij domyślnie.<br />

delete - skasuj obiekt.<br />

do - wykonaj.<br />

double - podwójna (długość/precyzja).<br />

else - w przeciwnym wypadku.<br />

enum - wylicz kolejno.<br />

_export - dotyczy tylko OS/2, ignorowany.<br />

extern - zewnętrzna.<br />

- 63-


far - dalekie. Wskaźnik - podwójne słowo (w zakresie do 1 MB).<br />

float - zmiennoprzecinkowy, rzeczywisty.<br />

for - dla (wskazanie zmiennej roboczej w pętli).<br />

friend - zaprzyjaźniona funkcja z dostępem do prywatnych i<br />

€€€€€€€€€€chronionych członków danej klasy.<br />

goto - skocz do (skok bezwarunkowy).<br />

huge - daleki, podobnie do far.<br />

if - jeżeli (pod warunkiem, że...).<br />

inline - funkcja z rozwiniętym wstawionym kodem<br />

int - typ zmiennej, liczba całkowita, dwa bajty<br />

interrupt - przerwanie.<br />

_loadds - podobne do huge, ustawia rejestr DS (Data Segment).<br />

long - długi.<br />

near - bliski, wskaźnik o dł. 1 słowa. Obszar max. 64 K.<br />

new - nowy, utwórz nowy obiekt.<br />

operator - operator, określa nowy sposób działania operatora.<br />

pascal - deklar. funkcji zgodnej ze standardem przekazywania<br />

parametrów przyjętym w Pascalu.<br />

private - prywatna, wewnętrzna, niedostępna z zewnątrz.<br />

protected - chroniona, część danych i funkcji, do których<br />

dostęp. jest ograniczony.<br />

public - publiczna, dostępna z zewnątrz.<br />

register - zmienną przechwaj nie w pamięci a w rejestrze CPU.<br />

return - powrót, zwrot wartości.<br />

_saveregs - save registers, zachowaj zawartość rejestrów a<br />

następnie odtwórz rejestry przed powrotem.<br />

_seg - segment.<br />

short - krótka (mała ilość cyfr).<br />

signed - ze znakiem (+/-).<br />

unsigned - bez znaku (+/-).<br />

- 64-


sizeof - podaj wielkość.<br />

static - statyczna.<br />

struct - struktura.<br />

switch - przełącz.<br />

this - ten, wstazanie bieżącego, własnego obiektu (tylko <strong>C++</strong>).<br />

typedef - definicja typu.<br />

union - unia, zmienna wariantowa.<br />

virtual - wirtualna, pozorna.<br />

void - nieokreślona.<br />

volatile - ulotna.<br />

while - dopóki.<br />

Panuje mnienanie, że język <strong>C++</strong> posługuje się stosunkowo skromnym<br />

zestawem słów kluczowych. To prawda, ale nie cała prawda o<br />

języku <strong>C++</strong>. Zauważyłeś zapewne, że nie ma tu:<br />

define, include, printf<br />

i innych znanych Ci już słów. To po prostu jeszcze nie cały<br />

słownik języka. Zdając sobie sprawę z nieprecyzyjności tego<br />

porównania możesz przyjąć, że to coś na kształt listy<br />

czasowników. A są przecież jeszcze i inne słowa - o innej roli i<br />

przeznaczeniu.<br />

[???]€A GDZIE SIĘ PODZIAŁY REJESTRY ???<br />

Nazwy rejestrów mikroprocesora Intel 80X86:<br />

_AX€€€€€€€_AL€€€€€€€_AH€€€€€€€_SI€€€€€€€_CS<br />

_BX€€€€€€€_BL€€€€€€€_BH€€€€€€€_SP€€€€€€€_DS<br />

_CX€€€€€€€_CL€€€€€€€_CH€€€€€€€_BP€€€€€€€_ES<br />

_DX€€€€€€€_DL€€€€€€€_DH€€€€€€€_DI€€€€€€€_SS<br />

_FLAGS<br />

Takie oznaczenia wynikają z architektury konkretnej rodziny<br />

mikroprocesorów, nie mogą stanowić uniwersalnego standardu<br />

języka <strong>C++</strong>. Efekt dostosowania <strong>C++</strong> do IBM PC to np. odnoszące<br />

się do modeli pamięci słowa kluczowe near, far i huge.<br />

Wymóg zgodności ze standardem ANSI C spowodował, że w <strong>C++</strong> nazwy<br />

rejestrów pozostają nazwami o zastrzeżonym znaczeniu, ale<br />

nazywają się PSEUDOZMIENNYMI REJESTROWYMI (ang.: Register<br />

Pseudovariables).<br />

- 65-


Próba użycia słowa o zastrzeżonym znaczeniu w jakiejkolwiek<br />

innej roli (np. jako nazwa Twojej zmiennej) może spowodować<br />

wadliwe działanie programu lub uniemożliwić kompilację. Unikaj<br />

przypadkowego zastosowania słów o zastrzeżonym znaczeniu!<br />

[???] A SKĄD MAM WIEDZIEC ?<br />

Listę nazw, które mają już nadane ściśle określone znaczenie w<br />

<strong>C++</strong> znajdziesz w Help. Dostęp do spisu uzyskasz przez:<br />

* Rozwinięcie menu Help [Alt]-[H];<br />

* Wybranie z menu Help rozkazu Index (spis).<br />

Wrócić do edytora IDE <strong>C++</strong> możesz przez [Esc].<br />

SŁOWA TYPOWE DLA PROGRAMÓW OBIEKTOWYCH.<br />

W porównaniu z klasycznym językiem C (wobec którego <strong>C++</strong> jest<br />

nadzbiorem - ang. superset), w nowoczesnych programach<br />

obiektowych i zdarzeniowych pisanych w <strong>C++</strong> mogą pojawiać się i<br />

inne słowa. Przyjrzyjmy się na trochę inną technikę<br />

programowania - bardziej charakterystyczną dla <strong>C++</strong>.<br />

Procesy wprowadzania i wyprowadzania danych do- i z- komputera<br />

nazywają się Input i Output - w skrócie I/O (lub bardziej<br />

swojsko We/Wy). Obsługa We/Wy komputera to sała obszerna wiedza,<br />

na początek będzie nam jednak potrzebne tylko kilka najbardziej<br />

istotnych informacji.<br />

PROBLEM ˙WEJŚCIA/WYJŚCIA W PROGRAMACH - trochę bardziej ogólnie.<br />

Operacje wejścia i wyjścia są zwykle kontrolowane przez<br />

pracujący właśnie program. Jeśli uruchomiłeś program, który nie<br />

korzysta z klawiatury i nie oczekuje na wprowadzenie przez<br />

użytkownika żadnych informacji - możesz naciskać dowolne<br />

klawisze - program i tak ma to w nosie. Podobnie, jeśli w<br />

programie nie przewidziano wykorzystania drukarki, choćbyś<br />

"wyłaził ze skóry", żadne informacje nie zostaną przesłane do<br />

drukarki, dla programu i dla użytkownika drukarka pozostanie<br />

niedostępna. Aby programy mogły zapanować nad Wejściem i<br />

Wyjściem informacji, wszystkie języki programowania muszą<br />

zawierać specjalne rozkazy przeznaczone do obsługi<br />

Wejścia/Wyjścia (ang. Input/Output commands, lub I/O<br />

instructions). Bez umiejętności obsługi We/Wy, czyli bez<br />

możliwości porozumiewania się ze światem zewnętrznym psu na budę<br />

zdałby się każdy język programowania. Każdy program musi w<br />

większym, bądź mniejszym stopniu pobierać informacje ze świata<br />

zewnętrznego do komputera i wysyłać informacje z komputera na<br />

zewnątrz.<br />

Podobnie, jak wszystkie uniwersalne języki programowania - język<br />

<strong>C++</strong> zawiera pewną ilość rozkazów przeznaczonych do zarządzania<br />

obsługą wejścia i wyjścia. Dla przykładu, możemy w języku <strong>C++</strong><br />

zastosować OBIEKT cout obsługujący strumień danych wyjściowych.<br />

Obiekt cout (skonstruowany przez producenta i zdefiniowany w<br />

- 66-


pliku nagłówkowym IOSTREAM.H) pozwala programiście<br />

przesłać dane tekstowe i/lub numeryczne do strumienia wyjściwego<br />

i umieścić tekst na ekranie monitora.<br />

Wczytaj plik źródłowy z programem COUT1.CPP lub wpisz<br />

samodzielnie następujący program przykładowy. Program drukuje<br />

tekst na ekranie monitora.<br />

[P009.CPP]<br />

#include <br />

#include <br />


Bajt Nr X 0100 0001 - kod ASCII litery A<br />

Bajt Nr X+1 0000 0000 - kod ASCII 0 - znak końca<br />

Wiesz już, że clrscr(); stanowi wywołanie gotowej funkcji (tzw.<br />

funkcji bibliotecznej). Informacja dotycząca tej funkcji (tzw.<br />

prototyp funkcji) znajduje się w pliku CONIO.H, dlatego<br />

dołączyliśmy ten plik nagłówkowy na początku programu dyrektywą<br />

#include. A cóż to za dziwoląg ten "cout" ?<br />

Po cout nie ma pary nawiasów okrągłych (gdyby to była<br />

funkcja - powinno być cout()) - nie jest to zatem wywołanie<br />

funkcji. ˙Strumień danych wyjściowych cout - JEST OBIEKTEM (ang.<br />

I/O stream object - obiekt: strumień Wejścia/Wyjścia). Ale nie<br />

przestrasz się. Popularne wyobrażenie, że programowanie<br />

obiektowe jest czymś bardzo skomplikowanym nie ma z prawdą<br />

więcej wspólnego, niż powszechny dość pogląd, że baba z pustym<br />

wiadrem jest gorsza od czarnego kota. W gruncie rzeczy jest<br />

to proste. Strumień to nic innego jak zwyczajny przepływ<br />

informacji od jednego urządzenia do innego. W tym przypadku<br />

strumień (przepływ) danych oznacza przesłanie informacji<br />

(tekstu) z pamięci komputera na ekran monitora. Trójkątne<br />

nawiasy (>) wskazują kierunek przepływu informacji.<br />

Przesyłanie następuje w naszym przypadku z pamięci do strumienia<br />

Pojawiło się tu ważne słowo - OBIEKT. Obiekt podobnie jak<br />

program komputerowy jest to grupa danych i funkcji działających<br />

wspólnie i przeznaczonych razem do wykonania jakichś zadań. Dla<br />

przykładu obiekt cout służy do obsługi przesyłania danych na<br />

ekran monitora. Słowo "obiekt" jest często używane w opisach<br />

nowoczesnych technik programowania - tzw. PROGRAMOWANIA<br />

OBIEKTOWEGO. <strong>Programowanie</strong> obiektowe, ta "wyższa szkoła jazdy"<br />

dla programistów z lat 80-tych jest już właściwie w naszych<br />

czasach normą. Zresztą widzisz sam - napisałeś program obiektowy<br />

i co - i nic strasznego się nie stało. Na początek musisz<br />

wiedzieć tylko tyle, że aby posługiwać się obiektami -<br />

strumieniami wejście i wyjścia - należy dołączyć w <strong>C++</strong> plik<br />

nagłówkowy IOSTREAM.H. Dlatego dyrektywa #include <br />

znajduje się na początku przykładowego programu.<br />

KILKA ARGUMENTÓW FUNKCJI w praktyce.<br />

Jak starałem się wykazać w przykładzie z sinusem, funkcja może<br />

otrzymac jako argument stałą - np. określoną liczbę, bądź<br />

zmienną (niewiadomą). Niektóre funkcje mogą otrzymywać w<br />

momencie ich wywołania (użycia w programie) więcej niż jeden<br />

argument. Rozważmy to dokładniej na przykładzie funkcji<br />

fprintf() zbliżonej w działaniu do printf(), lecz bardziej<br />

uniwersalnej. Funkcja fprintf() pozwala wyprowadzać dane nie<br />

tylko na monitor, ale także na drukarkę. Skoro urządzenia<br />

wyjścia mogą być różne, trzeba funkcji przekazać jako jeden z<br />

jej argumentów informację o tym - na które urządzenie życzymy<br />

sobie w danej chwili wyprowadzać dane.<br />

Słowo stdout jest pierwszą informację (tzw. parametrem, bądź<br />

argumentem funkcji) przekazanym do funkcji fprintf(). Słowo<br />

- 68-


stdout jest skrótem od Standard Output - standardowe wyjście.<br />

Oznacza to w skrócie typowe urządzenie wyjściowe podłączone do<br />

komputera ˙i umożliwiające wyprowadzenie informacji z komputera.<br />

W komputerach osobistych zgodnych ze standardem IBM PC tym<br />

typowym urządzeniem wyjściowym jest prawie zawsze ekran<br />

monitora.<br />

Tekst, który ma zostać wydrukowany na ekranie monitora jest<br />

drugą informacją przekazywaną do funkcji fprintf() - inaczej -<br />

stanowi drugi parametr funkcji. Tekst - łańcuch znaków - musi<br />

zostać ujęty w znaki cudzysłowu.<br />

A jeśli zechcesz wyprowadzić tekst na drukarkę?<br />

W <strong>C++</strong> zapisuje się to bardzo łatwo. Wystarczy słowo stdout<br />

(oznaczające monitor) zamienić na słowo stdprn. Słowo stdprn to<br />

skrót od Standard Printer Device - standardowa drukarka. Oto<br />

przykład praktycznego użycia funkcji fprintf(). Program przesyła<br />

tekst na drukarkę. Przed uruchomieniem programu pamiętaj o<br />

włączeniu drukarki.<br />

[P010.CPP]<br />

#include <br />

#include <br />

int main(void)<br />

{<br />

clrscr();<br />

fprintf(stdout, "Drukuje...\n");<br />

fprintf(stdprn, "Pierwsza proba drukowania\n");<br />

fprintf(stdprn, "Autor: ....................");<br />

fprintf(stdout, "Koniec drukowania.");<br />

fprintf(stdout, "Skonczylem, nacisnij cosik...");<br />

getch();<br />

return 0;<br />

}<br />

Gdyby w programie nie było wiersza:<br />

fprintf(stdout, "Drukuje...\n");<br />

- użytkownik przez pewien czas nie mógłby się zorientować,<br />

czym ˙właściwie zajmuje się komputer. Wszystko stałoby się jasne<br />

dopiero wtedy, gdy drukarka rozpoczęłaby drukowanie tekstów.<br />

Jest uznawane za dobre maniery praktyczne stosowanie dwóch<br />

prostych zasad:<br />

BZU - Bez Zbędnych Udziwnień<br />

DONU - Dbaj O Nerwy Użytkownika<br />

Jeśli efekty działania programu nie są natychmiast zauważalne,<br />

należy poinformować użytkownika CO PROGRAM ROBI. Jeśli<br />

użytkownik odnosi wrażenie, że komputer nic nie robi - ma zaraz<br />

- 69-


wątpliwości. Często próbuje wtedy wykonać reset komputera i<br />

wypowiada mnóstwo słów, których nie wypada mi tu zacytować.<br />

Nietrudno zgadnąć, że <strong>C++</strong> powinien posiadać także środki obsługi<br />

wejścia. W <strong>C++</strong> jest specjalny obiekt (ang. input stream object)<br />

o nazwie cin służący do pobierania od użytkownika tekstów i<br />

liczb. Zanim zajmiemy się dokładniej obiektem cin i obsługą<br />

strumienia danych wejściowych - powinieneś zapoznać się ze<br />

ZMIENNYMI (ang. variables).<br />

ZMIENNE.<br />

Gdy wprowadzisz jakieś informacje do komputera - komputer<br />

umieszcza je i przechowuje w swojej pamięci (ang. memory -<br />

pamięć). Pamięć komputera może być jego pamięcią stałą. Taka<br />

pamięć "tylko do odczytu" nazywa się ROM (read only memory - to<br />

właśnie "tylko do odczytu"). Pamięć o swobodnym dostępie, do<br />

której i komputer i Ty możecie zapisywać wszystko, co Wam się<br />

spodoba - nazywa się RAM (od Random Access Memory - pamięć o<br />

swobodnym dostępie). Pamięci ROM i RAM podzielone są na małe<br />

"komóreczki" nazywane Bajtami, Każdy bajt w pamięci ma swój<br />

numer. Ten numer nazywany jest adresem w pamięci. Ponieważ nie<br />

wszystko da się pomieścić w jednym bajcie (to tylko 8 bitów -<br />

miejsca wystarczy na zapamiętanie tylko jednej litery), bajty<br />

(zwykle kolejne) mogą być łączone w większe komórki - tzw. pola<br />

pamięci (ang. memory fields). Najczęściej łączy się bajty:<br />

2 Bajty = 16 bitów = Słowo (WORD)<br />

4 Bajty = 32 bity = Podwójne słowo (DOUBLE WORD - DWORD)<br />

W uproszczeniu możesz wyobrazić sobie pamięć komputera jako<br />

miliony pojedynczych komórek, a w każdej z komórek jakaś jedna<br />

wartość (ang. value) zakodowana w postaci ZER i JEDYNEK. Każda<br />

taka "szara" komórka ma numer-adres. Numeracja komórek<br />

rozpoczyna się nie od 1 lecz od zera (pierwsza ma numer 0).<br />

Ilość tych komórek w Twoim komputerze zależy od tego ile pamięci<br />

zainstalujesz (np. 4MB RAM to 4x1024x124x8 bitów - chcesz -<br />

policz sam ile to bitów). Przeliczając zwróć uwagę, że kilobajt<br />

(KB to nie 1000 - lecz 1024 bajty a megabajt - 1024 kB).<br />

Zastanówmy się, skąd program może wiedzieć gdzie, w której<br />

komórce zostały umieszczone dane i jak się do nich dobrać, gdy<br />

staną się potrzebne. Właśnie do takich celów potrzebne są<br />

programowi ZMIENNE (ang. variables).<br />

Dawno, dawno temu rozwiązywałeś zapewne zadania typu:<br />

3 + [ ] = 5<br />

Otóż to [ ] było pierwszym sposobem przedstawienia Ci zmiennej.<br />

Jak widać - zmienna to miejsce na wpisanie jakiejś (czasem<br />

nieznanej w danej chwili wartości). Gdy przeszedłeś do następnej<br />

- 70-


klasy, zadania skomplikowały się:<br />

3 + [ ] = 5<br />

147.968 + [ ] = 123876.99875<br />

Na różne zmienne może być potrzeba różna ilość miejsca i na<br />

kartce i w pamięci komputera. Gdy "zestarzałeś się" jeszcze<br />

trochę - te same zadania zaczęto Ci zapisywać tak:<br />

3 + x = 5<br />

147.968 + y = 123876.99875<br />

Jak widać, zmienne mogą posiadać także swoje nazwy -<br />

identyfikatory (z których już niestety nie wynika jasno, ile<br />

miejsca potrzeba do zapisania bieżącej wartości zmiennej).<br />

[???] Jak <strong>C++</strong> wskazuje adres w pamięci?<br />

Podobnie, jak w bajeczce o zabawie w chowanego kotka i myszki<br />

(myszka mówiła: "Gdybyś mnie długo nie mógł znaleść - będę<br />

czekać na czwartej półce od góry..."), niektórzy producenci gier<br />

komputerowych życzą sobie czasem przy uruchamianiu gry podania<br />

hasła umieszczonego:<br />

"W instrukcji na str. 124 w czwartym wierszu do góry"<br />

No cóż. Zamiast nazywać zmienne - niewiadome x, y, czy z, bądź<br />

rezerwować dla nich puste miejsce [ ], możemy jeszcze<br />

wskazać miejsce, w którym należy ich szukać. Takie wskazanie to<br />

trzeci sposób odwoływania się do danych. W <strong>C++</strong> może się to<br />

nazywać referencją do zmiennej lub wskazaniem adresu zmiennej w<br />

pamięci przy pomocy wskaźnika. Wskaźnik w <strong>C++</strong> nazywa się<br />

"pointerem". Pointerem można wskazać także funkcje - podając ich<br />

adres startowy (początek kodu funkcji w pamięci RAM).<br />

Zmienne możesz sobie wyobrazić jako przegródki w pamięci<br />

komputera zaopatrzone w nazwę - etykietkę. Ponieważ nazwy dla<br />

tych przegródek nadaje programista w programie - czyli Ty sam,<br />

możesz wybrać sobie prawie każdą, dowolną nazwę. Zwykle nazwy<br />

nadaje się w taki sposób, by program stał się bardziej czytelny<br />

i łatwiejszy do zrozumienia. Dla przykładu, by nie przepadły z<br />

pamięci komputera wyniki gier komputerowych często stosuje się<br />

zmienną o nazwie WYNIK (ang. Score). Za każdym razem, gdy<br />

zmienia się wynik gracza (ang. player's score) w pamięci<br />

komputera (w to samo miejsce) zostaje zapisana nowa liczba. W<br />

taki sposób pewien niewielki (a zawsze ten sam) fragment pamięci<br />

komputera przechowuje dane potrzebne do pracy programu.<br />

PRZYPISYWANIE ZMIENNYM KONKRETNEJ WARTOŚCI.<br />

Aby komputer mogł pobrać informacje od użytkownika, możesz<br />

zastosować w programie np. obiekt - strumień wejściowy - cin<br />

(ang. input stream object). Obiekt cin i zmienne chodzą zwykle<br />

- 71-


parami. Przy obiekcie cin musisz zawsze podać operator<br />

pobierania ze strumienia wejściowego >> i nazwę zmiennej. Zapis<br />

cin >> nazwa_zmiennej;<br />

oznacza ˙w <strong>C++</strong> : pobierz dane ze strumienia wejściowego i umieść<br />

w zmiennej o nazwie "nazwa_zmiennej".Te informacje, które<br />

zostaną ˙wczytane, <strong>C++</strong> przechowuje w przgródce oznaczonej nazwą,<br />

którą nadajesz zmiennej. Oto program przykładowy ilustrujący<br />

zapamiętywanie danych wprowadzonych przez użytkownika z<br />

klawiatury, wczytanych do programu przy pomocy obiektu cin i<br />

zapamiętanych w zadeklarowanej wcześniej zmiennej x:<br />

[P011.CPP]<br />

#include <br />

#include <br />

void main(void)<br />

{<br />

int x;<br />

cout > x;<br />

cout


itp...(szczegóły - patrz niżej).<br />

Zwykle nie musisz się przejmować tym, w którym miejscu<br />

kompilator rozmieścił Twoje dane. Wszystkie czynności <strong>C++</strong> wykona<br />

automatycznie. Aby jednak wszystko przebiegało poprawnie - zanim<br />

zastosujesz jakąkolwiek zmienną w swoim programie - musisz<br />

ZADEKLAROWAĆ ZMIENNĄ. Deklaracja zmiennej to informacja dla<br />

kompilatora, ile i jakich zmiennych będziemy stosować w<br />

programie. Deklaracja zawiera nie tylko nazwę zmiennej, ale<br />

również typ wartości, jakie ta zmienna może przybierać.<br />

Przykładem deklaracji jest wiersz:<br />

int x;<br />

Słowo kluczowe int określa typ danych. Tu oznacza to, że zmienna<br />

x może przechowywać jako wartości liczby całkowite (ang. INTeger<br />

- całkowity) o wielkości zawartej w przedziale - 32768...+32767.<br />

Po określeniu typu danych następuje w deklaracji nazwa zmiennej<br />

i średnik.<br />

[S] Variable Declaration - Dekaracja Zmiennej.<br />

Deklaracja zmiennej w <strong>C++</strong> to określenie typu wartości zmiennej i<br />

podanie nazwy zmiennej.<br />

Zwróć uwagę w przykładowym programie, że kierując kolejno dane<br />

do strumienia wyjściwego cout możemy je poustawiać w tzw.<br />

łańcuch (ang. chain). Przesyłanie danych do obiektu cout<br />

operatorem


RÓŻNYCH TYPÓW - mogą być liczbami, mogą także być tekstami.<br />

Uruchom program jeszcze raz i zamiast liczby naciśnij w<br />

odpowiedzi na pytanie klawisz z literą. Program wydrukuje jakieś<br />

bzdury. Dzieje się tak dlatago, że program oczekuje podania<br />

liczby i zakłada, że wprowadzone przez użytkownika dane są<br />

liczbą.<br />

[???] A jeśli użytkownik nie czyta uważnie???<br />

<strong>C++</strong> zakłada, że użytkownik wie co robi gdy podaje wartość<br />

zmiennej. Jeśli wprowadzone zostaną dane niewłaściwego typu -<br />

<strong>C++</strong> nie przerywa działania programu i nie ostrzega przed<br />

niebezpieczeństwem błędu. Sam dokonuje tzw. konwersji typów -<br />

tzn. przekształca dane na wartość typu zgodnego z zadeklarowanym<br />

w programie typem zmiennej. To programista musi dopilnować, by<br />

pobrane od użytkownika dane okazały się wartością odpowiedniego,<br />

oczekiwanego przez program typu, lub przewidzieć w programie<br />

sposób obsługi sytuacji błędnych.<br />

Można utworzyć zmienną przeznaczoną do przechowywania w pamięci<br />

tekstu - napisu. Aby to zrobić musimy zadeklarować coś<br />

jakościowo nowego tzw. TABLICĘ ZNAKOWĄ (ang. character array).<br />

Jest to nazwa, przy pomocy której komputer lokalizuje w pamięci<br />

zbiór znaków. Aby zadeklarować zmienną (tablicę) znakową w <strong>C++</strong><br />

musimy zacząć od słowa kluczowego char (ang. CHARacter - znak).<br />

Następnie podajemy nazwę zmiennej a po nazwie w nawiasach<br />

kwadratowych ilość znaków, z których może składać się zmienny<br />

tekst, który zamierzamy przechowywać w pamięci pod tą nazwą.<br />

W programie poniżej zmienna x nie jest już miejscem w pamięci<br />

służącym do przechowywania pojedynczej liczby. Tym razem nazwa<br />

(identyfikator zmiennej) x oznacza tablicę znakową, w której<br />

można przechowywać tekst o długości do 20 znaków. W <strong>C++</strong> ostatnim<br />

znakiem w łańcuchu znakowym (tekście) bądź w tablicy znakowej<br />

zwykle jest tzw. NULL CHARACTER - niewidoczny znak o kodzie<br />

ASCII 0 (zero). W <strong>C++</strong> znak ten podaje się przy pomocy szyfru<br />

'\0'. Przy pomocy tego znaku <strong>C++</strong> odnajduje koniec tekstu,<br />

łańcucha znaków, bądź koniec tablicy znakowej. Tak więc w<br />

tablicy x[20] w rzeczywistości można przechować najwyżej 19<br />

dowolnych znaków plus na końcu obowiązkowy NULL (wartownik).<br />

[P012.CPP]<br />

#include <br />

#include <br />

void main(void)<br />

{<br />

char x[20]; //


cin >> x;<br />

cout


LEKCJA 9. O SPOSOBACH ODWOŁYWANIA SIĘ DO DANYCH.<br />

________________________________________________________________<br />

W trakcie tej lekcji poznasz:<br />

* sposoby wyprowadzania napisów w różnych kolorach<br />

* sposoby zapamiętywania tekstów<br />

* sposoby odwoływania się do danyc i zmiennych przy pomocy ich<br />

nazw - identyfikatorów.<br />

________________________________________________________________<br />

Możemy teraz poświęcić chwilę na zagadnienie kolorów, które<br />

pojawiają się na monitorze. Po uruchomieniu program przykładowy<br />

poniżej wygeneruje krótki dźwięk i zapyta o imię. Po wpisaniu<br />

imienia program zapiszczy jeszcze raz i zapyta o nazwisko. Po<br />

wpisaniu nazwiska program zmieni kolor na ekranie monitora i<br />

wypisze komunikat kolorowymi literami. Różne kolory zobaczysz<br />

oczywiście tylko wtedy, gdy masz kolorowy monitor. Dla<br />

popularnego zestawu VGA mono będą to różne odcienie szarości.<br />

Tekst powinien zmieniać kolor i "migać" (ang. - blinking text).<br />

[P012.CPP]<br />

#include <br />

#include <br />

main()<br />

{<br />

char imie[20];<br />

char nazwisko[20];<br />

clrscr();<br />

cout > imie;<br />

cout > nazwisko;<br />

cout


Operator >> pobiera ze strumienia danych wejściowych cin wpisane<br />

przez Ciebie imię i zapisuje ten tekst do tablicy znakowej<br />

imie[20]. Po wypisaniu na ekranie następnego pytania następuje<br />

pobranie drugiego łańcucha znaków (ang. string) wpisanego przez<br />

Ciebie jako odpowiedź na pytanie o nazwisko i umieszczenie tego<br />

łańcucha w tablicy znakowej nazwisko[]. Wywołana następnie<br />

funkcja textcolor() powoduje zmianę roboczego koloru<br />

wyprowadzanego tekstu. Tekst nie tylko zmieni kolor, lecz także<br />

będzie "migać" (blink). Funkcja cprintf() wyprowadza na ekran<br />

końcowy napis. Funkcja cprintf() to Color PRINTing Function -<br />

funkcja drukowania w kolorze.<br />

Funkcja textcolor() pozwala na zmianę koloru tekstu<br />

wyprowadzanego na monitor. Można przy pomocy tej funkcji także<br />

"zmusić" tekst do migotania. Aby funkcja zadziałała - musimy<br />

przekazać jej ARGUMENT. Argument funkcji to numer koloru. Zwróć<br />

jednak uwagę, że zamiast prostego, zrozumiałego zapisu:<br />

textcolor(4); /* 4 oznacza kolor czerwony */<br />

mamy w programie podany argument w postaci wyrażenia (sumy dwu<br />

liczb):<br />

textcolor(4+128);<br />

// to samo, co: textcolor(132);<br />

Wbrew pierwszemu mylnemu wrażeniu te dwie liczby stanowią jeden<br />

argument funkcji. <strong>C++</strong> najpierw dokona dodawania 4+128 a dopiero<br />

uzyskany wynik 132 przekaże funkcji textcolor jako jej argument<br />

(parametr). Liczba 4 to kod koloru czerwonego, a zwiększenie<br />

kodu koloru o 128 powoduje, że tekst będzie migał.<br />

Numery (kody) kolorów, które możesz przekazać jako argumenty<br />

funkcji textcolor() podano w tabeli poniżej. Jeśli tekst ma<br />

migać - należy dodać 128 do numeru odpowiedniego koloru.<br />

Kod koloru przekazywany do funkcji textcolor().<br />

________________________________________________________________<br />

Kod Kolor (ang) Kolor (pol) Stała<br />

n<br />

(przykład)<br />

________________________________________________________________<br />

0 Black Czarny BLACK<br />

1 Blue Niebieski BLUE<br />

2 Green Zielony GREEN<br />

3 Cyan Morski CYAN<br />

4 Red Czerwony<br />

5 Magenta Fioletowy<br />

6 Brown Brązowy<br />

7 White Biały<br />

8 Gray Szary<br />

9 Light blue Jasno niebieski<br />

- 77-


10 Light green Jasno zielony<br />

11 Light cyan Morski - jasny<br />

12 Light red Jasno czerwony<br />

13 Light magenta Jasno fio;etowy (fiol-różowy)<br />

14 Yellow Żółty<br />

15 Bright white Biały rozjaśniony<br />

128 + n Blinking Migający BLINK<br />

________________________________________________________________<br />

[!!!]UWAGA:<br />

________________________________________________________________<br />

* W pliku CONIO.H są predefiniowane stałe (skrajna prawa kolumna<br />

- przykłady), które możesz stosować jako argumenty funkcji.<br />

Kolor tła możesz ustawić np. przy pomocy funkcji<br />

textbackground() - np. textbacground(RED);<br />

* Manipulując kolorem tekstu musisz pamiętać, że jeśli kolor<br />

napisu:<br />

- foreground color, text color<br />

i kolor tła:<br />

- background color<br />

okażą się identyczne - tekst zrobi się NIEWIDOCZNY. Jeśli każesz<br />

komputerowi pisać czerwonymi literami na czerwonym tle -<br />

komputer wykona rozkaz. Jednakże większość ludzi ma kłopoty z<br />

odczytywaniem czarnego tekstu na czarnym tle. Jest to jednak<br />

metoda stosowana czasem w praktyce programowania do kasowania<br />

tekstów i elementów graficznych na ekranie.<br />

________________________________________________________________<br />

Powołując się na nasze wcześniejsze porównanie (NIE TRAKTUJ GO<br />

ZBYT DOSŁOWNIE!),zajmiemy się teraz czymś, co trochę przypomina<br />

rzeczowniki w normalnym języku.<br />

O IDENTYFIKATORACH - DOKŁADNIEJ.<br />

Identyfikatorami (nazwami) mogą być słowa, a dokładniej ciągi<br />

liter, cyfr i znaków podkreślenia rozpoczynające się od litery<br />

lub znaku podkreślenia (_). Za wyjątkiem słów kluczowych, (które<br />

to słowa kluczowe - MUSZĄ ZAWSZE BYĆ PISANE MAŁYMI LITERAMI)<br />

można stosować i małe i duże litery. Litery duże i małe są<br />

rozróżniane. Przykład:<br />

[P013.CPP]<br />

#include <br />

#include <br />

float PI = 3.14159;


int main(void)<br />

{<br />

clrscr();<br />

printf("Podaj promien ?\n");<br />

scanf("%f", &r);<br />

printf("\nPole wynosi P = %f", PI*r*r );<br />

getch();<br />

return 0;<br />

}<br />

* Użyte w programie słowa kluczowe:<br />

int, float, void, return.<br />

* Identyfikatory<br />

- nazwy funkcji (zastrzeżone):<br />

main, printf, scanf, getch, clrscr.<br />

- nazwy zmiennych (dowolne):<br />

PI, r.<br />

* Dyrektywy preprocesora:<br />

# include<br />

Zwróć uwagę, że w wierszu:<br />

float PI = 3.14159;<br />

nie tylko DEKLARUJEMY, zmienną PI jako zmiennoprzecinkową, ale<br />

także od razu nadajemy liczbie PI jej wartość. Jest to tzw.<br />

ZAINICJOWANIE zmiennej.<br />

[Z]<br />

________________________________________________________________<br />

1. Uruchom program przykładowy. Spróbuj zamienić identyfikator<br />

zmiennej PI na pisane małymi literami pi. Powinien wystąpić<br />

błąd.<br />

________________________________________________________________<br />

Dla porównania ten sam program w wersji obiektowo-strumieniowej:<br />

[P013-1.CPP]<br />

#include <br />

#include <br />

const float PI = 3.14159;


}<br />

LITERAŁY.<br />

Literałem nazywamy reprezentujący daną NAPIS, na podstawie<br />

którego można jednoznacznie zidentyfikować daną, jej typ,<br />

wartość i inne atrybuty. W języku <strong>C++</strong> literałami mogą być:<br />

* łańcuchy znaków - np. "Napis";<br />

* pojedyncze znaki - np. 'X', '?';<br />

* liczby - np. 255, 3.14<br />

[!!!] Uwaga: BARDZO WAŻNE !!!<br />

________________________________________________________________<br />

* Rolę przecinka dziesiętnego spełnia kropka. Zapis Pi=3,14 jest<br />

nieprawidłowy.<br />

* Próba zastosowania przecinka w tej roli SPOWODUJE BŁĘDY !<br />

________________________________________________________________<br />

Liczby całkowite mogą być:<br />

* Dziesiętne (przyjmowane domyślnie - default);<br />

* Ósemkowe - zapisywane z zerem na początku:<br />

017 = 1*8 + 7 = 15 (dziesiętnie);<br />

* Szesnastkowe - zapisywane z 0x na początku:<br />

0x17 = 1*16 + 7 = 23 (dziesiętnie);<br />

0x100 = 16^2 + 0 + 0 = 256 .<br />

Liczby rzeczywiste mogą zawierać część ułamkową lub być zapisane<br />

w postaci wykładniczej (ang. scientific format) z literą "e"<br />

poprzedzającą wykładnik potęgi.<br />

Przykład:<br />

Zapis liczby€€€€€€€€Wartość dziesiętna<br />

.0123€€€€€€€€€€€€€€€0.0123<br />

123e4€€€€€€€€€€€€€€€123 * 10^4 = 1 230 000<br />

1.23e3€€€€€€€€€€€€€€€1.23 * 10^3 = 1230<br />

123e-4€€€€€€€€€€€€€€0.0123<br />

Literały składające się z pojedynczych znaków mają jedną z<br />

trzech postaci:<br />

* 'z' - gdzie z oznacza znak "we własnej osobie";<br />

* '\n' - symboliczne oznaczenie znaku specjalnego - np.<br />

sterującego - tu: znak nowej linii;<br />

* '\13' - nr znaku w kodzie ASCII.<br />

UWAGA:<br />

'\24' - kod Ósemkowy ! (dziesiętnie 20)<br />

'\x24' - kod SZESNASTKOWY ! (dziesiętnie 36)<br />

[S]€€SLASH, BACKSLASH.<br />

- 80-


€€€€€Kreska "/" nazywa się SLASH (czyt. "slasz") - łamane,<br />

ukośnik zwykły. Kreska "\" nazywa się BACKSLASH (czyt.<br />

"bekslasz") - ukośnik odwrotny.<br />

Uzupełnimy teraz listę symboli znaków z poprzedniej lekcji.<br />

Znak €€€€ÓSEMKOWO€€ASCII (10)€€€€€ZNACZENIE<br />

\a€€€€€€€'\7'€€€€€€7€€€€€€€€€€€€€€- sygn. dźwiękowy BEL<br />

\n€€€€€€€'\12'€€€€€10€€€€€€€€€€€€€- nowy wiersz LF<br />

\t€€€€€€€'\11'€€€€€9€€€€€€€€€€€€€€- tabulacja pozioma HT<br />

\v€€€€€€ '\13'€€€€€11€€€€€€€€€€€€€- tabulacja pionowa VT<br />

\b€€€€€€€'\10'€€€€€8€€€€€€€€€€€€€€- cofnięcie kursora o 1 znak<br />

\r€€€€€€€'\15'€€€€€13€€€€€€€€€€€€€- powrót do początku linii CR<br />

\f€€€€€€€'\14'€€€€€12€€€€€€€€€€€€€- nowa strona (form feed) FF<br />

\\€€€€€€€'\134'€€€€92€€€€€€€€€€€€€- poprostu znak backslash "\"<br />

\'€€€€€€€'\47'€€€€€39€€€€€€€€€€€€€- apostrof "'"<br />

\"€€€€€€€'\42'€€€€€34€€€€€€€€€€€€€- cudzysłów (")<br />

\0€€€€€€€'\0'€€€€€€0€€€€€€€€€€€€€€- NULL (znak pusty)<br />

Komputer przechowuje znak w swojej pamięci jako "krótką", bo<br />

zajmującą tylko jeden bajt liczbę całkowitą (kod ASCII znaku).<br />

Na tych liczbach wolno Ci wykonywać operacje arytmetyczne !<br />

(Od czego mamy komputer?) Przekonaj się o tym uruchamiając<br />

następujący program.<br />

[P014.CPP]<br />

# include //prototypy printf() i scanf()<br />

# include //prototypy clrscr() i getch()<br />

int liczba;<br />

//deklaracja zmiennej "liczba"<br />

int main(void)<br />

{<br />

clrscr();<br />

printf("Wydrukuje A jako \nLiteral znakowy:\tKod ASCII:\n");<br />

printf("%c", 'A');<br />

printf("\t\t\t\t%d", 'A');<br />

printf("\nPodaj mi liczbe ? ");<br />

scanf("%d", &liczba);<br />

printf("\n%c\t\t\t\t%d\n", 'A'+liczba, 'A'+liczba);<br />

scanf("%d", &liczba);<br />

printf("\n%c\t\t\t\t%d", 'A'+liczba, 'A'+liczba);<br />

getch();<br />

return 0;<br />

}<br />

Uruchom program kilkakrotnie podając różne liczby całkowite z<br />

zakresu od 1 do 100.<br />

Przyjrzyj się sposobowi formatowania wyjścia:<br />

%c, %d, \t, \n<br />

Jeśli pamiętasz, że kody ASCII kolejnych liter A,B,C... i<br />

- 81-


kolejnych cyfr 1, 2, 3 są kolejnymi liczbami, to zauważ, że<br />

wyrażenia:<br />

'5' + 1 = '6' oraz 'A' + 2 = 'C'<br />

(czytaj: kod ASCII "5" + 1 = kod ASCII "6")<br />

są poprawne.<br />

[!!!]Jak sprawdzić kod ASCII znaku?<br />

________________________________________________________________<br />

Można oczywście nauczyć się tabeli kodów ASCII na pamięć (dla<br />

początkowych i najważniejszych stronic kodowych - przede<br />

wszystkom od 0 do 852). Dla hobbystów - stronica kodowa 1250 i<br />

1252 też czasem się przydaje.<br />

(to oczywiście żart - autor nie zna ani jednego faceta o tak<br />

genialnej pamięci)<br />

Można skorzystać z edytora programu Norton Commander. W trybie<br />

Edit [F4] po wskazaniu kursorem znaku w górnym wierszu po prawej<br />

stronie zostanie wyświetlony jego kod ASCII.<br />

________________________________________________________________<br />

CZY PROGRAM NIE MÓGŁBY CHODZIĆ W KÓŁKO?<br />

Twoja intuicja programisty z pewnością podpowiada Ci, że gdyby<br />

zmusić komputer do pracy w pętli, to nie musiałbyś przykładowych<br />

programów uruchamiać wielokrotnie. Spróbujmy nakazać programowi<br />

przykładowemu chodzić "w kółko". To proste - dodamy do programu:<br />

* na końcu rozkaz skoku bezwarunkowego goto (idź do...),<br />

* a żeby wiedział dokąd ma sobie iść - na początku programu<br />

zaznaczymy miejsce przy pomocy umownego znaku - ETYKIETY.<br />

Zwróć uwagę, że pisząc pliki wsadowe typu *.BAT w języku BPL<br />

(Batch Programming Language - język programowania wsadowego)<br />

stawiasz dwukropek zawsze na początku etykiety:<br />

:ETYKIETA (BPL)<br />

a w języku <strong>C++</strong> zawsze na końcu etykiety:<br />

ETYKIETA: (C/<strong>C++</strong>)<br />

Przystępujemy do opracowania programu.<br />

[P015.CPP]<br />

# include <br />

short int liczba;<br />

int main(void)<br />

{<br />

clrscr();<br />

- 82-


printf("Wydrukuje A jako \nLiteral znakowy:\tKod ASCII:\n");<br />

printf("%c", 'A');<br />

printf("\t\t\t\t%d", 'A');<br />

etykieta:<br />

printf("\npodaj mi liczbe ? ");<br />

scanf("%d", &liczba);<br />

printf("\n%c\t\t\t\t%d\n", 'A'+liczba, 'A'+liczba);<br />

goto etykieta;<br />

return 0;<br />

}<br />

Skompiluj program do wersji *.EXE:<br />

Compile | Make<br />

(rozkazem Make EXE file z menu Compile). Musisz nacisnąć<br />

następujące klawisze:<br />

[Alt]-[C], [M]. (lub [F9])<br />

* Jeśli wystąpiły błędy, popraw i powtórz próbę kompilacji.<br />

* Uruchom program [Alt]-[R], [R] (lub [Ctrl]-[F9]).<br />

* Podaj kilka liczb: np. 1,2,5,7,8 itp.<br />

* Przerwij działanie programu naciskając kombinację klawiszy<br />

[Ctrl]+[Break] lub [Ctrl]+[C].<br />

* Sprawdź, jaki jest katalog wyjściowy kompilatora.<br />

- Rozwiń menu Options [Alt]-[O],<br />

- Otwórz okienko Directories... [D],<br />

- Sprawdź zawartość okienka tekstowego Output Directory.<br />

Teraz wiesz już gdzie szukać swojego programu w wersji *.EXE.<br />

- Uruchom program poza środowiskiem IDE.<br />

- Sprawdź reakcję programu na klawisze:<br />

[Esc], [Ctrl]-[C], [Ctrl]-[Break].<br />

Uruchom powtórnie kompilator <strong>C++</strong> i załaduj program rozkazem:<br />

BC A:\GOTOTEST.CPP<br />

Wykonaj od nowa kompilację programu [F9].<br />

[???] ... is up to date...<br />

________________________________________________________________<br />

Jeśli <strong>C++</strong> nie zechce powtórzyć kompilacji i odpowie Ci:<br />

€€€€€€€€€€€€€€€€€€€€Making<br />

A:\GOTOTEST.CPP<br />

€€€€€€€€€€€€€€€is up to date<br />

(Program w takiej wersji już skompilowałem, więcej nie będę!)<br />

nie przejmuj się. Dokonaj jakiejkolwiek pozornej zmiany w<br />

programie (np. dodaj spację lub pusty wiersz w dowolnym<br />

miejscu). Takich pozornych zmian wystarczy by oszukać <strong>C++</strong>. <strong>C++</strong><br />

nie jest na tyle inteligentny, by rozróżniać zmiany rzeczywiste<br />

w pliku źródłowym od pozornych.<br />

________________________________________________________________<br />

- 83-


Powtórz kompilację programu. Nie musisz uruchamiać programu.<br />

Zwróć uwagę tym razem na pojawiające się w okienku komunikatów<br />

ostrzeżenie:<br />

Warning: A:\GOTOTEST.CPP 14: Unreachable code in function main.<br />

(Uwaga: Kod programu zawiera takie rozkazy, które nigdy nie<br />

zostaną wykonane inaczej - "są nieosiągalne").<br />

O co chodzi? Przyjrzyj się tekstowi programu. Nawet jeśli po<br />

rozkazie skoku bezwarunkowego:<br />

goto etykieta;<br />

dopiszesz jakikolwiek inny rozkaz, to program nigdy tego rozkazu<br />

nie wykona. Właśnie o to chodzi. Program nie może nawet nigdy<br />

wykonać rozkazu "return 0", który dodaliśmy "z przyzwyczajenia".<br />

Pętla programowa powinna być wykonywana w nieskończoność. Taka<br />

pętla nazywa się pętlą nieskończoną (ang. infinite loop).<br />

Mimo to i w środowisku IDE (typowy komunikat: User break) i w<br />

środowisku DOS tę pętlę uda Ci się przerwać.<br />

Kto wobec tego przerwał działanie Twojego programu? Nieskończoną<br />

pętlę programową przerwał DOS. Program zwrócił się do systemu<br />

DOS, a konkretnie do którejś z DOS'owskich funkcji obsługi<br />

WEJŚCIA/WYJŚCIA i to DOS wykrył, że przycisnąłeś klawisze<br />

[Ctrl]-[C] i przerwał obsługę Twojego programu. Następnie DOS<br />

"wyrzucił" twój program z pamięci operacyjnej komputera i<br />

zgłosił gotowość do wykonania dalszych Twoich poleceń - swoim<br />

znakiem zachęty C:\>_ lub A:\>_.<br />

Spróbujmy wykonać taki sam "face lifting" i innych programów<br />

przykładowych, dodając do nich najprostszą pętlę. Zanim jednak<br />

omówimy szczegóły techniczne pętli programowych w <strong>C++</strong> rozważmy<br />

prosty przykład. Wyobraźmy sobie, że chcemy wydrukować na<br />

ekranie kolejne liczby całkowite od 2 do np. 10. Program<br />

powinien zatem liczyć ilość wykonanych pętli, bądź sprawdzać,<br />

czy liczba przeznaczona do drukowania nie stała się zbyt duża.<br />

W <strong>C++</strong> do takich konstrukcji używa się kilku bardzo ważnych słów<br />

kluczowych:<br />

[S] some important keywords - kilka ważnych słów kluczowych<br />

________________________________________________________________<br />

for - dla (znaczenie jak w Pascalu i BASICu)<br />

while - dopóki<br />

do - wykonuj<br />

if - jeżeli<br />

break - przerwij wykonywanie pętli<br />

continue - kontynuuj pętelkowanie<br />

goto - skocz do wskazanej etykiety<br />

________________________________________________________________<br />

- 84-


Nasz program mógłby przy zastosowaniu tych słów zostać napisany<br />

np. tak:<br />

[LOOP-1]<br />

#include <br />

void main()<br />

{<br />

int x = 2;<br />

petla:<br />

cout


{<br />

cout


[P016.CPP]<br />

// Przyklad FACELIFT.CPP<br />

// Program przykladowy 10na16.CPP / 16na10.CPP FACE LIFTING.<br />

# include <br />

int liczba;<br />

int main()<br />

{<br />

clrscr();<br />

printf("Kropka = KONIEC \n");<br />

for(;;)<br />

{<br />

printf("Podaj liczbe dziesietna calkowita ? \n");<br />

scanf("%d", &liczba);<br />

printf("Szesnastkowo to wynosi:\n");<br />

printf("%X",liczba);<br />

getch();<br />

printf("Podaj liczbe SZESNASTKOWA-np.DF- DUZE LITERY: \n");<br />

scanf("%X", &liczba);<br />

printf("%s","Dziesietnie to wynosi: ");<br />

printf("%d",liczba);<br />

if(getch() == '.') break;<br />

}<br />

return 0;<br />

}<br />

- Uruchom program Run, Run.<br />

- Dla przetestowania działania programu:<br />

* podaj kolejno liczby o różnej długości 1, 2, 3, 4, 5, 6<br />

cyfrowe;<br />

* zwróć uwagę, czy program przetwarza poprawnie liczby dowolnej<br />

długości?<br />

- Przerwij program naciskając klawisz z kropką [.]<br />

- Zapisz program na dysk [F2].<br />

- Wyjdź z IDE naciskając klawisze [Alt]-[X].<br />

Zwróć uwagę na dziwny wiersz:<br />

if(getch() == '.') break;<br />

<strong>C++</strong> wykona go w następującej kolejności:<br />

1) - wywoła funkcję getch(), poczeka na naciśnięcie klawisza i<br />

wczyta znak z klawiatury:<br />

getch()<br />

2) - sprawdzi, czy znak był kropką:<br />

(getch() == '.') ?<br />

3) - jeśli TAK - wykona rozkaz break i przerwie pętlę,<br />

if(getch() == '.') break;<br />

- jeśli NIE - nie zrobi nic i pętla "potoczy się" dalej.<br />

if(getch() != '.') ...--> printf("Podaj liczbe dziesietna...<br />

- 87-


[Z]<br />

________________________________________________________________<br />

2. Opracuj program pobierający znak z klawiatury i podający w<br />

odpowiedzi kod ASCII pobranego znaku dziesiętnie.<br />

3. Opracuj program pobierający liczbę dziesiętną i podający w<br />

odpowiedzi:<br />

* kod ósemkowy,<br />

* kod szesnastkowy,<br />

* znak o zadanym<br />

** dziesiętnie<br />

** szesnastkowo<br />

kodzie ASCII.<br />

_______________________________________________________________<br />

- 88-


LEKCJA 10. Jakie operatory stosuje <strong>C++</strong>.<br />

_______________________________________________________________<br />

Podczas tej lekcji:<br />

* Poznasz operatory języka <strong>C++</strong>.<br />

* Przetestujesz działanie niektórych operatorów.<br />

* Dowiesz się więcej o deklarowaniu i inicjowaniu zmiennych.<br />

_______________________________________________________________<br />

Słów kluczowych jest w języku <strong>C++</strong> stosunkowo niewiele, za to<br />

operatorów wyraźnie więcej niż np. w Basicu. Z kilku operatorów<br />

już korzystałeś w swoich programach. pełną listę operatorów<br />

wraz z krótkim wyjaśnieniem przedstawiam poniżej. Operatory <strong>C++</strong><br />

są podzielone na 16 grup i można je scharakteryzować:<br />

* priorytetem<br />

** najwyższy priorytet ma grupa 1 a najniższy grupa 16 -<br />

przecinek, np. mnożenie ma wyższy priorytet niż dodawanie;<br />

** wewnątrz każdej z 16 grup priorytet operatorów jest równy;<br />

* łącznością (wiązaniem).<br />

[S!] Precedence - kolejność, priorytet.<br />

________________________________________________________________<br />

Dwie cechy opertorów <strong>C++</strong> priorytet i łączność decydują o<br />

sposobie obliczania wartości wyrażeń.<br />

Precedence - kolejność, priorytet.<br />

Associativity - asocjatywność, łączność, wiązanie. Operator jest<br />

łączny lewo/prawo-stronnie, jeśli w wyrażeniu zawierającym na<br />

tym samym poziomie hierarchii nawiasów min. dwa identyczne<br />

operatory najpierw jest wykonywany operator lewy/prawy. Operator<br />

jest łączny, jeśli kolejność wykonania nie wpływa na wynik.<br />

________________________________________________________________<br />

Przykład:<br />

a+b+c+d = (a+d)+(c+b)<br />

[S]<br />

________________________________________________________________<br />

ASSIGN(ment) - Przypisanie.<br />

EQAL(ity) - Równy, odpowiadający.<br />

BITWISE - bit po bicie (bitowo).<br />

REFERENCE - odwołanie do..., powołanie się na..., wskazanie<br />

na... .<br />

Funkcje logiczne:<br />

OR - LUB - suma logiczna (alternatywa).<br />

AND - I - iloczyn logiczny.<br />

XOR (eXclusive OR) - ALBO - alternatywa wyłączająca.<br />

NOT - NIE - negacja logiczna.<br />

________________________________________________________________<br />

Oznaczenia łączności przyjęte w Tabeli:<br />

{L->R} €€€(Left to Right) z lewa na prawo.<br />

{L


Lista operatorów języka <strong>C++</strong>.<br />

________________________________________________________________<br />

Kategoria€| Operator€€€€€| €€€Co robi / jak działa<br />

----------|--------------|--------------------------------------<br />

1. Highest| ()€€€€€€€€€€€| * ogranicza wyrażenia,<br />

(Najwyższy|Parentheses | * izoluje wyrażenia warunkowe,<br />

priorytet)|€€€€€€€€€€€€€€| * wskazuje na wywołanie funkcji,<br />

{L->R}€€€|€€€€€€€€€€€€€€| grupuje argumenty funkcji.<br />

€€€€€€€€€€|--------------|--------------------------------------<br />

€€€€€€€€€€| []€€€€€€€€€€€| zawartość jedno- lub wielowymiarowych<br />

€€€€€€€€€€|Brackets | tablic<br />

€€€€€€€€€€|--------------|--------------------------------------<br />

€€€€€€€€€€| . |(direct component selector)<br />

€€€€€€€€€€| -> |(indirect, or pointer, selection)<br />

€€€€€€€€€€|€€€€€€€€€€€€€€| Bezpośrednie lub pośrednie wskazanie<br />

| | elementu unii bądź struktury.<br />

|--------------|--------------------------------------<br />

| :: | Operator specyficzny dla <strong>C++</strong>.<br />

| | Pozwala na dostęp do nazw GLOBALNYCH,<br />

| | nawet jeśli zostały "przysłonięte"<br />

| | przez LOKALNE.<br />

----------|--------------|--------------------------------------<br />

2. €€€€€€€| ! €€€€€€€€€€€| Negacja logiczna (NOT)<br />

Jednoar-€|--------------|------------------------------------<br />

gumentowe | ~ | Zamiana na kod KOMPLEMENTARNY bit po<br />

(Unary) | | bicie. Dotyczy liczb typu int.<br />

{L


----------|--------------|--------------------------------------<br />

4. Dostępu| .* | Operatory specyficzne dla <strong>C++</strong>.<br />

(Member |(dereference) | Skasowanie bezpośredniego wskazania<br />

access) | | na członka klasy (Class Member).<br />

{L->R} |--------------|--------------------------------------<br />

| ->* | Skasowanie pośredniego wskazania typu<br />

objektowe | | "wskaźnik do wskaźnika"<br />

----------|--------------|--------------------------------------<br />

5. Addy - | + | Dodawanie dwuargumentowe.<br />

tywne |--------------|--------------------------------------<br />

{L->R} | - | Odejmowanie dwuargumentowe.<br />

----------|--------------|--------------------------------------<br />

6. Przesu-| > | Binarne przesunięcie w prawo.<br />

{L->R} | | (bit po bicie)<br />

----------|--------------|--------------------------------------<br />

7. Relacji| < | Mniejsze niż...<br />

{L->R} |--------------|--------------------------------------<br />

| > | Większe niż....<br />

|--------------|--------------------------------------<br />

| = | Większe lub równe.<br />

----------|--------------|--------------------------------------<br />

8.Równości| == | Równe (równa się).<br />

{L->R} | != | Nie równe.<br />

----------|--------------|--------------------------------------<br />

9. | & | AND binarnie (Bitwise AND)<br />

{L->R} | | UWAGA: Druga rola "&".<br />

----------|--------------|--------------------------------------<br />

10. | ^ | XOR binarnie (Alternatywa wyłączna).<br />

{L->R} | | UWAGA: To nie potęga !<br />

----------|--------------|-------------------------------------<br />

11.{L->R} | | | OR binarnie (bit po bicie)<br />

----------|--------------|-------------------------------------<br />

12.{L->R} | && | Iloczyn logiczny (Logical AND).<br />

----------|--------------|-------------------------------------<br />

13.{L->R} | || | Suma logiczna (Logical OR).<br />

----------|--------------|--------------------------------------<br />

14. Oper. | ?: | Zapis a ? x : y oznacza:<br />

Warunkowy | | "if a==TRUE then x else y"<br />

Conditional | gdzie TRUE to logiczna PRAWDA "1".<br />

{L


|--------------|--------------------------------------<br />

| -= | Przypisz różnicę X-=5 ozn. "X:=X-5"<br />

|--------------|--------------------------------------<br />

| &= | Przypisz iloczyn binarny ( Bitwise<br />

| | AND)<br />

| | bit po bicie.<br />

|--------------|--------------------------------------<br />

| ^= | Przypisz XOR bit po bicie.<br />

|--------------|--------------------------------------<br />

| |= | Przypisz sumę log. bit po bicie.<br />

|--------------|--------------------------------------<br />

| = | j. w. o jeden bit w prawo.<br />

----------|--------------|--------------------------------------<br />

16. Prze- | , | Oddziela elementy na liście argu -<br />

cinek | | mentów funkcji,<br />

(Comma) | | Stosowany w specjalnych wyrażeniach<br />

{L->R} | | tzw. "Comma Expression".<br />

----------|--------------|-------------------------------------<br />

UWAGI:<br />

* Operatory # i ## stosuje się tylko w PREPROCESORZE.<br />

* Operatory > mogą w <strong>C++</strong> przesyłać tekst do obiektów cin i<br />

cout dzięki tzw. Overloadingowi (rozbudowie, przeciążeniu)<br />

operatorów. Takiego rozszerzenia ich działania dokonali już<br />

programiści producenta w pliku nagłówkowym IOSTREAM.H><br />

Gdyby okazało się, że oferowane przez powyższy zestaw operatory<br />

nie wystarczają Ci lub niezbyt odpowiadają, <strong>C++</strong> pozwala na tzw.<br />

OVERLOADING, czyli przypisanie operatorom innego, wybranego<br />

przez użytkownika działania. Można więc z operatorami robić<br />

takie same sztuczki jak z identyfikatorami. Sądzę jednak, że ten<br />

zestaw nam wystarczy, w każdym razie na kilka najbliższych<br />

lekcji.<br />

Podobnie, jak pieniądze na bezludnej wyspie, niewiele warta jest<br />

wiedza, której nie można zastosować praktycznie. Przejdźmy więc<br />

do czynu i przetestujmy działanie niektórych operatorów w<br />

praktyce.<br />

TEST OPERATORÓW JEDNOARGUMENTOWYCH.<br />

Otwórz plik nowego programu:<br />

* Open [F3],<br />

* Wpisz:<br />

A:\UNARY.CPP<br />

* Wybierz klawisz [Open] w okienku lub naciśnij [Enter].<br />

Wpisz tekst programu:<br />

- 92-


[P017.CPP ]<br />

// UNARY.CPP - operatory jednoargumentowe<br />

# include <br />

# include <br />

float x;<br />

void main(void)<br />

{<br />

clrscr();<br />

for(;;)<br />

{<br />

printf("\n Podaj liczbe...\n");<br />

scanf("%f", &x);<br />

printf("\n%f\t%f\t%f\n", x, +x, -x );<br />

printf("\n%f", --x );<br />

printf("\t%f", x );<br />

printf("\t%f", ++x);<br />

if(getch() = '.') break;<br />

};<br />

}<br />

Zwróć uwagę, że po nawiasie zamykającym pętlę nie ma tym razem<br />

żadnego rozkazu. Nie wystąpi także ostrzeżenie (Warning:) przy<br />

kompilacji.<br />

Uruchom program Run | Run. Popraw ewentualne błędy.<br />

Podając różne wartości liczby x:<br />

- dodatnie i ujemne,<br />

- całkowite i rzeczywiste,<br />

przeanalizuj działanie operatorów.<br />

Przerwij program naciskając klawisz [.]<br />

Zmodyfikuj w programie deklarację typu zmiennej X wpisując<br />

kolejno:<br />

- float x; (rzeczywista)<br />

- int x; €€€€€€(całkowita)<br />

- short int x; €€€€€(krótka całkowita)<br />

- long int x;€€€€€(długa całkowita)<br />

Zwróć uwagę, że zmiana deklaracji zmiennej bez JEDNOCZESNEJ<br />

zmiany formatu w funkcjach scanf() i printf() spowoduje<br />

komunikaty o błędach.<br />

Spróbuj samodzielnie dobrać odpowiednie formaty w funkcjach<br />

scanf() i printf(). Spróbuj zastosować zamiast funkcji printf()<br />

i scanf() strumienie cin i cout. Pamiętaj o dołączeniu<br />

właściwych plików nagłówkowych.<br />

Jeśli miałeś kłopot z dobraniem stosownych formatów, nie<br />

przejmuj się. Przyjrzyj się następnym przykładowym programom.<br />

- 93-


Zajmijmy się teraz dokładniej INKREMENTACJĄ, DEKREMENTACJĄ i<br />

OPERATORAMI PRZYPISANIA.<br />

1. Zamknij zbędne okna na ekranie. Pamuiętaj o zapisaniu<br />

programów na dyskietkę/dysk w tej wersji, która poprawnie działa<br />

lub w ostatniej wersji roboczej.<br />

2. Otwórz plik:<br />

ASSIGN.CPP<br />

3. Wpisz tekst programu:<br />

[P018.CPP]<br />

# include <br />

# include <br />

long int x;<br />

short int krok;<br />

char klawisz;<br />

int main()<br />

{<br />

clrscr();<br />

printf("Test operatora przypisania += \n");<br />

x=0;<br />

printf("Podaj KROK ? \n");<br />

scanf("%d",&krok);<br />

for(;;)<br />

{<br />

printf("\n%d\n", x+=krok);<br />

printf("[Enter] - dalej [K] - Koniec\n");<br />

klawisz = getch();<br />

if (klawisz=='k'|| klawisz=='K') goto koniec;<br />

}<br />

koniec:<br />

printf("\n Nacisnij dowolny klawisz...");<br />

getch();<br />

return 0;<br />

}<br />

W tym programie już sami "ręcznie" sprawdzamy, czy nie pora<br />

przerwać pętlę. Zamiast użyć typowej instrukcji break (przerwij)<br />

stosujemy nielubiane goto, gdyż jest bardziej uniwersalne i w<br />

przeciwieństwie do break pozwala wyraźnie pokazać dokąd<br />

następuje skok po przerwaniu pętli. Zwróć uwagę na nowe elementy<br />

w programie:<br />

* DEKLARACJE ZMIENNYCH:<br />

long int x; (długa, całkowita)<br />

short int krok; €€€€(krótka, całkowita)<br />

char klawisz;€€€€€(zmienna znakowa)<br />

- 94-


* INSTRUKCJĘ WARUNKOWĄ:<br />

if (KLAWISZ=='k'|| KLAWISZ=='K') goto koniec;<br />

(JEŻELI zmienna KLAWISZ równa się "k" LUB równa się "K"<br />

idź do etykiety "koniec:")<br />

* Warunek sprawdzany po słowie if jest ujęty w nawiasy.<br />

* Nadanie wartości zmiennej znakowej char klawisz przez funkcję:<br />

klawisz = getch();<br />

4. Skompiluj program. Popraw ewentualne błędy.<br />

5. Uruchom program. Podając różne liczby (tylko całkowite!)<br />

prześledź działanie operatora.<br />

6. Zapisz poprawną wersję programu na dysk/dyskietkę [F2].<br />

7. Jeśli masz już dość, wyjdź z TC - [Alt]-[X], jeśli nie,<br />

pozamykaj tylko zbędne okna i możesz przejść do zadań do<br />

samodzielnego rozwiązania -> [Z]!<br />

[Z]<br />

________________________________________________________________<br />

1. Do programu przykładowego wstaw kolejno różne operatory<br />

przypisania:<br />

*=, -=, /= itp.<br />

Prześledź działanie operatorów.<br />

2. W programie przykładowym zmień typ zmiennych:<br />

long int x; na float x;<br />

short int KROK;<br />

float KROK;<br />

Przetestuj działanie operatorów w przypadku liczb<br />

zmiennoprzecinkowych.<br />

3. Zapisz w języku <strong>C++</strong><br />

* negację iloczynu logicznego,<br />

* sumę logiczną negacji dwu warunków.<br />

________________________________________________________________<br />

TEST OPERATORÓW PRE/POST-INKREMENTACJI.<br />

W następnym programie zilustrujemy działanie wszystkich pięciu<br />

operatorów inkrementacji (dekrementacja to też inkrementacja<br />

tylko w przeciwną stronę).<br />

[P019.CPP]<br />

# include <br />

# include <br />

int b,c,d,e;<br />

int i;<br />

int STO = 100;<br />

void main(void)<br />

{<br />

clrscr();<br />

- 95-


printf("Demonstruje dzialanie \n");<br />

printf(" PREinkrementacji POSTinkrementacji");<br />

printf("\nNr€€€€--X€€€€€€++X€€€€€€€€€€€€X--€€€€€€€X++ \n");<br />

b = c = d = e = STO;<br />

for(i=1; i


Zapraszamy teraz debugger do pracy wydając mu polecenie "Wykonaj<br />

Inspekcję" [Alt]-[D] | Inspect. Pojawia się okienko dialogowe<br />

"Inspect".<br />

* Wpisz do okienka tekstowego nazwę zmiennej b i naciśnij<br />

[Enter].<br />

Pojawiło się okienko dialogowe "Inspecting b" zawierające<br />

fizyczny adres pamięci RAM, pod którym umieszczono zmienną b i<br />

wartość zmiennej b (zero; instrukcja przypisania nada jej<br />

wartość 100). Naciśnij [Esc]. Okienko zniknęło.<br />

[F7] - for(i=1; i


...<br />

i


LEKCJA 11. Jak deklarować zmienne. Co to jest wskaźnik.<br />

________________________________________________________________<br />

W trakcie tej lekcji:<br />

1. Dowiesz się więcej o deklaracjach.<br />

2. Poprawisz trochę system MS DOS.<br />

3. Dowiesz się co to jest wskaźnik i do czego służy.<br />

________________________________________________________________<br />

Więcej o deklaracjach.<br />

Deklarować można w języku <strong>C++</strong>:<br />

* zmienne;<br />

* funkcje;<br />

* typy (chodzi oczywiście o typy "nietypowe").<br />

Zmienne w języku <strong>C++</strong> mogą mieć charakter:<br />

* skalarów - którym przypisuje się nierozdzielne dane np.<br />

całkowite, rzeczywiste, wskazujące (typu wskaźnik) itp.<br />

* agregatów - którym przypisuje się dane typu strukturalnego np.<br />

obiektowe, tablicowe czy strukturowe.<br />

Powyższy podział nie jest tak całkiem precyzyjny, ponieważ<br />

pomiędzy wskaźnikami a tablicami istnieje w języku <strong>C++</strong> dość<br />

specyficzna zależność, ale więcej na ten temat dowiesz się z<br />

późniejszych lekcji.<br />

Zmienne mogą być:<br />

* deklarowane,<br />

* definiowane i<br />

* inicjowane.<br />

Stała to to taka zmienna, której wartość można przypisać tylko<br />

raz. Z punktu widzenia komputera niewiele się to różni, bo<br />

miejsce w pamięci i tak, stosownie do zadeklarowanego typu<br />

zarezerwować trzeba, umieścić w tablicy i zapamiętać sobie<br />

identyfikator i adres też. Jedyna praktyczna różnica polega na<br />

tym, że zmiennej zadeklarowanej jako stała, np.:<br />

const float PI = 3.142;<br />

nie można przypisać w programie żadnej innej wartości, innymi<br />

słowy zapis:<br />

const float PI = 3.14;<br />

jest jednocześnie DEKLARACJĄ, DEFINICJĄ i ZAINICJOWANIEM stałej<br />

PI.<br />

Przykład :<br />

- 99-


float x,y,z;€€€€€€€€€€€€€€€€€€€€€€€(DEKLARACJA)<br />

const float TEMP = 36.6;€€€€€€€€€€€(DEFINICJA)<br />

x = 42;€€€€€€€€€€€€€€€€€€€€€€€€€€€€€(ZAINICJOWANIE zmiennej)<br />

[S!] constant/variable - STAŁA czy ZMIENNA.<br />

________________________________________________________________<br />

const - (CONSTant) - stała. Deklaracja stałej, słowo kluczowe w<br />

języku C.<br />

var - (VARiable) - zmienna. W języku C przyjmowane domyślnie.<br />

Słowo var (stosowane w Pascalu) NIE JEST słowem kluczowym języka<br />

C ani <strong>C++</strong> (!).<br />

________________________________________________________________<br />

Skutek praktyczny:<br />

* Ma sens i jest poprawna deklaracja:<br />

const float PI = 3.1416;<br />

* Niepoprawna natomiast jest deklaracja:<br />

var float x;<br />

Jeśli nie zadeklarowano stałej słowem const, to "zmienna" (var)<br />

przyjmowana jest domyślnie.<br />

Definicja powoduje nie tylko określenie, jakiego typu<br />

wartościami może operować dana zmienna bądź funkcja, która<br />

zostaje od tego momentu skojarzona z podanym identyfikatorem,<br />

ale dodatkowo powoduje:<br />

* w przypadku zmiennej - przypisanie jej wartości,<br />

* W przypadku funkcji - przyporządkowanie ciała funkcji.<br />

Zdefiniujmy dla przykładu kilka własnych funkcji.<br />

Przykład:<br />

void UstawDosErrorlevel(int n) /* nazwa funkcji*/<br />

{<br />

exit(n); /* skromne ciało funkcji */<br />

}<br />

Przykład<br />

int DrukujAutora(void)<br />

{<br />

printf("\nAdam MAJCZAK AD 1993/95 - <strong>C++</strong> w 48 godzin!\n");<br />

printf("\n Wydanie II Poprawione i uzupełnione.")<br />

return 0;<br />

}<br />

Przykład<br />

void Drukuj_Pytanie(void)<br />

{<br />

printf("Podaj liczbe z zakresu od 0 do 255");<br />

printf("\nUstawie Ci ERRORLEVEL\t");<br />

}<br />

- 100-


W powyższych przykładach zwróć uwagę na:<br />

* sposób deklarowania zmiennej, przekazywanej jako parametr do<br />

funkcji - n i err;<br />

* definicje funkcji i ich wywołanie w programie (podobnie jak w<br />

Pascalu).<br />

Zilustrujemy zastosowanie tego mechanizmu w programie<br />

przykładowym. Funkcje powyższe są PREDEFINIOWANE w pliku<br />

FUNKCJE1.H na dyskietce dołączonej do książki. Wpisz i uruchom<br />

program:<br />

[P020.CPP]<br />

# include "stdio.h"<br />

# include "A:\funkcje1.h"<br />

int err;<br />

void main(void)<br />

{<br />

DrukujAutora();<br />

Drukuj_Pytanie();<br />

scanf("%d", &err);<br />

UstawDosErrorlevel(err);<br />

}<br />

Wykorzystajmy te funkcje praktycznie, by zilustrować sposób<br />

przekazywania informacji przez pracujący program do systemu DOS.<br />

Zmienna otoczenia systemowego DOS ERRORLEVEL może być z wnętrza<br />

programu ustawiona na zadaną - zwracaną do systemu wartość.<br />

[Z]<br />

________________________________________________________________<br />

1. Sprawdź, w jakim pliku nagłówkowym znajduje się prototyp<br />

funkcji exit(). Opracuj najprostszy program PYTAJ.EXE<br />

ustawiający zmienną systemową ERRORLEVEL według schematu:<br />

main()<br />

{<br />

printf("....Pytanie do użytkownika \n...");<br />

scanf("%d", &n);<br />

exit(n);<br />

}<br />

2. Zastosuj program PYTAJ.EXE we własnych plikach wsadowych typu<br />

*.BAT według wzoru:<br />

@echo off<br />

:LOOP<br />

cls<br />

echo 1. Wariant 1<br />

echo 2. Wariant 2<br />

echo 3. Wariant 3<br />

echo Wybierz wariant działania programu...1,2,3 ?<br />

- 101-


PYTAJ<br />

IF ERRORLEVEL 3 GOTO START3<br />

IF ERRORLEVEL 2 GOTO START2<br />

IF ERRORLEVEL 1 GOTO START1<br />

echo Chyba zartujesz...?<br />

goto LOOP<br />

:START1<br />

'AKCJA WARIANT 1<br />

GOTO KONIEC<br />

:START2<br />

'AKCJA WARIANT 2<br />

GOTO KONIEC<br />

:START3<br />

'AKCJA WARIANT 3<br />

:KONIEC<br />

'AKCJA WARIANT n - oznacza dowolny ciąg komend systemu DOS, np.<br />

COPY, MD, DEL, lub uruchomienie dowolnego programu. Do<br />

utworzenia pliku wsadowego możesz zastosować edytor systemowy<br />

EDIT.<br />

3. Skompiluj program posługując się oddzielnym kompilatorem<br />

TCC.EXE. Ten wariant kompilatora jest pozbawiony zintegrowanego<br />

edytora. Musisz uruchomić go pisząc odpowiedni rozkaz po<br />

DOS-owskim znaku zachęty C:\>. Zastosowanie przy kompilacji<br />

małego modelu pamięci pozwol Ci uzyskać swój program w wersji<br />

*.COM, a nie *.EXE. Wydaj rozkaz:<br />

c:\borlandc\bin\bcc -mt -lt c:\pytaj.cpp<br />

Jeśli pliki znajdują się w różnych katalogach, podaj właściwe<br />

ścieżki dostępu (path).<br />

________________________________________________________________<br />

[???] CO TO ZA PARAMETRY ???<br />

________________________________________________________________<br />

Przez swą "ułomność" - 16 bitową szynę i segmentację pamięci<br />

komputery IBM PC wymusiły wprowadzenie modeli pamięci:<br />

TINY, SMALL, COMPACT, MEDIUM, LARGE, HUGE. Więcej informacji na<br />

ten temat znajdziesz w dalszej części książki.<br />

Parametry dotyczą sposobu kompilacji i zastosowanego modelu<br />

pamięci:<br />

-mt - kompiluj (->*.OBJ) wykorzystując model TINY<br />

-lt - konsoliduj (->*.COM) wykorzystując model TINY i zatem<br />

odpowiednie biblioteki (do każdego modelu jest odpowiednia<br />

biblioteka *.LIB).<br />

Możesz stosować także:<br />

ms, mm, ml, mh, ls, lm, ll, lh.<br />

________________________________________________________________<br />

Po instalacji BORLAND <strong>C++</strong>/Turbo <strong>C++</strong> standardowo jest przyjmowany<br />

model SMALL. Zatem kompilacja, którą wykonujesz z IDE daje taki<br />

- 102-


sam efekt, jak zastosowanie kompilatora bcc/tcc w następujący<br />

sposób:<br />

tcc -ms -ls program.c<br />

Mogą wystąpić kłopoty z przerobieniem z EXE na COM tych<br />

programów, w których występują funkcje realizujące arytmetykę<br />

zmiennoprzecinkową (float). System DOS oferuje Ci do takich<br />

celów program EXE2BIN, ale lepiej jest "panować" nad tym<br />

problemem na etapie tworzenia programu.<br />

PODSTAWOWE TYPY DANYCH W JĘZYKU <strong>C++</strong>.<br />

Język C/<strong>C++</strong> operuje pięcioma podstawowymi typami danych:<br />

* char (znak, numer znaku w kodzie ASCII) - 1 bajt;<br />

* int (liczba całkowita) - 2 bajty;<br />

* float (liczba z pływającym przecinkiem) - 4 bajty;<br />

* double (podwójna ilość cyfr znaczących) - 8 bajtów;<br />

* void (nieokreślona) 0 bajtów.<br />

Zakres wartości przedstawiono w Tabeli poniżej.<br />

Podstawowe typy danych w <strong>C++</strong>.<br />

________________________________________________________________<br />

Typ Znak Bajtów Zakres wartości<br />

________________________________________________________________<br />

char signed 1 -128...+127<br />

int signed 2 -32768...+32767<br />

float signed 4 +-3.4E+-38 (dokładność: 7 cyfr)<br />

double signed 8 1.7E+-308 (dokładność: 15 cyfr)<br />

void nie dotyczy 0 bez określonej wartości.<br />

________________________________________________________________<br />

signed - ze znakiem, unsigned - bez znaku.<br />

Podstawowe typy danych mogą być stosowane z jednym z czterech<br />

modyfikatorów:<br />

* signed / unsigned - ze znakiem albo bez znaku<br />

* long / short - długi albo krótki<br />

Dla IBM PC typy int i short int są reprezentowane przez taki sam<br />

wewnętrzny format danych. Dla innych komputerów może być<br />

inaczej.<br />

Typy zmiennych w języku <strong>C++</strong> z zastosowaniem modyfikatorów<br />

(dopuszczalne kombinacje).<br />

________________________________________________________________<br />

Deklaracja Znak Bajtów Wartości Dyr. assembl.<br />

________________________________________________________________<br />

- 103-


char signed 1 -128...+127 DB<br />

int signed 2 -32768...+32767 DB<br />

short signed 2 -32768...+32767 DB<br />

short int signed 2 -32768...+32767 DB<br />

long signed 4 -2 147 483 648... DD<br />

+2 147 483 647<br />

long int signed 4 -2 147 483 648... DW<br />

+2 147 483 647<br />

unsigned char unsigned 1 0...+255 DB<br />

unsigned unsigned 2 0...+65 535 DW<br />

unsigned int unsigned 2 0...+65 535 DW<br />

unsigned short unsigned 2 0...+65 535 DW<br />

signed int signed 2 -32 768...+32 767 DW<br />

signed signed 2 -32 768...+32 767 DW<br />

signed long signed 4 -2 147 483 648... DD<br />

+2 147 483 647<br />

enum unsigned 2 0...+65 535 DW<br />

float signed 4 3.4E+-38 (7 cyfr) DD<br />

double signed 8 1.7E+-308 (15 cyfr) DQ<br />

long double signed 10 3.4E-4932...1.1E+4932 DT<br />

far * (far pointer, 386) 6 unsigned 2^48 - 1 DF, DP<br />

________________________________________________________________<br />

UWAGI:<br />

* DB - define byte - zdefiniuj bajt;<br />

DW - define word - zdefiniuj słowo (16 bitów);<br />

DD - double word - podwójne słowo (32 bity);<br />

DF, DP - define far pointer - daleki wskaźnik w 386;<br />

DQ - quad word - poczwórne słowo (4 * 16 = 64 bity);<br />

DT - ten bytes - dziesięć bajtów.<br />

* zwróć uwagę, że typ wyliczeniowy enum występuje jako odrębny<br />

typ danych (szczegóły w dalszej części książki).<br />

________________________________________________________________<br />

Ponieważ nie ma liczb ani short float, ani unsigned short float,<br />

słowo int może zostać opuszczone w deklaracji. Poprawne są zatem<br />

deklaracje:<br />

short a;<br />

unsigned short b;<br />

Zapis +-3.4E-38...3.4E+38 oznacza:<br />

-3.4*10^+38...0...+3.4*10^-38...+3.4*10^+38<br />

Dopuszczalne są deklaracje i definicje grupowe z zastosowaniem<br />

listy zmiennych. Zmienne na liście należy oddzielić przecinkami:<br />

int a=0, b=1, c, d;<br />

float PI=3.14, max=36.6;<br />

Poświęcimy teraz chwilę drugiej funkcji, którą już wielokrotnie<br />

- 104-


stosowaliśmy - funkcji wejścia - scanf().<br />

FUNKCJA scanf().<br />

Funkcja formatowanego wejścia ze standardowego strumienia<br />

wejściowego (stdin). Funkcja jest predefiniowana w pliku STDIO.H<br />

i korzystając z funkcji systemu operacyjnego wczytuje dane w<br />

postaci tekstu z klawiatury konsoli. Interpretacja pobranych<br />

przez funkcję scanf znaków nastąpi zgodnie z życzeniem<br />

programisty określonym przez zadany funkcji format (%f, %d, %c<br />

itp.). Wywołanie funkcji scanf ma postać:<br />

scanf(Format, Adres_zmiennej1, Adres_zmiennej2...);<br />

dla przykładu<br />

scanf("%f%f%f", &X1, &X2, &X3);<br />

wczytuje trzy liczby zmiennoprzecinkowe X1, X2 i X3.<br />

Format decyduje, czy pobrane znaki zostaną zinterpretowane np.<br />

jako liczba całkowita, znak, łańcuch znaków (napis), czy też w<br />

inny sposób. Od sposobu interpretacji zależy i rozmieszczenie<br />

ich w pamięci i późniejsze "sięgnięcie do nich", czyli odwołanie<br />

do danych umieszczonych w pamięci operacyjnej komputera.<br />

Zwróć uwagę, że podając nazwy (identyfikatory) zmiennych należy<br />

poprzedzić je w funkcji scanf() operatorem adresowym [&].<br />

Zapis:<br />

int X;<br />

...<br />

scanf("%d", &X);<br />

oznacza, że zostaną wykonane następujące działania:<br />

* Kompilator zarezerwuje 2 bajty pomięci w obszarze pamięci<br />

danych programu na zmienną X typu int;<br />

* W momencie wywołania funkcji scanf funkcji tej zostanie<br />

przekazany adres pamięci pod którym ma zostać umieszczona<br />

zmienna X, czyli tzw. WSKAZANIE DO ZMIENNEJ;<br />

* Znaki pobrane z klawiatury przez funkcję scanf mają zostać<br />

przekształcone do postaci wynikającej z wybranego formatu %d -<br />

tzn. do postaci zajmującej dwa bajty liczby całkowitej ze<br />

znakiem.<br />

[???] A JEŚLI PODAM INNY FORMAT ?<br />

________________________________________________________________<br />

<strong>C++</strong> wykona Twoje rozkazy najlepiej jak umie, niestety nie<br />

sprawdzając po drodze formatów, a z zer i jedynek zapisanych w<br />

pamięci RAM żaden format nie wynika. Otrzymasz błędne dane.<br />

________________________________________________________________<br />

Poniżej przykład skutków błędnego formatowania. Dołącz pliki<br />

- 105-


STDIO.H i CONIO.H.<br />

[P021.CPP]<br />

//UWAGA: Dołącz właściwe pliki nagłówkowe !<br />

void main()<br />

{<br />

float A, B;<br />

clrscr();<br />

scanf("%f %f", &A, &B);<br />

printf("\n%f\t%d", A,B);<br />

getch();<br />

}<br />

[Z]<br />

________________________________________________________________<br />

3 Zmień w programie przykładowym, w funkcji printf() wzorce<br />

formatu na %s, %c, itp. Porównaj wyniki.<br />

________________________________________________________________<br />

Adres w pamięci to taka sama liczba, jak wszystkie inne i wobec<br />

tego można nią manipulować. Adresami rządzą jednak dość<br />

specyficzne prawa, dlatego też w języku <strong>C++</strong> występuje jeszcze<br />

jeden specjalny typ zmiennych - tzw. ZMIENNE WSKAZUJĄCE (ang.<br />

pointer - wskaźnik). Twoja intuicja podpowiada Ci zapewne, że są<br />

to zmienne całkowite (nie ma przecież komórki pamięci o adresie<br />

0.245 ani 61/17). Pojęcia "komórka pamięci" a nie np. "bajt"<br />

używam świadomie, ponieważ obszar zajmowany w pamięci przez<br />

zmienną może mieć różną długość. Aby komputer wiedział ile<br />

kolejnych bajtów pamięci zajmuje wskazany obiekt (liczba długa,<br />

krótka, znak itp.), deklarując wskaźnik trzeba podać na co<br />

będzie wskazywał. W sposób "nieoficjalny" już w funkcji scanf<br />

korzystaliśmy z tego mechanizmu. Jest to zjawisko specyficzne<br />

dla języka <strong>C++</strong>, więc zajmijmy się nim trochę dokładniej.<br />

POJĘCIE ZMIENNEJ WSKAZUJĄCEJ I ZMIENNEJ WSKAZYWANEJ.<br />

Wskaźnik to zmienna, która zawiera adres innej zmiennej w<br />

pamięci komputera. Istnienie wskaźników umożliwia pośrednie<br />

odwoływanie się do wskazywanego obiektu (liczby, znaku, łańcucha<br />

znaków itp.) a także stosunkowo proste odwołanie się do obiektów<br />

sąsiadujących z nim w pamięci. Załóżmy, że:<br />

x - jest umieszczoną gdzieś w pamięci komputera zmienną<br />

całkowitą typu int zajmującą dwa kolejne bajty pamięci, a<br />

px - jest wskaźnikiem do zmiennej x.<br />

Jednoargumentowy operator & podaje adres obiektu, a zatem<br />

instrukcja:<br />

px = &x;<br />

- 106-


przypisuje wskaźnikowi px adres zmiennej x. Mówimy, że:<br />

px wskazuje na zmienną x lub<br />

px jest WSKAŹNIKIEM (pointerem) do zmiennej x.<br />

Jednoargumentowy operator * (naz. OPERATOREM WYŁUSKANIA)<br />

powoduje, że zmienna "potraktowana" tym operatorem jest<br />

traktowana jako adres pewnego obiektu. Zatem, jeśli przyjmiemy,<br />

że y jest zmienną typu int, to działania:<br />

y = x;<br />

oraz<br />

px = &x;<br />

y = *px;<br />

będą mieć identyczny skutek. Zapis y = x oznacza:<br />

"Nadaj zmiennej y dotychczasową wartość zmiennej x";<br />

a zapis y=*px oznacza:<br />

"Nadaj zmiennej y dotychczasową wartość zmiennej, której adres w<br />

pamięci wskazuje wskaźnik px" (czyli właśnie x !).<br />

Wskaźniki także wymagają deklaracji. Poprawna deklaracja w<br />

opisanym powyżej przypadku powinna wyglądać tak:<br />

int x,y;<br />

int *px;<br />

main()<br />

......<br />

Zapis int *px; oznacza:<br />

"px jest wskaźnikiem i będzie wskazywać na liczby typu int".<br />

Wskaźniki do zmiennych mogą zamiast zmiennych pojawiać się w<br />

wyrażeniach po PRAWEJ STRONIE, np. zapisy:<br />

int X,Y;<br />

int *pX;<br />

...<br />

pX = &X;<br />

.......<br />

Y = *pX + 1; €€€€€€/* to samo, co Y = X + 1 */<br />

printf("%d", *pX);€€€€€€€/* to samo, co printf("%d", X); */<br />

Y = sqrt(*pX);€€€€€€€€€€€/* pierwiastek kwadrat. z X */<br />

......<br />

są w języku <strong>C++</strong> poprawne.<br />

Zwróć uwagę, że operatory & i * mają wyższy priorytet niż<br />

operatory arytmetyczne, dzięki czemu<br />

* najpierw następuje pobranie spod wskazanego przez<br />

wskaźnik adresu zmiennej;<br />

- 107-


* potem następuje wykonanie operacji arytmetycznej;<br />

(operacja nie jest więc wykonywana na wskaźniku, a na<br />

wskazywanej zmiennej!).<br />

W języku <strong>C++</strong> możliwa jest także sytuacja odwrotna:<br />

Y = *(pX + 1);<br />

Ponieważ operator () ma wyższy priorytet niż * , więc:<br />

najpierw wskaźnik zostaje zwiększony o 1;<br />

potem zostaje pobrana z pamięci wartość znajdująca się pod<br />

wskazanym adresem (w tym momencie nie jest to już adres zmiennej<br />

X, a obiektu "następnego" w pamięci) i przypisana zmiennej Y.<br />

Taki sposób poruszania się po pamięci jest szczególnie wygodny,<br />

jeśli pod kolejnymi adresami pamięci rozmieścimy np. kolejne<br />

wyrazy z tablicy, czy kolejne znaki tekstu.<br />

Przyjrzyjmy się wyrażeniom, w których wskaźnik występuje po<br />

LEWEJ STRONIE. Zapisy:<br />

*pX = 0;€€€€€€€€€€€€i€€€€€€€€€X = 0;<br />

*pX += 1;€€€€€€€€€€€i€€€€€€€€€X += 1;<br />

(*pX)++;€€€€€€€€€€€€i€€€€€€€€€X++; /*3*/<br />

mają identyczne działanie. Zwróć uwagę w przykładzie /*3*/, że<br />

ze względu na priorytet operatorów<br />

() - najwyższy - najpierw pobieramy wskazaną zmienną;<br />

++ - niższy, potem zwiększmy wskazaną zmienną o 1;<br />

Gdyby zapis miał postać:<br />

*pX++;<br />

najpierw nastąpiłoby<br />

- zwiększenie wskaźnika o 1 i wskazanie "sąsiedniej" zmiennej,<br />

potem<br />

- wyłuskanie, czyli pobranie z pamięci zmiennej wskazanej przez<br />

nowy, zwiększony wskaźnik, zawartość pamięci natomiast, tj.<br />

wszystkie zmienne rozmieszczone w pamięci pozostałyby bez zmian.<br />

[???] JAK TO WŁAŚCIWIE JEST Z TYM PRIORYTETEM ?<br />

________________________________________________________________<br />

Wszystkie operatory jednoargumentowe (kategoria 2, patrz Tabela)<br />

mają taki sam priorytet, ale są PRAWOSTRONNIE ŁĄCZNE {L


Zwróć uwagę, że kolejność {L


clrscr();<br />

ptr1=&a;<br />

ptr2=&a;<br />

printf("Skok o 2Bajty Skok o 4Bajty");<br />

}<br />

for(i=0; i


77€€€€€€€€€€€€€28448<br />

88€€€€€€€€€€€€€8258<br />

99€€€€€€€€€€€€€27475<br />

10€€€€€€€€€€€€€2844<br />

Zwróć uwagę, że to deklaracja wskaźnika decyduje, co praktycznie<br />

oznacza operacja *(ptr + 1). W pierwszym przypadku wskaźnik<br />

powiększa się o 2 a w drugim o 4 bajty. Te odpowiednio 2 i 4<br />

bajty stanowią długość komórki pamięci lub precyzyjniej, pola<br />

pamięci przeznaczonego dla zmiennych określonego typu.<br />

Wartości pojawiające się w drugiej kolumnie po 99 są<br />

przypadkowe i u Ciebie mogą okazać się inne.<br />

<strong>C++</strong> pozwala wskaźnikom nie tylko wskazywać adres zmiennej w<br />

pamięci. Wskaźnik może również wskazywać na inny wskaźnik. Takie<br />

wskazania:<br />

int X; €€€int pX; €€int ppX;<br />

pX = &X;€€ppX = &pX;<br />

oznaczamy:<br />

*pX€€- pX wskazuje BEZPOŚREDNIO zmienną X;<br />

**ppX€- ppX skazuje POŚREDNIO zmienną X (jest wskaźnikiem do<br />

wskaźnika).<br />

***pppX - pppX wskazuje pośrednio wskaźnik do zmiennej X itd.<br />

[Z]<br />

________________________________________________________________<br />

4 Wybierz dowolne dwa przykładowe programy omawiane wcześniej i<br />

przeredaguj je posługując się zamiast zmiennych - wskaźnikami do<br />

tych zmiennych. Pamiętaj, że przed użyciem wskaźnika należy:<br />

* zadeklarować na jaki typ zmiennych wskazuje wskaźnik;<br />

* przyporządkować wskaźnik określonej zmiennej.<br />

5 Zastanów się, co oznacza ostrzeżenie wypisywane podczas<br />

uruchomienia programu przykładowego:<br />

Warning 8: Suspicious pointer conversion in function main.<br />

________________________________________________________________<br />

- 111-


LEKCJA 12. Wskaźniki i tablice w C i <strong>C++</strong>.<br />

________________________________________________________________<br />

W czasie tej lekcji:<br />

1. Dowiesz się więcej o zastosowaniu wskaźników.<br />

2. Zrozumiesz, co mają wspólnego wskaźniki i tablice w języku<br />

C/<strong>C++</strong>.<br />

________________________________________________________________<br />

WSKAŹNIKI I TABLICE W C i <strong>C++</strong>.<br />

W języku C/<strong>C++</strong> pomiędzy wskaźnikami a tablicami istnieje bardzo<br />

ścisły związek. Do ponumerowania elementów w tablicy służą tzw.<br />

INDEKSY. W języku C/<strong>C++</strong><br />

* KAŻDA OPERACJA korzystająca z indeksów może zostać wykonana<br />

przy pomocy wskaźników;<br />

* posługiwanie się wskaźnikiem zamiast indeksu na ogół<br />

przyspiesza operację.<br />

Tablice, podobnie jak zmienne i funkcje wymagają przed użyciem<br />

DEKLARACJI. Upraszczając problem - komputer musi wiedzieć ile<br />

miejsca zarezerwować w pamięci i w jaki sposób rozmieścić<br />

kolejne OBIEKTY, czyli kolejne elementy tablicy.<br />

[???] CO Z TYMI OBIEKTAMI ?<br />

________________________________________________________________<br />

OBIEKTEM w szerokim znaczeniu tego słowa jest każda liczba,<br />

znak, łańcuch znaków itp.. Takimi klasycznymi obiektami języki<br />

programowania operowały już od dawien dawna. Prawdziwe<br />

programowanie obiektowe w dzisiejszym, węższym znaczeniu<br />

rozpoczyna się jednak tam, gdzie obiektem może stać się także<br />

coś "nietypowego" - np. rysunek. Jest to jednak właściwy chyba<br />

moment, by zwrócić Ci uwagę, że z punktu widzenia komputera<br />

obiekt to coś, co zajmuje pewien obszar pamięci i z czym wiadomo<br />

jak postępować.<br />

________________________________________________________________<br />

Deklaracja:<br />

int A[12];<br />

oznacza:<br />

należy zarezerwować 12 KOLEJNYCH komórek pamięci dla 12 liczb<br />

całkowitych typu int (po 2 bajty każda). Jednowymiarowa tablica<br />

(wektor) będzie się nazywać "A", a jej kolejne elementy zostaną<br />

ponumerowane przy pomocy indeksu:<br />

- zwróć uwagę, że w C zaczynamy liczyć OD ZERA A NIE OD JEDYNKI;<br />

A[0], A[1], A[2], A[3], .... A[11].<br />

Jeśli chcemy zadeklarować:<br />

- indeks i;<br />

- wskaźnik, wskazujący nam początek (pierwszy, czyli zerowy<br />

- 112-


element) tablicy;<br />

- samą tablicę;<br />

to takie deklaracje powinny wyglądać następująco:<br />

int i;<br />

int *pA;<br />

int A[12];<br />

Aby wskaźnik wskazywał na początek tablicy A[12], musimy go<br />

jeszcze zainicjować:<br />

pA = &A[0];<br />

Jeśli poszczególne elementy tablicy są zawsze rozmieszczane<br />

KOLEJNO, to:<br />

*pA[0]<br />

oznacza:<br />

"wyłuskaj zawartość komórki pamięci wskazanej przez wskaźnik",<br />

czyli inaczej - pobierz z pamięci pierwszy (zerowy!) element<br />

tablicy A[]. Jeśli deklaracja typów elementów tablicy i<br />

deklaracja typu wskaźnika są zgodne i poprawne, nie musimy się<br />

dalej martwić ile bajtów zajmuje dany obiekt - element tablicy.<br />

Zapisy:<br />

*pA[0];€€€€€€€€*pA;€€€€€€€€€€€A[0]<br />

*(pA[0]+1)€€€€€*(pA+1)€€€€€€€€A[1]<br />

*(pA[0]+2)€€€€€*(pA+2)€€€€€€€€A[2]€€€€€€itd.<br />

są równoważne i oznaczają kolejne wyrazy tablicy A[].<br />

Jeśli tablica jest dwu- lub trójwymiarowa, początek tablicy<br />

oznacza zapis:<br />

A[0][0];<br />

A[0][0][0];<br />

itd.<br />

Zwróć uwagę, że wskaźnik do tablicy *pA oznacza praktycznie<br />

wskaźnik do POCZĄTKOWEGO ELEMENTU TABLICY:<br />

*pA == *pA[0]<br />

To samo można zapisać w języku <strong>C++</strong> w jeszcze inny sposób. Jeśli<br />

A jest nazwą tablicy, to zapis:<br />

*A<br />

oznacza wskazanie do początku tablicy A, a zapisy:<br />

*(A+1)€€€€€€€€€€€€*(pA+1)€€€€€€€€A[1]<br />

*(A+8)€€€€€€€€€€€€*(pA+8)€€€€€€€€A[8] itd.<br />

są równoważne. Podobnie identyczne znaczenie mają zapisy:<br />

- 113-


x = &A[i]€€€€€€€€x=A+i<br />

*pA[i]€€€€€€€€€*(A+i)<br />

Należy jednak podkreślić, że pomiędzy nazwami tablic (w naszym<br />

przykładzie A) a wskaźnikami istnieje zasadnicza różnica.<br />

Wskaźnik jest ZMIENNĄ, zatem operacje:<br />

pA = A;<br />

pA++;<br />

są dopuszczalne i sensowne. Nazwa tablicy natomiast jest STAŁĄ,<br />

zatem operacje:<br />

A = pA;€€€€€€€€€€€€€ŹLE !<br />

A++;€€€€€€€€€€€€€€€€ŹLE !<br />

są niedopuszczalne i próba ich wykonania spowoduje błędy !<br />

DEKLAROWANIE I INICJOWANIE TABLIC.<br />

Elementom tablicy, podobnie jak zmiennym możemy nadawać<br />

watrości. Wartości takie należy podawać w nawiasach klamrowych,<br />

a wielkość tablicy - w nawiasach kwadratowych.<br />

Przykład<br />

int WEKTOR[5];<br />

Tablica WEKTOR jest jednowymiarowa i składa się z 5 elementów<br />

typu int: WEKTOR[0]....WEKTOR[4].<br />

Przykład<br />

float Array[10][5];<br />

Tablica Array jest dwuwymiarowa i składa się z 50 elementów typu<br />

float: Array[0][0], Array[0][1]......Array[0][4]<br />

€€€€€€€Array[1][0], Array[1][1]......Array[1][4]<br />

€€€€€...........................................<br />

€€€€€ Array[9][0], Array[9][1]......Array[9][4]<br />

Przykład<br />

const int b[4]={1,2,33,444};<br />

Elementom jednowymiarowej tablicy (wektora) b przypisano<br />

wartośći: b[0]=1; b[1]=2; b[2]=33; b[3]=444;<br />

Przykład<br />

int TAB[2][3]={{1, 2, 3},{2, 4, 6}};<br />

€€€€€TAB[0][0]=1€€€€TAB[0][1]=2€€€€TAB[0][2]=3<br />

€€€€€TAB[1][0]=2€€€€TAB[1][1]=4€€€€TAB[1][2]=6<br />

Przykład : Tablica znakowa. Obie formy zapisu dają ten sam<br />

efekt.<br />

- 114-


char hej[5]="Ahoj";<br />

char hej[5]={'A', 'h', 'o', 'j'};<br />

€€€€€hej[0]='A'€€€€€hej[1]='h'€€€€€hej[2]='o' itp.<br />

Przykład : Tablica uzupełniona zerami przez domniemanie.<br />

float T[2][3]={{1, 2.22}, {.5}};<br />

kompilator uzupełni zerami do postaci:<br />

€€€€€T[0][0]=1€€€€€€T[0][1]=2.22€€€€€€€€T[0][2]=0<br />

€€€€€T[1][0]=0.5€€€€T[1][1]=0€€€€€€€€€€€T[1][2]=0<br />

Jeśli nawias kwadratowy zawierający wymiar pozostawimy pusty, to<br />

kompilator obliczy jego domniemaną zawartość w oparciu o podaną<br />

zawartość tablicy. Nie spowoduje więc błędu zapis:<br />

char D[]="Jakis napis"<br />

int A[][2]={{1,2}, {3,4}, {5,6}}<br />

Jeśli nie podamy ani wymiaru, ani zawartości:<br />

int A[];<br />

kompilator "zbuntuje się" i wykaże błąd.<br />

Dla przykładu, skompiluj program przykładowy. Zwróć uwagę na<br />

sposób zainicjowania wskaźnika.<br />

[P023.CPP]<br />

# include "stdio.h"<br />

# include <br />

int a[][2]={ {1,2},{3,4},{5,6},{7,8},{9,10},{11,12} };<br />

char b[]={ "Poniedzialek" };<br />

int i;<br />

int *pa;<br />

char *pb;<br />

void main()<br />

{<br />

pa = &a[0][0];<br />

pb = b; // lub pb = b[0];<br />

clrscr();<br />

for (i=0; i


...<br />

TAB3D[i][j][k];<br />

Jest w dobrym stylu panować nad swoimi danymi i umieszczać je w<br />

tzw. BUFORACH, czyli w wydzielonych obszarach pamięci o znanym<br />

adresie, wielkości i przeznaczeniu. W następnym programie<br />

przykładowym utworzymy taki bufor w postaci tablicy bufor[20] i<br />

zastosujemy zamiast funkcji scanf() czytającej bezpośrednio z<br />

klawiatury parę funkcji:<br />

gets() - GET String - pobierz łańcuch znaków z klawiatury do<br />

bufora;<br />

sscanf(bufor) - odczytaj z bufora (z pamięci).<br />

Aby uniknąć nielubianego goto stosujemy konstrukcję for - break.<br />

Dokładniej pętlę for omówimy w trakcie następnej lekcji.<br />

Ponieważ mam nadzieję, że "podstawową" postać pętli for<br />

pamiętasz z przykładów LOOP-n:<br />

for(i=1; i


}<br />

printf("Suma wynosi: %d\n", suma);<br />

printf("Srednia wynosi: %d\n", (suma / ile));<br />

getch();<br />

}<br />

Poniżej trochę bardziej "elegancka wersja" z zastosowaniem pętli<br />

typu while. Więcej o pętlach dowiesz się z następnej Lekcji.<br />

[P025.CPP]<br />

# include <br />

# include <br />

int liczba, ile=1, suma=0;<br />

void main()<br />

{<br />

char bufor[20];<br />

clrscr();<br />

printf("podaj liczby - ja oblicze SREDNIA i SUMA\n");<br />

printf("ZERO = KONIEC\n");<br />

gets(bufor);<br />

sscanf(bufor, "%d", &liczba);<br />

while (liczba != 0)<br />

{<br />

suma += liczba;<br />

gets(bufor);<br />

sscanf(bufor, "%d", &liczba);<br />

if(liczba == 0)<br />

printf("I to by bylo na tyle...\n");<br />

else<br />

ile++;<br />

}<br />

printf("Suma wynosi: %d\n", suma);<br />

printf("Srednia wynosi: %d\n", suma / ile);<br />

getch();<br />

}<br />

Program powyższy, choć operuje tablicą, robi to trochę jakby za<br />

kulisami. Utwórzmy zatem inną - bardziej "dydaktyczną" tablicę,<br />

której elementy byłyby łatwo rozpoznawalne.<br />

PRZYKŁADY TABLIC WIELOWYMIAROWYCH.<br />

Dzięki matematyce bardziej jesteśmy przyzwyczajeni do zapisu<br />

tablic w takiej postaci:<br />

€€€€€€€€€€a11€€a12€€a13€€a14€€a15€€a16<br />

€€€€€€€€€€a21€€a22€€a23€€a24€€a25€€a26<br />

€€€€€€€€€€a31€€a32€€a33€€a34€€a35€€a36<br />

€€€€€€€€€€a41€€a42€€a43€€a44€€a45€€a46<br />

- 117-


gdzie a i,j /** indeks**/ oznacza element tablicy zlokalizowany<br />

w:<br />

- wierszu i<br />

- kolumnie j<br />

Przypiszmy kolejnym elementom tablicy następujące wartości:<br />

€€€€€€€€€€11€€€12€€€13€€€14€€€15€€€16<br />

€€€€€€€€€€21€€€22€€€23€€€24€€€25€€€26<br />

€€€€€€€€€€31€€€32€€€33€€€34€€€35€€€36<br />

€€€€€€€€€€41€€€42€€€43€€€44€€€45€€€46<br />

Jest to tablica dwuwymiarowa o wymiarach 4WIERSZE X 6KOLUMN,<br />

czyli krócej 4X6. Liczby będące elementami tablicy są typu<br />

całkowitego. Jeśli zatem nazwiemy ją TABLICA, to zgodnie z<br />

zasadami przyjętymi w języku C/<strong>C++</strong> możemy ją zadeklarować:<br />

int TABLICA[4][6];<br />

Pamiętajmy, że <strong>C++</strong> liczy nie od jedynki a od zera, zatem<br />

TABLICA[0][0] = a11 = 11,<br />

TABLICA[2][3] = a34 = 34 itd.<br />

Znając zawartość tablicy możemy ją zdefiniować/zainicjować:<br />

int TABLICA[4][6]={{11,12,13,14,15,16},{21,22,23,24,25,26}<br />

{31,32,33,34,35,36},{41,42,43,44,45,46}};<br />

Taki sposób inicjowania tablicy, aczkolwiek pomaga wyjaśnić<br />

metodę, z punktu widzenia programistów jest trochę<br />

"nieelegancki". Liczbę przypisywaną danemu elementowi tablicy<br />

można łatwo obliczyć.<br />

TABLICA[i][j] = (i+1)*10 + (j+1);<br />

Przykładowo:<br />

TABLICA[2][5] = (2+1)*10 +(5+1) = 36<br />

Najbardziej oczywistym rozwiązaniem byłoby napisanie pętli<br />

int i, j;<br />

for (i=0; i


{31,32,33,34,35,36},{41,42,43,44,45,46}};<br />

# include <br />

# include <br />

int *pT;<br />

int i, j;<br />

void main()<br />

{<br />

clrscr();<br />

printf("OTO NASZA TABLICA \n");<br />

for (i=0; i


printf("i INKREMENTUJEMY wskaźnik *pT++ \n");<br />

pT=&TABLICA[0][0];<br />

for (i=0; i


}<br />

printf("\n");<br />

}<br />

printf("\n Inicjujemy i INKREMENTUJEMY wskaźnik *pT++ \n\n");<br />

pT=&T[0][0];<br />

for (k=0; k


pT =&T[0][0];<br />

printf("\t TABLICA znakowa (ineksy)\n\n");<br />

for (i=0; i


1. Posługując się wskaźnikiem i inkrementując wskaźnik z różnym<br />

krokiem - np. pT += 2; pT += 3 itp., zmodyfikuj programy<br />

przykładowe tak, by uzyskać wydruk tylko części tablicy.<br />

2. Spróbuj zastąpić inkrementację wskaźnika pT++ dekrementacją,<br />

odwracając tablicę "do góry nogami". Jak należałoby poprawnie<br />

zainicjować wskaźnik?<br />

3. Napisz program drukujący tabliczkę mnożenia w układzie<br />

szesnastkowym - od 1 * 1 do F * F.<br />

4. Wydrukuj nazwy dni tygodnia pionowo i wspak.<br />

5. Zinterpretuj następujące zapisy:<br />

int *pt_int;<br />

float *pt_float;<br />

int p = 7, d = 27;<br />

float x = 1.2345, Y = 32.14;<br />

void *general;<br />

pt_int = &p;<br />

*pt_int += d;<br />

general = pt_int;<br />

pt_float = &x;<br />

Y += 5 * (*pt_float);<br />

general = pt_float;<br />

const char *name1 = "Jasio"; // wskaźnik do STALEJ<br />

char *const name2 = "Grzesio"; // wskaźnik do STALEGO ADRESU<br />

________________________________________________________________<br />

- 123-


LEKCJA 13. Jak tworzyć w programie pętle i rozgałęzienia.<br />

_______________________________________________________________<br />

W trakcie tej lekcji:<br />

1. Dowiesz się znacznie więcej o pętlach.<br />

2. Przeanalizujemy instrukcje warunkowe i formułowanie warunków.<br />

_______________________________________________________________<br />

Zaczniemy tę lekcję nietypowo - od słownika, ponieważ dobrze<br />

jest rozumieć dokładnie co się pisze. Tym razem słownik jest<br />

trochę obszerniejszy. Pozwalam sobie przytoczyć niektóre słowa<br />

powtórnie - dla przypomnienia i Twojej wygody. Do organizacji<br />

pętli będą nam potrzebne następujące słowa:<br />

[S!] conditional expressions - wyrażenia warunkowe<br />

structural loops - pętle strukturalne<br />

________________________________________________________________<br />

if - jeżeli (poprzedza warunek do sprawdzenia);<br />

else - a jeśli nie, to (w przeciwnym wypadku...);<br />

for - dla;<br />

while - dopóki (dopóki nie spełnimy warunku);<br />

do - wykonaj, wykonuj;<br />

break - przerwij (wykonanie pętli);<br />

switch - przełącz;<br />

case - przypadek, wariant (jedna z możliwości);<br />

goto - idź do...<br />

default - domyślny, (automatyczny, pozostały);<br />

continue - kontynuuj (pętlę);<br />

________________________________________________________________<br />

UWAGA: W C/<strong>C++</strong> nie stosuje się słowa THEN.<br />

PĘTLA TYPU for.<br />

Ogólna postać pętli for jest następująca:<br />

for (W_inicjujące; W_logiczne; W_kroku) Instrukcja;<br />

gdzie skrót W_ oznacza wyrażenie. Każde z tych wyrażeń może<br />

zostać pominięte (patrz --> for(;;)).<br />

Wykonanie pętli for przebiega następująco:<br />

1. Wykonanie JEDEN raz WYRAŻENIA INICJUJĄCEGO.<br />

2. Obliczenie wartości LOGICZNEJ wyrażenia logicznego.<br />

3. Jeśli W_logiczne ma wartość PRAWDA (TRUE) nastąpi wykonanie<br />

Instrukcji.<br />

4. Obliczenie wyrażenia kroku.<br />

5. Powtórne sprawdzenie warunku - czy wyrażenie logiczne ma<br />

wartość różną od zera. Jeśli wyrażenie logiczne ma wartość zero,<br />

nastąpi zakończenie pętli.<br />

Warunek jest testowany PRZED wykonaniem instrukcji. Jeśli zatem<br />

nie zostanie spełniony warunek, instrukcja może nie wykonać się<br />

ANI RAZ.<br />

- 124-


Instrukcja może być INSTRUKCJĄ GRUPUJĄCĄ, składającą się z<br />

instrukcji prostych, deklaracji i definicji zmiennych lokalnych:<br />

{ ciąg deklaracji lub definicji;<br />

ciąg instrukcji; }<br />

Ogromnie ważny jest fakt, że <strong>C++</strong> ocenia wartość logiczną<br />

wyrażenia według zasady:<br />

0 - FALSE, FAŁSZ, inaczej ZERO LOGICZNE jeśli WYRAŻENIE == 0 lub<br />

jest fałszywe w znaczeniu logicznym;<br />

1 - TRUE, PRAWDA, JEDYNKA LOGICZNA, jeśli wyrażenie ma DOWOLNĄ<br />

WARTOŚĆ NUMERYCZNĄ RÓŻNĄ OD ZERA (!) lub jest prawdziwe w sensie<br />

logicznym.<br />

Przykład:<br />

"Klasycznie" zastosowana pętla for oblicza pierwiastki<br />

kwadratowe kolejnych liczb całkowitych.<br />

#include <br />

#include <br />

void main()<br />

{<br />

int n;<br />

for (n=0; n


€€€€€}<br />

getch();<br />

}<br />

UWAGA:<br />

Sprawdź, czy nawias (sqrt(n)-y)


const float d=1.2345;<br />

void main()<br />

{<br />

for (a=5,b=3.14,c=10; c; ++a,b*=d,c--)<br />

printf("\n%f\t%f\t%f", a,b,c);<br />

getch();<br />

}<br />

Zwróć uwagę, że zapisy warunku:<br />

if (c)...; i if (c != 0)...;<br />

są w <strong>C++</strong> równoważne.<br />

Przykład:<br />

Program będzie pisał kropki aż do naciśnięcia dowolnego<br />

klawisza, co wykryje funkcja kbhit(), będąca odpowiednikem<br />

KeyPressed w Pascalu. Zapis !kbhit() oznacza "NIE NACIŚNIĘTO<br />

KLAWISZA", czyli w buforze klawiatury nie oczekuje znak. Zwróć<br />

uwagę, że funkcja getch() może oczekiwać na klawisz w<br />

nieskończoność. Aby uniknąć kłopotliwych sytuacji, czasem<br />

znacznie wygodniej jest zastosować kbhit(), szczególnie, jeśli<br />

czekamy na DOWOLNY klawisz.<br />

void main()<br />

{<br />

for (; !kbhit(); printf("."));<br />

}<br />

Przykład:<br />

Wskaźnik w charakterze zmiennej roboczej w pętli typu for. Pętla<br />

powoduje wypisanie napisu.<br />

char *Ptr = "Jakis napis";<br />

void main()<br />

{<br />

for (; (*Ptr) ;)<br />

printf("%c",*Pt++);<br />

getch();<br />

}<br />

AUTOMATYCZNE GENEROWANIE TABLIC W PĘTLI for<br />

Na dyskietce znajdziesz jeszcze kilka przykładów FORxx.CPP<br />

użycia pętli. A teraz, zanim będziemy kontynuować naukę -<br />

przykładowy program do zabawy. Pętla for służy do wykrywania<br />

zgodności klawisza z elementami tablicy TABL[]. W tablicy D[]<br />

umieszczone zostały częstotliwości kolejnych dźwięków, które<br />

program oblicza sam, wykorzystując przybliżony współczynnik.<br />

[P030.CPP]<br />

- 127-


# include "conio.h"<br />

# include "dos.h"<br />

# include "math.h"<br />

# include "stdio.h"<br />

char TABL[27]={"zsxdcvgbhnjm,ZSXDCVGBHNJM


cout


void main(){<br />

float SUMA=0, n=0;<br />

clrscr();<br />

while (SUMA


if (Wyrażenie) Instrukcja;<br />

if (Wyrażenie) Instrukcja1 else Instrukcja2;<br />

Jeśli Wyrażenie ma wartość różną od zera (LOGICZNĄ bądź<br />

NUMERYCZNĄ !) to zostanie wykonana Instrukcja1, w przeciwnym<br />

razie wykonana zostanie Instrukcja2. Instrukcje mogą być<br />

instrukcjami grupującymi. Słowa kluczowe if i else mogą być<br />

stosowane wielokrotnie. Pozwala to tworzyć np. tzw. drzewa<br />

binarne.<br />

Przykład:<br />

void main()<br />

{<br />

float a;<br />

scanf("%f", &a);<br />

if (a0) if (a0) {if(a0 && ab)? MAX=a : MAX=b;<br />

inaczej:<br />

if (a>b) MAX=a; else MAX=b;<br />

INSTRUKCJE break i continue.<br />

Instrukcja break powoduje natychmiastowe bezwarunkowe<br />

opuszczenie pętli dowolnego typu i przejście do najbliższej<br />

instrukcji po zakończeniu pętli. Jeśli w pętli for opuścimy<br />

wyrażenie logiczne, to zostanie automatycznie przyjęte 1. Pętla<br />

będzie zatem wykonywana bezwarunkowo w nieskończoność. W<br />

przykładzie poniżej nieskończoną pętlę przerywa po podaniu z<br />

kalwiatury zera instrukcja break.<br />

- 131-


Przykład:<br />

float a, sigma=0;<br />

void main(){<br />

for (;;)<br />

{<br />

printf("\n Podaj liczbe do sumowania\n");<br />

scanf("%f", &a);<br />

if (a==0) break;<br />

sigma+=a;<br />

printf("\n SUMA: %f",sigma);<br />

}<br />

printf("Nastapil BREAK");<br />

getch();<br />

}<br />

Instrukcja continue.<br />

Instrukcja continue powoduje przedwczesne, bezwarunkowe<br />

zakończenie wykonania wewnętrznej instrukcji pętli i podjęcie<br />

próby realizacji następnego cyklu pętli. Próby, ponieważ<br />

najpierw zostanie sprawdzony warunek kontynuacji pętli. Program<br />

z przykładu poprzedniego zmodyfikujemy w taki sposób, by<br />

* jeśli liczba jest dodatnia - dodawał ją do sumy sigma;<br />

* jeśli liczba jest ujemna - nie robił nic, pomijał bieżącą<br />

pętlę przy pomocy rozkazu continue;<br />

(Ponieważ warunek wejściowy pętli jest zawsze spełniony, to<br />

pętlę zawsze uda się kontynuować.)<br />

* jeśli liczba równa się zero - przerywał pętlę instrukcją break<br />

Przykład:<br />

float a, sigma=0;<br />

void main()<br />

{<br />

for (;;)<br />

{<br />

printf("\n Sumuje tylko liczby dodatnie\n");<br />

scanf("%f", &a);<br />

if (a


przypadków - wariantów (case). Każdy wariant jest oznaczony przy<br />

pomocy stałej - tzw. ETYKIETY WYBORU. Wyrażenie przełączające<br />

może przyjmować wartości typu int. Ogólna postać istrukcji jest<br />

następująca:<br />

switch (selector)<br />

{<br />

case STAŁA1: Ciąg_instrukcji-wariant 1;<br />

case STAŁA2: Ciąg_instrukcji-wariant 2;<br />

...............................<br />

case STAŁAn: Ciąg_instrukcji-wariant n;<br />

default : Ostatni_ciąg_instrukcji;<br />

}<br />

Należy podkreślić, że po dokonaniu wyboru i skoku do etykiety<br />

wykonane zostaną również WSZYSTKIE INSTRUKCJE PONIŻEJ DANEJ<br />

ETYKIETY. Jeśli chcemy tego uniknąć, musimy dodać rozkaz break.<br />

[P033.CPP]<br />

# define pisz printf //dla przypomnienia<br />

# include <br />

void main()<br />

{<br />

int Numer_Dnia;<br />

pisz("\nPodaj numer dnia tygodnia\n");<br />

scanf("%d", &Numer_Dnia);<br />

switch(Numer_Dnia)<br />

{<br />

case 1: pisz("PONIEDZIALEK.");<br />

case 2: pisz("WTOREK");<br />

case 3: pisz("SRODA.");<br />

case 4: pisz("CZWARTEK.");<br />

case 5: pisz("PIATEK.");<br />

case 6: pisz("SOBOTA.");<br />

case 7: pisz("NIEDZIELA.");<br />

default: pisz("\n *********************");<br />

}<br />

}<br />

Zwróć uwagę, że w przykładzie wariant default zostanie wykonany<br />

ZAWSZE, nawet jeśli podasz liczbę większą niż 7.<br />

[P034.CPP]<br />

# define pisz printf<br />

# include <br />

void main()<br />

{<br />

int Numer_Dnia;<br />

pisz("\nPodaj numer dnia tygodnia\n");<br />

scanf("%d", &Numer_Dnia);<br />

switch(Numer_Dnia)<br />

- 133-


{<br />

case 1: pisz("PON."); break;<br />

case 2: pisz("WTOR"); break;<br />

case 3: pisz("SRO."); break;<br />

case 4: pisz("CZW."); break;<br />

case 5: pisz("PIO."); break;<br />

case 6: pisz("SOB."); break;<br />

case 7: pisz("NIEDZ."); break;<br />

default: pisz("\n ?????");<br />

}<br />

}<br />

Instrukcja break przerywa wykonanie. Wariant default zostanie<br />

wykonany TYLKO w przypadku podania liczby większej niż 7.<br />

INSTRUKCJA POWROTU return.<br />

Służy do zakończenia wykonania zawierającej ją funkcji i może<br />

mieć postać:<br />

return;<br />

return stała;<br />

return Wyrażenie;<br />

return (wyrażenie);<br />

Przykład:<br />

Definiujemy funkcję _dodaj() zwracającą, poprzez instrukcję<br />

return wartość przekazanego jej w momencie wywołania argumentu<br />

powiększoną o 5.<br />

float _dodaj(float x)<br />

{<br />

x+=5;<br />

return x;<br />

}<br />

Funkcja _dodaj() zwraca wartość i nadaje tę wartość zmiennej<br />

wynik zadeklarowanej nazewnątrz funkcji i znanej w programie<br />

głównym. A oto program w całości.<br />

[P035.CPP]<br />

float funkcja_dodaj(float x)<br />

{<br />

x += 5;<br />

return x;<br />

}<br />

float dana = 1, wynik = 0;<br />

void main()<br />

{<br />

clrscr();<br />

wynik = funkcja_dodaj(dana);<br />

- 134-


printf("%f", wynik);<br />

}<br />

INSTRUKCJA SKOKU BEZWARUNKOWEGO goto I ETYKIETY.<br />

Składnia instrukcji skoku goto jest następująca:<br />

goto Identyfikator_etykiety;<br />

UWAGA: Po każdej etykiecie musi wystąpić CO NAJMNIEJ JEDNA<br />

INSTRUKCJA. Jeśli etykieta oznacza koniec programu, to musi po<br />

niej wystąpić instrukcja pusta. Instrukcja goto nie cieszy się<br />

powodzeniem ani dobrą sławą (niesłusznie!). Ostrożne i umiejętne<br />

jej stosowanie jeszcze nikomu nie zaszkodziło. Należy tu<br />

zaznaczyć, że etykieta nie wymaga deklaracji.<br />

Przykład:<br />

Program poniżej generuje dźwięki i "odlicza".<br />

[P036.CPP]<br />

#include <br />

#include <br />

void main()<br />

{<br />

int czestotliwosc=5000, n=10, milisekundy=990;<br />

printf("\n");<br />

start:<br />

{<br />

sound(czestotliwosc);<br />

delay(milisekundy);<br />

nosound();<br />

czestotliwosc/=1.2;<br />

printf("%d\b", --n);<br />

if (n) goto start; //petle strukturalne zrob sam(a)<br />

}<br />

koniec: ;<br />

} // Tu jest instrukcja pusta.<br />

[S!] DOS API function names - nazwy funkcji z interfejsu DOS<br />

________________________________________________________________<br />

sound - dźwięk;<br />

delay - opóźnienie, zwłoka;<br />

nosound - bez dźwięku (wyłącz dźwięk);<br />

________________________________________________________________<br />

[Z]<br />

________________________________________________________________<br />

1. Biorąc pod uwagę, że iloraz częstotliwości kolejnych dźwięków<br />

jest stały tzn. Fcis/Fc=Ffis/Ff=....=const oraz, że oktawa to<br />

- 135-


podwojenie częstotliwości, opracuj program i oblicz<br />

częstotliwości poszczególnych dźwięków.<br />

2. Spróbuj zastosować w programie przykładowym kolejno pętle<br />

for, while, do...while.<br />

3. Zastosuj we własnym programie doświadczalnym instrukcję<br />

switch.<br />

- 136-


LEKCJA 14. Jak tworzyć i stosować struktury.<br />

________________________________________________________________<br />

W trakcie tej lekcji poznasz pojęcia:<br />

* Klasy zmiennej.<br />

* Struktury.<br />

* Pola bitowego.<br />

* Unii.<br />

Dowiesz się także więcej o operacjach logicznych.<br />

________________________________________________________________<br />

CO TO JEST KLASA ZMIENNEJ?<br />

W języku C i <strong>C++</strong> programista ma większy wpływ na rozmieszczenie<br />

zmiennych w pamięci operacyjnej komputera i w rejestrach<br />

mikroprocesora. Może to mieć decydujący wpływ na dostępność<br />

danych z różnych miejsc programu i szybkość działania programu.<br />

Należy podkreślić, że TYP ZMIENNEJ (char, int, float itp.)<br />

decyduje o sposobie interpretacji przechowywanych w pamięci zer<br />

i jedynek, natomiast KLASA ZMIENNEJ decyduje o sposobie<br />

przechowywania zmiennej w pamięci. W <strong>C++</strong> występują cztery klasy<br />

zmiennych.<br />

ZMIENNE STATYCZNE - static.<br />

Otrzymują stałą lokalizację w pamięci w momencie uruchamiania<br />

programu. Zachowują swoją wartość przez cały czas realizacji<br />

programu, chyba, że świadomie zażądamy zmiany tego stanu - np.<br />

instrukcją przypisania.<br />

Przykład deklaracji: static float liczba;<br />

W większości kompilatorów <strong>C++</strong> zmienne statyczne, które nie<br />

zostały jawnie zainicjowane w programie, otrzymują po<br />

zadeklarowaniu wartość ZERO.<br />

ZMIENNE AUTOMATYCZNE - auto.<br />

Otrzymują przydział miejsca w pamięci dynamicznie - na stosie<br />

procesora, w momencie rozpoczęcia wykonania tego bloku programu,<br />

w którym zmienne te zostały zadeklarowane. Przydzielenie pamięci<br />

nie zwalnia nas z obowiązku zainicjowania zmiennej (wcześniej<br />

wartość zmiennej jest przypadkowa). Zmienne automatyczne<br />

"znikają" po zakończeniu wykonywania bloku. Pamięć im<br />

przydzielona zostaje zwolniona. Przykład: auto long suma;<br />

ZMIENNE REJESTROWE - register.<br />

Zmienne rejestrowe są także zmiennymi lokalnymi, widocznymi<br />

tylko wewnątrz tego bloku programu, w którym zostały<br />

zadeklarowane. <strong>C++</strong> może wykorzystać dwa rejestry mikroprocesora<br />

- DI i SI do przechowywania zmiennych. Jeśli zadeklarujemy w<br />

- 137-


programie więcej zmiennych jako zmienne rejestrowe - zostaną one<br />

umieszczone na stosie. Znaczne przyspieszenie działania programu<br />

powoduje wykorzystanie rejestru do przechowywania np. licznika<br />

pętli.<br />

Przykład:<br />

register int i;<br />

.....<br />

for (i=1; i


int wiek;<br />

char pokrewienstwo[10]<br />

};<br />

Jeśli określimy już typ struktury - czyli rodzaj, wielkość i<br />

przeznaczenie poszczególnych pól struktury, możemy dalej tworzyć<br />

- deklarować i inicjować konkretne struktury danego typu.<br />

Przykład. Deklaracja zmiennych - struktur tego samego typu.<br />

struct Ludzie Moi, Twoi, Jego, Jej, Szwagra;<br />

Deklarację struktur można połączyć.<br />

Przykład. Połączona deklaracja struktur.<br />

struct Ludzie<br />

{ char pokrewienstwo[10];<br />

char Imiona[30];<br />

int wiek;<br />

} Moi, Twoi, Szwagra;<br />

Struktury statyczne<br />

* mają stałe miejsce w pamięci w trakcie całego programu;<br />

* są "widoczne" i dostępne w całym programie.<br />

Zadeklarujemy teraz typ struktury i zainicjujemy dwie struktury.<br />

Przykład. Zainicjowanie dwu struktur statycznych.<br />

struct Ludzie<br />

{ char pokrewienstwo[10];<br />

char Imiona[30];<br />

int wiek;<br />

};<br />

struct Ludzie Moi, Szwagra;<br />

static struct Ludzie Moi = { "Stryjek", "Walenty", 87 };<br />

static struct Ludzie Szwagra = { "ciotka", "Ala", 21 };<br />

Zapis<br />

static struct Ludzie Szwagra;<br />

oznacza:<br />

statyczna struktura typu "Ludzie" pod nazwą "Szwagra".<br />

Do struktury w całości możemy odwoływać się za pomocą jej nazwy<br />

a do poszczególnych elementów struktury poprzez nazwę struktury<br />

i nazwę pola struktury - ROZDZIELONE KROPKĄ ".". Zademonstrujmy<br />

to na przykładzie. Zwróć uwagę na różne sposoby przekazywania<br />

danych pomiędzy strukturami:<br />

- 139-


C4.Wiek=Czlowiek2.Wiek; - przekazanie zawartości pojedynczego<br />

pola numerycznego;<br />

C4=Czlowiek3; - przekazanie zawartości całej struktury Czlowiek3<br />

do C4.<br />

Przykład. Program manipulujący prostą strukturą.<br />

[P037.CPP]<br />

int main()<br />

{<br />

struct Ludzie<br />

{<br />

char Imie[20];<br />

int Wiek;<br />

char Status[30];<br />

char Tel_Nr[10];<br />

};<br />

static struct Ludzie<br />

Czlowiek1={"Ala", 7, "Ta, co ma Asa","?"},<br />

Czlowiek2={"Patrycja", 13, "Corka", "8978987"},<br />

Czlowiek3={"Krzysztof", 27, "Kolega z przedszkola", "23478"};<br />

struct Ludzie C4, C5;<br />

C4=Czlowiek3;<br />

C4.Wiek=Czlowiek2.Wiek;<br />

C5=Czlowiek1;<br />

clrscr();<br />

printf("%s %d %s\n", C4.Imie, C4.Wiek, C4.Status);<br />

printf("%s %s",C5.Imie, C5.Status);<br />

return 0;<br />

}<br />

Tablice mogą być elementami struktur, ale i odwrotnie - ze<br />

struktur, jak z cegiełek można tworzyć konstrukcje o wyższym<br />

stopniu złożoności - struktury struktur i tablice struktur.<br />

Jeśli tablica składa się z liczb typu int, to deklarujemy ją:<br />

int TABLICA[10];<br />

jeśli tablica składa się ze struktur, to deklarujemy ją:<br />

struct TABLICA[50];<br />

W przykładzie poniżej przedstawiono<br />

- 140-


* deklarację jednowymiarowej tablicy LISTA[50],<br />

* elementami tablicy są struktury typu SCzlowiek,<br />

* jednym z elementów każdej struktury SCzlowiek jest struktura<br />

"niższego rzędu" typu Adres;<br />

[P038.CPP]<br />

int main()<br />

{<br />

struct Adres<br />

{<br />

char Ulica[30];<br />

int Nr_Domu;<br />

int Nr_Mieszk;<br />

};<br />

struct SCzlowiek<br />

{<br />

char Imie[20];<br />

int Wiek;<br />

struct Adres Mieszkanie;<br />

};<br />

struct SCzlowiek LISTA[50];<br />

LISTA[1].Wiek=34;<br />

LISTA[1].Mieszkanie.Nr_Domu=29;<br />

printf("%d", LISTA[1].Mieszkanie.Nr_Domu);<br />

return 0;<br />

}<br />

Zapis<br />

printf("%d", LISTA[1].Mieszkanie.Nr_Domu<br />

oznacza:<br />

* wybierz element nr 1 z tablicy LISTA;<br />

(jak wynika z deklaracji tablicy, każdy jej element będzie miał<br />

wewnętrzną strukturę zorganizowaną tak, jak opisano w deklaracji<br />

struktury SCzlowiek);<br />

* wybierz ze struktury typu SCzlowiek pole Mieszkanie;<br />

(jak wynika z deklaracji, pole Mieszkanie będzie miało<br />

wewnętrzną organizację zgodną ze strukturą Adres);<br />

* ze struktury typu Adres wybierz pole Nr_Domu;<br />

* Wydrukuj zawartość pola pamięci interpretując ją jako liczbę<br />

typu int - w formacie %d.<br />

Słowo struktura tak doskonale pasuje, że chciałoby się<br />

powiedzieć:<br />

jeśli struktura struktur jest wielopoziomowa, to podobnie, jak<br />

przy wielowymiarowych tablicach, każdy poziom przy nadawaniu<br />

wartości musi zostać ujęty w dodatkową parę nawiasów klamrowych.<br />

- 141-


[???] A CO Z ŁAŃCUCHAMI ZNAKOWYMI ?<br />

________________________________________________________________<br />

Język <strong>C++</strong> oferuje do kopiowania łańcuchów znakowych specjalną<br />

funkcję strcpy(). Nazwa funkcji to skrót STRing CoPY (kopiuj<br />

łańcuch). Sposób wykorzystania tej funkcji:<br />

strcpy(Dokąd, Skąd); lub<br />

strcpy(Dokąd, "łańcuch znaków we własnej osobie");<br />

Szczegóły - patrz Lekcja o łańcuchach znakowych.<br />

________________________________________________________________<br />

STRUKTURY I WSKAŹNIKI.<br />

Wskaźniki mogą wskazywać strukturę w całości lub element<br />

struktury. Język C/<strong>C++</strong> oferuje specjalny operator -> który<br />

pozwala na odwoływanie się do elementów struktury. W przykładzie<br />

poniżej przedstawiono różne sposoby odwołania się do elementów<br />

trzech identycznych struktur STA, STB, STC.<br />

[P039.CPP]<br />

int main()<br />

{<br />

struct<br />

{<br />

char Tekst[20];<br />

int Liczba1;<br />

float Liczba2;<br />

} STA, STB, STC, *Pointer;<br />

STA.Liczba1 = 1;<br />

STA.Liczba2 = 2.2;<br />

strcpy(STA.Tekst, "To jest tekst");<br />

STB=STA;<br />

Pointer = &STC;<br />

Pointer->Liczba1 = 1;<br />

Pointer->Liczba2 = 2.2;<br />

strcpy(Pointer->Tekst, STA.Tekst);<br />

printf("\nLiczba1-STA Liczba2-STB Tekst-STC\n\n");<br />

printf("%d\t", STA.Liczba1);<br />

printf("%f\t", STB.Liczba2);<br />

printf("%s", Pointer->Tekst);<br />

return 0;<br />

}<br />

- 142-


Rozszyfrujmy zapis:<br />

strcpy(Pointer->Tekst, STA.Tekst);<br />

Skopiuj łańcuch znaków z pola Tekst struktury STA do pola Tekst<br />

struktury wskazywanej przez pointer. Prawda, że to całkiem<br />

proste?<br />

[???] CZY MUSIMY TO ROZDZIELAĆ ?<br />

________________________________________________________________<br />

Jak zauważyłeś, liczby moglibyśmy zapisywać także jako łańcuchy<br />

znaków, ale wtedy nie moglibyśmy wykonywać na tych liczbach<br />

działań. Konwersję liczba - łańcuch znaków lub odwrotnie łańcuch<br />

znaków - liczba wykonują w C specjalne funkcje np.:<br />

atoi() - Ascii TO Int.;<br />

itoa() - Int TO Ascii itp.<br />

Więcej informacji na ten temat i przykłady znajdziesz w dalszej<br />

części książki.<br />

________________________________________________________________<br />

Elementami struktury mogą być zmienne dowolnego typu, łądznie z<br />

innymi strukturami.<br />

Ciekawostka:<br />

________________________________________________________________<br />

Wskaźnik do deklarowanej struktury może być w języku C/<strong>C++</strong> jak<br />

jeden z jej WŁASNYCH elementów. Jeśli wskaźnik wchodzący w skład<br />

struktury wskazuje na WŁASNĄ strukturę, to nazywa się to<br />

AUTOREFERENCJĄ STRUKTURY.<br />

________________________________________________________________<br />

POLA BITOWE.<br />

Często zdarza się, że jakaś zmienna ma zawężony zakres wartości.<br />

Dla przykładu zmienne logiczne (tzw. flagi) to zawsze tylko 0<br />

lub 1. Wiek rzadko przekracza 255 lat a liczba dzieci zwykle nie<br />

jest większa niż 15. Nawet najbardziej niestali panowie nie<br />

zdążą ożenić się i rozwieść więcej niż 7 razy. Gdybyśmy zatem<br />

chcieli zapisać informacje<br />

* płeć 0 - mężczyzna, 1 - kobieta ( 1 bit );<br />

* wiek 0 - 255 lat (8 bitów);<br />

* ilość dzieci 0 - 15 (4 bity);<br />

* kolejny numer małżeństwa 0 - 7 (3 bity);<br />

to przecież wszystkie te informacje mogą nam się zmieścić w<br />

jednym szesnastobitowym rejestrze lub w dwu bajtach pamięci.<br />

Takie kilka bitów wydzielone i mające określone znaczenie to<br />

właśnie pole bitowe. <strong>C++</strong> pozwala także na uwzględnianie znaku w<br />

polach bitowych. Pola bitowe mogą być typu int i unsigned int<br />

- 143-


(czyli takie jak w przykładzie poniżej). Jeśli jakieś dane<br />

chcemy przechowywać w postaci pola bitowego, w deklaracji<br />

struktury sygnalizujemy to dwukropkiem. Stwarza to dwie istotne<br />

możliwości:<br />

* bardziej ekonomicznego wykorzystania pamięci;<br />

* łatwego dodatkowego zaszyfrowania danych.<br />

[P040.CPP]<br />

//Pamietaj o dolaczeniu plikow naglowkowych !<br />

int main()<br />

{<br />

struct USC {<br />

int Sex : 1;<br />

unsigned Wiek : 8;<br />

unsigned Dzieci : 4;<br />

unsigned Ktora : 3; } Facet;<br />

int bufor;<br />

clrscr();<br />

Facet.Sex = 0;<br />

printf("\n Ile ma lat ? : ");<br />

scanf("%d", &bufor); Facet.Wiek = bufor;<br />

printf("\n Ktore malzenstwo ? : ");<br />

scanf("%d", &bufor); Facet.Ktora = bufor;<br />

printf("\n Ile dzieci ? : ");<br />

scanf("%d", &bufor); Facet.Dzieci = bufor;<br />

printf("\n\n");<br />

if (Facet.Ktora) printf("Facet ma %d zone", Facet.Ktora);<br />

printf("\nPlec: Dzieci: Wiek (lat): \n\n");<br />

printf("%d\t%d\t%d", Facet.Sex, Facet.Dzieci, Facet.Wiek);<br />

getch();<br />

return 0;<br />

}<br />

Uruchom program i sprawdź co się stanie, jeśli Facet będzie miał<br />

np. 257 lat lub 123 żonę. Przekroczenie zadeklarowanego zakresu<br />

powoduje obcięcie części bitów.<br />

Aby uzyskać "wyrównanie" pola bitowego do początku słowa należy<br />

przed interesującym naspolem bitowym zdefiniować tzw. pole<br />

puste:<br />

* pole bitowe bez nazwy;<br />

* długość pola pustego powinna wynosić 0.<br />

Poniżej przedstawiam przykład pola bitowego zajmującego trzy<br />

kolejne słowa 16 bitowe. Dodanie pola pustego wymusza<br />

rozpoczęcie pola pole_IV od początku trzeciego słowa maszynowego<br />

(zakładamy, że pracujemy z komputerem 16 bitowym).<br />

- 144-


struct<br />

{<br />

unsigned pole_I:4;<br />

unsigned pole_II:10;<br />

unsigned pole_III:4;<br />

unsigned :0; /* to jest pole puste */<br />

unsigned pole_IV:5;<br />

} pole_przykladowe;<br />

Zwróć uwagę, że część bitów w drugim i trzecim słowie maszynowym<br />

nie zostanie wykorzystana.<br />

UNIE czyli ZMIENNE WARIANTOWE.<br />

Unie to specyficzne struktury, w których pola pamięci<br />

przeznaczone na objekty różnego typu nakładają się. Jeśli jakaś<br />

zmienna może być reprezentowana na kilka sposobów (wariantów) to<br />

sensowne jest przydzielenie jej nie struktury a unii. W danej<br />

chwili pole pamięci należące do unii może zawierać TYLKO JEDEN<br />

WARIANT. W przykładzie - albo cyfrę (która znakowo jest widziana<br />

jako znak ASCII o kodzie 2,3,4 itd.) albo napis. Do<br />

zadeklarowania unii służy słowo kluczowe union.<br />

[P041.CPP]<br />

#include "string.h"<br />

#include "stdio.h"<br />

int BUFOR, i;<br />

int main()<br />

{<br />

union<br />

{<br />

int Cyfra;<br />

char Napis[20];<br />

} Unia;<br />

for (i=1; i


printf(" %d\t\t\t%s", Unia.Cyfra, Unia.Napis);<br />

}<br />

return 0;<br />

}<br />

Pętla w przykładzie nie ma znaczenia. Służy tylko dla Twojej<br />

wygody - dzięki niej nie musisz uruchamiać programu<br />

przykładowego wielokrotnie. Podobnie zmienne BUFOR oraz i mają<br />

znaczenie pomocnicze. Zwróć uwagę, że nieprawidłowa<br />

interpretacja zawartości pola unii może spowodować wadliwe<br />

działanie programu.<br />

[Z]<br />

________________________________________________________________<br />

1. W programie przykładowym zamień unię na strukturę. Porównaj<br />

działanie.<br />

2 Przydziel na Wiek w strukturze Facet o jeden bit mniej. Ile<br />

lat może teraz mieć Facet ?<br />

3. Zmodyfikuj program przykładowy tak, by napis o liczbie<br />

mężów/żon zależał od płci - pola Sex.<br />

4. Zamieniwszy unię na strukturę w programie, sprawdź, czy<br />

wpływa to na wielkość pliku *.EXE.<br />

________________________________________________________________<br />

OPERACJE LOGICZNE.<br />

Zaczniemy od operacji logicznych na pojedynczych bitach liczb<br />

całkowitych. W <strong>C++</strong> mamy do dyspozycji następujące operatory:<br />

~€€€€Zaprzeczenie (NOT) ~0=1; ~1=0;<br />

|€€€€Suma (OR) 0|0=0; 0|1=1; 1|0=1; 1|1=1;<br />

&€€€€Iloczyn (AND) 0&0=0; 0&1=0; 1&0=0; 1&1=1;<br />

^€€€€Alternatywa wyłączna ALBO...ALBO (XOR)<br />

€€€€€0^0=0; 0^1=1; 1^0=1; 1^1=0;<br />

>2=2<br />

Miło byłoby pooglądać to trochę dokładniej w przykładowych<br />

programach, ale potrzebne nam do tego będą funkcje. Zajmijmy się<br />

więc uważniej funkcjami.<br />

- 146-


LEKCJA 15. Jak posługiwać się funkcjami.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się więcej o:<br />

* funkcjach i prototypach funkcji;<br />

* przekazywaniu argumentów funkcji;<br />

* współpracy funkcji ze wskaźnikami.<br />

_______________________________________________________________<br />

Aby przedstawić działanie operatorów logicznych opracujemy<br />

własną funkcję Demo() i zastosujemy ją w programie przykładowym<br />

[najważniejszy fragment].<br />

int Demo(int Liczba)<br />

{<br />

int MaxNr=15;<br />

for (; MaxNr>=0; MaxNr--)<br />

{<br />

if ((Liczba>>MaxNr)&1)<br />

printf("1");<br />

else<br />

printf("0");<br />

}<br />

return 0; //Funkcja nie musi nic zwracac<br />

}<br />

Funkcja przesuwa liczbę o kolejno 15, 14, 13 itd. bitów w prawo<br />

i sprawdza, czy 16, 15, 14 bit jest jedynką, czy zerem. Iloczyn<br />

logiczny z jedynką ( 0000000000000001 ) gwarantuje nam, że wpływ<br />

na wynik operacji będzie miał tylko ten jeden bit (patrz wyżej -<br />

jak działają operatory logiczne).<br />

[P042.CPP]<br />

# include <br />

int Demo(int Liczba)<br />

{<br />

int MaxNr=15;<br />

for (; MaxNr>=0; MaxNr--)<br />

if ((Liczba>>MaxNr)&1) printf("1");<br />

else printf("0");<br />

return 0;<br />

}<br />

char odp;<br />

int main()<br />

{<br />

int X, Y;<br />

clrscr();<br />

printf("\nPodaj dwie liczby calkowite od -32768 do +32767\n");<br />

printf("\nLiczby X i Y rozdziel spacja");<br />

printf("\nPo podaniu drugiej liczby nacisnij [Enter]");<br />

printf("\nLiczby ujemne sa w kodzie dopelniajacym");<br />

printf("\nskrajny lewy bit oznacza znak 0-Plus, 1-Minus");<br />

- 147-


for(;;)<br />

{<br />

printf("\n");<br />

scanf("%d %d", &X, &Y);<br />

printf("\nX:\t"); Demo(X);<br />

printf("\nY:\t"); Demo(Y);<br />

printf("\n~Y:\t"); Demo(~Y);<br />

printf("\nX&Y:\t"); Demo(X&Y);<br />

printf("\nX|Y:\t"); Demo(X|Y);<br />

printf("\nX^Y:\t"); Demo(X^Y);<br />

printf("\nY:\t"); Demo(Y);<br />

printf("\nY>>1:\t"); Demo(Y>>1);<br />

printf("\nY


np:<br />

itoa() - Integer TO Ascii - zamiana liczby typu int na łańcuch<br />

znaków ASCII;<br />

ltoa() - Long int TO Ascii - zamiana long int -> ASCII;<br />

atoi() - zamiana Ascii -> int;<br />

atol() - zamiana Asdii -> long int .<br />

Wszystkie wymienione funkcje przekształcając liczby na łańcuchy<br />

znaków potrzebują trzech parametrów:<br />

p1 - liczby do przekształcenia;<br />

p2 - bufora, w którym będą przechowywać wynik - łańcuch ASCII;<br />

p3 - podstawy (szesnastkowa, dziesiętna itp.).<br />

Jeśli chcemy korzystać z tych funkcji, powinniśmy dołączyć plik<br />

nagłówkowy z ich prototypami - stdlib.h (STandarD LIBrary -<br />

standardowa biblioteka). A oto przykład.<br />

[P043.CPP]<br />

# include "stdio.h"<br />

# include "stdlib.h"<br />

main()<br />

{<br />

int i;<br />

char B10[10], B2[20], B16[10]; //BUFORY<br />

for (i=1; i


odzaju typu zmiennej "bardziej pojemnego" rodzaju przed<br />

wykonaniem operacji;<br />

Po drugie:<br />

my sami możemy zmusić <strong>C++</strong> do zmiany typu FORSUJĄC typ świadomie<br />

w programie.<br />

W przykładzie poniżej podając w nawiasach żądany typ zmiennej<br />

forsujemy zmianę typu int na typ float.<br />

[P044.CPP]<br />

# include "stdio.h"<br />

void main()<br />

{<br />

int a=7;<br />

printf("%f", (float) a);<br />

}<br />

Konwersja typów nazywana bywa także "rzutowaniem" typów (ang.<br />

type casting). A oto kilka przykładów "forsowania typów":<br />

int a = 2;<br />

float x = 17.1, y = 8.95, z;<br />

char c;<br />

c = (char)a + (char)x;<br />

c = (char)(a + (int)x);<br />

c = (char)(a + x);<br />

c = a + x;<br />

z = (float)((int)x * (int)y);<br />

z = (float)((int)x * (int)y);<br />

z = (float)((int)(x * y));<br />

z = x * y;<br />

c = char(a) + char(x);<br />

c = char(a + int(x));<br />

c = char(a + x);<br />

c = a + x;<br />

z = float(int(x) * int(y));<br />

z = float(int(x) * int(y));<br />

z = float(int(x * y));<br />

z = x * y;<br />

FUNKCJE BIBLIOTECZNE I WŁASNE W JĘZYKU C/<strong>C++</strong> .<br />

Pojęcie funkcji obejmuje w C/<strong>C++</strong> zarówno pascalowe procedury,<br />

jak i basicowe podprogramy. Funkcji zdefiniowanych w <strong>C++</strong> przez<br />

prducenta jest bardzo dużo. Dla przykładu, funkcje arytmetyczne,<br />

które możesz wykorzystać do obliczeń numerycznych to np.:<br />

abs() - wartość bezwzględna,<br />

- 150-


cos() - cosinus, sin() - sinus, tan() - tangens,<br />

asin(), atan(), acos(), - funkcje odwrotne ARCUS SINUS...<br />

funkcje hiperboliczne: sinh(), cosh(), tanh(),<br />

wykładnicze i logarytmiczne:<br />

exp() - e^x<br />

log() - logarytm naturalny,<br />

log10() - logarytm dziesiętny.<br />

Jeśli skorzystasz z systemu Help i zajrzysz do pliku math.h<br />

(Help | Index | math.h), znajdziesz tam jeszcze wiele<br />

przydatnych funkcji.<br />

Funkcja może, ale nie musi zwracać wartość do programu -<br />

dokładniej do funkcji wyższego poziomu, z której została<br />

wywołana. W ciele funkcji służy do tego instrukcja return.<br />

Użytkownik może w <strong>C++</strong> definiować własne funkcje. Funkcja może<br />

być bezparametrowa. Oto przykład bezparametrowej funkcji,<br />

zwracającej zawsze liczbę całkowitą trzynaście:<br />

int F_Trzynascie()<br />

{<br />

return 13;<br />

}<br />

Poprawne wywołanie naszej funkcji w programie głównym miałoby<br />

postać:<br />

int main()<br />

{<br />

......<br />

int X;<br />

........ // Funkcja typu int nie musi byc deklarowana.<br />

X = F_Trzynascie();<br />

......<br />

}<br />

Jeśli funkcja musi pobrać jakieś parametry od programu (funkcji<br />

wyższego poziomu, wywołującej)? Zwróć uwagę, że program główny w<br />

C/<strong>C++</strong> to też funkcja - main(). Przykład następny pokazuje<br />

definicję funkcji obliczającej piątą potęgę pobranego argumentu<br />

i wywołanie tej funkcji w programie głównym.<br />

Przykład:<br />

int F_XdoPiatej(int argument)<br />

{<br />

int robocza; //automatyczna wewnetrzna zmienna funkcji<br />

robocza = argument * argument;<br />

robocza = robocza * robocza * argument;<br />

return (robocza);<br />

}<br />

int main()<br />

- 151-


{<br />

int Podstawa, Wynik, a, b;<br />

... /* Funkcja nie jest deklarowana przed uzyciem */<br />

Wynik = F_XdoPiatej(Podstawa);<br />

.....<br />

a = F_XdoPiatej(b);<br />

.....<br />

return 0;<br />

}<br />

Zwróć uwagę, że definiując funkcję podajemy nazwę i typ<br />

ARGUMENTU FORMALNEGO funkcji - tu: argument. W momencie<br />

wywołania na jego miejsce podstawiany jest rzeczywisty bieżący<br />

argument funkcji.<br />

Aby zapewnić wysoką dokładność obliczeń wymienione wyżej funkcje<br />

biblioteczne sqrt(), sin() itp. "uprawiają" arytmetykę na<br />

długich liczbach typu double. Funkcję taką przed użyciem w swoim<br />

programie MUSISZ ZADEKLAROWAĆ. Przykład:<br />

[P045.CPP]<br />

main()<br />

{<br />

double a, b;<br />

double sqrt();<br />

// tu skasuj deklaracje funkcji sqrt()<br />

// a otrzymasz bledny wynik !<br />

clrscr();<br />

printf("Podaj liczbe\n");<br />

scanf("%lf", &a);<br />

b = sqrt(a);<br />

printf("\n %Lf", (long double) b);<br />

getch();<br />

return 0;<br />

}<br />

PROTOTYPY FUNKCJI, czyli jeszcze o deklaracjach funkcji.<br />

Prototyp funkcji to taka deklaracja, która:<br />

* została umieszczona na początku programu poza funkcją main(),<br />

* zawiera deklarację zarówno typu funkcji, jak i typów<br />

argumentów.<br />

Przykład prototypu (funkcja2.cpp):<br />

double FUNKCJA( double X, double Y);<br />

main()<br />

{<br />

double A=0, B=3.14;<br />

printf("Wynik działania funkcji: \n");<br />

printf("%lf", FUNKCJA(A,B));<br />

- 152-


eturn 0; }<br />

double FUNKCJA(double X, double Y)<br />

{<br />

return ((1+X)*Y);<br />

}<br />

Prototyp mógłby równie dobrze wyglądać tak:<br />

double FUNKCJA(double, double);<br />

nazwy parametrów formalnych nie są istotne i można je pominąć.<br />

Jeśli prototyp funkcji wygląda tak:<br />

int Funkcja(int, char*, &float)<br />

oznacza to, że parametrami funkcji są wskaźniki do zmiennych,<br />

bądź referencje do zmiennych. Przy rozszyfrowywaniu takiej<br />

"abrakadabry" warto wiedzieć, że<br />

char* oraz char *<br />

int& oraz int &<br />

ma w tym przypadku identyczne znaczenie.<br />

W <strong>C++</strong> wolno nie zwracać wartości funkcjom typu void. To dlatego<br />

właśnie często rozpoczynaliśmy programy od<br />

void main()<br />

Skutek praktyczny: Jeśli w ciele funkcji typu void występuje<br />

instrukcja return (nie musi wystąpić) to instrukcja ta nie może<br />

mieć argumentów.<br />

Oto przykład prototypu, definicji i wywołania funkcji typu void:<br />

[P046.CPP]<br />

#include <br />

#include <br />

void RYSUJPROSTOKAT( int Wys, int Szer, char Wzorek);<br />

void main()<br />

{<br />

clrscr();<br />

RYSUJPROSTOKAT(5, 20, '€'); // klocek ASCII 176 - [Alt]-[176]<br />

getch();<br />

RYSUJPROSTOKAT(15, 15, '€'); //[Alt]-[177]<br />

getch();<br />

}<br />

void RYSUJPROSTOKAT( int Wys, int Szer, char Wzorek)<br />

{<br />

- 153-


int i, j; // automatyczne zmienne wewnętrzne funkcji<br />

for(i=1; i


{<br />

int Zmienna;<br />

clrscr();<br />

Zmienna = 7;<br />

//Zmienna funkcji main, rzeczywisty argument<br />

FUNKCJA( Zmienna); //Wywolanie funkcji<br />

printf("%d", Zmienna); //Wydruk wyniku<br />

}<br />

void FUNKCJA( int Argument) //Definicja funkcji<br />

{<br />

Argument = 10 * Argument + Argument;<br />

}<br />

FUNKCJA() jest jak widać trywialna. będzie zamieniać np. 2 na<br />

22, 3 na 33 itp. tylko w tym celu, by łatwo było stwierdzić, czy<br />

funkcja zadziałała czy nie.<br />

Rozbudujmy program tak by prześledzić kolejne stadia.<br />

[P047.CPP]<br />

void FUNKCJA( int ); //Prototyp<br />

int Zmienna;<br />

void main()<br />

{<br />

clrscr();<br />

printf("Stadium: \tZmienna Argument");<br />

printf("\nStadium 1\t%d\tnie istnieje\n", Zmienna);<br />

Zmienna = 7;<br />

printf("Stadium 2\t%d\tnie istnieje\n", Zmienna );<br />

FUNKCJA( Zmienna);<br />

printf("Stadium 3\t%d", Zmienna);<br />

// printf("%d", Argument);<br />

// taka proba sie NIE UDA !<br />

getch();<br />

}<br />

void FUNKCJA( int Argument) //Definicja funkcji<br />

{<br />

printf("jestesmy wewnatrz funkcji\n");<br />

printf("Nastapilo kopiowanie Zmienna -> Argument\n" );<br />

printf("\t\t%d\t%d\n", Zmienna, Argument);<br />

getch();<br />

Argument = 10*Argument + Argument;<br />

printf("\t\t%d\t%d\n", Zmienna, Argument);<br />

getch();<br />

}<br />

Próba wydrukowania zmiennej Argument gdziekolwiek poza wnętrzem<br />

FUNKCJI() nie uda się i spowoduje komunikat o błędzie. Oznacza<br />

- 155-


to, że POZA FUNKCJĄ zmienna Argument NIE ISTNIEJE. Jest tworzona<br />

na stosie jako zmienna automatyczna na wyłączny użytek funkcji,<br />

w której została zadeklarowana i znika po wyjściu z funkcji.<br />

Przy takiej organizacji funkcji i programu funkcja otrzymuje<br />

kopię zmiennej, na niej wykonuje swoje działania, natomiast<br />

zmienna (zmienne) wewnętrzna funkcji znika po wyjściu z funkcji.<br />

Problem przekazania parametrów pomiędzy funkcjami wywołującymi<br />

("wyższego rzędu" - tu: main) i wywoływanymi (tu: FUNKCJA) można<br />

rozwiązać przy pomocy<br />

* instrukcji return (zwrot do programu jednej wartości) lub<br />

* wskaźników.<br />

Możemy przecież funkcji przekazać nie samą zmienną, a wskaźnik<br />

do zmiennej (robiliśmy to już w przypadku funkcji scanf() -<br />

dlatego, że samej zmiennej jeszcze nie było - miała zostać<br />

dopiero pobrana, ale istniało już przeznaczone na tą nową<br />

zmienną - zarezerwowane dla niej miejsce. Mogł zatem istnieć<br />

wskaźnik wskazujący to miejsce). wskaźnik należy oczywiście<br />

zadeklarować. Nasz program przybrałby zatem nową postać.<br />

Wskaźnik do zmiennej nazwiemy *Argument.<br />

[P048.CPP]<br />

//Pamietaj o plikach naglowkowych !<br />

void FUNKCJA( int *Argument); //Prototyp<br />

int Zmienna;<br />

void main()<br />

{<br />

clrscr();<br />

printf("Stadium: \tZmienna Argument");<br />

printf("\nStadium 1\t%d\tnie istnieje\n", Zmienna);<br />

Zmienna = 7;<br />

printf("Stadium 2\t%d\tnie istnieje\n", Zmienna );<br />

FUNKCJA( &Zmienna); //Pobierz do funkcji ADRES Zmiennej<br />

printf("Stadium 3\t%d", Zmienna);<br />

// printf("%d", Argument);<br />

// taka proba sie NIE UDA !<br />

getch();<br />

}<br />

void FUNKCJA( int *Argument) // Definicja funkcji<br />

{<br />

printf("jestesmy wewnatrz funkcji\n");<br />

printf("Nastapilo kopiowanie ADRESOW a nie zmiennej\n" );<br />

printf("ADRESY:\t\t %X\t%X\n", &Zmienna, Argument);<br />

getch();<br />

*Argument = 10* *Argument + *Argument; /* DZIALANIE */<br />

printf("\t\t%d\t%d\n", Zmienna, *Argument);<br />

getch();<br />

}<br />

- 156-


W linii /* DZIALANIE */ mnożymy i dodajemy to, co wskazuje<br />

wskaźnik, czyli Zmienną. Funkcja działa zatem nie na własnej<br />

kopii zmiennej a bezpośrednio na zmiennej zewnętrznej. Zwróć<br />

uwagę na analogię w sposobie wywołania funkcji:<br />

FUNKCJA( &Zmienna );<br />

scanf( "%d", &Zmienna );<br />

A jeśli argumentem funkcji ma być tablica? Rozważ przykładowy<br />

program. Program zawiera pewną nadmiarowość (ku większej<br />

jasności mechanizmów).<br />

[P049.CPP]<br />

# include <br />

# include <br />

SUMA( int k, int Tablica[] )<br />

{<br />

int i, SumTab=0;<br />

for (i=0; i10)<br />

{ printf("TO ZA DUZO ! - max. 10");<br />

continue;<br />

}<br />

suma = SUMA( N,TAB );<br />

printf("\nTO JEST suma z progr. glownego %d", suma);<br />

printf("\n Jeszcze raz ? T/N");<br />

Odp = getch();<br />

}<br />

while (Odp!='N' && Odp!='n');<br />

return 0;<br />

}<br />

Kompilacja w <strong>C++</strong> jest wieloprzebiegowa (PASS 1, PASS 2), więc<br />

- 157-


definicja funkcji może być zarówno na początku jak i na końcu.<br />

A oto następny przykład. Operując adresem - wskaźnikiem do<br />

obiektu (tu wskaźnikami do dwu tablic) funkcja Wypelniacz()<br />

zapisuje pod wskazany adres ciąg identycznych znaków. Na końcu<br />

każdego łańcucha znaków zostaje dodany NUL - (\0) jako znak<br />

końca. Taki format zapisu łańcuchów znakowych nazywa się ASCIIZ.<br />

[P050.CPP]<br />

void Wypelniacz(char *BUFOR, char Znak, int Dlugosc);<br />

char TAB2D[5][10]; // Tablica 5 X 10 = 50 elementow<br />

char TAB_1D[50]; // Tablica 1 X 50 = 50 elementow<br />

int k;<br />

main()<br />

{<br />

clrscr();<br />

Wypelniacz( TAB_1D, 'X', 41); //Wypelnia X-ami<br />

printf("%s\n\n", TAB_1D);<br />

for (k=0; k


[P051.CPP]<br />

int BALAGAN[10];<br />

int PORZADEK[10]; // Tablica koncowa - uporzadkowana<br />

int k, *pointer , MAX=10000 ;<br />

int *Minimum(int Ilosc, int *TABL);<br />

main()<br />

{<br />

clrscr();<br />

printf("Podaj 10 liczb calkowitych od -10000 do 10000\n");<br />

for (k=0; k


takie funkcje, które<br />

* pobierają jeden argument typu double float;<br />

* zwracają do programu wartość typu double float. "<br />

Dostępne są dla nas zatem wszystkie standardowe funkcje<br />

arytmetyczne z pliku MATH.H (MATH pochodzi od MATHematics -<br />

matematyka.)<br />

[P052.CPP]<br />

# include <br />

# include <br />

double NASZA( double ); //Deklaracja zwyklej funkcji<br />

double (*Funkcja)(double ARG); //pointer do funkcji<br />

double Liczba, Wynik; //Deklaracje zmiennych<br />

int WYBOR;<br />

main()<br />

{<br />

clrscr();<br />

printf("Podaj Liczbe \n");<br />

scanf("%lf", &Liczba);<br />

printf("CO MAM ZROBIC ?\n");<br />

printf("1 - Sinus \n");<br />

printf("2 - Pierwiastek\n");<br />

printf("3 - Odwrotnosc 1/x\n");<br />

scanf("%d", &WYBOR);<br />

switch(WYBOR)<br />

{<br />

case 1: Funkcja=sin; break;<br />

case 2: Funkcja=sqrt; break;<br />

case 3: Funkcja=NASZA; break;<br />

}<br />

Wynik=Funkcja(Liczba); // Wywolanie wybranej funkcji<br />

printf("\n\nWYNIK = %lf", Wynik);<br />

getch();<br />

return 0;<br />

}<br />

double NASZA(double a)<br />

{<br />

printf("\n A TO NASZA PRYWATNA FUNKCJA\n");<br />

if (a!=0) a=1/a; else printf("???\n");<br />

return a;<br />

}<br />

main() - FUNKCJA SPECJALNA.<br />

Ta książka siłą rzeczy, ze względu na swoją skromną objętość i<br />

skalę zagadnienia o którym traktuje (autor jest zdania, że język<br />

C to cała filozofia nowoczesnej informatyki "w pigułce") pełna<br />

jest skrótów. Nie możemy jednak pozostawić bez, krótkiego<br />

- 160-


choćby, opisu pomijanego dyskretnie do tej pory problemu<br />

PRZEKAZANIA PARAMETRÓW DO PROGRAMU.<br />

Konwencja funkcji w języku C/<strong>C++</strong> wyraźnie rozgranicza dwa różne<br />

punkty widzenia. Funkcja pozwala na swego rodzaju separację<br />

świata wewnętrznego (lokalnego, własnego) funkcji od świata<br />

zewnętrznego. Nie zdziwi Cię więc zapewne, że i sposób widzenia<br />

parametrów przekazywanych programowi przez DOS i sposób widzenia<br />

"od wewnątrz" argumentów pobierabych przez funkcję main() jest<br />

diametralnie różny.<br />

To, co DOS widzi tak:<br />

PROGRAM PAR1 PAR2 PAR3 PAR4 PAR5 [...][Enter]<br />

funkcja main() widzi tak:<br />

main(int argc, char **argv, char **env)<br />

lub tak:<br />

main(int argc, char *argv[], char *env[])<br />

[???]CO TO JEST ???<br />

________________________________________________________________<br />

Zapisane zgodnie z obyczajami stosowanymi w prototypach funkcji:<br />

int argc - liczba całkowita (>=1, bo parametr Nr 1 to nazwa<br />

samego programu, za pośrednictwem której DOS wywołuje funkcję<br />

main). Liczba argumentów - parametrów może być zmienna.<br />

UWAGA: Język programowania wsadowego BPL przyjmuje nazwę<br />

programu za parametr %0 a <strong>C++</strong> uznaje ją za parametr o numerze<br />

argv[0], tym niemniej, nawet jeśli nie ma żadnych parametrów<br />

argc = 1.<br />

argv - to tablica zawierająca wskaźniky do łańcuchów tekstowych<br />

reprezentowanych w kodzie ASCIIZ - nazw kolejnych paramentrów, z<br />

którymi został wywołany program.<br />

Pierszy element tej tablicy to nazwa programu. Ostatni element<br />

tej tablicy, o numerze argv - 1 to ostatni niezerowy parametr<br />

wywołania programu.<br />

env - to także tablica zawierająca wskaźniki do łańcuchów<br />

znakowych w kodzie ASCIIZ reprezentujących parametry środowiska<br />

(environment variables). Wskaźnik o wartości NUL sygnalizuje<br />

koniec tablicy. W Turbo <strong>C++</strong> istnieje także predefiniowana<br />

zmienna globalna (::), przy pomocy której można uzyskać dostęp<br />

do środowiska operacyjnego - environ .<br />

________________________________________________________________<br />

Przykłady poniżej przedstawiają sposób wykorzystania parametrów<br />

wejściowych programu.<br />

- 161-


[P053.CPP]<br />

# include "stdio.h"<br />

# include "stdlib.h"<br />

main(int argc, char *argv[], char *env[])<br />

{<br />

printf("Parametry srodowiska DOS: \n");<br />

int i = 0;<br />

do<br />

{<br />

printf("%s \n", env[i]);<br />

i++;<br />

};<br />

while (env[i] != NULL);<br />

printf("Lista parametrow programu: \n");<br />

for(i=1; i


3 Spróbuj przeprowadzić rzutowanie typu we własnym programie.<br />

4 Przekaż wartość w programie przykładowym posługując się<br />

instrukcją:<br />

return (10*Argument + Argument);<br />

5 Rozszerz zestaw funkcji do wyboru w programie przykładowym.<br />

________________________________________________________________<br />

- 163-


LEKCJA 16. ASEMBLER TASM i BASM.<br />

________________________________________________________________<br />

W trakcie tej lekcji:<br />

* dowiesz się , jak łączyć <strong>C++</strong> z assemblerem<br />

* poznasz wewnętrzne formaty danych<br />

________________________________________________________________<br />

WEWNĘTRZNY FORMAT DANYCH I WSPÓŁPRACA Z ASSEMBLEREM.<br />

W zależności od wybranej wersji kompilatora <strong>C++</strong> zasady<br />

współpracy z asemblerem mogą się trochę różnić. Generalnie,<br />

kompilatory współpracują z tzw. asemblerami in-line (np. BASM),<br />

lub asemblerami zewnętrznymi (stand alone assembler np. MASM,<br />

TASM). Wstawki w programie napisane w assemblerze powinny zostać<br />

poprzedzone słowem asm (BORLAND/Turbo <strong>C++</strong>), bądź _asm (Microsoft<br />

<strong>C++</strong>). Przy kompilacji należy zatem stosownie do wybranego<br />

kompilatora przestrzegać specyficznych zasad współpracy. Np. dla<br />

BORLAND/Turbo <strong>C++</strong> można stosować do kompilacji BCC.EXE/TCC.EXE<br />

przy zachowaniu warunku, że TASM.EXE jest dostępny na dysku w<br />

bieżącym katalogu.<br />

Typowymi sposobami wykorzystania assemblera z poziomu <strong>C++</strong> są:<br />

* umieszczenie ciągu instrukcji assemblera bezpośrednio w<br />

źródłowym tekście programu napisanym w języku C/<strong>C++</strong>,<br />

* dołączeniu do programu zewnętrznych modułów (np. funkcji)<br />

napisanych w assemblerze.<br />

W <strong>C++</strong> w tekście źródłowym programu blok napisany w asemblerze<br />

powinien zostać poprzedzony słowem kluczowym asm (lub _asm):<br />

# pragma inline<br />

void main()<br />

{<br />

asm mov dl, 81<br />

asm mov ah, 2<br />

asm int 33<br />

}<br />

Program będzie drukował na ekranie literę "Q" (ASCII 81).<br />

JAK POSŁUGIWAĆ SIĘ DANYMI W ASEMBLERZE.<br />

Napiszemy w asemblerze program drukujący na ekranie napis "tekst<br />

- test". Rozpczynamy od zadeklarowania łańcucha znaków:<br />

void main()<br />

{<br />

char *NAPIS = "tekst - test$"; /* $ - ozn. koniec */<br />

- 164-


Umieściliśmy w pamięci łańcuch, będący w istocie tablicą<br />

składającą się z elementów typu char. Wskaźnik do łańcucha może<br />

zostać zastąpiony nazwą-identyfikatorem tablicy. Zwróć uwagę, że<br />

po łańcuchu znakowym dodaliśmy znak '$'. Dzięki temu możemy<br />

skorzystać z DOS'owskiej funkcji nr 9 (string-printing DOS<br />

service 9). Możemy utworzyć kod w asemblerze:<br />

asm mov dx, NAPIS<br />

asm mov ah, 9<br />

asm int 33<br />

Cały program będzie wyglądał tak:<br />

[P054.CPP]<br />

# pragma inline<br />

void main()<br />

{<br />

char *NAPIS = "\n tekst - test $";<br />

asm {<br />

MOV DX, NAPIS<br />

MOV AH, 9<br />

INT 33<br />

}<br />

}<br />

Zmienna NAPIS jest pointerem i wskazuje adres w pamięci, od<br />

którego rozpoczyna się łańcuch znaków. Możemy przesłać zmienną<br />

NAPIS bezpośrednio do rejestru i przekazać wprost przerywaniu<br />

Int 33. Program assemblerowski (tu: TASM) mógłby wyglądać np.<br />

tak:<br />

[P055.ASM]<br />

.MODEL SMALL ;To zwylke robi TCC<br />

.STACK 100H ;TCC dodaje standardowo 4K<br />

.DATA<br />

NAPIS DB 'tekst - test','$'<br />

.CODE<br />

START:<br />

MOV AX, @DATA<br />

MOV DS, AX ;Ustawienie segmentu danych<br />

ASM:<br />

MOV DX, OFFSET NAPIS<br />

MOV AH, 9<br />

INT 21H ;Drukowanie<br />

KONIEC:<br />

MOV AH, 4CH<br />

INT 21H ;Zakończenie programu<br />

END START<br />

Inne typy danych możemy stosować podobnie. Wygodną taktyką jest<br />

deklarowanie danych w tej części programu, która została<br />

- 165-


napisana w <strong>C++</strong>, aby inne fragmenty programu mogły się do tych<br />

danych odwoływać. Możemy we wstawce asemblerowskiej odwoływać<br />

się do tych danych w taki sposób, jakgdyby zostały zadeklarowane<br />

przy użyciu dyrektyw DB, bądź DW.<br />

WEWNĘTRZNE FORMATY DANYCH W <strong>C++</strong>.<br />

LICZBY CAŁKOWITE typów char, short int i long int.<br />

Liczba całkowita typu short int stanowi 16-bitowe słowo i może<br />

zostać zastosowana np. w taki sposób:<br />

[P056.CPP]<br />

#pragma inline<br />

void main()<br />

{<br />

char *napis = "\nRazem warzyw: $";<br />

int marchewki = 2, pietruszki = 5;<br />

asm {<br />

MOV DX, napis<br />

MOV AH, 9<br />

INT 33<br />

MOV DX, marchewki<br />

ADD DX, pietruszki<br />

ADD DX, '0'<br />

MOV AH, 2<br />

INT 33<br />

}<br />

}<br />

Zdefiniowaliśmy dwie liczby całkowite i łańcuch znaków - napis.<br />

Ponieważ obie zmienne (łańcuch znków jest stałą) mają długość<br />

jednego słowa maszynowego, to efekt jest taki sam, jakgdyby<br />

zmienne zostały zadeklarowane przy pomocy dyrektywy asemblera DW<br />

(define word). Możemy pobrać wartość zmiennej marchewki do<br />

rejestru instrukcją<br />

MOV DX, marchewki<br />

;marchewki -> DX<br />

W rejestrze DX dokonujemy dodawania obu zmiennych i wyprowadzamy<br />

na ekran sumę, posługując się funkcją 2 przerywania DOS 33<br />

(21H).<br />

W wyniku działania tego programu otrzymamy na ekranie napis:<br />

Razem warzyw: 7<br />

Jeczsze jeden szczegół techniczny. Ponieważ stosowana funkcja<br />

DOS pracuje w trybie znakowym i wydrukuje nam znak o kodzie<br />

ASCII przechowywanym w rejestrze, potrzebna jest manipulacja:<br />

- 166-


ADD DX, '0' ;Dodaj kod ASCII "zera" do rejestru<br />

Możesz sam sprawdzić, że po przekroczeniu wartości 9 przez sumę<br />

wszystko się trochę skomplikuje (kod ASCII zera - 48). Z równym<br />

skutkiem możnaby zastosować rozkaz<br />

ADD DX, 48<br />

Jeśli prawidłowo dobierzemy format danych, fragment programu<br />

napisany w asemblerze może korzystać z danych dokładnie tak<br />

samo, jak każdy inny fragment programu napisany w C/<strong>C++</strong>. Możemy<br />

zastosować dane o jednobajtowej długości (jeśli drugi, pusty<br />

bajt nie jest nam potrzebny). Zwróć uwagę, że posługujemy się w<br />

tym przypadku tylko "połówką" rejestru DL (L - Low - młodszy).<br />

[P057.CPP]<br />

#pragma inline<br />

void main()<br />

{<br />

const char *napis = "\nRazem warzyw: $";<br />

char marchewki = 2, pietruszki = 5;<br />

asm {<br />

MOV DX, napis<br />

MOV AH, 9<br />

INT 33<br />

MOV DL, marchewki<br />

ADD DL, pietruszki<br />

ADD DL, '0'<br />

MOV AH, 2<br />

INT 33<br />

}<br />

}<br />

W tej wersji zadeklarowaliśmy zmienne marchewki i pietruszki<br />

jako zmienne typu char, co jest równoznaczne zadeklarowaniu ich<br />

przy pomocy dyrektywy DB.<br />

Zajmijmy się teraz maszynową reprezentacją liczb typu unsigned<br />

long int (długie całkowite bez znaku). Ze względu na specyfikę<br />

zapisu danych do pamięci przez mikroprocesory rodziny Intel<br />

80x86 długie liczby całkowite (podwójne słowo - double word) np.<br />

12345678(hex) są przechowywane w pamięci w odwróconym szyku.<br />

Zamieniony miejscami zostaje starszy bajt z młodszym jak również<br />

starsze słowo z młodszym słowem. Liczba 12345678(hex) zostanie<br />

zapisana w pamięci komputera IBM PC jako 78 56 34 12.<br />

Gdy inicjujemy w programie zmienną<br />

long int x = 2;<br />

zostaje ona umieszczona w pamięci tak: 02 00 00 00 (hex).<br />

Młodsze słowo (02 00) jest umieszczone jako pierwsze. To właśnie<br />

- 167-


słowo zawiera interesującą nas informację, możemy wczytać to<br />

słowo do rejestru rozkazem<br />

MOV DX, X<br />

Jeśli będzie nam potrzebna druga połówka zmiennej - starsze<br />

słowo (umieszczone w pamięci jako następne), możemy zastosować<br />

pointer (czyli podać adres następnego słowa pamięci).<br />

[P058.CPP]<br />

# pragma inline<br />

void main()<br />

{<br />

unsigned long marchewki = 2, pietruszki = 5;<br />

const char *napis = "\nRazem warzyw: $";<br />

asm<br />

{<br />

MOV DX, napis<br />

MOV AH, 9<br />

INT 33<br />

MOV DX, marchewki<br />

ADD DX, pietruszki<br />

ADD DX, '0'<br />

MOV AH, 2<br />

INT 33<br />

}<br />

}<br />

W przypadku liczb całkowitych ujemnych <strong>C++</strong> stosuje zapis w<br />

kodzie komplementarnym. Aby móc manipulować takimi danymi każdy<br />

szanujący się komputer powinien mieć możliwość stosowania liczb<br />

ujemnych.<br />

Najstarszy bit w słowie, bądź bajcie (pierwszy z lewej) może<br />

spełniać rolę bitu znakowego. O tym, czy liczba jest ze znakiem,<br />

czy też bez decyduje wyłącznie to, czy zwracamy uwagę na ten<br />

bit. W liczbach bez znaku, obojętnie, czy o długości słowa, czy<br />

bajtu, ten bit również jest (i był tam zawsze!), ale<br />

traktowaliśmy go, jako najstarszy bit nie przydając mu poza tym<br />

żadnego szczególnego znaczenia. Aby liczba stała się liczbą ze<br />

znakiem - to my musimy zacząć ją traktować jako liczbę ze<br />

znakiem, czyli zacząć zwracać uwagę na ten pierwszy bit.<br />

Pierwszy, najstarszy bit liczby ustawiony do stanu 1 będzie<br />

oznaczać, że liczba jest ujemna - jeśli zechcemy ją potraktować<br />

jako liczbę ze znakiem.<br />

Filozofia postępowania z liczbami ujemnymi opiera się na<br />

banalnym fakcie:<br />

(-1) + 1 = 0<br />

Twój PC "rozumuje" tak: -1 to taka liczba, która po dodaniu 1<br />

- 168-


stanie się 0. Czy można jednakże wyobrazić sobie np.<br />

jednobajtową liczbę dwójkową, która po dodaniu 1 da nam w<br />

rezultacie 0 ? Wydawałoby się, że w dowolnym przypadku wynik<br />

powinien być conajmniej równy 1.<br />

A jednak. Jeśli ograniczymy swoje rozważania do ośmiu bitów<br />

jednego bajtu, może wystąpić taka, absurdalna tylko z pozoru<br />

sytuacja. Jeśli np. dodamy 255 + 1 (dwójkowo 255 = 11111111):<br />

1111 1111 hex FF dec 255<br />

+ 1 + 1 + 1<br />

___________ _____ _____<br />

1 0000 0000 100 256<br />

otrzymamy 1 0000 0000 (hex 100). Dla Twojego PC oznacza to, że w<br />

ośmiobitowym rejestrze pozostanie 0000 0000 , czyli po prostu 0.<br />

Nastąpi natomiast przeniesienie (carry) do dziewiątego (nie<br />

zawsze istniejącego sprzętowo bitu).<br />

Wystąpienie przeniesienia powoduje ustawienie flagi CARRY w<br />

rejestrze FLAGS. Jeśli zignorujemy flagę i będziemy brać pod<br />

uwagę tylko te osiem bitów w rejestrze, okaże się, że<br />

otrzymaliśmy wynik 0000 0000. Krótko mówiąc FF = (-1), ponieważ<br />

FF + 1 = 0.<br />

Aby odwrócić wszystkie bity bajtu, bądź słowa możemy w<br />

asemblerze zastosować instrukcję NOT. Jeśli zawartość rejestru<br />

AX wynosiła np. 0000 1111 0101 0101 (hex 0F55), to instrukcja<br />

NOT AX zmieni ją na 1111 0000 1010 1010 (hex F0AA). Dokładnie<br />

tak samo działa operator bitowy ~_AX w C/<strong>C++</strong>. W zestawie<br />

rozkazów mikroprocesorów rodziny Intel 80x86 jest także<br />

instrukcja NEG, powodująca zamianę znaku liczby (dokonując<br />

konwersji liczby na kod komplementarny). Instrukcja NEG robi to<br />

samo, co NOT, ale po odwróceniu bitów dodaje jeszcze jedynkę.<br />

Jeśli rejestr BX zawierał 0000 0000 0000 0001 (hex 0001), to po<br />

operacji NEG AX zawartość rejestru wyniesie 1111 1111 1111 1111<br />

(hex FFFF).<br />

Zastosujmy praktycznie uzupełnienia dwójkowe przy współdziałaniu<br />

asemblera z <strong>C++</strong>:<br />

[P059.CPP]<br />

#pragma inline<br />

void main()<br />

{<br />

const char *napis = "\nRazem warzyw: $";<br />

int marchewki = -2, pietruszki = 5;<br />

asm {<br />

MOV DX, napis<br />

MOV AH, 9<br />

- 169-


}<br />

INT 33<br />

MOV DX, marchewki<br />

NEG DX<br />

ADD DX, pietruszki<br />

ADD DX, '0'<br />

MOV AH, 2<br />

INT 33<br />

}<br />

Dzięki zamianie (-2) na 2 przy pomocy instrukcji NEG DX<br />

otrzymamy wynik, jak poprzednio równy 7.<br />

Przypomnijmy prezentację działania operatorów bitowych <strong>C++</strong>.<br />

Wykorzystaj program przykładowy do przeglądu bitowej<br />

reprezentacji liczb typu int (ze znakiem i bez).<br />

[P060.CPP]<br />

/* Program prezentuje format liczb i operatory bitowe */<br />

# include "iostream.h"<br />

# pragma inline<br />

void demo(int liczba)<br />

{<br />

int n = 15;<br />

for (; n >= 0; n--)<br />

if ((liczba >> n) & 1)<br />

cout


cin >> x >> y;<br />

cout


void drukuj(void) //Definicja funkcji<br />

{<br />

asm MOV DX, TEKST<br />

asm MOV AH, 9<br />

asm INT 33<br />

}<br />

Funkcja może oczywiście nie tylko zgłosić się napisem, ale także<br />

zrobić dla nas coś pożytecznego. W kolejnym programie<br />

przykładowym czyścimy bufor klawiatury (flush), co czasami się<br />

przydaje, szczególnie na starcie programów.<br />

[P062.CPP]<br />

# pragma inline<br />

char *TEKST = "\nBufor klawiatury PUSTY. $";<br />

void czysc_bufor();<br />

void main()<br />

{<br />

czysc_bufor();<br />

}<br />

//Też prototyp funkcji<br />

//Czyszczenie bufora klawiatury<br />

void czysc_bufor(void)<br />

{<br />

START:<br />

asm MOV AH, 11<br />

asm INT 33<br />

asm OR AL, AL<br />

asm JZ KOMUNIKAT<br />

asm MOV AH, 7<br />

asm INT 33<br />

asm JMP START<br />

KOMUNIKAT:<br />

asm MOV DX, TEKST<br />

asm MOV AH, 9<br />

asm INT 33<br />

}<br />

//Definicja funkcji<br />

Póki nie wystąpi problem przekazania parametrów, napisanie dla<br />

<strong>C++</strong> funkcji w asemblerze jest banalnie proste. Zwróć uwagę, że<br />

zmienne wskazywane w programach przez pointer *TEKST zostały<br />

zadeklarowane poza funkcją main() - jako zmienne globalne.<br />

Dzięki temu nasze funkcje drukuj() i czysc_bufor() mają dostęp<br />

do tych zmiennych.<br />

Spróbujemy przekazać funkcji parametr. Nazwiemy naszą funkcję<br />

wyswietl() i będziemy ją wywoływać przekazując jej jako argument<br />

znak ASCII przeznaczony do wydrukowania na ekranie:<br />

wyswietl('A'); . Pojawia się zatem problem - gdzie program<br />

- 172-


"pozostawia" argumenty przeznaczone dla funkcji przed jej<br />

wywołaniem? W Tabeli poniżej przedstawiono w skrócie "konwencję<br />

wywoływania funkcji" (ang. Function Calling Convention) języka<br />

<strong>C++</strong>.<br />

Konwencje wywołania funkcji.<br />

________________________________________________________________<br />

Język Argumenty na stos Postać Typ wart. zwrac.<br />

________________________________________________________________<br />

BASIC Kolejno offset adresu Return n<br />

<strong>C++</strong> Odwrotnie wartości Return<br />

Pascal Kolejno wartości Return n<br />

________________________________________________________________<br />

Return n oznacza liczbę bajtów zajmowanych łącznie przez<br />

wszystkie odłożone na stos parametry.<br />

W <strong>C++</strong> parametry są odkładane na stos w odwróconej kolejności.<br />

Jeśli chcemy, by parametry zostały odłożone na stos kolejno,<br />

powinniśmy zadeklarować funkcję jako "funkcję z Pascalowskimi<br />

manierami" - np.:<br />

pascal void nazwa_funkcji(void);<br />

Dodatkowo, w <strong>C++</strong> argumenty są przekazywane poprzez swoją<br />

wartość, a nie przez wskazanie adresu parametru, jak ma to<br />

miejsce np. w BASICU. Istnieje tu kilka wyjątków przy<br />

przekazywaniu do funkcji struktur i tablic - bardziej<br />

szczegółowo zajmiemy się tym w dalszej części książki.<br />

Rozbudujemy nasz przykładowy program w taki sposób, by do<br />

funkcji były przekazywane dwa parametry - litery 'A' i 'B'<br />

przeznaczone do wydrukowania na ekranie przez funkcję:<br />

# pragma inline<br />

void wyswietl(char, char);<br />

void main()<br />

{<br />

wyswietl('A', 'B');<br />

}<br />

//Prototyp funkcji<br />

//Wywolanie funkcji<br />

void wyswietl(char x, char y)<br />

{<br />

....<br />

//Definicja (implementacja)<br />

Parametry zostaną odłożone na stos:<br />

PUSH 'B'<br />

PUSH 'A'<br />

Każdy parametr (mimo typu char) zajmie na stosie pełne słowo.<br />

<strong>C++</strong> nie potrafi niestety układać na stosie bajt po bajcie.<br />

- 173-


Funkcja wyswietl() musi uzyskać dostęp do przekazanych jej<br />

argumentówów. Odwołamy się do zmiennych <strong>C++</strong> w taki sposób, jak<br />

robiłaby to każda inna funkcja w <strong>C++</strong>:<br />

[P063.CPP]<br />

# pragma inline<br />

void wyswietl(char, char); //Prototyp funkcji<br />

void main()<br />

{<br />

_AH = 2; //BEEEEE !<br />

wyswietl('A', 'B'); //Wywolanie funkcji<br />

}<br />

void wyswietl(char x, char y) //Definicja (implementacja)<br />

{<br />

_DH = 0; // To <strong>C++</strong> nie TASM, to samo, co asm MOV DH, 0<br />

_DL = x; // asm MOV DL, x<br />

asm INT 33<br />

_DH = 0; // asm MOV DH, 0<br />

_DL = y; // asm MOV DL, y<br />

asm INT 33<br />

}<br />

Aby pokazać jak dalece BORLAND <strong>C++</strong> jest elastyczny wymieszaliśmy<br />

tu w jednaj funkcji instrukcje <strong>C++</strong> (wykorzystując pseudozmienne)<br />

i instrukcje assemblera. Może tylko przesadziliśmy trochę<br />

ustawiając rejestr AH - numer funkcji DOS dla przerywania int 33<br />

przed wywołaniem funkcji wyswietl() w programie głównym. To<br />

brzydka praktyka (ozn. //BEEEE), której autor nie zaleca.<br />

Jak widzisz, przekazanie parametrów jest proste.<br />

- 174-


LEKCJA 17. TROCHĘ SZCZEGÓLÓW TECHNICZNYCH.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się więcej o szczegółach działania<br />

komputera widzianych z poziomu assemblera.<br />

________________________________________________________________<br />

LICZBY ZMIENNOPRZECINKOWE TYPU float.<br />

To, że <strong>C++</strong> przy wywołaniu funkcji jest "przyzwyczajony" do<br />

odkładania argumentów na stos zawsze po dwa bajty może nam<br />

sprawić trochę kłopotów, gdy zechcemy zastosować argument typu<br />

float, double, bądź long - znacznie przekraczający długością<br />

dwubajtowe słowo maszynowe.<br />

# include


dziesiętnych. Przesunięcie przecinka powoduje, że 12345.67 =<br />

1.234567 * 10^4. Aby wróciła do swojej starej "zwykłej" postaci<br />

(jest to tzw. "rozwinięcie" liczby - ang. expand) należy<br />

przesunąć przecinek o jedno miejsce w prawo - otrzymamy znowu<br />

11.1 . W liczbach dziesiętnych pierwsza cyfra może być różna<br />

(tylko nie zero), a w dowolnej poddanej normalizacji<br />

zmiennoprzecinkowej liczbie dwójkowej pierwszą cyfrą jest zawsze<br />

1. Skoro w formacie liczb zmiennoprzecinkowych pierwsza jedynka<br />

jest przyjmowana "z definicji" (ang. implicit), więc można ją<br />

pominąć. Zostanie nam zatem zamiast 1.11 tylko 11 i ta<br />

przechowywana część liczby jest nazywana jej częścią znaczącą<br />

(ang. significant). To jeszcze nie wszystko - powinien tam być<br />

wykładnik potęgi. Wystarczy zapamiętać wykładnik, bo podstawa<br />

jest zawsze ta sama - 2. Niestety wykładniki są przechowywane<br />

nie w sposób naturalny, a po dodaniu do nich tzw. przesunięcia<br />

(ang. offset lub bias). Pozwala to uniknąć kłopotów z<br />

określaniem znaku wykładnika potęgi.<br />

Dla liczb typu float offset wykładnika wynosi +127 a dla liczb<br />

double float +1023. Wrócmy do naszej przykładowej liczby. Jeśli<br />

nasza liczba 3.5 = 11.1(B) ma być zapisana w postaci<br />

zmiennoprzecinkowej - float, zapisany w pamięci wykładnik potęgi<br />

wyniesie:<br />

1 + 127 = 128 = 80 (hex)<br />

A teraz znak liczby. Pierwszy bit każdej liczby<br />

zmiennoprzecinkowej określa znak liczby (ang. sign bit). Liczby<br />

zmiennoprzecinkowe nie są przechowywane w postaci dwójkowych<br />

uzupełnień. Jeśli pierwszy bit - bit znaku równy jest 1 - liczba<br />

jest ujemna. natomiast jeżeli 0, liczba jest dodatnia. Jest to<br />

jedyna różnica pomiędzy dodatnimi a ujemnymi liczbami<br />

zmiennoprzecinkowymi. Nasza liczba 3.5 = 11.1 zostanie<br />

zakodowana jako:<br />

znak liczby - 0<br />

wykładnik potęgi - 1000 0000<br />

cyfry znaczące liczby - 110000000....<br />

Ponieważ wiemy, że mamy do dyspozycji dla liczb float 4 bajty<br />

(możesz to sprawdzić sizeof(float x=3.5)), uzupełnijmy brakujące<br />

do 32 bity zerami:<br />

3.5 = 0100 0000 0110 0000 0000 0000 0000 0000 = 40 60 00 00<br />

zapis 40600000 to oczywiście szesnastkowa postać naszej liczby.<br />

Jeśli teraz weźmiemy pod uwagę, że nasz PC zamieni miejscami<br />

starsze słowo z młodszym 00 00 40 60 a następnie w obrębie<br />

każdego słowa dodatkowo starszy bit z młodszym, to zrozumiemy,<br />

dlaczego nasza liczba "siedziała" w pamięci w zaszyfrowanej<br />

- 176-


postaci 00 00 60 40.<br />

Rozpatrzmy szkielet programu wykorzystującego funkcję z "długim"<br />

argumentem. Aby zapanować nad zapisem liczby zmiennoprzecinkowej<br />

do pamięci naszego PC możemy na poziomie assemblera postąpić np.<br />

tak:<br />

# include > 16;<br />

x1starsze = (int) x;<br />

_DX = x1starsze;<br />

_BX = x2mlodsze;<br />

asm {<br />

...... //Tu funkcja już może działać<br />

Forsując konwersję typu na (int), spowodujemy, że młodsze słowo<br />

zostanie przypisane zwyczajnej krótkiej zmiennej x2mlodsze.<br />

Następnie zawartość długiej zmiennej zostanie przesunięta o 16<br />

bitów w prawo (starsze słowo zostanie przesunięte na miejsce<br />

młodszego). Powtórzenie operacji przypisania spowoduje<br />

przypisanie zmiennej x1starsze starszej połówki słowa. Od tej<br />

chwili możemy odwołać się do tych zmiennych w naszym fragmencie<br />

napisanym w asemblerze. Postępujemy tak, by to <strong>C++</strong> martwił się o<br />

szczegóły techniczne i sam manipulował stosem i jednocześnie<br />

pilnował poprawności konwersji danych.<br />

ZWROT WARTOŚCI PRZEZ FUNKCJĘ.<br />

A teraz kilka słów o tym, co się dzieje, gdy funkcja zapragnie<br />

- 177-


zwrócić jakąś wartość do programu.<br />

Wykorzystanie przez funkcje rejestrów do zwrotu wartości.<br />

________________________________________________________________<br />

Typ wartości Funkcja używa rejestru (lub pary)<br />

________________________________________________________________<br />

signed char / unsigned char AL<br />

short<br />

AX<br />

int<br />

AX<br />

enum<br />

AX<br />

long<br />

para DX:AX (starsze słowo DX, młodsze<br />

AX)<br />

float AX = Adres (jeśli far to DX:AX)<br />

double AX = Adres (jeśli far to DX:AX)<br />

struct AX = Adres (jeśli far to DX:AX)<br />

near pointer AX<br />

far pointer DX:AX<br />

________________________________________________________________<br />

Zależnie od typu wartości zwracanej przez funkcję (określonej w<br />

prototypie funkcji), <strong>C++</strong> odczytuje zawartość odpowiedniego<br />

rejestru: AL, AX lub DX:AX. Jeśli funkcja ma np. zwrócić wartość<br />

o długości jednego bajtu, to przed wyjściem z funkcji należy ją<br />

"zostwić" w rejestrze AL. Jeśli wywołując funkcję <strong>C++</strong> oczekuje<br />

zwrotu wartości jednobajtowej, to po powrocie z funkcji<br />

automatycznie pobierze bajt z rejestru AL. Krótkie wartości<br />

(typu short int) są "pozostawiane" przez funkcję w AX, a długie<br />

w parze rejestrów: DX - starsze, AX - młodsze słowo.<br />

Zastosujmy to w programie. Funkcja będzie odejmować dwie liczby<br />

całkowite. Pobierze dwa argumenty typu int, wykona odejmowanie i<br />

zwróci wynik typu int (return (_AX)). Dla modelu pamięci small<br />

będzie to wyglądać tak:<br />

[P064.CPP]<br />

# include <br />

# pragma inline<br />

int funkcja(int, int);<br />

//Prototyp funkcji<br />

void main()<br />

{<br />

cout


}<br />

return (_AX);<br />

}<br />

//Zwróć zawartość rejestru AX<br />

Zwróć uwagę, że po return(_AX); stawiamy średnik, natomiast po<br />

instrukcjach assemblera nie:<br />

asm MOV AX, DX<br />

chyba, że chcemy umieścić kilka instrukcji assemblera w jednej<br />

linii (patrz niżej).<br />

<strong>C++</strong> i assembler są równoprawnymi partnerami. <strong>C++</strong> może odwoływać<br />

się do zmiennych i funkcji assemblera, jeśli zostały<br />

zadeklarowane, jako publiczne (public) oraz zewnętrzne<br />

(EXTeRNal) i vice versa. <strong>C++</strong> oczekuje, że zewnętrzne<br />

identyfikatory będą się rozpoczynać od znaku podkreślenia "_".<br />

Jeśli w programie pisanym w BORLAND <strong>C++</strong> zastosujemy zewnętrzne<br />

zmienne i funkcje, <strong>C++</strong> sam automatycznie doda do identyfikatorów<br />

znak podkreślenia. Turbo Assembler nie robi tego automatycznie i<br />

musimy zadbać o to "ręcznie". Przykładowo, współpraca pomiędzy<br />

programem P .CPP i modułem MODUL.ASM będzie przebiegać<br />

poprawnie:<br />

[P065.CPP]<br />

extern int UstawFlage(void);<br />

int Flaga;<br />

void main()<br />

{<br />

UstawFlage();<br />

}<br />

//Prototyp funkcji<br />

[MODUL.ASM]<br />

.MODEL SMALL<br />

.DATA<br />

EXTRN _Flaga:WORD<br />

.CODE<br />

PUBLIC _UstawFlage<br />

_UstawFlage PROC<br />

CMP [_Flaga], 0<br />

JNZ SKASUJ_Flage<br />

MOV [_Flaga], 1<br />

JMP SHORT KONIEC<br />

SKASUJ_Flage: MOV [_Flaga], 0<br />

KONIEC:<br />

RET<br />

_UstawFlage ENDP<br />

END<br />

Kompilacja może przebiegać oddzielnie wg schematu:<br />

- 179-


PROGRAM.CPP --> PROGRAM.OBJ<br />

MODUL.ASM --> MODUL.OBJ<br />

TLINK PROGRAM.OBJ MODUL.OBJ --> PROGRAM.EXE<br />

Lub możemy powierzyć tę pracę kompilatorowi, który sam wywoła<br />

TASM i TLINK:<br />

TCC PROGRAM.CPP MODUL.ASM<br />

W BORLAND <strong>C++</strong> 3.1 mamy do dyspozycji zintegrowany assembler<br />

(ang. in-line) - BASM. Ma on jednak w stosunku do<br />

"wolnostojącego" Turbo Assemblera pewne ograniczenia:<br />

* ma zawężony w stosunku do TASM zestaw dyrektyw (tylko DB, DD,<br />

DW, EXTRN);<br />

* nie pozwala na stosowanie składni typowej dla trybu "Ideal<br />

mode";<br />

* nie pozwala na zastosowanie makra;<br />

* nie pozwala stosować instrukcji charakterystycznych dla 386<br />

ani 486.<br />

Możesz stosować kilka rozkazów assemblera w jednej linii, ale<br />

powinieneś rozdzielać je wewnątrz linii średnikami:<br />

asm {<br />

POP AX; POP DX; POP DS<br />

IRET<br />

}<br />

Komentarz we wstawce assemblerowskiej musi zostać poprzedzony<br />

typowym dla C - /* (sam średnik, jak w TASM jest<br />

niedopuszczalny):<br />

asm {<br />

MOV DX, 1 ;TAK NIE MOŻNA W BASM !<br />

...<br />

asm {<br />

ADD AX, BX; /* Taki komentarz może być */<br />

[???] KŁOPOTY Z REJESTRAMI ?<br />

________________________________________________________________<br />

Jeśli zastosujesz rejestry DI i SI we wstawce assemblerowaj,<br />

kompilator <strong>C++</strong> nie będzie miał gdzie umieścić zmiennych klasy<br />

register z programu głónego. Zastanów się - co się bardziej<br />

opłaca.<br />

________________________________________________________________<br />

O WEKTORACH PRZERYWAŃ DOS<br />

Mikroprocesory Intel 80X86 rezerwują w pamięci naszych PC<br />

początkowe 1024 Bajty (adresy fizyczne 00000...00400 hex) na 256<br />

wektorów przerywań (każdy wektor składa się z dwu słów i może<br />

być traktowany jako DW, bądź far pointer). Następne 256 bajtów<br />

(00400...00500 hex) zajmuje BIOS, a kolejne 256 (00500...00600<br />

- 180-


hex) wykorzystuje DOS i Basic.<br />

Wektor to w samej rzeczy pełny adres początku procedury<br />

obsługującej przerywanie o danym numerze<br />

UWAGA:<br />

Wektor zapisywany jest w pamięci w odwrotnej kolejności:<br />

Adres pamięci: 0000:0000 [OFFSET Wekt. int 0]<br />

0000:0002 [SEGMENT int 0]<br />

0000:0004 [OFFSET Wekt. int 1]<br />

0000:0006 [SEGMENT int 1]<br />

0000:0008 [OFFSET int 2]<br />

.... ....<br />

Procesory 80X86 zamieniają jeszcze dodatkowo starszy bajt z<br />

młodszym.<br />

Posługując się systemowym debuggerem DEBUG możesz łatwo<br />

przejrzeć tablicę wektorów przerywań własnego komputera. Jeśli<br />

wydasz rozkaz:<br />

C:\DOS\DEBUG<br />

-D 0:0<br />

zobaczysz zawartość pierwszych 32 wektorów int #0...int#31,<br />

czyli pierwsze 128 bajtów pamięci:<br />

-d 0:0<br />

0000:0000 FB 91 32 00 F4 06 70 00-78 F8 00 F0 F4 06 70 00<br />

0000:0010 F4 06 70 00 54 FF 00 F0-53 FF 00 F0 53 FF 00 F0<br />

0000:0020 A5 FE 00 F0 87 E9 00 F0-23 FF 00 F0 23 FF 00 F0<br />

0000:0030 23 FF 00 F0 CE 02 00 C8-57 EF 00 F0 F4 06 70 00<br />

0000:0040 D1 0C BD 1B 4D F8 00 F0-41 F8 00 F0 74 07 70 00<br />

0000:0050 39 E7 00 F0 4A 08 70 00-2E E8 00 F0 D2 EF 00 F0<br />

0000:0060 00 00 FF FF FB 07 70 00-5D 0C 00 CA 9F 01 BD 1B<br />

0000:0070 53 FF 00 F0 A0 7C 00 C0-22 05 00 00 2F 58 00 C0<br />

Po zdeszyfrowaniu okaże się, że pierwszy wektor (przerywanie 0)<br />

wskazuje na adres startowy: 0032:91FB (adres absolutny 0951B).<br />

Generalnie możliwe są cztery sytuacje. Wektor może wskazywać:<br />

* adres startowy procedur ROM-BIOS: blok F - Fxxx:xxxx,<br />

* adres funkcji DOS,<br />

* adres funkcji działającego właśnie debuggera (DEBUG przejmuje<br />

obsługę niektórych przerywań), lub innego programu rezydującego<br />

w pamięci - np. NC.EXE,<br />

* wektor może być pusty - 00 00:00 00 jeśli dane przerywanie nie<br />

jest obsługiwane.<br />

Jeśli zechcesz sprawdzić, jak obsługiwane jest dane przerywanie<br />

możesz znów zastosować debugger, wydając mu rozkaz<br />

zdezasamblowania zawartości pamięci począwszy od wskazanego<br />

adresu:<br />

- 181-


-u 32:91FB<br />

0032:91FB BE6B47 MOV SI,476B<br />

0032:91FE 2E CS:<br />

0032:91FF 8B1E7E47 MOV BX,[477E]<br />

0032:9203 2E CS:<br />

0032:9204 8E16D73D MOV SS,[3DD7]<br />

0032:9208 BCA007 MOV SP,07A0<br />

0032:920B ˙˙˙˙˙˙˙˙˙˙˙E80200 ˙˙˙˙˙˙˙˙˙˙CALL ˙˙˙˙˙˙˙˙˙˙9210<br />

0032:920E EBDA JMP 91EA<br />

0032:9210 ˙˙˙˙˙˙˙˙˙˙˙16 ˙˙˙˙˙˙˙˙˙˙˙PUSH ˙˙˙˙˙˙˙˙˙˙˙SS<br />

0032:9211 07 POP ES<br />

0032:9212 ˙˙˙˙˙˙˙˙˙˙˙16 ˙˙˙˙˙˙˙˙˙˙˙PUSH ˙˙˙˙˙˙˙˙˙˙˙SS<br />

0032:9213 1F POP DS<br />

0032:9214 C606940308 MOV BYTE PTR [0394],08<br />

0032:9219 C606920316 MOV BYTE PTR [0392],16<br />

Z poziomu assemblera do wektora i odpowiednio do funkcji<br />

obsługującej przerywanie możesz odwołać się instrukcją INT<br />

numer.<br />

Zmienna numer może tu przyjmować wartości od 00 do FF. Jeśli<br />

wydasz taki rozkaz, komputer zapisze na stos (żeby sobie nie<br />

zapomnieć) zawartość rejestrów CS - bież. segment rozkazu, IP -<br />

bieżący offset rozkazu i FLAGS. Następnie wykona daleki (far<br />

jump) skok do adresu wskazanego przez wektor.<br />

Jeśli jednak część przerywań jest "niewykorzystana", lub w Twoim<br />

programie trzeba je obsługiwać inaczej - niestandardowo ? W<br />

BORLAND <strong>C++</strong> masz do dyspozycji specjalny typ funkcji: interrupt.<br />

Aby Twoja funkcja mogła stać się "handlerem" przerywania, możesz<br />

zadeklarować ją tak:<br />

void interrupt MojaFunkcja(bp, di, si, ds .....)<br />

Do funkcji klasy interrupt przekazywane są jako argumenty<br />

rejestry, nie musisz zatem stosować pseudozmiennych _AX, _FLAGS<br />

itp.. Jeśli zadeklarujesz funkcję jako handler przy pomocy słowa<br />

"interrupt", funkcja automatycznie zapamiętuje stan rejestrów:<br />

AX, BX, CX, DX, SI, DI, BP, ES i DS.<br />

Po powrocie z funkcji rejestry zostaną automatycznie odtworzone.<br />

Przykładem funkcji obsługującej przerywanie może być piszczek()<br />

posługujący się wbudowanym głośniczkiem i portem:<br />

# define us unsigned<br />

# include <br />

- 182-


# include <br />

void InstalujWektor(void interrupt (*adres)(), int numer_wekt);<br />

void interrupt Piszczek(us bp, us di, us si, us ds, us es,<br />

void main()<br />

{<br />

.....<br />

}<br />

....<br />

us ax, us bx, us cx, us dx);<br />

Po zadeklarowaniu prototypów dwu funkcji:<br />

Piszczek() - nasz handler przerywania;<br />

InstalujWektor() - funkcja instalująca nasz handler;<br />

możemy przystąpić do zdefiniowania oby funkcji. Posłużymy się<br />

zmiennymi<br />

nowe_bity, stare_bity. Wydawanie dźwięku polega na włączaniu i<br />

wyłączaniu głośniczka. Pusta pętla posłuży nam do zwłoki w<br />

czasie.<br />

void interrupt Piszczek(us bp, us di, us si, us ds, us es,<br />

us ax, us bx, us cx, us dx)<br />

{<br />

char nowe_bity, stare_bity, i;<br />

int n;<br />

unsigned char licznik = ax >> 8;<br />

stare_bity = inportb(0x61);<br />

for(nowe_bity = stare_bity, n = 0; n


cout


SI -> 0...31 ESI w tym (SI = 0..15)<br />

DI -> 0...31 EDI w tym (DI = 0..15)<br />

BP -> 0...31 EBP w tym (BP = 0..15)<br />

SP -> 0...31 ESP w tym (SP = 0..15)<br />

IP -> 0...31 EIP w tym (IP = 0..15)<br />

FLAGS -> 0...31 EFLAGS w tym (FLAGS = 0..15)<br />

Wszystkie "stare" połówki dostępne pod starą nazwą.<br />

Rejestry segmentowe pozostały 16 bitowe, ale jest ich o dwa<br />

więcej: CS, DS, ES, SS oraz nowe FS i GS.<br />

Nowe 32 bitowe rejestry działają według tych samych zasad:<br />

.386<br />

...<br />

MOV EAX, 1 ;zapisz 1 do rejestru EAX<br />

SUB EBX, EBX ;wyzeruj rejestr EBX<br />

ADD EBX, EAX ;dodaj (EAX)+(EBX) --> EBX<br />

Dostęp do starszej połowy rejestru można uzyskać np. poprzez<br />

przesuwanie (rotation):<br />

.386<br />

...<br />

MOV AX, Liczba_16_bitowa<br />

ROR EDX, 16<br />

MOV AX, DX<br />

ROR EDX, 16<br />

... itp.<br />

W assemblerze możesz stosować wobec procesora 386 nowe<br />

instrukcje (testowania nie istniejących wcześniej bitów,<br />

przenoszenia krótkich liczb do 32 bitowych rejestrów z<br />

uwzględnieniem zaku i uzupełnieniem zerami itp.):<br />

BSF, BSR, BTR, BTS, LFS, LGS, MOVZX, SETxx,<br />

BT, BTC, CDQ, CWDE, LSS, MOVSX, SHLD i SHRD.<br />

Przy pomocji instrukcji MOV w trybie uprzywilejowanym (tzw.<br />

most-privileged level 0 - tylko w trybie .386P) możesz dodatkowo<br />

uzyskać dostęp do specjalnych rejestrów mikroprocesora 80386.<br />

CR0, CR2, CR3,<br />

DR0, DR1, DR2, DR3, DR6, DR7<br />

TR6, TR7<br />

Występuje tu typ danych - FWORD - 48 bitów (6 bajtów). Obok<br />

znanych dyrektyw DB i DW pojawia się zatem nowa DF, a oprócz<br />

znajomych wskaźników BYTE PTR, WORD PTR pojawia się nowy FWORD<br />

PTR. Przy pomocy dyrektywy .387 możesz skorzystać z koprocesora.<br />

Jak wynika z zestawu dodatkowych insrukcji:<br />

FCOS, FSINCOS, FUCOMP, FPREM1, FUCOM, FUCOMPP, FSIN<br />

- 185-


warto dysponować koprocesorem, jeśli często korzystasz z<br />

grafiki, animacji i funkcji trygonometrycznych (kompilacji nie<br />

przyspieszy to niestety ani o 10% - tam odbywają się operacje<br />

stałoprzecinkowe).<br />

Zwróć uwagę, że procesory 386 i wcześniejsze wymagały instalacji<br />

dodatkowego układu 387 zawierającego koprocesor<br />

zmiennoprzecinkowy. Procesory 486 jeśli mają rozszerzenie DX -<br />

zawierają już koprocesor wewnątrz układu scalonego.<br />

________________________________________________________________<br />

- 186-


LEKCJA 18. O ŁAŃCUCHACH TEKSTOWYCH<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się,<br />

* jak manipulować łańcuchami tekstowymi i poznasz kilka<br />

specjalnych funkcji, które służą w <strong>C++</strong> właśnie do takich celów;<br />

* jak wykonują się operacje plikowo-dyskowe.<br />

________________________________________________________________<br />

OPERACJE NA ŁAŃCUCHACH TEKSTOWYCH.<br />

String, czyli łańcuch - to gupa znaków "pisarskich" (liter, cyfr<br />

i znaków specjalnych typu ?, !, _ itp.). Ponieważ <strong>C++</strong> nie ma<br />

odzielnego typu danych "string" - łańcuchy znaków to tablice<br />

złożone z pojedynczych znaków (typowe elementy typu char).<br />

Techniką obiektową można utworzyć klasę - nowy typ danych<br />

"string". W bibliotekach Microsoft <strong>C++</strong> istnieje predefiniowana<br />

klasa CString, ale zanim przejdziemy do programowania<br />

obiektowego i zdarzeniowego - rozważmy manipulowanie tekstami w<br />

sposób najprostszy.<br />

Maksymalną możliwą długość napisu należy podać wtedy, gdy w<br />

programie deklaruje się zmienną tekstową:<br />

char tekst1[40];<br />

Jest to poprawna deklaracja zmiennej tekstowej o nazwie<br />

(identyfikator) tekst1. Maksymalna długość tekstu, który można<br />

umieścić w tej zmiennej tekstowej to - 40 znaków (liter, cyfr,<br />

itp.). A jeśli chcę zastosować tylko pojedynczy znak zamiast<br />

całego napisu? To proste:<br />

char napis[1];<br />

Skoro długość łańcucha wynosi 1, to przecież nie jest żaden<br />

łańcuch! Informacja o długości (size - wielkość) wpisywana w<br />

nawiasy jest zbędna. Uproszczona wersja utworzenia zmiennej<br />

jednoznakowej i nadania zmiennej nazwy wygląda w tak:<br />

char znak;<br />

Nie jest to już jednak deklaracja zmiennej łańcuchowej - lecz<br />

deklaracja zmiennej znakowej. Łańcuch znaków (string) to grupa<br />

znaków (dokł. tablica znakowa) zakończona zwykle przez tzw.<br />

"wartownika" - znak NULL (zero). A pojedynczy znak to tylko<br />

pojedynczy znak. Nie ma tu miejsca (i sensu) dodawanie po<br />

pojedynczym znaku "wartownika" końca tekstu - zera.<br />

Gdy w deklaracjach zmiennych tekstowych rezerwujesz miejsce w<br />

pamięci dla napisów - zawsze możesz zażądać od kompilatora <strong>C++</strong><br />

zarezerwowania większej ilości miejsca - na zapas. Zawsze lepiej<br />

mieć zbyt dużo miejsca, niż zbyt mało.<br />

[???] LEPIEJ MIEĆ NIŻ NIE MIEĆ.<br />

- 187-


________________________________________________________________<br />

Upewnij się, czy kompilator zarezerwował (a Ty zadeklarowałeś)<br />

wystarczająco dużo miejsca dla Twoich tekstów. <strong>C++</strong> niestety nie<br />

sprawdza tego w trakcie działania programu. Jeśli będziesz<br />

próbował umieścić w pamięci tekst o zbyt dużej długości (dłuższy<br />

niż zadeklarowałeś w programie), <strong>C++</strong> posłusznie zapisze go do<br />

pamięci, ale może to spowodować nieprawidłowe działanie, bądź<br />

nawet "zawieszenie" programu.<br />

________________________________________________________________<br />

Inną przydatną w praktyce programowania cechą języka <strong>C++</strong> jest<br />

możliwość zadeklarowania zawartości zmiennej tekstowej w<br />

momencie zadeklarowania samej zmiennej. Takie nadanie<br />

początkowej wartości nazywa się zdefiniowaniem, bądź<br />

zainicjowaniem zmiennej. W programie zapisuje się to tak:<br />

char napis[] = "To jest jakis napis";<br />

Powoduje to przypisanie zmiennej tekstowej "napis" konkretnego<br />

łańcucha tekstowego "To jest jakiś napis". Zwróć uwagę, że w<br />

nawiasach nie podajemy ilości znaków, z których składa się<br />

tekst. Kompilator sam policzy sobie ilość znaków (tu 19) i<br />

zarezerwuje miejsce w pamięci dla napisu. Jeśli wolisz sam<br />

zadecydować, możesz zapisać deklarację tak:<br />

char napis[35] = "To jest jakis napis";<br />

Jeśli to zrobisz, kompilator <strong>C++</strong> zarezerwuje w pamięci miejsce<br />

dla 35 znaków, a nie dla 19.<br />

W programach często inicjuje się teksty posługując się nie<br />

tablicą znakową - lesz wskaźnikiem do tekstu. Deklaracja i<br />

zainicjowanie wskaźnika (wskaźnik wskazuje pierwszy element<br />

łańcucha znakowego) wygląda wtedy tak:<br />

char *p = "Jakis tam napis";<br />

Rzućmy okiem na kilka gotowych funkcji, które do manipulowania<br />

tekstami oferuje <strong>C++</strong>.<br />

ŁĄCZENIE TEKSTÓW.<br />

[S] String Concatenation - łączenie łańcuchów tekstowych.<br />

Zlepek/skrót. Słowo strcat w języku <strong>C++</strong> znaczy sklej.<br />

W praktycznych programach zapewne często pojawi się dwa lub<br />

więcej tekstów, które trzeba będzie połączyć w jeden napis.<br />

Wyobraźmy sobie, że imię i nazwisko użytkownika mamy zapisane<br />

jako dwa oddzielne łańcuchy tekstowe. Aby połączyć te dwa teksty<br />

w jeden trzeba przeprowadzić tzw. sklejanie (ang. concatenation)<br />

tekstów. W języku <strong>C++</strong> mamy w tym celu do dyspozycji specjalną<br />

- 188-


funkcję:<br />

strcat() - STRing conCATenation - sklejanie łańcuchów.<br />

Aby połączyć dwa łańcuchy tekstowe napis1 i napis2 w jeden<br />

należy zastosować tę funkcję w taki sposób:<br />

strcat(napis1, napis2);<br />

Funkcja strcat() zadziała w taki sposób, że łańcuch znaków<br />

napis2 zostanie dołączony do końca łańcucha napis1. Po<br />

zakończeniu działania funkcji zmienna napis1 zawiera "swój<br />

własny" napis i dołączony na końcu napis zawarty uprzednio w<br />

zmiennej napis2.<br />

Program poniżej przedstawia praktyczny przykład zastosowania<br />

funkcji strcat().<br />

[P066.CPP]<br />

#include <br />

#include <br />

#include //W tym pliku jest prototyp strcat()<br />

int main(void)<br />

{<br />

char imie[50], nazwisko[30];<br />

clrscr();<br />

cout > imie;<br />

cout > nazwisko;<br />

strcat(imie, " ");<br />

strcat(imie, nazwisko);<br />

cout


strcat(imie, nazwisko);


łańcucha pozwala mieć pewność, że tablica nie zawiera jakichś<br />

starych, zbędnych danych.<br />

Możliwość sprawdzenia, jaką długość ma łańcuch tekstowy może się<br />

to przydać np. do rozmieszczenia napisów na ekranie. Dla<br />

przykładu, pozycja na ekranie, od której rozpocznie się<br />

wyświetlanie napisu zależy od długości tekstu, który został<br />

wyświetlony wcześniej. Do określania długości tekstu masz w <strong>C++</strong><br />

do dyspozycji gotową funkcję:<br />

strlen() - STRing LENgth - długość łańcucha znakowego.<br />

Funkcję strlen() stosuje się w następujący sposób:<br />

unsigned int dlugosc;<br />

char tekst[...];<br />

...<br />

dlugosc = strlen(tekst);<br />

Funkcja ma jeden argument - napis, którego długość należy<br />

określić (tu: zmienna nazywa się tekst). Funkcja strlen() w<br />

wyniku swojego działania ZWRACA długość łańcucha tekstowego jako<br />

liczbę całkowitą bez znaku (nieujemną). Liczba zwrócona jako<br />

wynik przez funkcję strlen() może zostać użyta w dowolny sposób<br />

- jak każda inna wartość numeryczna.<br />

Funkcja strlen() nie podaje w odpowiedzi na wywołanie (mądrze<br />

nazywa się to "zwraca do programu wartość") długości łańcucha<br />

tekstowego, która została zadeklarowana (maksymalnej<br />

teoretycznej), lecz FAKTYCZNĄ DŁUGOŚĆ tekstu. Jeśli, dla<br />

przykładu, zadeklarujemy zmienną tekstową tak:<br />

char string1[30] = "Lubie <strong>C++</strong> ";<br />

zadeklarowana maksymalna długość łańcucha znakowego wynosi 30,<br />

natomiast faktyczna długość łańcucha znakowego wynosi 10 znaków.<br />

Jeśli wywołamy strlen() i każemy jej określić długość łańcucha<br />

znakowego string1:<br />

unsigned int dlugosc = strlen(string1);<br />

funkcja przypisze zmiennej dlugosc wartość 10 a nie 30.<br />

Jeśli wpisałeś poprzedni program program przykładowy do okienka<br />

edycyjnego - wystarczy dodać dwa nowe wiersze.<br />

[P067.CPP]<br />

#include <br />

#include <br />

#include <br />

- 191-


main()<br />

{<br />

char imie[50], nazwisko[20];<br />

int dlugosc;<br />

clrscr();<br />

cout > imie;<br />

cout > nazwisko;<br />

strcat(imie, " ");<br />

strcat(imie, nazwisko);<br />

cout


W tym przykładzie wywołujemy funkcję strncpy() przekazując jej<br />

przy wywołaniu trzy argumenty:<br />

tab_B - destination string - wynikowy łańcuch tekstowy (ten<br />

nowy, który powstanie);<br />

tabn_A - source string - łańcuch źródłowy (ten, z którego<br />

będziemy "obcinać" kawałek);<br />

3 - maksymalna liczba znaków, którą należy obciąć . Obcięte<br />

znaki utworzą "substring" - "BAB".<br />

Pobieranie i "wycinanie" znaków rozpocznie się od pierwszego<br />

znaku łańcucha źródłowego tab_A[80], więc funkcja wywołana w<br />

taki sposób:<br />

strncpy(string1, string2, 3);<br />

spowoduje pobranie pierwszych 3 znaków z łańcucha string2 i<br />

skopiowanie ich do łańcucha string1.<br />

Funkcja strcpy() (Uwaga! bez "n") powoduje skopiowanie całego<br />

łańcucha znaków. Sposób zastosowania funkcji jest podobny do<br />

przykładu z strncpy(), z tym, że nie trzeba podawać liczby<br />

całkowitej określającej ilość znaków do kopiowania. Jak<br />

wszystkie, to wszystkie (jak mawiała babcia), zatem wywołanie<br />

funkcji:<br />

strcpy(string1, string2);<br />

spowoduje skopiowanie całego łańcucha znaków zawartego w<br />

zmiennej string2 do zmiennej string1. Jeśli, dla przykładu,<br />

zmiennej string2 przypiszemy łańcuch tekstowy<br />

string2 = "BABCIA";<br />

to po zadziałaniu funkcji strcpy(string1, string2) zmiennej<br />

string1 zostanie przypisany dokładnie taki sam łańcuch.<br />

Rozważmy program przykładowy. Po uruchomieniu program poprosi o<br />

wpisanie łańcucha tekstowego. Wpisz dowolny tekst. Tekst<br />

powinien zawierać więcej niż 3 znaki. Po pobraniu<br />

wyjściowego/źródłowego tekstu od użytkownika, program pobierze z<br />

tego tekstu kilka mniejszych łańcuchów tekstowych typu<br />

"substring" i wyświetli je na ekranie.<br />

[P068.CPP]<br />

#include <br />

#include <br />

#include <br />

#include <br />

main()<br />

{<br />

- 193-


char napis1[80] = "";<br />

char napis2[80] = "";<br />

char napis3[80] = "";<br />

clrscr();<br />

cout


wprowadzać napisów zawierających spacje. Jeśli zastosowalibyśmy<br />

w programie<br />

cin >> string1;<br />

i wpisali tekst zawierający spacje, np.:<br />

To nie ważne, czy Polska...<br />

wczytane zostałyby tylko znaki To (do pierwszej spacji). Z kolei<br />

funkcja gets() pozwala wczytać wiersz tekstu zawierający dowolne<br />

znaki uznając za koniec znak CRLF (powrót karetki, zmiana<br />

wiersza) generowany po naciśnięciu [Entera]. Przeciwną,<br />

symetryczną funkcją do gets() jest funkcja puts() (ang. PUT<br />

String - wyprowadź wiersz tekstu). Prototypy funkcji gets() i<br />

puts() znajdują się w pliku nagłówkowym STDIO.H. Dlatego ten<br />

plik nagłówkowy został dołączony na początku dyrektywą #include.<br />

WYSZUKIWANIE TEKSTÓW.<br />

Wyobraźmy sobie, że mamy listę imion i chcemy na tej liście<br />

odszukać znajome imię np. Alfons. Specjalnie do takich celów <strong>C++</strong><br />

dysponuje funkcją:<br />

strstr() - STRing's subSTRing - część łańcucha tekstowego<br />

Aby wyszukać w większym tekście mniejszy fragment, powinniśmy<br />

wywołując funkcję przekazać jej dwie informacje:<br />

GDZIE SZUKAĆ - wskazać łańcuch tekstowy do przeszukiwania;<br />

i<br />

CZEGO SZUKAĆ - podać ten tekst, który nas interesuje i który<br />

funkcja powinna dla nas odnaleść.<br />

Funkcja strstr(), powinna zatem mieć dwa argumenty:<br />

char Lista[] = "Adam, Buba, Adolf, Magda";<br />

...<br />

gdzie = strstr(Lista, "Adolf");<br />

Funkcja strstr() wyszukuje pierwsze wystąpienie danego tekstu.<br />

Po wyszukaniu, funkcja powinna nam w jakiś sposób wskazać, gdzie<br />

znajduje się interesujący nas tekst. Jak wiesz, do wskazywania<br />

różnych interesujących rzeczy służą w <strong>C++</strong> WSKAŹNIKI (pointer). W<br />

przykładzie powyżej funkcja strstr() w wyniku swojego działania<br />

zwraca wskaźnik do szukanego tekstu "Alfons". Aby wskaźnik nam<br />

nie przepadł, trzeba go zapamiętać. Funkcja zatem przypisuje<br />

wskaźnik zmiennej "gdzie". W miejscu przeznaczonym dla tej<br />

zmiennej w pamięci będzie odtąd przechowywany wskaźnik,<br />

- 195-


wskazujący nam - gdzie w pamięci kmputera znajduje się<br />

interesujący nas tekst "Alfons\0".<br />

Aby komputer zarezerwował miejsce w pamięci dla wskaźnika,<br />

trzeba go o to "poprosić" na początku programu, deklarując, że w<br />

programie zamierzamy posługiwać się wskaźnikiem. Deklaracja<br />

wskaźnika do zmiennej tekstowej wygląda tak:<br />

char *wskaznik;<br />

Przykładowy program pniżej demonstruje sposób zadeklarowania<br />

wskaźnika i wyszukiwanie tekstu. Program nie oczekuje żadnej<br />

informacji wejściowej od użytkownika. Uruchom program i<br />

przeanalizuj wydruk na ekranie porównując go z tekstem programu.<br />

[P069.CPP]<br />

#include <br />

#include <br />

#include <br />

main()<br />

{<br />

char string1[] = "Ala, Magda, Adam, Alfons, Jasiek, Alfons, As";<br />

char *pointer;<br />

clrscr();<br />

cout


wyszukanego pierwszego wystąpienia zadanego łańcucha znaków.<br />

[P070.CPP]<br />

#include <br />

#include <br />

#include <br />

#include <br />

main()<br />

{<br />

char str1[80], str2[80];<br />

char *ptr;<br />

clrscr();<br />

cout


{<br />

char string1[80];<br />

clrscr();<br />

cout


LEKCJA 19: KILKA INNYCH PRZYDATNYCH FUNKCJI.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak zapisać teksty na dysku i<br />

jak jeszcze można nimi manipulować przy pomocy gotowych funkcji<br />

<strong>Borland</strong> <strong>C++</strong>.<br />

________________________________________________________________<br />

Program poniżej demonstruje zastosowanie trzech przydatnych<br />

funkcji:<br />

[P072.CPP]<br />

#include <br />

int main(void)<br />

{<br />

int i, x = 0, y = 0;<br />

clrscr();<br />

for (i = 1; i < 10; i++)<br />

{<br />

y = i;<br />

x = 5*i;<br />

textbackground(16-i);<br />

textcolor(i);<br />

gotoxy(x, y);<br />

cprintf("Wspolrzedne: x=%d y=%d", x, y);<br />

getch();<br />

}<br />

return 0;<br />

}<br />

textbackground() - ustaw kolor tła pod tekstem<br />

texcolor() - ustaw kolor tekstu<br />

gotoxy() - rozpocznij drukowanie tekstu od punktu o<br />

współrzędnych ekranowych<br />

x - numer kolumny (w normalnym trybie: 1-80)<br />

y - numer wiersza (w normalnym trybie: 1-25)<br />

[Z]<br />

________________________________________________________________<br />

1. Rozmieść na ekranie napisy i znaki semigraficzne tworzące<br />

rysunek tabelki.<br />

2. Opracuj program, w którym pojedyncze znaki, bądź napisy będą<br />

poruszać się po ekranie.<br />

3. Spróbuj przyspieszyć działanie swojego programu z<br />

poprzedniego zadania poprzez wstawkę w assemblerze.<br />

________________________________________________________________<br />

OPERACJE PLIKOWE - NIEOBIEKTOWO.<br />

W systemia DOS dane i programy są zgrupowane w pliki. Pliki<br />

(ang. file) mogą być TEKSTOWE i BINARNE. Najczęstszymi<br />

- 199-


operacjami na plikach są:<br />

* Utworzenie nowego pliku (ang. CREATE);<br />

* Odczyt z pliku (ang. READ);<br />

* Zapis do pliku (WRITE);<br />

* Otwarcie pliku (OPEN);<br />

* Zamknięcie pliku (CLOSE);<br />

* Wyszukanie danej w pliku (SEEK);<br />

W kontaktach z urządzeniami - np. z dyskiem pośredniczą DOS i<br />

BIOS. To system DOS wie, gdzie na dysku szukać pliku (katalogu)<br />

o podanej nazwie i w których sektorach dysku znajdują się<br />

fizycznie dane należące do danego pliku. Operacje z plikami<br />

opierają się o odwoływanie do systemu operacyjnego za<br />

pośrednictwem tzw. Deskryptora pliku (File Descriptor - numer<br />

identyfikacyjny pliku).<br />

Zestaw "narzędzi" potrzebnych nam do pracy to:<br />

IO.H - prototypy funkcji obsługi WEjścia/WYjścia (ang.<br />

Input/Output=IO);<br />

FCNTL.H - plik zawierający definicje wymienionych poniżej<br />

stałych:<br />

O_BINARY - otwarcie pliku w trybie binarnym;<br />

O_TEXT - otwarcie pliku w trybie tekstowym;<br />

O_RDONLY (Open for Read Only) - otwórz tylko do odczytu;<br />

O_WRONLY (...Write Only) - tylko dla zapisu;<br />

O_RDWR (Reading and Writing) dozwolony zapis i odczyt;<br />

STAT.H - zawiera definicje stałych<br />

S_IREAD - plik tylko do odczytu (przydatne dla funkcji creat);<br />

S_IWRITE - tylko zapis (przydatne dla funkcji creat);<br />

FUNKCJE:<br />

int open(p1, p2, p3) - trójparametrowa funkcja otwierająca plik;<br />

(parametry patrz przykład) zwraca do programu Wynik = -1<br />

(operacja zakończona niepowodzeniem - np. nie ma pliku)<br />

lub Wynik = File Descriptor - numer pliku przekazany przez DOS.<br />

int creat(p1, p2) - funkcja tworząca nowy plik;<br />

int read(...) - funkcja czytająca z pliku;<br />

int write(...) - funkcja zapisu do pliku;<br />

imt close(...) - zamknięcie pliku.<br />

Po uruchomieniu program otwiera automatycznie trzy standardowe<br />

pliki, związane z urządzeniami:<br />

0 - stdin - standardowy plik wejściowy (norm. klawiatura<br />

konsoli);<br />

1 - stdout - standardowy plik wyjściowy (norm. monitor);<br />

2 - stderr - standardowy plik wyjściowy - diagnostyczny<br />

(komunikaty o błędach).<br />

[S] STD...<br />

STandarD INput - standardowe wejście.<br />

- 200-


STD<br />

STD<br />

OUTput - standardowe wyjście.<br />

ERRors - plik diagnostyczny.<br />

//[P072-2.CPP]<br />

# include <br />

# include <br />

# include //Duze litery tylko dla podkreslenia<br />

# include <br />

# include <br />

char *POINTER;<br />

int IL_znakow, DLUG_pliku, TRYB_dostepu, Wynik, i;<br />

int Plik_1, Plik_2;<br />

char BUFOR[20] = {"TEKST DO PLIKU"};<br />

char STOS[3], ZNAK='X';<br />

main()<br />

{<br />

POINTER = &BUFOR[0];<br />

printf("Wloz dyskietke do A: i nacisnij cos...\n");<br />

Plik_1 = creat( "a:\\plik1.dat", S_IWRITE);<br />

if (Plik_1 == -1)<br />

printf("\n Nie udalo sie zalozyc plik1.dat...");<br />

Plik_2 = creat( "a:\\plik_2.dat", S_IWRITE);<br />

if (Plik_2 == -1)<br />

printf("\n Klops przy Plik2.dat");<br />

_fmode = O_BINARY; //Bedziemy otwierac w trybie binarnym<br />

Wynik = open( "a:\\plik1.dat", O_WRONLY );<br />

if (Wynik == -1)<br />

printf("\n Nie udalo sie otworzyc pliku...");<br />

IL_znakow = 15;<br />

//Ilosc znakow do zapisu<br />

Wynik =write( Plik_1, POINTER, IL_znakow );<br />

printf("Zapisalem %d znakow do pliku.", Wynik);<br />

close( Plik_1 );<br />

Plik_1 = open("a:\\Plik1.dat", O_RDONLY );<br />

Plik_2 = open("a:\\Plik_2.dat", O_WRONLY );<br />

POINTER = &STOS[0];<br />

for (i=1; ZNAK; i++) //Kopiuje plik + spacje<br />

{<br />

STOS[1] = ZNAK;<br />

write( Plik_2, POINTER, 2);<br />

- 201-


ead( Plik_1, &ZNAK, 1);<br />

}<br />

close(Plik_1); close(Plik_2);<br />

getch();<br />

return 0;<br />

}<br />

Przykładowy program wykonuje następujące czynności:<br />

1. Tworzy plik a:\plik1.dat (potrzebny dostęp do dyskietki a:).<br />

2. Tworzy plik a:\plik_2.dat.<br />

3. Otwiera plik a:\plik1.dat w trybie binarnym tylko do zapisu.<br />

(ZWRÓĆ UWAGĘ, że tryb binarny nie przeszkadza zapisać tekstu.)<br />

4. Dokonuje zapisu do pliku.<br />

5. Zamyka plik a:\plik1.dat.<br />

6. Otwiera plik1.dat w trybie binarnym tylko do odczytu.<br />

7. Otwiera plik_2.dat tylko do zapisu.<br />

8. Kopiuje plik1.dat do plik_2.dat dodając spacje.<br />

Zwróć uwagę na konstrukcję:<br />

for(i=1; ZNAK; i++)<br />

Wyjaśnienie. Póki jest znak wykonuj kopiowanie. Przypominam, że<br />

koniec to NUL - '\0'.<br />

Jeśli czytamy i piszemy po kolei - wszystko jest proste. Jeżeli<br />

natomiast chcemy wyszukać w pliku określone miejsce, to będzie<br />

nam jeszcze dodatkowo potrzebny mechanizm do określenia pozycji<br />

w pliku - tzw. WSKAŹNIK PLIKOWY. Pozycję można określać względem<br />

początku pliku:<br />

SEEK_SET - stała określająca pozycjonowanie względem początku<br />

pliku;<br />

SEEK_CUR - względem położenia bieżącego (ang. Current -<br />

bieżący);<br />

SEEK_END - określenie pozycji względem końca pliku;<br />

EOF - End Of File - znak końca pliku.<br />

Funkcja lseek():<br />

WSK_PLK = long int lseek( plik, o_ile, kierunek);<br />

służy do pozycjonowania w pliku.<br />

Liczba typu long int określająca pozycję w pliku nazywana jest<br />

WSKAŹNIKIEM PLIKOWYM ( w programie przykładowym została<br />

oznaczona long int WSK_PLK).<br />

W programie przykładowym wykonywane jest kolejno:<br />

* utworzenie na dysku pliku PROBA.DAT;<br />

* zapis do pliku wprowadzonych z klawiatury liczb całkowitych<br />

typu int;<br />

- 202-


* zamknięcie pliku;<br />

* otwarcie pliku do odczytu;<br />

* ustawienie wskaźnika na końcu pliku;<br />

* odczyt z pliku od końca;<br />

* wyprowadzenie odczytanych z pliku danych na ekran.<br />

[P073.CPP]<br />

# include "sys\stat.h"<br />

# include "conio.h"<br />

# include "stdio.h"<br />

# include "io.h"<br />

# include "fcntl.h"<br />

# define Cofnij_o_Zero 0<br />

# define dwa_bajty 2<br />

int Numer = 0;<br />

int Plik, L, M, i;<br />

long int Dlug_Pliku;<br />

main()<br />

{<br />

clrscr();<br />

creat("A:\PROBA.DAT", S_IWRITE);<br />

printf("\nPodaj liczbe rozna od zera, zero - KONIEC");<br />

_fmode=O_BINARY;<br />

Plik=open("A:\PROBA.DAT", O_WRONLY);<br />

do<br />

{<br />

printf("\n Nr liczby \t%d\t\t", Numer++);<br />

scanf("%d", &L);<br />

if (L) write(Plik, &L, 2);<br />

}<br />

while (L != 0);<br />

close(Plik);<br />

getch();<br />

printf("\n Teraz odczytam te liczby z pliku \n");<br />

Plik=open("A:\PROBA.DAT", O_RDONLY);<br />

Dlug_Pliku=lseek(Plik, 0, SEEK_END);<br />

for (i=Dlug_Pliku-dwa_bajty; i>=0; i-=2)<br />

{<br />

lseek(Plik, i, SEEK_SET);<br />

read(Plik, &M, dwa_bajty);<br />

printf("%d, ", M);<br />

}<br />

close(Plik);<br />

getch();<br />

return 0;<br />

}<br />

[Z]<br />

________________________________________________________________<br />

- 203-


Opracuj program wykonujący operacje na tekstach opisane<br />

wcześniej na łańcuchach tekstowych pobieranych z zewnętrznych<br />

plików dyskowych i umieszczanych w wynikowych plikach<br />

tekstowych.<br />

________________________________________________________________<br />

- 204-


LEKCJA 20. JEŚLI PROGRAM POWINIEN URUCHOMIĆ INNY PROGRAM...<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak w <strong>C++</strong> można programować<br />

* procesy potomne<br />

* pisać programy rezydujące w pamięci (TSR)<br />

________________________________________________________________<br />

O programach rezydentnych (TSR) i procesach potomnych.<br />

Warunek zewnętrznej zgodności z poprzednimi wersjami DOS<br />

wyraźnie hamuje ewolucję systemu MS DOS w kierunku "poważnych"<br />

systemów operacyjnych umożliwjających pracę wieloprogramową w<br />

trybie "multiuser", "multitasking" i "time sharing". Pewną<br />

namiastkę pracy wieloprocesowej dają nam już DOS 5/6 i Windows<br />

3.1. Można już otwierać wiele okien programów jednocześnie,<br />

można np. drukować "w tle", można wreszcie pisać rezydujące<br />

stale w pamięci programy klasy TSR (ang. Terminated and Stay<br />

Resident) uaktywniające się "od czasu do czasu".<br />

O bloku PSP.<br />

System DOS przydziela programom blok - "nagłówek" wstępny<br />

nazywany PSP (ang. Program Segment Prefix). Blok ten zawiera<br />

informacje o stanie systemu DOS w momencie uruchamiania programu<br />

(nazywanego tu inaczej procesem). Znajdują się tam informacje o<br />

bieżącym stanie zmiennych otoczenia systemowego (ang.<br />

environment variables) i parametrach uruchomieniowych. Blok PSP<br />

zajmuje 256 bajtów na początku kodu programu w zakresie adresów:<br />

CS:0000 ... CS:0100 (hex)<br />

Właściwy kod programu zaczyna się zatem od adresu CS:0100.<br />

Interpreter rozkazów systemu DOS ładuje programy do pamięci<br />

posługując się funkcją systemową nr 75 (4B hex). Wszystko jest<br />

proste dopóki mamy do czynienia z programem "krótkim" typu<br />

*.COM. Jeśli jednakże program uruchamiany jest w wersji<br />

"długiej" - *.EXE, dowolna może być nie tylko długość pliku, ale<br />

także początkowa zawartość rejestrów CS, SS, SP i IP. W plikach<br />

typu *.EXE początek bloku PSP wskazują rejestry DS (DS:0000) i<br />

ES. W <strong>Borland</strong> <strong>C++</strong> masz do dyspozycji specjalną funkcję getpsp()<br />

przy pomocy której możesz uzyskać dostęp do bloku PSP programu.<br />

Krótki przykład zastosowania tej funkcji poniżej:<br />

/* Przykład zastosowania funkcji getpsp(): */<br />

# include <br />

# include <br />

main()<br />

{<br />

static char TAB[128];<br />

char far *ptr;<br />

- 205-


int dlugosc, i;<br />

printf("Blok PSP: %u \n", getpsp());<br />

ptr = MK_FP(_psp, 0x80);<br />

dlugosc = *ptr;<br />

for (i = 0; i < dlugosc; i++)<br />

TAB[i] = ptr[i+1];<br />

printf("Parametry uruchomieniowe: %s\n", TAB);<br />

}<br />

W normalnych warunkach po wykonaniu "swojej roboty" program<br />

zostaje usunięty z pamięci operacyjnej (czym zajmuje się funkcja<br />

systemowa nr 76 - 4C (hex)). Aby tak się nie stało, program<br />

może:<br />

* uruchomić swój proces (program) potomny;<br />

* wyjść "na chwilę" do systemu DOS - tj. uruchomić jako swój<br />

proces potomny interpreter COMMAND.COM;<br />

* przekazać sterowanie programowi COMMAND.COM pozostając w<br />

pamięci w postaci "uśpionej" oczekując na uaktywninie.<br />

Poniżej kilka prostych przykładów uruchamiania jednych procesów<br />

przez inne w <strong>Borland</strong> <strong>C++</strong>:<br />

/* Funkcja execv(): uruchomienie programu "potomnego"*/<br />

# include <br />

# include <br />

# include <br />

void main(int argc, char *argv[])<br />

{<br />

int i;<br />

printf("Parametry uruchomieniowe:");<br />

for (i=0; i


void main()<br />

{<br />

printf("Wyjscie do DOS i wykonanie jednego rozkazu:\n");<br />

system("dir > c:\plik.dir");<br />

}<br />

/* Funkcje grupy spawn...() : spawnl() */<br />

# include <br />

# include <br />

# include <br />

void main()<br />

{<br />

int rezultat;<br />

rezultat = spawnl(P_WAIT, "program.exe", NULL);<br />

if (rezultat == -1)<br />

{<br />

perror(" Fiasko !");<br />

exit(1);<br />

}<br />

}<br />

/* Funkcja spawnle() */<br />

# include <br />

# include <br />

# include <br />

void main()<br />

{<br />

int rezultat;<br />

rezultat = spawnle(P_WAIT, "program.exe", NULL, NULL);<br />

if (rezultat == -1)<br />

{<br />

perror("Fiasko !");<br />

exit(1);<br />

}<br />

}<br />

Zagadnienie uruchamiania programów potomnych (ang. child<br />

process) przez programy macieżyste (ang. parent process) jest<br />

rozpracowane w <strong>C++</strong> dość dokładnie i zarazem obszernie. Istnieje<br />

wiele gotowych funkcji bibliotecznych, z usług których możesz tu<br />

skorzystać. Wszystko to nie jest jednak "prawdziwym" programem<br />

TSR. Przyjrzyjmy się zatem dokładniej dopuszcalnym przez system<br />

DOS sposobom zakończenia programu nie powodującym usunięcia<br />

programu z pamięci.<br />

Jeśli program rezydentny jest niewielki (kod < 64 K), możemy<br />

- 207-


zakończyć program posługując się przerywaniem INT 39 (27 hex).<br />

Jeśli natomiast zamierzamy posługiwać się dłuższymi programami,<br />

mamy do dyspozycji funkcję systemową nr 49 (31 hex). Należy tu<br />

zwrócić uwagę, że zakończenie programu w taki sposób (z<br />

pozostawieniem w pamięci) nie spowoduje automatycznego<br />

zamknięcia plików, a jedynie opróżnienie buforów. Programy<br />

rezydentne dzieli się umownie na trzy kategorie:<br />

[BP] - background process - procesy działające "w tle";<br />

[SV] - services - programy usługowe - np. PRINT;<br />

[PP] - pop up programs - uaktywniane przez określoną kombinację<br />

klawiszy;<br />

System DOS dysponuje tzw. przerywaniem multipleksowym<br />

(naprzemiennym) wykorzystywanym często przez programy<br />

rezydentne. Jest to przerywanie nr INT 47 (2F hex). MS DOS<br />

załatwia takie problemy funkcjami nr 37 (25 hex) - zapisanie<br />

wektora przerywania i 53 (35 hex) - odczytanie wektora<br />

przerywania.<br />

Z jakich funkcji <strong>C++</strong> można skorzystać?<br />

W <strong>C++</strong> masz do dyspozycji parę funkcji getvect() i setvect()<br />

(ang. GET/SET VECTor - pobierz/ustaw wektor przerywania).<br />

Poniżej krótkie przykłady zastosowań tych funkcji.<br />

/* Opcja: Options | Compiler | Code generation | Test Stack<br />

Overflow powinna zostać wyłączona [ ] (off) */<br />

# include "stdio.h"<br />

# include "dos.h"<br />

# include "conio.h"<br />

/* INT 28 (1C hex) - Przerywanie zegarowe */<br />

void interrupt ( *oldhandler)(void);<br />

int licznik = 0;<br />

void interrupt handler(void)<br />

{<br />

/* Inkrementacja globalnej zmiennej licznik */<br />

licznik++;<br />

/* Wywolujemy stary "handler" zegara */<br />

oldhandler();<br />

}<br />

void main()<br />

{<br />

/* Zapamiętaj poprzedni wektor przerywania 28 */<br />

oldhandler = getvect(28);<br />

/* Zainstaluj nową funkcje obslugi przerywania */<br />

setvect(28, handler);<br />

- 208-


* Inkrementuj licznik */<br />

for (; licznik < 10; ) printf("licznik: %d\n",licznik);<br />

//odtworz stara funkcje obslugi przerywania: interrupt handler<br />

setvect(28, oldhandler);<br />

}<br />

# include <br />

# include <br />

void interrupt nowa_funkcja(); // prototyp funkcji - handlera<br />

void interrupt (*oldfunc)(); /* interrupt function pointer */<br />

int warunek = 1;<br />

main()<br />

{<br />

printf("\n [Shift]+[Print Screen] = Quit \n");<br />

printf("Zapamietaj, i nacisnij cosik....");<br />

while(!kbhit());<br />

/* zapamietaj stary wektor */<br />

oldfunc = getvect(5);<br />

/* INT 5 to przerywanie Sys Rq, albo Print Screen */<br />

/* zainstaluj nowa funkcje obslugi: interrupt handler */<br />

setvect(5, nowa_funkcja);<br />

while (warunek) printf(".");<br />

/* Odtworz stary wektor przerywania */<br />

setvect(5, oldfunc);<br />

printf("\n Udalo sie... nacisnij cosik...");<br />

while(!kbhit());<br />

}<br />

/* Definicja nowego handlera */<br />

void interrupt nowa_funkcja()<br />

{<br />

warunek = 0;<br />

/* jesli warunek == 0, petla zostanie przerwana*/<br />

}<br />

Jeśli nasz program zamierza korzystać z przerywania<br />

multipleksowego INT 47 (2F hex), należy pamiętać, że przerywanie<br />

to wykorzystują także inne programy systemowe. Rozróżniać te<br />

programy można przy pomocy identyfikatorów (podaję dziesiętnie):<br />

01 - PRINT.EXE<br />

06 - ASSIGN.COM<br />

- 209-


16 - SHARE.EXE (10 hex)<br />

26 - ANSI.SYS<br />

67 - HIMEM.SYS<br />

72 - DOSKEY.COM<br />

75 - TASK SWITCHER<br />

173 - KEYB.COM<br />

174 - APPEND.EXE<br />

176 - GRAFTABL.COM<br />

183 - APPEND.EXE<br />

Identyfikator programu TSR jest przekazywany za pośrednictwem<br />

rejestru AH.<br />

System DOS jest na razie systemem w zasadzie jednozadaniowym i<br />

jednoużytkownikowym, w którym zasoby są przydzielane procesom<br />

kolejno (ang. serially reusable resources). Aby uchronić się<br />

przed potencjalnym konfliktem, powinniśmy upewnić się, czy DOS<br />

"nic nie robi". Często stosowaną "sztuczką techniczną" jest<br />

zastosowanie flag ErrorMode i InDos systemu oraz wykorzystanie<br />

mechanizmów przerywań nr 36 i 40 (24 i 28 hex). Przydatną<br />

informacją jest także identyfikator programu - PID. Na taką<br />

ewntualność <strong>Borland</strong> <strong>C++</strong> dysponuje makrem getpid zdefiniowanym w<br />

pliku nagłówkowym :<br />

# define getpid() (_psp)<br />

Inną przydatną funkcją może okazać się keep() (ang. keep<br />

resident - pozostań rezydentny). Oto krótki przykład<br />

zastosowania tej funkcji - znów z wykorzystaniem przerywań<br />

zegarowych.<br />

# include <br />

# define INTR 0x1C /* przerywanie INT 28 */<br />

# define ATTR 0x7900<br />

/* ograniczenie wielkości sterty (heap length) i stosu (stack<br />

length): */<br />

extern unsigned _heaplen = 1024;<br />

extern unsigned _stklen = 512;<br />

void interrupt ( *oldhandler)(void);<br />

void interrupt handler(void)<br />

{<br />

unsigned int (far *ekran)[80];<br />

static int licznik;<br />

// Adres pamieci dla monitora barwnego: B800:0000.<br />

// Dla monitora monochromatycznego: B000:0000.<br />

ekran = MK_FP(0xB800,0);<br />

// piloksztaltna zmiana licznika w przedziale 0 ... 9<br />

- 210-


licznik++;<br />

licznik %= 10;<br />

ekran[0][79] = licznik + '0' + ATTR;<br />

// wywołaj stara funkcje obslugi - old interrupt handler:<br />

oldhandler();<br />

}<br />

void main()<br />

{<br />

oldhandler = getvect(INTR);<br />

// zainstaluj nowa funkcje interrupt handler<br />

setvect(INTR, handler);<br />

/* _psp - to adres początku programu, SS:SP to adres stosu,<br />

czyli koniec programu. Biorac pod uwage przesuniecie<br />

SEGMENT/OFFSET o jedna tetrade: SS:SP = SS + SP/16; */<br />

keep(0, (_SS + (_SP/16) - _psp));<br />

}<br />

Kilka istotnych drobiazgów technicznych.<br />

W <strong>Borland</strong> <strong>C++</strong> masz do dyspozycji predefiniowane struktury<br />

BYTEREGS (rejestry jednobajtowe - "połówki") i WORDREGS<br />

(rejestry dwubajtowe). Możesz po tych strukturach dziedziczyć i<br />

np. taką metodą wbudować je do swoich własnych klas. Nic nie<br />

stoi na przeszkodzie, by utworzyć np. klasę<br />

class REJESTRY : public WORDREGS<br />

{<br />

...<br />

};<br />

czy też własną strukturę:<br />

struct REJESTRY : WORDREGS { ... };<br />

Definicje tych struktur w <strong>Borland</strong> <strong>C++</strong> wyglądają następująco:<br />

struct BYTEREGS<br />

{<br />

unsigned int al, ah, bl, bh, cl, ch, dl, dh;<br />

};<br />

struct WORDREGS<br />

{<br />

unsigned int ax, bx, cx, dx, si, di, cflag, flags;<br />

};<br />

Rejestry segmentowe mają własną strukturę:<br />

- 211-


struct SREGS<br />

{<br />

unsigned int es, cs, ss, ds;<br />

};<br />

Pole WORDREGS::cflag odpowiada stanowi flagi przeniesienia (ang.<br />

Carry Flag) rejestru flags mikroprocesora, a pole<br />

WORDREGS::flags odpowiada stanowi całości rejestru (w wersji 16<br />

- bitowej). Ponieważ rejestry mogą być widziane alternatywnie<br />

jako podzielone na miezależne połówki - lub jako całość, to<br />

właśnie "albo - albo" wyraża w <strong>C++</strong> unia. W <strong>Borland</strong> <strong>C++</strong> taka<br />

predefiniowana unia nazywa się REGS:<br />

union REGS<br />

{<br />

struct WORDREGS x;<br />

struct BYTEREGS h;<br />

};<br />

Z tych predefiniowanych struktur danych korzystają m. in.<br />

funkcje int86() intdosx() i int86x() ("x" pochodzi od eXtended -<br />

rozszerzony). Oto krótkie przykłady zastosowania tych funkcji.<br />

# include <br />

# include <br />

# include <br />

# define INT_NR 0x10 // 10 hex == 16 (Nr przerywania) VIDEO<br />

void UstawKursor(int x, int y)<br />

{<br />

union REGS regs;<br />

regs.h.ah = 2; // ustaw kursor<br />

regs.h.dh = y; // Wspolrzedne kursora na ekranie<br />

regs.h.dl = x;<br />

regs.h.bh = 0; // Aktywna stronica ekranu --> video page 0<br />

int86(INT_NR, &regs, &regs);<br />

}<br />

void main()<br />

{<br />

clrscr();<br />

UstawKursor(30, 12);<br />

printf("Tekst - Test");<br />

while(!kbhit());<br />

}<br />

# include <br />

# include <br />

# include <br />

- 212-


void main()<br />

{<br />

char nazwapliku[40];<br />

union REGS inregs, outregs;<br />

struct SREGS segregs;<br />

printf("\nPodaj nazwe pliku: ");<br />

gets(nazwapliku); // gets() == GET String<br />

inregs.h.ah = 0x43;<br />

inregs.h.al = 0x21;<br />

inregs.x.dx = FP_OFF(nazwapliku);<br />

segregs.ds = FP_SEG(nazwapliku);<br />

int86x(0x21, &inregs, &outregs, &segregs);<br />

printf("\n Atrybuty pliku: %X\n", outregs.x.cx);<br />

}<br />

# include <br />

# include <br />

int SkasujPlik(char far*) // Prototyp<br />

void main()<br />

{<br />

int error;<br />

err = SkasujPlik("PLIK.DAT");<br />

if (!error) printf("\nSkasowalem plik PLIK.DAT");<br />

else<br />

printf("\nNie moge skasowac pliku PLIK.DAT");<br />

}<br />

int SkasujPlik(char far *nazwapliku)<br />

{<br />

union REGS regs; struct SREGS sregs;<br />

int wynik;<br />

regs.h.ah = 0x41; // Funkcja kasowania pliku<br />

regs.x.dx = FP_OFF(nazwapliku);<br />

sregs.ds = FP_SEG(nazwapliku);<br />

wynik = intdosx(&regs, &regs, &sregs);<br />

return(regs.x.cflag ? wynik : 0);<br />

// Jesli CF == 1, nastapilo fiasko operacji<br />

}<br />

I wreszcie na zakończenie szczegóły techniczne działania funkcji<br />

systemowej nr 49 (31 hex) odpowiedzialnej za obsługę programów<br />

rezydujących w pamięci (załadowanie procesu z pozostawieniem w<br />

pamięci).<br />

1. Wywołanie funkcji:<br />

AL = kod powrotu (ang. return code);<br />

AH = 0031 (hex) - nr funkcji;<br />

DX = długość programu TSR w paragrafach - Size/16 [Bajtów];<br />

2. Działanie:<br />

* funkcja nie zamyka plików, lecz opróżnia bufory;<br />

- 213-


* funkcja odtwarza wektory przerywań nr 34, 35, 36 (hex 21, 22,<br />

23);<br />

* proces macieżysty może uzyskać kod powrotu przy pomocy funkcji<br />

nr 77 (4D hex).<br />

Wykorzystanie struktury SDA (ang. Swappable Data Area - obszar<br />

wymiennych danych) nie jest praktyką zalecaną.<br />

Tworząc programy rezydentne bądź bardzo ostrożny i pamiętaj o<br />

jednej z podstawowych zasad - NIE JESTEŚ (tzn Twój program nie<br />

jest) SAM.<br />

________________________________________________________________<br />

- 214-


LEKCJA 21: KILKA PROCESÓW JEDNOCZEŚNIE.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak to zrobić, by Twój PC mógł<br />

wykonywać kilka rzeczy jednocześnie.<br />

________________________________________________________________<br />

Procesy współbieżne.<br />

Sprzęt, czyli PC ma możliwości zdecydowanie pozwalające na<br />

techniczną realizację pracy wielozadaniowej. Nie ma też żadnych<br />

przeciwskazań, by zamiast koprocesora umożliwić w PC instalację<br />

drugiego (trzeciego) równoległego procesora i uprawiać na PC<br />

poważne programowanie współbieżne. Po co? To proste. Wyobraź<br />

sobie Czytelniku, że masz procesor pracujący z częstotliwością<br />

25 MHz (to 25 MILIONÓW elementarnych operacji na sekundę!).<br />

Nawet, jeśli wziąć pod uwagę, że niektóre operacje (dodawanie,<br />

mnożenie, itp.) wymagają wielu cykli - i tak można w<br />

uproszczeniu przyjąć, że Twój procesor mógłby wykonać od<br />

kilkuset tysięcy do kilku milionów operacji w ciągu sekundy.<br />

Jeśli pracujesz np. z edytorem tekstu i piszesz jakiś tekst -<br />

znacznie ponad 99% czasu Twój procesor czeka KOMPLETNIE<br />

BEZCZYNNIE (!) na naciśnięcie klawisza. Przecież Twój komputer<br />

mogłby w tym samym czasie np. i formatować dyskietkę (dyskietka<br />

też jest powolna), i przeprowadzać kompilację programu, i<br />

drukować dokumenty, i przeprowadzić defragmentację drugiego<br />

dysku logicznego, itp. itd..<br />

Nawet taka pseudowspółbieżność realizowana przez DOS, Windows,<br />

czy sieć jest ofertą dostatecznie atrakcyjną, by warto było<br />

przyjrzeć się mechanizmom PSEUDO-współbieżności w C i <strong>C++</strong>.<br />

Współbieżność procesów, może być realizowana na poziomie<br />

* sprzętowym (architektura wieloprocesorowa),<br />

* systemowym (np. Unix, OS/2),<br />

* nakładki (np. sieciowej - time sharing, token passing)<br />

* aplikacji (podział czasu procesora pomiędzy różne<br />

funkcje/moduły tego samego pojedynczego programu).<br />

My zajmiemy się tu współbieżnością widzianą z poziomu aplikacji.<br />

Funkcje setjmp() (ang. SET JuMP buffer - ustaw bufor<br />

umożliwiający skok do innego procesu) i longjmp() (ang. LONG<br />

JuMP - długi skok - poza moduł) wchodzą w skład standardu C i w<br />

związku z tym zostały "przeniesine" do wszystkich kompilatorów<br />

<strong>C++</strong> (nie tylko Borlanada).<br />

Porozmawiajmy o narzędziach.<br />

Zaczniemy od klasycznego zestawu narzędzi oferowanego przez<br />

<strong>Borland</strong>a. Aby zapamiętać stan przerwanego procesu stosowana jest<br />

w C/<strong>C++</strong> struktura PSS (ang. Program Status Structure) o nazwie<br />

jmp_buf (JuMP BUFfer - bufor skoku). W przypadku współbieżności<br />

- 215-


wielu procesów (więcej niż dwa) stosuje się tablicę złożoną ze<br />

struktur typu<br />

struct jmp_buf TablicaBuforow[n];<br />

Struktura służy do przechowywania informacji o stanie procesu<br />

(rejestrach procesora w danym momencie) i jest predefiniowana w<br />

pliku SETJMP.H:<br />

typedef struct<br />

{<br />

unsigned j_sp, j_ss, j_flag, j_cs;<br />

unsigned j_ip, j_bp, j_di, j_es;<br />

unsigned j_si, j_ds;<br />

} jmb_buf[1];<br />

Prototypy funkcji:<br />

int setjmp(jmp_buf bufor);<br />

void longjmp(jmp_buf bufor, int liczba);<br />

W obu przypadkach jmp_buf bufor oznacza ten sam typ bufora<br />

(niekoniecznie ten sam bufor - może ich być wiele), natomiast<br />

int liczba oznacza tzw. return value - wartość zwracaną po<br />

powrocie z danego procesu. Liczba ta może zawierać informację, z<br />

którego procesu nastąpił powrót (lub inną przydatną w<br />

programie), ale nie może być ZEREM. Jeśli funkcja longjmp()<br />

otrzyma argument int liczba == 0 - zwróci do programu wartość 1.<br />

Wartość całkowita zwracana przez funkcję setjmp() przy pierwszym<br />

wywołaniu jest zawsze ZERO a przy następnych wywołaniach (po<br />

powrocie z procesu) jest równa parametrowi "int liczba"<br />

przekazanemu do ostatnio wywołanej funkcji longjmp().<br />

Przyjrzyjmy się temu mechanizmowi w praktyce. Wyobraźmy sobie,<br />

że chcemy realizować współbieżnie dwa procesy - proces1 i<br />

proces2. Proces pierwszy będzie naśladował w uproszczeniu<br />

wymieniony wyżej edytor tekstu - pozwoli na wprowadzanie tekstu,<br />

który będzie powtarzany na ekranie. Proces drugi będzie<br />

przesuwał w dolnej części ekranu swój numerek - cyferkę 2 (tylko<br />

po to, by było widać, że działa). Program główny wywołujący oba<br />

procesy powinien wyglądać tak:<br />

...<br />

void proces1(void);<br />

void proces2(void);<br />

int main(void)<br />

{<br />

clrscr();<br />

- 216-


proces1();<br />

proces2();<br />

return 0;<br />

}<br />

Ależ tu nie ma żadnej współbieżności! Oczywiście. Aby<br />

zrealizować współbieżność musimy zadeklarować bufor na bieżący<br />

stan rejestrów i zastosować funkcje setjmp():<br />

#include <br />

void proces1(void);<br />

void proces2(void);<br />

jmp_buf bufor1;<br />

int main(void)<br />

{<br />

clrscr();<br />

if(setjmp(bufor1) != 0) proces1(); //Powrót z procesu2 był?<br />

proces2();<br />

return 0;<br />

}<br />

Po wywołaniu funkcji setjmp() zostanie utworzony bufor1, w<br />

którym zostanie zapamiętany stan programu. Funkcja, jak zawsze<br />

przy pierwszym wywołaniu zwróci wartość ZERO, więc warunek<br />

if(setjmp(bufor1) != 0) ...<br />

nie będzie spełniony i proces1() nie zostanie wywołany. Program<br />

pójdzie sobie dalej i uruchomi proces2():<br />

void proces2(void)<br />

{<br />

for(;;)<br />

{<br />

gotoxy(10,20);<br />

printf("PROCES 2: ");<br />

for(int i = 1; i


clrscr();<br />

if(setjmp(bufor1)) proces1();<br />

proces2();<br />

return 0;<br />

}<br />


while(kbhit())<br />

{<br />

gotoxy(1,1);<br />

printf("PROCES1, Pisz tekst:<br />

gotoxy(pozycja,2);<br />

znak = getch();<br />

printf("%c", znak);<br />

pozycja++;<br />

}<br />

if(znak == '.') exit (0);<br />

}<br />

[Kropka - Koniec]");<br />

void proces2(void)<br />

{<br />

for(;;)<br />

{<br />

gotoxy(10,20);<br />

printf("PROCES 2: ");<br />

for(int i = 1; i


char znak;<br />

int pozycja = 1;<br />

int main(void)<br />

{<br />

clrscr();<br />

if(setjmp(bufor1)) proces1();<br />

if(setjmp(bufor2)) proces2();<br />

proces3();<br />

return 0;<br />

}<br />

void proces1(void)<br />

{<br />

while(kbhit())<br />

{<br />

gotoxy(1,1);<br />

printf("PROCES1, Pisz tekst: [Kropka - Koniec]");<br />

gotoxy(pozycja,2);<br />

znak = getch();<br />

printf("%c", znak);<br />

pozycja++;<br />

}<br />

if(znak == '.') exit (0);<br />

}<br />

void proces2(void)<br />

{<br />

for(;;)<br />

{<br />

gotoxy(10,20);<br />

printf("PROCES 2: ");<br />

for(int i = 1; i


longjmp(bufor2,2);<br />

}<br />

}<br />

Procesy odbywają się z różną prędkością. Kolejność uruchamiania<br />

procesów będzie:<br />

- proces3()<br />

- proces2()<br />

- proces1()<br />

Po uruchomieniu programu zauważysz, że proces pierwszy (pisania)<br />

został spowolniony. Można jednak temu zaradzić przez ustawienie<br />

flag i priorytetów. Jeśli dla przykładu uważamy, że pisanie jest<br />

ważniejsze, możemy wykrywać zdarzenie - naciśnięcie klawisza w<br />

każdym z mniej ważnych procesów i przerywać wtedy procesy mniej<br />

ważne. Wprowadzanie tekstu w przykładzie poniżej nie będzie<br />

spowolnione przez pozostałe procesy.<br />

[P077.CPP]<br />

#include <br />

#include <br />

#include <br />

#include <br />

#include <br />

void proces1(void);<br />

void proces2(void);<br />

void proces3(void);<br />

jmp_buf BuforStanu_1, BuforStanu_2;<br />

char znak;<br />

int pozycja = 1;<br />

int main(void)<br />

{<br />

clrscr();<br />

if(setjmp(BuforStanu_1)) proces1();<br />

if(setjmp(BuforStanu_2)) proces2();<br />

proces3();<br />

return 0;<br />

}<br />

void proces1(void)<br />

{<br />

while(kbhit())<br />

{<br />

gotoxy(1,1);<br />

printf("PROCES1, Pisz tekst: [Kropka - Koniec]");<br />

gotoxy(pozycja,2);<br />

znak = getch();<br />

- 221-


printf("%c", znak);<br />

pozycja++;<br />

}<br />

if(znak == '.') exit (0);<br />

}<br />

void proces2(void)<br />

{<br />

for(;;)<br />

{<br />

gotoxy(10,20);<br />

printf("PROCES 2: ");<br />

for(int i = 1; i


powrocie z procesu identyfikować - z którego procesu nastąpił<br />

powrót i podejmować stosowną decyzję np. przy pomocy instrukcji<br />

switch:<br />

switch(setjmp(bufor))<br />

{<br />

case 1 : proces2();<br />

case 2 : proces3();<br />

.....<br />

default : proces0();<br />

}<br />

[!!!]UWAGA<br />

________________________________________________________________<br />

* Zmienne sterujące przełączaniem procesów powinny być zmiennymi<br />

globalnymi, bądź statycznymi. Także dane, które nie mogą ulec<br />

nadpisaniu bezpieczniej potraktować jako globalne.<br />

________________________________________________________________<br />

W przypadku wielu procesów celowe jest utworzenie listy, bądź<br />

kolejki procesów. Przydatny do tego celu bywa mechanizm tzw.<br />

"łańcuchowej referencji". W obiektach klasy PozycjaListy należy<br />

umieścić pole danych - strukturę i pointer do następnego<br />

procesu, któremu (zgodnie z ustalonym priorytetem) należy<br />

przekazać sterowanie:<br />

static jmp_buf Bufor[m];<br />

...<br />

class PozycjaListy<br />

{<br />

public:<br />

jmp_buf Bufor[n];<br />

PozycjaListy *nastepna;<br />

}<br />


Proces();<br />


LEKCJA 22. NA ZDROWY CHŁOPSKI ROZUM PROGRAMISTY.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się:<br />

* jak przyspieszać działanie programów w <strong>C++</strong><br />

* jakie dodatkowe narzędzia zyskujesz "przesiadając się" na<br />

nowoczesny kompilator <strong>C++</strong><br />

________________________________________________________________<br />

UNIKAJMY PĘTLI, które nie są NIEZBĘDNE !<br />

Unikanie zbędnych pętli nazywa się fachowo "rozwinięciem pętli"<br />

(ang. loop unrolling). Zwróć uwagę, że zastępując pętlę jej<br />

rozwinięciem (ang. in-line code):<br />

* zmniejszamy ilość obliczeń,<br />

* zmniejszamy ilość zmiennych.<br />

Wyobraźmy sobie pętlę:<br />

for (i = 0; i < max; i++)<br />

T[i] = i;<br />

Jeśli "unowocześnimy" ją tak:<br />

for (i = 0; i < max; )<br />

{<br />

T[i++] = i - 1;<br />

T[i++] = i - 1;<br />

}<br />

ilość powtórzeń pętli zmniejszy się dwukrotnie. Czai się tu<br />

jednak pewne niebezpieczeństwo: tablica może mieć NIEPARZYSTĄ<br />

liczbę elementów. Np. dla 3-elementowej tablicy (max = 3)<br />

nastąpiłyby w pierwszym cyklu operacje:<br />

i = 0;<br />

0 < 3 ? == TRUE --> T[0] = 0 // Tu nastepuje i++; //<br />

T[1] = 1 itd...<br />

To, co następuje w tak "spreparowanej" tablicy możesz<br />

prześledzić uruchamiając program:<br />

[P078.CPP]<br />

# include <br />

# include <br />

# include <br />

# define p(x) printf("%d\t", x)<br />

int T[99+1], i, max;<br />

main()<br />

- 225-


{<br />

cout > max;<br />

cout


zadziała szybciej niż<br />

for(j = 1; j < 1000; j++) (2)<br />

for(i = 1; i < 5; i++)<br />

{ A[i][j] = i + j; }<br />

W przypadku (1) zmienna robocza pętli wewnętrznej będzie<br />

inicjowana pięć razy, a w przypadku (2) - tysiąc (!) razy.<br />

Czasami zdarza się, że w programie można połączyć kilka pętli w<br />

jedną.<br />

for(i = 1; i < 5; i++)<br />

TAB_1[i] = i;<br />

...<br />

for(k = 0; k < 5; k++)<br />

TAB_2[k] = k;<br />

Zmniejsza to i ilość zmiennych, i tekst programu i czas pracy<br />

komputera:<br />

TAB_2[0] = 0;<br />

for(i = 1; i < 5; i++)<br />

TAB_1[i] = i;<br />

TAB_2[i] = i;<br />

Czasami wykonywanie pętli do końca pozbawione jest sensu.<br />

Przerwać pętlę w trakcie wykonywania można przy pomocy<br />

instrukcji break (jeśli pętle są zagnieżcżone, często lepiej<br />

użyć niepopularnego goto przerywającego nie jedną - a wszystkie<br />

pętle). Stosując umiejętnie break, continue i goto możesz<br />

zaoszczędzić swojemu komputerowi wiele pracy i czasu. Rutynowym<br />

"szkolno-strukturalnym" zapętlaniem programu<br />

main() {<br />

char gotowe = 0;<br />

...<br />

while (!gotowe)<br />

{<br />

znak = wybrano_z_menu();<br />

if (znak == 'q' || znak == 'Q') gotowe = 1;<br />

else<br />

.......<br />

gotowe = 1;<br />

}<br />

powodujesz często zupełnie niepotrzebne dziesiątki operacji,<br />

które już niczemu nie służą.<br />

char gotowe;<br />

main() {<br />

...<br />

while (!gotowe)<br />

{<br />

- 227-


znak = wybrano_z_menu();<br />

if (znak == 'q' || znak == 'Q') break; //Quit !<br />

else<br />

.......<br />

gotowe = 1;<br />

}<br />

Tym razem to, co następuje po else zostanie pominięte.<br />

Wskaźniki działają w <strong>C++</strong> szybciej, niż indeksy, stosujmy je w<br />

miarę możliwości w pętlach, przy manipulowaniu tablicami i w<br />

funkcjach.<br />

INSTRUKCJE STERUJĄCE I WYRAŻENIA ARYTMETYCZNE.<br />

Na "chłopski rozum" programisty wiadomo, że na softwarowych<br />

rozstajach, czyli na rozgałęzieniach programów<br />

prawdopodobieństwo wyboru każdwgo z wariantów działania programu<br />

z reguły bywa różne. Kolejność sprawdzania wyrażeń warunkowych<br />

nie jest zatem obojętna. Wyobraźmy sobie lekarza, który<br />

zwiezionego na toboganie narciarza pyta, czy ktoś w rodzinie<br />

chorował na żółtaczkę, koklusz, reumatyzm, podagrę, itp. zamiast<br />

zająć się najpierw wariantem najbardziej prawdopodobnym - czyli<br />

zagipsowaniem nogi nieszczęśnika. Absurdalne, prawda? Ale<br />

przecież (uderzmy się w piersi) nasze programy czasami postępują<br />

w taki właśnie sposób...<br />

NAJPIERW TO, CO NAJBARDZIE PRAWDOPODOBNE I NAJPROSTSZE.<br />

Jeśli zmienna x w naszym programie może przyjmować (równie<br />

prawdopodobne) wartości 1, 2, 3, 4, 5, to "przesiew"<br />

if (x >= 2) { ... }<br />

else if (x == 1) { ... }<br />

else { ... }<br />

okaże się w praktyce skuteczniejszy, niż<br />

if (x == 0) { ... }<br />

else if (x == 1) { ... }<br />

else { ... }<br />

Należy pamiętać, że w drabince if-else-if po spełnieniu<br />

pierwszego warunku - następne nie będą już analizowane.<br />

Zasada ta stosuje się także do wyrażeń logicznych, w których<br />

stosuje się operatory logiczne || (lub) i && (i). W wyrażeniach<br />

tych, których ocenę <strong>C++</strong> prowadzi tylko do uzyskania pewności,<br />

jaka będzie wartość logiczna (a nie koniecznie do końca<br />

wyrażenia) należy zastosować kolejność:<br />

- 228-


MAX || W1 || W2 || W3 ...<br />

MIN && W1 && W2 && W3 ...<br />

gdzie MAX - oznacza opcję najbardziej prawdopodobną, a MIN -<br />

najmniej prawdopodobną.<br />

Podobnie rzecz ma się z pracochłonnością (zatem i<br />

czso-chłonnością) poszczególnych wariantów. Jeśli wariant<br />

najprostszy okaże się prawdziwy, pozostałe możliwości możemy<br />

pominąć.<br />

NIE MNÓŻ I NIE DZIEL BEZ POTRZEBY.<br />

Prawa MATEMATYKI pozostają w mocy dla IBM PC i pozostaną zawsze,<br />

nawet dla zupełnie nieznanych nam komputerów, które skonstruują<br />

nasze dzieci i wnuki. Znajomość praw de Morgana i zasad<br />

arytmetyki jest dla programisty wiedzą niezwykle przydatną. Jako<br />

próbkę zapiszmy kilka trywialnych tożsamości przetłumaczonych na<br />

<strong>C++</strong>:<br />

2 * a == a + a == a 1, ale to nie zawsze<br />

prawda. Przesunięcie w prawo liczb nieparzystych spowoduje<br />

obcięcie części ułamkowej. W przypadku wyrażeń logicznych:<br />

(x && y) || (x && z) == x && (y || z)<br />

(x || y) && (x || z) == x || (y && z)<br />

W arytmetycznej sumie i iloczynie NIE MA takiej symetrii.<br />

!x && !y == !(x || y)<br />

!x || !y == !(x && y)<br />

Jeśli w skomplikowanych wyrażeniach arytmetycznych i logicznych<br />

zastosujemy zasady arytmetyki i logiki, zwykle stają się krótsze<br />

i prostsze. Podobnie jak licząc na kartce, możemy zastosować<br />

zmienne pomocnicze do przechowywania często powtarzających się<br />

wyrażeń składowych. Wyrażenie<br />

wynik = (x * x) + (x * x);<br />

możemy przekształcić do postaci<br />

zm_pomocn = x * x;<br />

wynik = zm_pomocn


zoptymalizować. Jako przykład zastosujmy funkcję biblioteczną<br />

strcmp() (string compare - porównaj łańcuchy znaków). Porównanie<br />

łańcuchów<br />

if (strcmp(string1, string2) == 0) cout


stosując zamiast if-else operator ? : możemy to zapisać tak:<br />

(x == MAXIMUM) ? (x = 0) : (x++);<br />

Mnożenie jest zwykle trochę szybsze niż dzielenie. Zapis<br />

a = b / 10;<br />

można zatem zastąpić szybszym:<br />

a = b * .1;<br />

Jeśli mamy do czynienia ze stałą STALA, to zapis w programie<br />

y = x / STALA;<br />

--> y = x * (1.0 / STALA);<br />

z pozoru bzdurny spowoduje w większości implementacji<br />

wyznaczenie wartości mnożnika 1.0/STALA przez kompilator na<br />

etapie kompilacji programu (compile-time), a w ruchu (run-time)<br />

będzie obliczany iloczyn zamiast ilorazu.<br />

W programach często stosuje się flagi binarne (jest-nie ma). <strong>C++</strong><br />

stosujemy jako flagi zmienne typu int lub char a w Windows BOOL.<br />

Jeśli weźmiemy pod uwagę fakt, że operatory relacji generują<br />

wartości typu TRUE/FALSE, typowy zapis:<br />

if (a > b)<br />

Flaga = 1;<br />

else<br />

Flaga = 0;<br />

zastąpimy krótszym<br />

Flaga = (a > b);<br />

Taki krótszy zapis NIE ZAWSZE powoduje wygenerowanie szybszego<br />

kodu. Jest to zależne od specyfiki konkretnej implementacji.<br />

Jeśli natomiast uprościsz swój program tak:<br />

if (x > 1) a = 3; --> a = 3 * (x > 1);<br />

else a = 0;<br />

spowoduje to wyraźne spowolnienie programu (mnożenie trwa).<br />

Kompilator <strong>C++</strong> rozróżnia dwa rodzaje wyrażeń:<br />

* general expressions - wyrażenia ogólne - zawierające zmienne i<br />

wywołania funkcji, których wartości nie jest w stanie określić<br />

na etapie kompilacji i<br />

* constant expressions - wyrażenia stałe, których wartość można<br />

- 231-


wyznaczyć na etapie kompilacji.<br />

Zapis<br />

wynik = 2 * x * 3.14;<br />

możesz zatem przekształcić do postaci<br />

wynik = 2 * 3.14 * x;<br />

Kompilator przekształci to wyrażenia na etapie kompilacji do<br />

postaci<br />

wynik = 6.28 * x;<br />

co spowoduje zmniejszenie ilości operacji w ruchu programu. Aby<br />

ułatwić takie działanie kompilatora trzeba umieścić stałe obok<br />

siebie.<br />

- 232-


LEKCJA 23. Co nowego w <strong>C++</strong>?<br />

________________________________________________________________<br />

Z tej lekcji dowiesz się, jakie mechanizmy <strong>C++</strong> pozwalają na<br />

stosowanie nowoczesnego obiektowego i zdarzeniowego stylu<br />

programowania i co programy robią z pamięcią.<br />

________________________________________________________________<br />

W porównaniu z klasycznym C - <strong>C++</strong> posiada:<br />

* rozszerzony zestaw słów kluczowych (ang. keywords):<br />

** nowe słowa kluczowe <strong>C++</strong>:<br />

class - klasa,<br />

delete - skasuj (dynamicznie utworzony obiekt),<br />

friend - "zaprzyjaźnione" funkcje z dostępem do danych,<br />

inline - wpleciony (funkcje przeniesione w formie rozwiniętej<br />

do programu wynikowego),<br />

new - utwórz nowy obiekt,<br />

operator - przyporządkuj operatorowi nowe działanie,<br />

private - dane i funkcje prywatne klasy (obiektu), do których<br />

zewnętrzne funkcje nie mają prawa dostępu,<br />

protected - dane i funkcje "chronione", dostępne z<br />

ograniczeniami,<br />

public - dane i funklcje publiczne, dostępne bez ograniczeń,<br />

template - szablon,<br />

this - ten, pointer wskazujący bieżący obiekt,<br />

virtual - funkcja wirtualna, abstrakcyjna, o zmiennym<br />

działaniu.<br />

* nowe operatory (kilka przykładów już widzieliśmy), np.:<br />

> - pobierz ze strumienia wejściowego.<br />

* nowe typy danych:<br />

klasy,<br />

obiekty,<br />

abstrakcyjne typy danych (ang. ADT).<br />

* nowe zasady posługiwania się funkcjami:<br />

funkcje o zmiennej liczbie argumentów,<br />

funkcje "rozwijane" inline,<br />

funkcje wirtualne, itp.;<br />

Przede wszystkim (i od tego właśnie rozpoczniemy) zobaczymy<br />

funkcje o nowych możliwościach.<br />

ROZSZERZENIE C - FUNKCJE.<br />

Funkcje uzyskują w <strong>C++</strong> znacznie więcej możliwości. Przegląd<br />

rozpoczniemy od sytuacji często występującej w praktyce<br />

programowania - wykorzystywania domyślnych (ang. default)<br />

parametrów.<br />

FUNKCJE Z DOMYŚLNYMI ARGUMENTAMI.<br />

Prototyp funkcji w <strong>C++</strong> pozwala na podanie deklaracji domyślnych<br />

wartości argumentów funkcji. Jeśli w momencie wywołania funkcji<br />

- 233-


w programie jeden (lub więcej) argument (ów) zostanie pominięte,<br />

kompilator wstawi w puste miejsce domyślną wartość argumentu.<br />

Aby uzyskać taki efekt, prototyp funkcji powinien zostać<br />

zadeklarowany w programie np. tak:<br />

void Funkcja(int = 7, float = 1.234);<br />

Efekt takiego działania będzie następujący:<br />

Wywołanie w programie: Efekt:<br />

________________________________________________________________<br />

Funkcja(99, 5.127); Normalnie: Funkcja(99, 5.127);<br />

Funkcja(99); Funkcja(99, 1.234);<br />

Funkcja(); Funkcja(7, 1.234);<br />

________________________________________________________________<br />

[!!!] Argumentów może ubywać wyłącznie kolejno. Sytuacja:<br />

Funkcja(5.127);<br />

Funkcja(99);<br />

//ŹLE<br />

//DOBRZE<br />

jest w <strong>C++</strong> niedopuszczalna. Kompilator potraktuje liczbę 5.127<br />

jako pierwszy argument typu int i wystąpi konflikt.<br />

[P079.CPP]<br />

#include <br />

void fun_show(int = 1234, float = 222.00, long = 333L);<br />

main()<br />

{<br />

fun_show();<br />

fun_show(1);<br />

fun_show(11, 2.2);<br />

fun_show(111, 2.22, 3L);<br />

return 0;<br />

}<br />

// Trzy arg. domyslne<br />

// Pierwszy parametr<br />

// Dwa parametry<br />

// Trzy parametry<br />

void fun_show(int X, float Y, long Z)<br />

{<br />

cout


<strong>C++</strong> pozwala deklarować zmienne w dowolnym miejscu, z<br />

zastrzeżeniem, że deklaracja zmiennej musi nastąpić przed jej<br />

użyciem. Umieszczanie deklaracji zmiennych możliwie blisko<br />

miejsca ich użycia znacznie poprawia czytelność (szczególnie<br />

dużych "wieloekranowych") programów. Klasyczny sposób deklaracji<br />

zmiennych:<br />

int x, y, z;<br />

...<br />

main()<br />

{<br />

...<br />

z = x + y + 1;<br />

...<br />

}<br />

może zostać zastąpiony deklaracją w miejscu zastosowania (w tym<br />

np. wewnątrz pętli):<br />

main()<br />

{<br />

...<br />

for ( int i = 1; i


eturn 0;<br />

}<br />

void daj_x(void)<br />

{<br />

cout


switch (n)<br />

{<br />

case niewymowne: cout


suffix) pozwalające na określenie typu parametrów,<br />

* pozwala na tworzenie tzw. funkcji polimorficznych (kilka<br />

różnych funkcji o tej samej nazwie), itp.<br />

Zwykły C tego nie potrafi i nie robi. Dlatego też do<br />

wprowadzenia takiego podziału kompetencji należy czasem<br />

zastosować deklarację extern "C". Funkcja rand() w programie<br />

poniżej generuje liczbę losową.<br />

[P081.CPP]<br />

#include <br />

extern "C"<br />

{<br />

# include //Prototyp rand() w STDLIB.H<br />

}<br />

main()<br />

{<br />

cout


W programie przykładowym funkcje z STDLIB.H zostaną skompilowane<br />

przez kompilator C. Określenie trybu kompilacji deklaracją<br />

extern "C" jest umieszczane zwykle nie wewnątrz programu<br />

głównego a w dołączanych plikach nagłówkowych *.H. Jest to<br />

możliwość szczególnie przydatne, jeśli dysponujesz bibliotekami<br />

funkcji dla C a nie masz chęci, czasu, bądź możliwości<br />

przerabiania ich na wersję przystosowaną do wymagań <strong>C++</strong>. Drugi<br />

przykład poniżej zajmuje się sortowaniem krewnych przy pomocy<br />

funkcji C qsort().<br />

[P082.CPP]<br />

# include <br />

# include <br />

# include <br />

extern "C" int comp(const void*, const void*);<br />

main()<br />

{<br />

int max;<br />

for(;;)<br />

{<br />

cout > max;<br />

if( max > 0 && max < 7) break;<br />

cout


Program wykonuje następujące czynności:<br />

* deklaruje prototyp funkcji typu C,<br />

* deklaruje statyczną tablicę wskaźników do łańcuchów znakowych,<br />

* sortuje wskaźniki,<br />

* wyświetla posortowane łańcuchy znakowe,<br />

* definiuje funkcję comp() - porównaj,<br />

* wykorzystuje funkcję biblioteczną C - strcmp() - String<br />

Compare do porównania łańcuchów znaków.<br />

O PAMIĘCI.<br />

Program w <strong>C++</strong> dzieli dostępną pamięć na kilka obszarów o<br />

określonym z góry przeznaczeniu. Dla zaawansowanego programisty<br />

zrozumienie i efektywne wykorzystanie mechanizmów zarządzania<br />

pamięcią w <strong>C++</strong> może okazać się wiedzą wielce przydatną.<br />

Zaczniemy, jak zwykle od "elementarza".<br />

CO PROGRAM ROBI Z PAMIĘCIĄ.<br />

W klasycznym C najczęściej stosowanymi do zarządzania pamięcią<br />

funkcjami są:<br />

* malloc() - przyporządkuj pamięć,<br />

* farmalloc() - przyporządkuj odległą pamięć,<br />

* realloc() - przyporządkuj powtórnie (zmienioną) ilość pamięci,<br />

* calloc() - przydziel pamięć i wyzeruj,<br />

* free() - zwolnij pamięć.<br />

Pamięć dzielona jest w obszarze programu na następujące bloki:<br />

___________________<br />

niskie adresy --> Ngłówek programu I.<br />

Adres startowy<br />

KOD:<br />

Kod programu<br />

___________________<br />

Zmienne statyczne II.<br />

DANE: 1. Zainicjowane Zmienne globalne<br />

___________________<br />

Zmienne statyczne III.<br />

DANE: 2. Niezainicjowane Zmienne globalne<br />

___________________<br />

STERTA: (heap) W miarę potrzeby IV.<br />

rośnie w dół.<br />

Tu operują funkcje<br />

malloc(), free().<br />

___________________<br />

POLE NICZYJE: V.<br />

___________________<br />

W miarę potrzeby VI.<br />

STOS: (stack) rośnie w górę.<br />

wysokie adresy --> ___________________<br />

- 240-


W obszarze kodu (I.) znajdują się instrukcje. Na stosie<br />

przechowywane są:<br />

* zmienne lokalne,<br />

* argumenty przekazywane funkcji w momencie jej wywołania,<br />

* adresy powrotne dla funkcji (RET == CS:IP).<br />

Na stercie natomiast przy pomocy funkcji (a jak przekonamy się<br />

za chwilę - także operatorów <strong>C++</strong>) możemy przydzielać pamięć dla<br />

różnych obiektów tworzonych w czasie pracy programu (ang.<br />

run-time memory allocation) - np. tworzyć bufory dla łańcuchów,<br />

tablic, struktur itp.. Zwróć uwagę, że obszar V. - POLE NICZYJE<br />

może być w czasie pracy programu stopniowo udostępniany dla<br />

stosu (który rozrasta się "w górę"), albo dla sterty (która<br />

rozrasta się "w dół"). W przykładowym programie poniżej podano,<br />

w którym obszarze pamięci zostanie umieszczony dany element<br />

programu.<br />

# include <br />

int a;<br />

int b = 6;<br />

main()<br />

{<br />

char *Dane;<br />

...<br />

float lokalna;<br />

...<br />

Dane = malloc(16);<br />

...<br />

}<br />

// III.<br />

// II.<br />

// VI.<br />

// IV.<br />

OPERATORY new I delete.<br />

Operatory new i delete działają podobnie do pary funkcji<br />

malloc() - free(). Pierwszy przyporządkowuje - drugi zwalnia<br />

pamięć. Dokładniej rzecz biorąc<br />

- operator new może zostać zastosowany wraz ze wskaźnikiem do<br />

bloku danych określonego typu:<br />

* struktury danych,<br />

* tablicy, itp. (wkrótce zastosujemy go także w stosunku do<br />

klas i obiektów);<br />

- przyporządkowuje pamięć blokowi danych;<br />

- przypisuje począkowy adres bloku pamięci wskaźnikowi.<br />

- operator delete zwalnia pamięć przyporządkowaną poprzednio<br />

blokowi danych,<br />

Operatory new i delete mogą współdziałać z danymi wieloma typami<br />

danych (wcale nie tylko ze strukturami), jednakże rozpoczniemy<br />

do struktury Data zawierającej datę urodzenia mojej córki.<br />

[P083.CPP]<br />

- 241-


# include "iostream.h"<br />

struct Data<br />

{<br />

int dzien;<br />

int miesiac;<br />

int rok;<br />

};<br />

void main()<br />

{<br />

Data *pointer = new Data;<br />

/* Dekl. wskaznik do struct typu Data */<br />

/* Przydziel pamiec dla struktury */<br />

pointer -> miesiac = 11; // pole "miesiac" = 11<br />

pointer -> dzien = 3;<br />

pointer -> rok = 1979;<br />

cout


int *pointer = new int[3]; // Przydziel pamiec<br />

pointer[0] = 3;<br />

pointer[1] = 11;<br />

pointer[2] = 1979;<br />

// Tabl_bez_nazwy[0] - dzien<br />

// Tabl_bez_nazwy[1] - miesiac<br />

cout


Podaj wielkosc tablicy (1...100) --> 20<br />

TABLICA:<br />

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17<br />

18 19<br />

Podaj wielkosc tablicy (1...100) --> 100<br />

TABLICA:<br />

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17<br />

18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33<br />

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49<br />

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66<br />

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82<br />

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99<br />

Skoro dynamiczne tablice o zmiennej wielkości "chodzą", możemy<br />

wykorzystać to w bardziej interesujący sposób.<br />

[P086.CPP]<br />

# include <br />

# include <br />

# include <br />

extern "C"<br />

{<br />

int Fporownaj(const void* x, const void* y)<br />

{<br />

return (strcmp(*(char **)x, *(char **)y));<br />

}<br />

}<br />

main()<br />

{<br />

cout > ilosc;<br />

char **pointer = new char *[ilosc];<br />

for (i = 0; i < ilosc; i++)<br />

{<br />

cout imie;<br />

if (strcmp(imie, "stop") == 0) break;<br />

else<br />

pointer[i] = new char[strlen(imie)+1];<br />

strcpy(pointer[i], imie);<br />

delete imie;<br />

}<br />

qsort(pointer, i, sizeof(char *), Fporownaj);<br />

- 244-


for (i = 0; i < ilosc; i++)<br />

cout ilosc;<br />

char **pointer = new char *[ilosc];<br />

for (i = 0; i < ilosc; i++)<br />

{<br />

cout imie;<br />

if (Fporown_string(imie, "stop") == 0) break;<br />

else<br />

- 245-


pointer[i] = new char[strlen(imie)+1];<br />

Fkopiuj_string(pointer[i], imie);<br />

delete imie;<br />

}<br />

/* w tym momencie i == ilosc */<br />

Fsortuj(pointer, i, sizeof(char *), Fporownaj);<br />

for (i = 0; i < ilosc; i++)<br />

cout


cout > Liczba;<br />

cout


# include <br />

# include <br />

static void Funkcja()<br />

{<br />

cout


LEKCJA 24. SKĄD WZIĘŁY SIĘ KLASY I OBIEKTY W <strong>C++</strong>.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, skąd w <strong>C++</strong> biorą się obiekty i<br />

jak z nich korzystać.<br />

________________________________________________________________<br />

Zajmiemy się teraz tym, z czego <strong>C++</strong> jest najbardziej znany -<br />

zdolnością posługiwania się obiektami. Główną zaletą<br />

programowania obiektowego jest wyższy stopień "modularyzacji"<br />

programów. "Mudularyzacja" jest tu rozumiana jako możliwość<br />

podziału programu na niemal niezależne fragmenty, które mogą<br />

opracowywać różne osoby (grupy) i które później bez konfliktów<br />

można łączyć w całość i uruchamiać natychmiast. <strong>C++</strong> powstał, gdy<br />

programy stały się bardzo (zbyt) długie. Możliwość skrócenia<br />

programów nie jest jednakże jedyną zaletą <strong>C++</strong>. W długich,<br />

rozbudowanych programach trudno spamiętać szczegóły dotyczące<br />

wszystkich części programu. Jeśli grupy danych i grupy funkcji<br />

uda się połączyć w moduły, do których można później sięgać, jak<br />

do pewnej odrębnej całości, znacznie ułatwia to życie<br />

programiście. Na tym, w pewnym uproszczeniu, polega idea<br />

programowania obiektowego.<br />

JAK STRUKTURY STAWAŁY SIĘ OBIEKTAMI.<br />

W <strong>C++</strong> struktury uzyskują "trochę więcej praw" niż w klasycznym<br />

C. Przykładowy program poniżej demonstruje kilka sposobów<br />

posługiwania się strukturą w <strong>C++</strong>.<br />

[P90.CPP]<br />

#include <br />

struct Data<br />

{<br />

int dzien;<br />

int miesiac;<br />

int rok;<br />

};<br />

Data NaszaStruktura = {3, 11, 1979}; //Inicjujemy strukture<br />

Data daty[16];<br />

//Tablca struktur<br />

Data *p = daty;<br />

//Wskaznik do tablicy<br />

void Fdrukuj(Data);<br />

//Prototyp funkcji<br />

int i; //Licznik automat. 0<br />

int main()<br />

{<br />

for (; i < 16; i++)<br />

{<br />

*(p + i) = NaszaStruktura;<br />

daty[i].rok += i;<br />

cout


cout


Data::Data(void)<br />

{<br />

dzien = 3;<br />

miesiac = 11;<br />

rok = 1979;<br />

}<br />

int main()<br />

{<br />

Data NStruktura;<br />

//Poczatkowa data - Konstruktor<br />

//Inicjujemy strukture<br />

cout


Data::Data(void)<br />

//Poczatkowa data - Konstruktor<br />

oznacza, że funkcja Data(void) nie pobiera od programu żadnych<br />

parametrów i tworzy (w pamięci komputera) strukturę typu Data.<br />

Takie dziwne funkcje konstruujące (inicjujące) strukturę (o czym<br />

dokładniej w dalszej części książki), nazywane w <strong>C++</strong><br />

konstruktorami nie zwracają do programu żadnej wartości. Zwróć<br />

uwagę, że konstruktory to specjalne funkcje, które:<br />

-- mają nazwę identyczną z nazwą typu własnej struktury,<br />

-- nie posiadają wyspecyfikowanego typu wartości zwracanej do<br />

programu,<br />

-- służą do zainicjowania w pamięci pól struktury,<br />

-- nie są wywoływane w programie w sposób jawny, lecz niejawnie,<br />

automatycznie.<br />

Podstawowym praktycznym efektem dodania do struktur funkcji<br />

stała się możliwość skutecznej ochrony danych zawartych na<br />

polach struktury przed dostępem funkcji z zewnątrz struktury.<br />

Przed dodaniem do struktury jej własnych wewnętrznych funkcji -<br />

wszystkie funkcje pochodziły z zewnątrz, więc "hermetyzacja"<br />

danych wewnątrz była niewykonalna. Zasady dostępu określa się w<br />

<strong>C++</strong> przy pomocy słów:<br />

public - publiczny, dostępny,<br />

protected - chroniony, dostępny z ograniczeniami,<br />

private - niedostępny spoza struktury.<br />

Przykładowy program poniżej demonstruje tzw. "hermetyzację"<br />

struktury (ang. encapsulation). W przykładzie poniżej:<br />

* definiujemy strukturę;<br />

* definiujemy funkcje;<br />

* przekazujemy i pobieramy dane do/od struktury typu Zwierzak.<br />

Zmienna int schowek powinna sugerować ukrytą przez strukturę i<br />

niedostępną dla nieuprawnionych funkcji część danych struktury a<br />

nie cechy anatomiczne zwierzaka.<br />

[STRUCT.CPP]<br />

# include "iostream.h"<br />

//UWAGA: schowek ma status private, jest niedostepny<br />

struct Zwierzak<br />

{<br />

private:<br />

int schowek; //DANE PRYWATNE - niedostepne<br />

public:<br />

void SCHOWAJ(int Xwe); //Funkcje dostepne zzewnatrz<br />

- 252-


int ODDAJ(void);<br />

};<br />

void Zwierzak::SCHOWAJ(int Xwe) //definicja funkcji<br />

{<br />

schowek = Xwe;<br />

}<br />

int Zwierzak::ODDAJ(void)<br />

{<br />

return (schowek);<br />

}<br />

main()<br />

{<br />

Zwierzak Ciapek, Azor, Kotek; // Struktury "Zwierzak"<br />

int Piggy; // zwykla zmienna<br />

Ciapek.SCHOWAJ(1);<br />

Azor.SCHOWAJ(22);<br />

Kotek.SCHOWAJ(-333);<br />

Piggy = -4444;<br />

cout


za chwilę - struktura Ciapek jest już właściwie obiektem, a typ<br />

danych Zwierzak jest już właściwie klasą obiektów. Wystarczy<br />

zamienić słowo "struct" na słowo "class".<br />

[CLASS.CPP]<br />

# include "iostream.h"<br />

//w klasach schowek ma status private AUTOMATYCZNIE<br />

//slowo private stalo sie zbedne<br />

class Zwierzak<br />

{<br />

int schowek;<br />

public:<br />

void SCHOWAJ(int Xwe); //Funkcje dostepne zzewnatrz<br />

int ODDAJ(void);<br />

};<br />

void Zwierzak::SCHOWAJ(int Xwe)<br />

{<br />

schowek = Xwe;<br />

}<br />

int Zwierzak::ODDAJ(void)<br />

{<br />

return (schowek);<br />

}<br />

main()<br />

{<br />

Zwierzak Ciapek, Azor, Kotek; // obiekty klasy "Zwierzak"<br />

int Piggy; // zwykla zmienna<br />

Ciapek.SCHOWAJ(1);<br />

Azor.SCHOWAJ(22);<br />

Kotek.SCHOWAJ(-333);<br />

Piggy = -4444;<br />

cout


Klasy służą do tworzenia formalnego typu danych. W przypadku<br />

klas wiadomo jednak "z definicji", że będzie to bardziej złożony<br />

typ (tzw. agregat) zawierający praktycznie zawsze i dane<br />

"tradycyjnych" typów i funkcje (nazywane "metodami"). Podobnie<br />

jak definiując strukturę tworzysz nowy formalny typ danych, tak<br />

i tu - definiując klasę tworzysz nowy typ danych. Jeśli<br />

zadeklarujesz użycie zmiennych danego typu formalnego, takie<br />

zmienne to właśnnie obiekty. Innymi słowy, klasy stanowią<br />

definicje formalnego typu, natomiast obiekty - to zmienne danego<br />

typu (danej klasy).<br />

Zamiast słowa struct stosujemy przy klasach słowo class.<br />

class Klasa<br />

{<br />

int prywatna_tab[80]<br />

public:<br />

int dane;<br />

void Inicjuj(void);<br />

int Funkcja(int arg);<br />

};<br />

Nasza pierwsza świadomie tworzona klasa nazywa się "Klasa" i<br />

stanowi nowy formalny typ zmiennych. Jeśli zadeklarujesz zmienną<br />

takiej klasy (tego typu formalnego), to taka zmienna będzie<br />

właśnie OBIEKTEM.<br />

Nasza pierwsza prawdziwa Klasa zawiera dane:<br />

prywatna_tab[80] - prywatną tablicę;<br />

dane - publiczną daną prostą typu int;<br />

oraz funkcje:<br />

Inicjuj() - zainicjuj - utwórz obiekt danej klasy w pamięci;<br />

Funkcja() - jakaś funkcja publiczna.<br />

Gdyby była to zwykła struktura, jej definicja w programie<br />

wyglądałaby tak:<br />

struct Klasa<br />

{<br />

private:<br />

int prywatna_tab[80]<br />

public:<br />

int dane;<br />

};<br />

void Inicjuj(void);<br />

int Funkcja(int arg);<br />

Jeżeli w dalszej części programu chcielibyśmy zastosować<br />

- 255-


struktury takiego typu, deklaracja tych struktur musiałaby<br />

wyglądać tak:<br />

struct rodzaj_struktur<br />

{<br />

private:<br />

int prywatna_tab[80]<br />

public:<br />

int dane;<br />

void Inicjuj(void);<br />

int Funkcja(int arg);<br />

} str1, str2, .... , nasza_struktura;<br />

bądź tak:<br />

struct rodzaj_struktur<br />

{<br />

private:<br />

int prywatna_tab[80]<br />

public:<br />

int dane;<br />

void Inicjuj(void);<br />

int Funkcja(int arg);<br />

};<br />

...<br />

(struct) rodzaj_struktur str1, str2, .... , nasza_struktura;<br />

Słowo kluczowe struct jest opcjonalne. Moglibyśmy więc<br />

zadeklarować strukturę w programie, wewnątrz funkcji main():<br />

struct rodzaj_struktur<br />

{<br />

private:<br />

int prywatna_tab[80]<br />

public:<br />

int dane;<br />

void Inicjuj(void);<br />

int Funkcja(int arg);<br />

};<br />

main()<br />

{<br />

...<br />

struct rodzaj_struktur nasza_struktura;<br />

//lub równoważnie:<br />

rodzaj_struktur nasza_struktura;<br />

Do pól struktury możemy odwoływać się przy pomocy operatora<br />

kropki (ang. dot operator). Podobnie dzieje się w przypadku<br />

klas. Jeśli zadeklarujemy zmienną typu Klasa, to ta zmienna<br />

będzie naszym pierwszym obiektem.<br />

class Klasa<br />

{<br />

int prywatna_tab[80]<br />

- 256-


public:<br />

int dane;<br />

void Inicjuj(void)<br />

int Funkcja(int our_param);<br />

} Obiekt;<br />

Podobnie jak wyżej, możemy zadeklarować nasz obiekt wewnątrz<br />

funkcji main():<br />

class Klasa<br />

{<br />

int prywatna_tab[80]<br />

public:<br />

int dane;<br />

void Inicjuj(void)<br />

int Funkcja(int argument);<br />

};<br />

main()<br />

{<br />

...<br />

Klasa Obiekt;<br />

...<br />

Przypiszemy elementom obiektu wartości:<br />

main()<br />

{<br />

...<br />

Klasa Obiekt;<br />

Obiekt.dane = 13;<br />

...<br />

Taką samą metodą, jaką stosowaliśmy do danych - pól struktury,<br />

możemy odwoływać się do danych i funkcji w klasach i obiektach.<br />

main()<br />

{<br />

...<br />

Klasa Obiekt;<br />

Obiekt.dane = 13;<br />

...<br />

Obiekt.Funkcja(44);<br />

Przyporządkowaliśmy obiektowi nie tylko dane, ale także funkcje<br />

poprzez umieszczenie prototypów funkcji wewnątrz deklaracji<br />

klasy:<br />

class Klasa<br />

{<br />

...<br />

public:<br />

...<br />

void Inicjuj(void) /* Prototypy funkcji */<br />

int Funkcja(int argument);<br />

};<br />

- 257-


[!!!] UWAGA!<br />

________________________________________________________________<br />

W <strong>C++</strong> nie możemy zainicjować danych wewnątrz deklaracji klasy:<br />

class Klasa<br />

{<br />

private:<br />

int prywatna_tab[80] = { 1, 2, ... }; //ŹLE !<br />

public:<br />

int dane = 123; //ŹŁE !<br />

...<br />

________________________________________________________________<br />

Inicjowanie danych odbywa się w programie głównym przy pomocy<br />

przypisania (dane publiczne), bądź za pośrednictwem funkcji<br />

należącej do danej klasy i mającej dostęp do wewnętrznych danych<br />

klasy/obiektu (dane prywatne). Inicjowania danych mogą dokonać<br />

także specjalne funkcje - tzw. konstruktory.<br />

Dane znajdujące się wewnątrz deklaracji klasy mogą mieć status<br />

public, private, bądź protected. Dopóki nie zażądasz inaczej -<br />

domyślnie wszystkie elementy klasy mają status private. Jeżeli<br />

część obiektu jest prywatna, to oznacza, że żaden element<br />

programu spoza obiektu nie ma do niej dostępu. W naszej Klasie<br />

prywatną część stanowi tablica złożona z liczb całkowitych:<br />

(default - private:) int prywatna_tab[80];<br />

Do (prywatnych) elementów tablicy dostęp mogą uzyskać tylko<br />

funkcje związane (ang. associated) z obiektem danej klasy.<br />

Funkcje takie muszą zostać zadeklarowane wewnątrz definicji<br />

danej klasy i są nazywane członkami klasy - ang. member<br />

functions. Funkcje mogą mieć status private i stać się dzięki<br />

temu wewnętrznymi funkcjami danej klasy (a w konsekwencji<br />

również prywatnymi funkcjami obiektów danej klasy). Jest to<br />

jedna z najważniejszych cech nowoczesnego stylu programowania w<br />

<strong>C++</strong>. Na tym polega idea hermetyzacji danych i funkcji wewnątrz<br />

klas i obiektów. Gdyby jednak cała zawartość (i dane i funkcje)<br />

znajdujące się w obiekcie zostały dokładnie "zakapsułkowane", to<br />

okazałoby się, że obiekt stał się "ślepy i głuchy", a w<br />

konsekwencji - niedostępny i kompletnie nieużyteczny dla<br />

programu i programisty. Po co nam obiekt, do którego nie możemy<br />

odwołać się z zewnątrz żadną metodą? W naszym obiekcie, w<br />

dostępnej z zewnątrz części publicznej zadeklarowaliśmy zmienną<br />

całkowitą dane oraz dwie funkcje - Inicjuj() oraz Funkcja().<br />

Jeśli dane i funkcje mają status public, to oznacza, że możemy<br />

się do nich odwołać z dowolnego miejsca programu i dowolnym<br />

sposobem. Takie odwołania przypominają sposób odwoływania się do<br />

elementów struktury:<br />

main()<br />

- 258-


{<br />

...<br />

Obiekt.dane = 5;<br />

Obiekt.Inicjuj();<br />

...<br />

Obiekt.Funkcja(3);<br />

//Przypisanie wartości zmiennej.<br />

//Wywołanie funkcji Inicjuj()<br />

//Wywołanie funkcji z argumentem<br />

[!!!] ZAWSZE PUBLIC !<br />

________________________________________________________________<br />

Dane zawarte w obiekcie, podobnie jak zwykłe zmienne wymagają<br />

zainicjowania. Funkcja inicjująca dane - zawartość obiektu musi<br />

zawsze posiadać status public aby mogła być dostępna z zewnątrz<br />

i zostać wywołana w programie głównym - funkcji main(). Funkcje<br />

i dane dostępne z zewnątrz stanowią tzw. INTERFEJS OBIEKTU.<br />

________________________________________________________________<br />

- 259-


LEKCJA 25. PRZYKŁAD OBIEKTU.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak praktycznie projektuje się<br />

klasy i obiekty. Twój pierwszy obiekt zacznie działać.<br />

________________________________________________________________<br />

Nasz pierwszy, doświadczalny obiekt będzie zliczać ile razy<br />

użytkownik nacisnął określony klawisz - np. literę "A". Najpierw<br />

podejdziemy do problemu "klasycznie". Utworzymy strukturę<br />

Licznik, którą można wykorzystać do przechowywania istotnych dla<br />

nas informacji:<br />

char znak - znak do zliczania<br />

int ile - ile razy wystąpił dany znak.<br />

Zwróć uwagę, że Licznik oznacza tu typ struktur (nowy formalny<br />

typ danych) a licznik oznacza naszą roboczą zmienną danego typu.<br />

struct Licznik //Licznik - nowy typ struktur<br />

{<br />

public: //Status public jest domyślny dla struktur<br />

char znak;<br />

int ile;<br />

...<br />

} licznik; //Zmienna typu "Licznik"<br />

Do pól struktury licznik.znak i licznik.ile możemy odwoływać się<br />

w programie w następujący sposób:<br />

//Przypisanie (zainicjowanie pola struktury)<br />

licznik.znak = 'A';<br />

cin >> licznik.znak;<br />

//Odczyt (wyprowadzenie) bież. zawartości pola struktury.<br />

cout


Aby funkcja inicjująca pole struktury zadziałała prawidłowo, jej<br />

definicja powinna wyglądać tak:<br />

void Inicjuj(char x)<br />

{<br />

licznik.znak = x;<br />

licznik.ile = 0;<br />

}<br />

//Deklaracja zmiennej znak.<br />

//x - wewnętrzna zmienna funkcji<br />

Inicjując strukturę licznik funkcja zeruje pole "ile" struktury.<br />

Przyda nam się jeszcze jedna funkcja PlusJeden(). Ta funkcja<br />

powinna zwiększyć zmienną służącą do zliczania ile razy wystąpił<br />

interesujący nas znak po każdym pojawieniu się odpowiedniego<br />

znaku (w tym przypadku "A").<br />

void PlusJeden(void) //Definicja funkcji<br />

{ //incrementującej licznik<br />

licznik.ile++;<br />

}<br />

Zbudowaliśmy licznik, który składa się z danych rozmieszczonych<br />

na polach struktury oraz dwu stowarzyszonych ze strukturą<br />

funkcji. Jeśli spróbujemy zastosować to w programie, gdzie:<br />

char znak_we - znak wczytany z klawiatury;<br />

program będzie wyglądać tak:<br />

void main()<br />

{<br />

char znak_we;<br />

Inicjuj('A');<br />

cout znak_we;<br />

if (znak_we == 'k' || znak_we == 'K') break;<br />

if(znak_we == licznik.znak) PlusJeden();<br />

}<br />

....<br />

W tekście mogą wystąpić zarówno duże jak i małe litery. Jeśli<br />

zechcemy zliczać i jedne i drugie, możemy posłużyć się funkcją<br />

biblioteczną C zamieniającą małe litery na duże - toupper().<br />

Najpierw poddamy wczytany zank konwersji a następnie porównamy z<br />

"zadanym" na polu licznik.znak:<br />

if(licznik.znak == toupper(znak_we)) PlusJeden();<br />

- 261-


Po przerwaniu pętli przez użytkownika wystarczy sprawdzić jaka<br />

wartość jest wpisana w polu licznik.ile i możemy wydrukować<br />

wynik zliczania wystąpień litery 'A' we wprowadzonym tekście.<br />

cout


obiekt. Zawartość naszego obiektu powinna wyglądać tak:<br />

Dane:<br />

char znak;<br />

int ile;<br />

Funkcje:<br />

void Inicjuj(char);<br />

void PlusJeden(void);<br />

Łączymy w całość funkcje operujące pewnymi danymi i te właśnnie<br />

dane. Co więcej, jeśli zaistnieją takie funkcje, które nie będą<br />

wykorzystywane przez nikogo więcej poza własnym obiektem i poza<br />

jego składnikami: funkcją Inicjuj() i funkcją PlusJeden(),<br />

funkcje te nie muszą być widoczne, ani dostępne dla reszty<br />

programu. Takie funkcje mogą wraz z danymi zostać uznane za<br />

prywatną część obiektu. Takie praktyki, szczególnie w programach<br />

przeznaczonych dla środowiska Windows są uzasadnione i<br />

pożyteczne. Rozważmy obiekt, modularyzację i hermetyzację<br />

obiektu na konkretnych przykładach.<br />

Zacznijmy od zdefiniowania klasy.<br />

class Licznik<br />

{<br />

char znak;<br />

int ile;<br />

public:<br />

void Inicjuj(char);<br />

void PlusJeden(void);<br />

};<br />

Następny krok, to zdefiniowanie obu funkcji. Zwróć uwagę, że<br />

funkcje nie są już definiowane "niezależnie", lecz w stosunku do<br />

własnej klasy:<br />

void Licznik::Inicjuj(char x)<br />

{<br />

znak = x;<br />

ile = 0;<br />

}<br />

void Licznik::PlusJeden(void)<br />

{<br />

ile++;<br />

}<br />

Skoro funkcje widzą już wyłącznie własną klasę, zapis<br />

licznik.znak może zostać uproszczony do --> znak<br />

i<br />

licznik.ile do --> ile<br />

Aby wskazać, że funkcje są członkami klasy Licznik stosujemy<br />

- 263-


operator :: (oper. widoczności/przesłaniania - ang. scope<br />

resolution operator). Taki sposób zapisu definicji funkcji<br />

oznacza dla <strong>C++</strong>, że funkcja jest członkiem klasy (ang. member<br />

function). Logika <strong>C++</strong> w tym przypadku wygląda tak:<br />

* Prototypy funkcji należy umieścić w definicji klasy.<br />

* Definicje funkcji mogą znajdować się w dowolnym miejscu<br />

programu, ponieważ operator przesłaniania :: pozwala rozpatrywać<br />

klasę podobnie jak zmienne globalne.<br />

* Wstawiając operator :: pomiędzy nazwę klasy i prototyp funkcji<br />

informujemy <strong>C++</strong> że dana funkcja jest członkiem określonej klasy.<br />

Funkcje - członkowie klas nazywane są często METODAMI.<br />

Definicje klas i definicje funkcji - METOD są często umieszczane<br />

razem - w plikach nagłówkowych. Jeśli posługujemy się taką<br />

metodą, wystarczy dołączyć odpowiedni plik dyrektywą # include.<br />

Kompilator <strong>C++</strong> skompiluje wtedy automatycznie wszystkie funkcje,<br />

które znajdzie w dołączonych plikach nagłówkowych.<br />

Możemy przystąpić do utworzenia programu.<br />

main()<br />

{<br />

char znak_we;<br />

Licznik licznik;<br />

licznik.Inicjuj('A');<br />

...<br />

//Dekl. zwyklej zmiennej<br />

//Deklarujemy obiekt klasy Licznik<br />

//Inicjujemy licznik<br />

Możemy teraz określić ilość wprowadzonych z klawiatury liter 'A'<br />

oraz 'a' i wyprowadzić ją na ekran monitora. Pojawia się jednak<br />

pewien problem. Nie uda się sięgnąć z zewnątrz do prywatnych<br />

danych obiektu tak, jak poprzednio:<br />

if(licznik.znak == toupper(znak_we)) ....<br />

Potrzebna nam będzuie jeszcze jedna metoda autoryzowana do<br />

dostępu do danych obiektu:<br />

char Licznik::Pokaz(void);<br />

która nie będzie w momencie wywołania pobierać od programu<br />

żadnych argumentów (void), natomiast pobierze znak z pola char<br />

Licznik.znak i przekaże tę informację w postaci zmiennej typu<br />

char do programu. Definicja takiej metody powinna być<br />

następująca:<br />

char Licznik::Pokaz(void)<br />

- 264-


{<br />

return znak;<br />

}<br />

Ten sam problem wystąpi przy próbie pobrania od obiektu efektów<br />

jego pracy - stanu pola licznik.ile. Do tego też niezbędna jest<br />

autoryzowana do dostępu metoda. Nazwiemy ją Efekt():<br />

int Licznik::Efekt(void)<br />

{<br />

return ile;<br />

}<br />

Program w wersji obiektowej będzie wyglądać tak:<br />

[P093.CPP]<br />

# include <br />

# include <br />

class Licznik<br />

{<br />

char znak;<br />

int ile;<br />

public:<br />

void Inicjuj(char);<br />

void PlusJeden(void);<br />

char Pokaz(void);<br />

int Efekt(void);<br />

};<br />

void main()<br />

{<br />

char znak_we;<br />

Licznik licznik;<br />

licznik.Inicjuj('A');<br />

cout


void Licznik::Inicjuj(char x)<br />

{<br />

znak = x;<br />

ile = 0;<br />

}<br />

void Licznik::PlusJeden(void)<br />

{<br />

ile++;<br />

}<br />

char Licznik::Pokaz(void)<br />

{<br />

return znak;<br />

}<br />

int Licznik::Efekt(void)<br />

{<br />

return ile;<br />

}<br />

Przejdziemy teraz do bardziej szczegółowego omówienia<br />

zasygnalizowanego wcześniej problemu inicjowania struktur w<br />

pamięci przy pomocy funkcji o specjalnym przeznaczeniu - tzw.<br />

KONSTRUKTORÓW.<br />

- 266-


LEKCJA 26. CO TO JEST KONSTRUKTOR.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, w jaki sposób w pamięci<br />

komputera są tworzone obiekty.<br />

________________________________________________________________<br />

<strong>C++</strong> zawiera specjalną kategorię funkcji - konstruktory w celu<br />

automatyzacji inicjowania struktur (i obiektów). Konstruktory to<br />

specjalne funkcje będące członkami struktur (kategorii member<br />

functions) które są automatycznie wywoływane i dokonują<br />

zainicjowania struktury zgodnie z naszymi życzeniami, po<br />

napotkaniu w programie pierwszej deklaracji struktury/obiektu<br />

danego typu.<br />

PRZYKŁADOWY KONSTRUKTOR.<br />

Struktura Licznik zawiera funkcję inicjującą obiekt (niech<br />

obiekt będzie na razie zmienną typu struktura):<br />

struct Licznik //Typ formalny struktur<br />

{<br />

char znak;<br />

int ile;<br />

} licznik; //Przykladowa struktura<br />

void Inicjuj(char x)<br />

{<br />

licznik.znak = x;<br />

licznik.ile = 0;<br />

}<br />

//Funkcja inicjująca<br />

Zdefiniujmy naszą strukturę w sposób bardziej<br />

"klasowo-obiektowy":<br />

struct Licznik<br />

{<br />

private:<br />

char znak;<br />

int ile;<br />

public:<br />

void Inicjuj(char);<br />

void PlusJeden(void);<br />

};<br />

Funkcja Inicjuj() wykonuje takie działanie jakie może wykonać<br />

konstruktor struktury (obiektu), z tą jednak różnicą, że<br />

konstruktor jest wywoływany automatycznie. Jeśli wyposażymy<br />

strukturę Licznik w konstruktor, to funkcja Inicjuj() okaże się<br />

zbędna. Aby funkcja Inicjuj() stała się konstruktorem, musimy<br />

zmienić jej nazwę na nazwę typu struktury, do której konstruktor<br />

ma należeć. Zwróć uwagę, że konstruktor, w przeciwieństwie do<br />

innych, "zwykłych" funkcji nie ma podanego typu wartości<br />

zwracanej:<br />

- 267-


struct Licznik<br />

{<br />

private:<br />

char znak;<br />

int ile;<br />

public:<br />

Licznik(void); //Konstruktor nie pobiera argumentu<br />

void PlusJeden(void);<br />

};<br />

Teraz powinniśmy zdefiniować konstruktor. Zrobimy to tak, jak<br />

wcześniej definiowaliśmy funkcję Inicjuj().<br />

Licznik::Licznik(void) //Konstruktor nie pobiera argumentu<br />

{<br />

ile = 0;<br />

}<br />

Jeśli formalny typ struktur (klasa) posiada kostruktor, to po<br />

rozpoczęciu programu i napotkaniu deklaracji struktur danego<br />

typu konstruktor zostanie wywołany automatycznie. Dzięki temu<br />

nie musimy "ręcznie" inicjować struktur na początku programu.<br />

Jednakże nasz przykładowy konstruktor nie załatwia wszystkich<br />

problemów - nie ustawia w strukturze zmiennej (pola) int znak -<br />

określającego, który znak powinien być zliczany w liczniku. W<br />

tak zainicjowanej strukturze zmienna ile jest zerowana, ale<br />

zawartość pola znak pozostaje przypadkowa. Niby wszystko w<br />

porządku, ale wygląda to niesolidnie. Czy nie możnaby przekazać<br />

parametru przy pomocy konstruktora? Można! Konstruktor<br />

"bezparametrowy"<br />

Licznik::Licznik(void)<br />

taki, jak powyżej to tylko szczególny przypadek - tzw.<br />

konstruktor domyślny (ang. default constructor).<br />

PRZEKAZYWANIE ARGUMENTÓW DO KOSTRUKTORA.<br />

Czasem chcemy zainicjować nową strukturę już z pewnymi<br />

ustawionymi parametrami. Te początkowe parametry struktury<br />

możemy przekazać jako argumenty konstruktora.<br />

struct Licznik<br />

{<br />

private:<br />

char znak;<br />

int ile;<br />

public:<br />

Licznik(char); //Konstruktor z argumentem typu char<br />

void PlusJeden(void);<br />

};<br />

Licznik::Licznik(char x) //Konstruktor z jednym argumentem<br />

- 268-


{<br />

...<br />

}<br />

main()<br />

{<br />

Licznik licznik('A'); //Deklaracja struktury typu Licznik<br />

// oznacza to automatyczne wywołanie konstruktora z argumentem<br />

....<br />

Poniewż nowy konstruktor pobiera od programu argument typu<br />

znakowego char, więc i definicję konstruktora należy zmienić:<br />

Licznik::Licznik(char x) //Konstruktor z jednym argumentem<br />

{<br />

ile = 0;<br />

znak = x;<br />

}<br />

Jeśli parametrów jest więcej niż jeden, możemy je przekazać do<br />

konstruktora, a konstruktor wykorzysta je do zainicjowania<br />

struktury w następujący sposób:<br />

struct Sasiedzi //sąsiedzi<br />

{<br />

private:<br />

char Tab_imion[4];<br />

...<br />

public:<br />

Sasiedzi(char *s1, char *s2, char *s3, char s4);<br />

...<br />

};<br />

main()<br />

{<br />

Sasiedzi chopy("Helmut", "Ulrich", "Adolf", "Walter");<br />

....<br />

Przekazanie konstruktorowi argumentów i w efekcie automatyczne<br />

ustawiamie przez konstruktor paramatrów struktury już w momencie<br />

zadeklarowania struktury w programie rozwiązuje wiele problemów.<br />

W <strong>C++</strong> istnieje jednakże pewne dość istotne ograniczenie - nie<br />

możemy zadeklarować tablicy złożonej z obiektów posiadających<br />

konstruktory, chyba że wszystkie konstruktory są bezparametrowe<br />

(typu default constructors).<br />

Udoskonalmy teraz nasz program zliczający wystąpienia w tekście<br />

litery a posługując się konstruktorem struktury.<br />

[P094.CPP] /* Wersja ze strukturą */<br />

# include <br />

# include <br />

- 269-


struct Licznik<br />

{<br />

private:<br />

char znak;<br />

int ile;<br />

public:<br />

Licznik(char);<br />

void PlusJeden(void);<br />

char Pokaz(void);<br />

int Efekt(void);<br />

};<br />

Licznik::Licznik(char x)<br />

{<br />

znak = x;<br />

ile = 0;<br />

}<br />

void main()<br />

{<br />

Licznik licznik('A');<br />

//Konstruktor<br />

//Def. konstruktora<br />

//Zainicjowanie przez konstruktor<br />

cout


[P095.CPP] /* Wersja z klasą i obiektem */<br />

# include <br />

# include <br />

class Licznik<br />

{<br />

char znak;<br />

int ile;<br />

public:<br />

Licznik(char);<br />

void PlusJeden(void);<br />

char Pokaz(void);<br />

int Efekt(void);<br />

};<br />

//Konstruktor<br />

Licznik::Licznik(char x) //Def. konstruktora<br />

{<br />

znak = x;<br />

ile = 0;<br />

}<br />

void main()<br />

{<br />

Licznik licznik('A');<br />

//Zainicjowanie obiektu licznik<br />

cout


do skasowania obiektu możemy zastosować tzw. desruktor (ang.<br />

destructor). Nazwy konstruktora i destruktora są identyczne z<br />

nazwą macieżystego typu struktur (macieżystej klasy), z tym, że<br />

nazwa destruktora poprzedzona jest znakiem "~" (tylda).<br />

CO TO JEST DESTRUKTOR.<br />

Specjalna funkcja - destruktor (jeśli zadeklarujemy zastosowanie<br />

takiej funkcji) jest wywoływana automatycznie, gdy program<br />

zakończy korzystanie z obiektu. Konstruktor towrzy, a destruktor<br />

(jak sama nazwa wskazuje) niszczy strukturę (obiekt) i zwalnia<br />

przyporządkowaną pamięć. Przykład poniżej to program<br />

manipulujący stosem, rozbudowany tak, by zawierał i konstruktor<br />

i destruktor struktury (obiektu). Zorganizujmy zarządzanie<br />

pamięcią przeznaczoną dla stosu w taki sposób:<br />

struct Stos<br />

{<br />

private:<br />

int *bufor_danych;<br />

int licznik;<br />

public:<br />

Stos(int ile_RAM);<br />

int Pop(int *ze_stosu);<br />

int Push(int na_stos);<br />

};<br />

/* Konstruktor<br />

gdzie:<br />

*bufor_danych - wskaźnik do bufora (wypełniającego rolę stosu),<br />

licznik - wierzchołek stosu, jeśli == -1, stos jest pusty.<br />

Stos::Stos(...) - konstruktor inicjujący strukturę typu Stos<br />

(lub obiekt klasy Stos),<br />

ile_RAM - ilość pamięci potrzebna do poprawnego działanie stosu,<br />

*ze_stosu - wskaźnik do zmiennej, której należy przypisać<br />

wartość zdjętą właśnie ze stosu,<br />

na_stos - liczba przeznaczona do zapisu na stos.<br />

Zajmijmy się teraz definicją konstruktora. Wywołując konstruktor<br />

w programie (deklarując użycie w programie struktury typu Stos)<br />

przekażemy mu jako argument ilość potrzebnej nam pamięci RAM w<br />

bajtach. Do przyporządkowznia pamięci na stercie dla naszego<br />

stosu wykorzystamy funkcję malloc().<br />

Stos::Stos(int n_RAM)<br />

//Konstruktor - def.<br />

{<br />

licznik = -1;<br />

bufor_danych = (int *) malloc(n_RAM);<br />

}<br />

Posługując się funkcją malloc() przyporządkowujemy buforowi<br />

danych, w oparciu o który organizujemy nasz obiekt (na razie w<br />

formie struktury) - stos 100 bajtów pamięci, co pozwala na<br />

- 272-


ozmieszczenie 50 liczb typu int (po 2 bajty każda). Liczbę<br />

potrzebnych bajtów pamięci - 100 przekazujemy jako argument<br />

konstruktorowi w momencie deklaracji struktury typu Stos. Nasza<br />

struktura w programie będzie się nazywać nasz_stos.<br />

main()<br />

{<br />

...<br />

Stos nasz_stos(100);<br />

...<br />

Kiedy wykorzystamy naszą strukturę w programie, możemy zwolnić<br />

pamięć przeznaczoną dla struktury posługując się funkcją<br />

biblioteczną C free(). Przykład przydziału pamięci przy pomocy<br />

pary operatorów new - delete już był, przedstawimy tu zatem<br />

tradycyjną (coraz rzadziej stosowaną metodę) opartą na<br />

"klasycznych" funkcjach z biblioteki C. Funkcją free() posłużymy<br />

się w destruktorze struktury nasz_stos - ~Stos(). Destruktory są<br />

wywoływane automatycznie, gdy kończy się działanie programu, lub<br />

też, gdy struktura (obiekt) przestaje być widoczna / dostępna w<br />

programie. Obiekt (struktura) przestaje być widoczny (podobnie<br />

ja zwykła zmienna lokalna/globalna), jeśli opuszczamy tę<br />

funkcję, wewnątrz której obiekt został zadeklarowany. Jest to<br />

właściwość bardzo ważna dla naszego przykładowego stosu. W<br />

naszym programie przykładowym pamięć przydzielona strukturze<br />

stack pozostaje zarezerwowana "na zawsze", nawet wtedy, gdy nasz<br />

stos przestaje być "widoczny" (ang. out of scope). Obiekt może<br />

przestać być widoczny np. wtedy, gdy działa funkcja "nie<br />

widząca" obiektu. Idąc dalej tym torem rozumowania, jeśli<br />

destruktor zostanie wywołany automatycznie zawsze wtedy, gdy<br />

obiekt przestanie być widoczny, istnienie destruktora w<br />

definicji typu struktur Stos pozwala na automatyczne wyzerowanie<br />

stosu. Deklarujemy destruktor podobnie do konstruktora, dodając<br />

przed nazwą destruktora znak ~ (tylda):<br />

struct Stos<br />

{<br />

...<br />

public:<br />

...<br />

~Stos(void);<br />

...<br />

}<br />

Jeśli program zakończy się lub struktura przestanie być<br />

widoczna, zostanie wywołany destruktor struktury nasz_stos i<br />

pamięć zostanie zwolniona. Praktycznie oznacza to, że możemy<br />

zwolnić pamięc przyporządkowaną strukturze w taki sposób:<br />

Stos::~Stos(void)<br />

//Definicja destruktora<br />

- 273-


{<br />

free(bufor_danych);<br />

cout


cout


void main()<br />

{<br />

...<br />

}<br />

Wykażemy, że zamiana struktury na klasę odbędzie się całkiem<br />

bezboleśnie. Mało tego, jeśli dokonamy zmian w implementacji w<br />

pliku nagłówkowym (struct --> class i usuniemy słowo private)<br />

nasz program główny nie zmieni się WCALE !<br />

Oto plik nagłówkowy A:\INCLUDE\STOSCL.HPP:<br />

[P097.CPP]<br />

# include <br />

# include <br />

/* ---------------------poczatek pliku STOSCL.HPP------------ */<br />

# define OK 1<br />

class Stos<br />

{<br />

int *bufor_danych;<br />

int licznik;<br />

public:<br />

Stos(int); /* Konstruktor */<br />

~Stos(void); /* Destruktor */<br />

int Pop(int*);<br />

int Push(int);<br />

};<br />

Stos::Stos(int n_RAM)<br />

//Konstruktor - def.<br />

{<br />

licznik = -1;<br />

bufor_danych = (int *) malloc(n_RAM);<br />

cout


{<br />

if(licznik >= 49) return 0;<br />

else bufor_danych[++licznik] = na_stos;<br />

return OK;<br />

}<br />

/* ------------------------koniec pliku STOSCL.HPP----------- */<br />

void main()<br />

{<br />

Stos nasz_stos(100);<br />

int i, Liczba;<br />

//OBIEKT Klasy Stos<br />

cout


#include <br />

#include <br />

#include <br />

struct Data<br />

{<br />

int miesiac, dzien, rok;<br />

void Display(void);<br />

};<br />

//Metoda "wyswietl"<br />

void Data::Display(void)<br />

{<br />

char *mon[] =<br />

{<br />

"Stycznia","Lutego","Marca","Kwietnia","Maja","Czerwca",<br />

"Lipca","Sierpnia","Wrzesnia","Pazdziernika","Listopada",<br />

"Grudnia"<br />

};<br />

cout


dzis.rok = 1900 + tim.tm_year;<br />

cout


LEKCJA 27. O DZIEDZICZENIU.<br />

________________________________________________________________<br />

W trakcie tej lakcji dowiesz się na czym polega dziedziczenie.<br />

________________________________________________________________<br />

Dziedziczenie (ang inheritance) jest próbą naśladowania w<br />

technice programowania najcenniejszego bodaj wynalazku Matki<br />

Natury - zdolności przekazywania cech. Jeśli wyobrazimy sobie<br />

typy struktur konik, lew, słoń, czy krokodyl, to jest oczywiste,<br />

że struktury te będą posiadać wiele wspólnych cech. Wspólnymi<br />

cechami mogą być zarówno wspólne dane (parametry) - np. nogi =<br />

4; jak i wspólne wykonywane przez nie funkcje - np. jedz(),<br />

śpij(), oddychaj() itd.. Mogą występować oczywiście i różnice,<br />

ale wiele danych i funkcji okaże się wspólnych.<br />

LOGIKA DZIEDZICZENIA.<br />

Rozwijając dalej myśl naszkicowaną we wstępie, w kategoriach<br />

obiegowego języka naturalnego można rzec, że słoń Trombalski<br />

byłby tu strukturą typu formalnego Słoń. Funkcjami wewnętrznymi<br />

słonia Trombalskiego i np. krokodyla Eugeniusza mogłyby być<br />

wspólne czynności tych struktur (lub obiektów):<br />

jedz()<br />

śpij()<br />

oddychaj()<br />

Projektanci <strong>C++</strong> wpadli na pomysł naśladowania mechanizmu<br />

dziedziczenia. Zamiast tworzyć te wszystkie struktury<br />

oddzielnie, możemy zdefiniować w <strong>C++</strong> jeden ogólny typ struktur<br />

(ang. generic structure), nazywany inaczej STRUKTURĄ BAZOWĄ<br />

(ang. base structure). Wszystkie wymienione wyżej struktury<br />

(słoń, krokodyl, itp.) stałyby się wtedy strukturami pochodnymi<br />

(ang. derived structures). Nasza struktura bazowa mogłaby<br />

nazywać się znów np. Zwierzak.<br />

Ponieważ niektóre funkcje są wspólne dla wszystkich struktur<br />

(wszystkie Zwierzaki muszą jeść, spać, itp.), moglibyśmy<br />

przyjąć, że każda struktura pochodna od bazowego typu Zwierzak<br />

musi zawierać funkcje jedz(), spij() i oddychaj(). Jeśli<br />

zdefiniujemy strukturę bazową Zwierzak i zadeklarujemy w tej<br />

klasie funkcje jedz(), spij() i oddychaj(), możemy spodziewać<br />

się, że struktura pochodna słoń powinna odziedziczyć funkcje -<br />

cechy po strukturze bazowej Zwierzak. . Słoń może oczywiście<br />

mieć i swoje odrębne cechy - dane i funkcje - np.:<br />

Slon.flaga_ssak<br />

Slon.trabie()<br />

Slon.tupie()<br />

"Gramatyka" <strong>C++</strong> przy opisywaniu wzajemnego pokrewieństwa<br />

struktur (i klas) wygląda następująco:<br />

struct NazwaStrukturyPochodnej : NazwaStrukturyBazowej<br />

- 280-


{<br />

private:<br />

Lista danych i funkcji prywatnych<br />

public:<br />

Lista danych i funkcji publicznych<br />

} Lista struktur danego typu;<br />

a dla klas i obiektów:<br />

class NazwaKlasyPochodnej : dostęp NazwaKlasyBazowej<br />

{<br />

Lista danych i funkcji prywatnych<br />

public:<br />

Lista danych i funkcji publicznych<br />

} Lista obiektow danej klasy;<br />

Bazowy typ struktur w <strong>C++</strong> wyglądałaby tak:<br />

struct Zwierzak<br />

{<br />

void jedz();<br />

void spij();<br />

void oddychaj();<br />

};<br />

Jeśli chcemy zasygnalizować, że pochodny typ struktur Slon ma<br />

odziedziczyć coś po typie bazowym Zwierzak, musimy w definicji<br />

klasy pochodnej podać nazwę klasy bazowej (jeśli mamy<br />

dziedziczyć - należy wskazać po kim):<br />

struct Slon : Zwierzak<br />

{<br />

int trabie();<br />

int tupie();<br />

};<br />

Przed nazwą typu struktury (klasy) bazowej (tu: Zwierzak) może<br />

pojawić się słowo określające zasady dostępu do danych i funkcji<br />

(tu: public).<br />

[!!!] RÓŻNIE MOŻNA DZIEDZICZYĆ...<br />

________________________________________________________________<br />

* Jeśli użyjemy w tym miejscu słowa public (przy strukturach<br />

domyślne), to atrybuty dostępu zostaną odziedziczone wprost.<br />

Oznacza to, że to, co było prywatne w strukturze bazowej<br />

zostanie przeniesione jako prywatne do struktury pochodnej, a<br />

to, co było publiczne w strukturze bazowej zostanie przeniesione<br />

jako publiczne do struktury pochodnej.<br />

* Jeśli natomiast użyjemy w tym miejscu słowa private, to<br />

wszystko, co struktura pochodna odziedziczy po strukturze<br />

bazowej stanie się w strukturze pochodnej prywatne.<br />

________________________________________________________________<br />

- 281-


Opracowanie przykładowego programu ilustrującego mechanizm<br />

dziedziczenia rozpoczniemy od zdefiniowania bazowego typu<br />

struktur i struktury pochodnej.<br />

struct Zwierzak<br />

{<br />

int nogi;<br />

void jedz();<br />

void spij();<br />

void oddychaj();<br />

};<br />


należącymi do obu typów struktur - bazowego: Zwierzak i<br />

pochodnego: Slon.<br />

[???] A CO Z UNIAMI ?<br />

_______________________________________________________________<br />

Unie nie mogą brać udziału w dziedziczeniu. Unia nie może być<br />

ani typem bazowym ani typem pochodnym.<br />

_______________________________________________________________<br />

Program w całości będzie wyglądał tak:<br />

[P099.CPP]<br />

# include <br />

struct Zwierzak<br />

{<br />

int nogi;<br />

void jedz();<br />

void spij();<br />

void oddychaj();<br />

};<br />

void Zwierzak::jedz(void) { cout


LEKCJA 28. DZIEDZICZENIE ZŁOŻONE.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak można odziedziczyć wiele<br />

cech po wielu różnych przodkach.<br />

________________________________________________________________<br />

Jeśli zechcemy dziedziczyć dalej według schematu<br />

dziadek-ojciec-syn-wnuk...? Nic nie stoi na przeszkodzie. Przy<br />

okazji zwróć uwagę, że następne pokolenia są coraz bardziej<br />

złożone (tak być nie musi, ale może). W przykładzie poniżej<br />

dziedziczymy według schematu Punkt-Okrąg-Elipsa.<br />

[P100.CPP]<br />

//Przyklad dziedziczenia "wielopokoleniowego"<br />

#include "stdio.h"<br />

#include "conio.h"<br />

struct punkt //BAZOWY typ struktur - punkt(x, y)<br />

{<br />

int x; //wspolrzedne punktu na ekranie<br />

int y;<br />

};<br />

struct kolo: punkt //Str. pochodna - kolo(x, y, R)<br />

{<br />

int promien; //wspolrzedne srodka x,y dziedziczymy<br />

};<br />

struct elipsa: kolo //dziedziczymy x,y i promien<br />

{<br />

int mniejszy_promien; //Str. pochodna elipsa(x, y, R, r)<br />

};<br />

punkt P;<br />

kolo C;<br />

elipsa E;<br />

//deklarujemy trzy struktury<br />

main()<br />

{<br />

clrscr();<br />

P.x = C.x = E.x = 1;<br />

P.y = C.y = E.y = 2;<br />

//Nadajemy wartosci polom struktur<br />

C.promien = E.promien = 4;<br />

E.mniejszy_promien = 3;<br />

//Sprawdzamy zawartosc pol struktur<br />

printf("%d %d %d %d %d %d \n",<br />

P.x, C.x, E.x, P.y, C.y, E.y);<br />

printf("%d %d %d",<br />

C.promien, E.promien, E.mniejszy_promien );<br />

getch();<br />

return 0;<br />

- 284-


}<br />

Można dziedziczyć po więcej niż jednym przodku także w inny<br />

sposób. Kwadrat, dla przykładu, dziedziczy cechy po prostokątach<br />

i po rombach jednocześnie (jest jednocześnie szczególnym<br />

przypadkiem prostokąta i szczególnym przypadkiem rombu). Typ<br />

pochodny w tym wypadku, zamiast "dziadka" i "ojca" powinien mieć<br />

DWU RÓŻNYCH OJCÓW (!). W <strong>C++</strong> takie dziedziczenie po dwu różnych<br />

typach bazowych jednocześnie nazywa się DZIEDZICZENIEM<br />

WIELOBAZOWYM (ang. multi-base inheritance). A oto przykład<br />

takiego dziedziczenia.<br />

[P101.CPP]<br />

#include <br />

struct BAZOWA1<br />

{ //Struktura bazowa pierwsza<br />

public:<br />

void Funkcja_a(void);<br />

};<br />

struct BAZOWA2<br />

{ //Struktura bazowa druga<br />

public:<br />

void Funkcja_b(void);<br />

};<br />

struct POCHODNA : BAZOWA1, BAZOWA2<br />

{<br />

public:<br />

void Funkcja_c(void);<br />

};<br />

//Lista "przodkow"<br />

void BAZOWA1::Funkcja_a(void){cout


(klas) bazowych w schemacie baza_1-baza_2-....-baza_n może być<br />

więcej niż 2.<br />

DZIEDZICZENIE KLAS.<br />

Oto "klasowo-obiektowa" wersja poprzedniego programu<br />

przykładowego ze słonikiem Cholerykiem. Typy struktur Zwierzak i<br />

Slon nazwiemy klasami, (odpowiednio - klasą bazową i klasą<br />

pochodną) a strukturę Slon Choleryk nazwiemy obiektem.<br />

[P102.CPP]<br />

#include <br />

class Zwierzak<br />

{<br />

public:<br />

int nogi;<br />

//Klasa bazowa (base class)<br />

void jedz();<br />

void spij();<br />

void oddychaj();<br />

};<br />

void Zwierzak::jedz(void) { cout


Pamiętając o problemie domyślnego statusu członków<br />

struktur/public i klas/private) możemy przejść do klas i<br />

obiektów.<br />

O KLASACH SZCZEGÓŁOWO.<br />

Aby wykazać możliwość modularyzacji programu zaprojektujemy<br />

moduł w postaci pliku nagłówkowego. Moduł będzie zawierać<br />

definicję naszej prywatnej klasy obiektów ZNAK.<br />

Zaczynamy od danych, które będą nam potrzebne do tworzenia w<br />

programach (różnych !) obiektów typu Znak.<br />

class ZNAK<br />

{<br />

char znak_dany;<br />

...<br />

//Kod ASCII znaku<br />

Aby obiekt został zainicjowany (tzn. wiedział jakim znakiem ma<br />

być w danym programie) dodamy do definicji klasy<br />

jednoparametrowy konstruktor<br />

class ZNAK<br />

{<br />

char znak_dany;<br />

public:<br />

ZNAK(...);<br />

...<br />

Dane mogą być prywatne, natomiast konstruktor i funkcje-metody<br />

powinny być publiczne, by można było wywoływać je w programach.<br />

Konstruktor będziemy wywoływać w programach tak:<br />

ZNAK Obiekt('a');<br />

Znaczy to: Utwórz w RAM obiekt klasy ZNAK pod nazwą "Obiekt" i<br />

wytłumacz mu, że jest znakiem 'a'.<br />

Konstruktor powinien pobierać od programu jeden argument typu<br />

char i przekazywać go obiektowi klasy ZNAK na jego pole danych<br />

znak_dany. Definicja konstruktora będzie zatem wyglądać tak:<br />

ZNAK::ZNAK(char x)<br />

{<br />

znak_dany = x;<br />

}<br />

Zakres dopuszczalnych znaków zawęzimy np. do kodów ASCII 65...90<br />

(od A do Z). Jeśli użytkownik "nie trafi", ustawimy zawsze "*"<br />

(asterisk). Dodatkowo, dla "elegancji" zamienimy ewentualne małe<br />

litery na duże.<br />

- 287-


ZNAK::ZNAK(char x)<br />

{<br />

znak_dany = x;<br />

if(znak_dany < 65 || znak_dany >122) znak_dany = '*';<br />

if(znak_dany > 97) znak_dany -= 32;<br />

}<br />

A jeśli użytkownik nie zechce podać żadnego znaku i zda się na<br />

domyślność obiektu? Żaden problem, wystarczy do klasy ZNAK dodać<br />

bezparametrowy konstruktor domyślny. Konstruktory domyślne<br />

spełniają w <strong>C++</strong> taką właśnie rolę:<br />

class ZNAK<br />

{<br />

char znak_dany;<br />

public:<br />

ZNAK(char);<br />

ZNAK(void);<br />

...<br />

//Konstruktor zwykly ("jednoznakowy")<br />

//Konstruktor domyślny (bezparametrowy)<br />

Słowo void (tu opcjonalne) może nie wystąpić. Aby "kłuło w<br />

oczy", który konstruktor jest konstruktorem domyślnym (ang.<br />

default konstructor), większość programistów zapisuje to tak:<br />

class ZNAK<br />

{<br />

char znak_dany;<br />

public:<br />

ZNAK(char);<br />

ZNAK(); //Z daleka widać, że nic nie ma !<br />

...<br />

Definicja konstruktora bezparametrowego będzie wyglądać tak:<br />

ZNAK::ZNAK() { znak_dany = 'X'; }<br />

W zależności od sposobu zadeklarowania obiektu w programie <strong>C++</strong><br />

wywoła automatycznie albo konstruktor ZNAK(char), albo<br />

konstruktor domyślny ZNAK():<br />

ZNAK obiekt; //Nie sprecyzowano jaki, konstruktor domyślny<br />

ZNAK obiekt('m'); //Wiadomo jaki, konstruktor jednoparametrowy<br />

Dzięki temu, że <strong>C++</strong> "pedantycznie" sprawdza przed wywołaniem<br />

funkcji zgodność typów argumentów przekazywanych do funkcji<br />

(konstruktor to też funkcja) i porównuje typ argumentów z<br />

życzeniem programisty wyrażonym w prototypie - bezbłędnie<br />

rozpozna (mimo identycznej nazwy), którą funkcję należy<br />

zastosować.<br />

Dodajmy do klasy ZNAK deklaracje (prototypy) funkcji-metod:<br />

- 288-


class ZNAK<br />

{<br />

char znak_dany;<br />

public:<br />

ZNAK(char);<br />

ZNAK();<br />

void Pokaz_sie();<br />

void Znikaj();<br />

void Skacz();<br />

};<br />

i zdefiniujmy te metody.<br />

void ZNAK::Pokaz_sie(void)<br />

{<br />

cout


ZNAK::ZNAK()<br />

{<br />

znak_dany = 'X';<br />

}<br />

ZNAK::ZNAK(char x)<br />

{<br />

znak_dany = x;<br />

if(znak_dany < 65 && znak_dany >122) znak_dany = '*';<br />

if(znak_dany > 97) znak_dany -= 32;<br />

}<br />

void ZNAK::Pokaz_sie(void)<br />

{<br />

cout


}<br />

Obiekt.Znikaj();<br />

getch();<br />

Obiekt.Skacz();<br />

ZNAK Obiekt2;<br />

Obiekt2.Skacz();<br />

//To bedzie domyslny 'X'<br />

I tu już widać pewne cechy nowoczesnego obiektowego stylu<br />

programowania. Tym razem sprwdzenie, czy słowo class można<br />

spokojnie zamienić na słowo struct pozostawim dociekliwym<br />

Czytelnikom.<br />

- 291-


LEKCJA 29. FUNKCJE I OVERLOADING.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak jeszcze w <strong>C++</strong> można<br />

wykorzystywać funkcje.<br />

________________________________________________________________<br />

w <strong>C++</strong> jedna funkcja może być definiowana wielokrotnie a każda z<br />

wersji funkcji może być przystosowana do obsługi innego typu<br />

argumentów. <strong>C++</strong> wybiera tę właściwą wersję funkcji<br />

automatycznie.<br />

JEDNA NAZWA FUNKCJI - WIELE ZASTOSOWAŃ.<br />

Overloading funkcji bywa czasem w podręcznikach dzielony na<br />

odrębne zagadnienia:<br />

* funkcja może tolerować różną liczbę argumentów (co dało się<br />

spokojnie realizować również w klasycznym C - vide printf());<br />

* funkcja może tolerować różne typy argumentów;<br />

* funkcja może realizować różne operacje dla różnych<br />

Wyobraźmy sobie, że mamy funkcję wydrukuj(), która potrafi<br />

wysłać na ekran otrzymany znak:<br />

void wydrukuj(char znak)<br />

{<br />

cout


}<br />

Łańcuch znaków jest widziany jako jednowymiarowa tablica<br />

zawierająca dane typu znakowego, czyli w taki sposób:<br />

char TABLICA[9] ={ "123456789" };<br />

Definice powinny mieć następującą postać:<br />

void KLASA::wydrukuj(char znak) {cout


main()<br />

{<br />

kopiuj_string(Piggie, "Panna Piggie");<br />

kopiuj_string(Kermit, "Kermit - to protokul transmisji", 6);<br />

cout


kluczowego "inline" w definicjach funkcji. Zwróć uwgę, że w<br />

samej definicji klasy słowo inline NIE POJAWIA SIĘ:<br />

[P105.CPP]<br />

# include <br />

class Klasa<br />

{<br />

public:<br />

void wydrukuj(char* tekst);<br />

void wydrukuj(char Znak);<br />

void wydrukuj(int KodASCII);<br />

};<br />

inline void Klasa::wydrukuj(char* tekst)<br />

{<br />

cout


typu inline.<br />

________________________________________________________________<br />

Status inline możemy nadać wszystkim trzem wersjom funkcji<br />

wydrukuj() umieszczając definicje funkcji bezpośrednio wewnątrz<br />

definicji klasy:<br />

class Klasa<br />

{<br />

public:<br />

inline void wydrukuj(char* a) { cout


for(int i = 1; i < ile; i++)<br />

cout


void main()<br />

{<br />

Klasa Obiekt1; //Konstr. domyślny<br />

Klasa Obiekt2('A'); // ile - domyslnie == 4<br />

Klasa Obiekt3('B', 3);<br />

Klasa Obiekt4(p);<br />

}<br />

- 298-


LEKCJA 30. WYMIANA DANYCH MIĘDZY OBIEKTAMI.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak można wymieniać dane i<br />

informacje pomiędzy różnymi obiektami.<br />

________________________________________________________________<br />

Hermetyzacja danych jest cenną zdobyczą, ale od czasu do czasu<br />

obiekty powinny dokonywać pomiędzy sobą wymiany informacji,<br />

także tych wewnętrznych - prywatnych. Ten problem może sprawiać<br />

programiście trochę kłopotów - należy zatem poświęcić mu trochę<br />

uwagi.<br />

DOSTĘP DO DANYCH PRZY POMOCY FUNKCJI KATEGORII friend.<br />

Aby wyjaśnić mechanizmy dostępu do danych obiektów będziemy<br />

potrzebować:<br />

* wielu obiektów;<br />

* danych prywatnych obiektów (dostęp do publicznych,<br />

"niezakapsułkowanych" danych jest prosty i oczywisty);<br />

* funkcji o specjalnych uprawnieniach.<br />

Takie funkcje o specjalnych uprawnieniach - z możliwością<br />

odwoływania się do prywatnych danych wielu obiektów (a nie tylko<br />

swojego) muszą w <strong>C++</strong> posiadać status "friend" (ang. friend -<br />

przyjaciel).<br />

Nasz przykładowy program będzie operował tablicą złożoną z<br />

obiektów klasy Licznik.<br />

class Licznik<br />

{<br />

char moja_litera;<br />

int ile;<br />

public:<br />

void Inicjuj_licznik(char);<br />

void Skok_licznika(void);<br />

void Pokazuj();<br />

};<br />

...<br />

Licznik TAB[MAX];<br />

Obiekty - liczniki będą zliczać wystąpienie (każdy swojego)<br />

określonego znaku w strumieniu znaków wejściowych (wczytywanym z<br />

klawiatury). Tablica będzie się składać z MAX == 26 elementów -<br />

obiektów - liczników, po jednym dla każdej dużej litery<br />

alfabetu. Tablica będzie nazywać się TAB[26]. Po zadeklarowaniu:<br />

- 299-


nazwa_klasy TAB[MAX];<br />

kolejne obiekty będą się nazywać:<br />

nazwa_klasy Obiekt1 == TAB[0]; //Licznik 1 - 'A'<br />

nazwa_klasy Obiekt2 == TAB[1]; //Licznik 2 - 'B'<br />

... ...<br />

nazwa_klasy ObiektN == TAB[N-1];<br />

Po wprowadzeniu znaku z klawiatury wywołamy wbudowaną do każdego<br />

obiektu funkcję Skok_licznika(), która doda jedynkę do<br />

wewnętrznego licznika obiektu. Wywołując funkcję zastosujemy<br />

zamiast typowej składni<br />

ObiektK.Skok_licznika();<br />

odpowiadającą jej w tym wypadku notację<br />

TAB[i].Skok_licznika();<br />

Powinniśmy jeszcze przed wywołaniem funkcji sprawdzić, czy znak<br />

jest dużą literą alfabetu. W przykładowym programie zrobimy to<br />

tak:<br />

...<br />

cin >> znak;<br />

//Pobranie znaku z klawiatury<br />

for(int i = 0; i < 26; i++)<br />

{<br />

if(i == (znak - 'A')) TAB[i].Skok_licznika();<br />

}<br />

...<br />

Dzięki temu wewnętrzny licznik obiektu TAB[2] zostanie<br />

powiększony tylko wtedy, gdy znak - 'A' == 2 (znak jest literą<br />

C, bo 'C' - 'A' == 2).<br />

Można to zapisać skuteczniej.<br />

...<br />

cin >> znak;<br />

TAB[znak - 'A'].Skok_licznika(); //Inkrementacja licznika<br />

...<br />

bądź jeszcze krócej:<br />

...<br />

TAB[getch() - 'A'].Skok_licznika();<br />

...<br />

Istnieje tu wszakże niebezpieczeństwo próby odwołania się do<br />

nieistniejącego elementu tablicy, przed czym powinniśmy się<br />

wystrzegać.<br />

W wyniku działania programu otrzymamy zliczoną ilość<br />

występowania danej litery w strumieniu znaków wejściowych.<br />

- 300-


[P107.CPP]<br />

# include //prototyp toupper()<br />

# include <br />

class Licznik<br />

{<br />

char moja_litera;<br />

int ile;<br />

public:<br />

void Inicjuj(char);<br />

void Skok_licznika();<br />

void Pokazuj();<br />

};<br />

void Licznik::Inicjuj(char z)<br />

{<br />

moja_litera = z;<br />

ile = 0;<br />

}<br />

void Licznik::Skok_licznika(void)<br />

{<br />

ile++;<br />

}<br />

void Licznik::Pokazuj(void)<br />

{<br />

cout


}<br />

/* sprawdzamy: ----------------------------------------*/<br />

char sprawdzamy;<br />

cout


kluczowego friend. A oto definicja:<br />

int Suma(int ilosc_obiektow)<br />

{<br />

int i, suma = 0;<br />

for(i = 0; i < ilosc_obiektow; i++)<br />

suma += TAB[i].ile;<br />

}<br />

return (suma);<br />

Dzięki zastosowaniu słowa "friend", funkcja Suma() jest<br />

zaprzyjaźniona ze wszystkimi 26 obiektami, ponieważ wszystkie<br />

obiekty należą do tej klasy, w której zadeklarowaliśmy funkcję:<br />

class ...<br />

{<br />

...<br />

friend int Suma(...);<br />

...<br />

} ... ;<br />

Tablica TAB[MAX] złożona z obiektów klasy Licznik została<br />

zadeklarowana nazewnątrz funkcji main() ma więc status tablicy<br />

GLOBALNEJ. Funkcja Suma() ma dostęp do prywatnych danych<br />

wszystkich obiektów, możemy więc zastosować ją w programie w<br />

następujący sposób:<br />

[P108.CPP]<br />

# include <br />

# include <br />

class Licznik<br />

{<br />

char moja_litera;<br />

int ile;<br />

public:<br />

void Inicjuj(char);<br />

void Skok_licznika();<br />

void Pokazuj();<br />

friend int Suma(int);<br />

}<br />

const MAX = 26;<br />

Licznik TAB[MAX];<br />

register int i;<br />

main()<br />

{<br />

/* inicjujemy liczniki: -------------------------------*/<br />

for(i = 0; i < MAX; i++)<br />

{<br />

TAB[i].Inicjuj('A' + i);<br />

}<br />

- 303-


* pracujemy - zliczamy: -------------------------------*/<br />

cout znak;<br />

if(znak == '.') break;<br />

for(i = 0; i < MAX; i++)<br />

{<br />

if(i == (znak - 'A')) TAB[i].Skok_licznika();<br />

}<br />

}<br />

/* sprawdzamy: ----------------------------------------*/<br />

char sprawdzamy;<br />

cout


{<br />

}<br />

ile++;<br />

//Wiadomo o ktory obiekt chodzi<br />

Funkcja klasy friend odwołuje się do pól obiektów tak:<br />

int Suma(int liczba)<br />

{<br />

...<br />

suma += TAB[i].ile;<br />

/* - wymaga dodatkowo wskazania, o który obiekt chodzi - */<br />

}<br />

Należy pamiętać, że dla funkcji kategorii friend wszystkie<br />

obiekty należące do danej klasy mają status public - są<br />

dostępne.<br />

O ZAPRZYJAŹNIONYCH KLASACH.<br />

W <strong>C++</strong> mogą być zaprzyjaźnione ze sobą wzajemnie także klasy.<br />

Pozwala to metodom zdefiniowanym wewnątrz jednej z klas na<br />

dostęp do prywatnych danych obiektów innych klas. W przypadku<br />

zaprzyjaźnionych klas słowem kluczowym friend poprzedzamy nazwę<br />

klasy (a nie każdej zaprzyjaźnionej metody z osobna, choć<br />

zamierzony skutek właśnie na tym polega). Oto praktyczny<br />

przykład zaprzyjaźnionych klas.<br />

[P109.CPP]<br />

# include <br />

class Data1;<br />

//Deklaracja (a nie definicja!) klasy<br />

class TEZ_DATA<br />

{<br />

int dz, rok;<br />

public:<br />

TEZ_DATA() {}<br />

TEZ_DATA(int d, int y) { dz = d; rok = y;}<br />

void Pokazuj() {cout


Data1::operator TEZ_DATA(void)<br />

{<br />

TEZ_DATA DT_Obiekt(0, rok);<br />

for (int i = 0; i < mc-1; i++)<br />

DT_Obiekt.dz += TAB[i];<br />

DT_Obiekt.dz += dz;<br />

return DT_Obiekt;<br />

}<br />

main()<br />

{<br />

Data1 dt_Obiekt(11,17,89);<br />

TEZ_DATA DT_Obiekt;<br />

DT_Obiekt = dt_Obiekt;<br />

DT_Obiekt.Pokazuj();<br />

return 0;<br />

}<br />

Zaprzyjaźnione są klasy Data1 i TEZ_DATA. Dzięki temu metody<br />

zadeklarowane wewnątrz zaprzyjaźnionej klasy Data1 mają dostęp<br />

do prywatnych danych obiektów klasy TEZ_DATA. Ponieważ klasa to<br />

nowy formalny typ danych, a obiekt to dane takiego nowego typu,<br />

nic nie stoi na przeszkodzie, by obiekty przekazywać do funkcji<br />

jako argumenty (tak jak wcześniej obiekty typów typowych - int,<br />

float itp.).<br />

W <strong>C++</strong> mamy jeszcze jedną metodę wymiany danych. Możemy nadać<br />

elementom klas i obiektów status static (statyczny).<br />

WYMIANA INFORMACJI PRZY POMOCY DANYCH STATYCZNYCH.<br />

Jeśli element klasy został zadeklarowany jako element statyczny<br />

(przy pomocy słowa kluczowego static), to bez względu na to jak<br />

wiele obiektów danej klasy utworzymy, w pamięci będzie istnieć<br />

TYLKO JEDEN EGZEMPLARZ (kopia) tego elementu. W przykładowym<br />

programie z obiektami-licznikami możemy osiągnąc taki efekt<br />

nadając zmiennej ile (stan licznika) status static int ile:<br />

class Licznik<br />

{<br />

char moja_litera;<br />

static int ile;<br />

...<br />

};<br />

Jeśli utworzymy wiele obiektów takiej klasy, to wszystkie te<br />

obiekty będą posługiwać się tą samą (wspólną!) zmienną ile. Dla<br />

przykładu, jeśli zechcemy zliczać ile razy w strumieniu danych<br />

wejściowych pojawiły się np. znaki 'a' , 'b' i 'c', możemy<br />

utworzyć trzy obiekty - liczniki: licznik_a, licznik_b i<br />

licznik_c. wszystkie te liczniki będą posługiwać się wspólną<br />

zmienną statyczną ile:<br />

class Licznik<br />

- 306-


{<br />

public:<br />

char moja_litera;<br />

static int ile;<br />

Licznik(char);<br />

...<br />

};<br />

//Konstruktor<br />

Do zainicjownia obiektów posłużymy się konstruktorem. Deklaracja<br />

obiektu spowoduje automatyczne wywołanie kostruktora i<br />

zainicjowanie obiektu w pamięci. Przy okazji przekazujemy<br />

obiektom znaki do zliczania.<br />

Licznik licznik_a('a'), licznik_b('b'), licznik_c('c');<br />

Jeśli teraz w strumieniu wejściowym pojawi się któraś z<br />

interesujących nas liter (a, b, bądź c), zostanie wywołana<br />

właściwa wersja metody Skok_licznika():<br />

int main(void)<br />

{<br />

char litera;<br />

...<br />

cin >> litera;<br />

...<br />

if(litera == licznik_a.moja_litera) licznik_a.Skok_licznika();<br />

...<br />

}<br />

if(litera == licznik_b.moja_litera) licznik_b.Skok_licznika();<br />

Zmienna ile jest zmienną statyczną, więc wsztstkie trzy funkcje<br />

dokonają inkrementacji zmiennej znajdującej się pod tym samym<br />

fizycznym adresem pamięci. Jeśli dla wszystkich obiektów danej<br />

klasy jakaś zmienna oznacza zawartość tego samego adresu<br />

pamięci, możemy się odwołać do tej zmiennej również tak:<br />

nazwa_klasy::nazwa_zmiennej<br />

Ten sposób można jednakże stosować wyłącznie wobec statycznych<br />

elementów klasy o statusie danych publicznych. Jeśli są to dane<br />

prywatne nie można jeszcze dodatkowo zapominać o hermetyzacji i<br />

zasadach dostępu. Jeżeli pole danej klasy jest polem statycznym,<br />

możemy do niego odwoływać się na dwa sposoby. Za pośrednictwem<br />

obiektów w taki sposób:<br />

identyfikator_obiektu.identyfikator_pola<br />

A za pośrednictwem nazwy klasy (podobnie jak do zmiennych<br />

globalnych), taką metodą:<br />

identyfikator_klasy::identyfikator_pola<br />

- 307-


Możemy zmodyfikować program przykładowy posługując się<br />

(globalną) zmienną statyczną. Zamiast wszystkich liter będziemy<br />

zliczać tylko wystąpienia 'a', 'b' i 'c'.<br />

[P110.CPP]<br />

# include "ctype.h"<br />

# include "iostream.h"<br />

class Licznik<br />

{<br />

public:<br />

char moja_litera;<br />

static int ile;<br />

Licznik(char);<br />

void Skok_licznika();<br />

void Pokazuj();<br />

};<br />

//Konstruktor<br />

void main()<br />

{<br />

/* inicjujemy liczniki: -------------------------------*/<br />

Licznik licznik_a('a'), licznik_b('b'), licznik_c('c');<br />

/* pracujemy - zliczamy: -------------------------------*/<br />

cout znak;<br />

if(znak == '.') break;<br />

if (znak == licznik_a.moja_litera) licznik_a.Skok_licznika();<br />

if (znak == licznik_b.moja_litera) licznik_b.Skok_licznika();<br />

if (znak == licznik_c.moja_litera) licznik_c.Skok_licznika();<br />

}<br />

/* sprawdzamy: ----------------------------------------*/<br />

cout


ile++;<br />

}<br />

void Licznik::Pokazuj(void)<br />

{<br />

cout


LEKCJA 31. PRZEKAZANIE OBIEKTÓW JAKO ARGUMENTÓW DO FUNKCJI.<br />

________________________________________________________________<br />

W trakcie tej lekcji poznasz sposoby manipulowania obiektami<br />

przy pomocy funkcji. Poznasz także trochę dokładniej referencje.<br />

________________________________________________________________<br />

Typowy sposób przekazywania argumentów do funkcji w <strong>C++</strong> to<br />

przekazanie przez wartość (ang. by value). W przypadku obiektów<br />

oznacza to w praktyce przekazanie do funkcji kopii obiektu. Jako<br />

przykład zastosujemy program zliczający wystąpienia znaków w<br />

strumieniu wejściowym. Zmienimy w tym programie sposób<br />

wyprowadzenia wyników. Funkcji Pokazuj() przekażemy jako<br />

argument obiekt. Obiekt-licznik zawiera w środku tę informację,<br />

której potrzebuje funkcja - ilość zliczonych znaków. Zacznijmy<br />

od zdefiniowania klasy.<br />

class Licznik<br />

{<br />

public:<br />

char moja_litera;<br />

int ile;<br />

Licznik(char litera);<br />

void Skok_licznika();<br />

};<br />

W programie głównym możemy zastosować konstruktor do<br />

zainicjowania obiektu np. tak:<br />

main()<br />

{<br />

Licznik licznik_a('a');<br />

...<br />

Zdefiniujmy funkcję. Obiekt licznik_a będzie argumentem funkcji<br />

Pokazuj(). Funkcja powinna wyprowadzić na ekran zawartość pola<br />

licznik_a.ile. Deklaracja - prototyp takiej pobierającej obiekt<br />

funkcji będzie wyglądać tak:<br />

wart_zwracana Nazwa_funkcji(nazwa_klasy nazwa_obiektu);<br />

Nazwa klasy spełnia dokładnie taką samą rolę jak każdy inny typ<br />

danych. W naszym przypadku będzie to wyglądać tak:<br />

void Pokazuj(Licznik obiekt);<br />

Ponieważ "obiekt" jest parametrem formalnym i jego nazwa nie<br />

jest tu istotna, możemy pominąć ją w prototypie funkcji (w<br />

definicji już nie!) i skrócić zapis do postaci:<br />

void Pokazuj(Licznik);<br />

Funkcja Pokazuj() otrzyma w momencie wywołania jako swój<br />

argument kopię obiektu, którą jako argument formalny funkcji<br />

nazwaliśmy "obiekt". W naszym programie wywołanie tej funkcji<br />

- 310-


ędzie wyglądać tak:<br />

Pokazuj(licznik_a);<br />

Obiekt "licznik_a" jest tu BIEŻĄCYM ARGUMENTEM FAKTYCZNYM. Typ<br />

(tzn. tu: klasa) argumentu faktycznego musi być oczywiście<br />

zgodny z zadeklarowanym wcześniej typem argumentu formalnego<br />

funkcji.<br />

Jeśli funkcja dostała własną kopię obiektu, może odwołać się do<br />

elementów tego obiektu w taki sposób:<br />

void Pokazuj(Licznik obiekt)<br />

{<br />

cout


int Pokazuj2(Licznik);<br />

void main()<br />

{<br />

/* inicjujemy licznik: -------------------------------*/<br />

Licznik licznik_a('a');<br />

/* pracujemy - zliczamy: -------------------------------*/<br />

cout znak;<br />

if(znak == '.') break;<br />

if (znak == licznik_a.moja_litera) licznik_a.Skok_licznika();<br />

}<br />

/* sprawdzamy: ----------------------------------------*/<br />

cout


Programy manipulujące obiektami w taki sposób mogą wymagać<br />

modelu pamięci większego niż przyjmowany domyślnie model SMALL.<br />

Typowy komunikat pojawiający się przy zbyt małym modelu pamięci<br />

to:<br />

Error 43: Type mismatch in parameter to call to<br />

Pokazuj1(Licznik)...<br />

(Źły typ argumentu przy wywołaniu funkcji Pokazuj(...)...)<br />

Programy obiektowe są z reguły szybke, ale niestety dość<br />

"pamięciochłonne". W IDE BORLAND <strong>C++</strong> masz do dyspozycji opcję:<br />

Options | Compiler | Code generation | Model<br />

Dokładniejsze informacje o modelach pamięci znajdziesz w dalszej<br />

części książki.<br />

________________________________________________________________<br />

O PROBLEMIE REFERENCJI.<br />

Typowy (domyślny) sposób przekazywania argumentów do funkcji w<br />

<strong>C++</strong> polega na tzw. "przekazaniu przez wartość" i jest inny niż<br />

Pascalu, czy Basicu. Ponieważ w polskich warunkach do C/<strong>C++</strong><br />

większość adeptów "dojrzewa" po przebrnięciu przez Basic i/lub<br />

Pascal, programiści ci obciążeni są już pewnymi nawykami i<br />

pewnym schematyzmem myślenia, który do <strong>C++</strong> niestety nie da się<br />

zastosować i jest powodem wielu pomyłek. To, co w Basicu wygląda<br />

zrozumiale (uwaga, tu właśnie pojawia się automatyzm myślenia):<br />

PRINT X<br />

INPUT X<br />

REM Wyprowadź bieżącą wartość zmiennej X<br />

REM Pobierz wartość zmiennej X<br />

a w Pascalu:<br />

writeln(X); { Wyprowadź bieżacą wartość zmiennej X }<br />

readln(X); { Pobierz wartość zmiennej X }<br />

przyjmuje w C/<strong>C++</strong> formę zapisu wyraźnie dualnego:<br />

printf("%d", X); //Wyprowadź wartość zmiennej X<br />

scanf("%d", &X); //Pobierz wartość zmiennej X<br />

Na czym polega różnica? Jeśli odrzucimy na chwilę automatyzm i<br />

zastanowimy się nad tą sytuacją, zauważymy, że w pierwszym<br />

przypadku (wyprowadzanie istniejących już danych - PRINT,<br />

wrilteln, printf()) w celu poprawnego działania funkcji<br />

powinniśmy przekazać jej BIEŻĄCĄ WARTOŚĆ ARGUMENTU X (adres<br />

zmiennej w pamięci nie jest funkcji potrzebny). Dla Basica,<br />

Pascala i <strong>C++</strong> bieżąca wartość zmiennej kojarzoana jest z jej<br />

identyfikatorem - tu: "X". W drugim jednakże przypadku (pobranie<br />

danych i umieszczenie ich pod właściwym adresem pamięci) jest<br />

- 313-


inaczej. Funkcji zupełnie nie interesuje bieżąca wartść zmiennej<br />

X, jest jej natomiast do poprawnego działania potrzebny adres<br />

zarezerwowany dla zmiennej X w pamięci. Ale tu okazuje się, że<br />

Basic i Pascal postępują dokładnie tak samo, jak poprzednio:<br />

INPUT X i read(X);<br />

Oznacza to, że X nie oznacza dla Pascala i Basica bieżącej<br />

wartości zmiennej, lecz oznacza (DOMYŚLNIE) przekazanie do<br />

funkcji adresu zmiennej X w pamięci. Funkcje oczywiście<br />

"wiedzą", co dostały i dalej już one same manipulują danymi we<br />

właściwy sposób.<br />

W <strong>C++</strong> jest inaczej. Zapis:<br />

Funkcja(X);<br />

oznacza w praktyce, że zostaną wykonane następujące operacje:<br />

* spod adresu pamięci przeznaczonego dla zmiennej X zostanie<br />

(zgodnie z zadeklarowanym formatem) odczytana bieżąca wartość<br />

zmiennej X;<br />

* wartość X zostanie zapisana na stos (PUSH X);<br />

* zostanie wywołana funkcja Funkcja();<br />

* Funkcja() pobierze sobie wartość argumentu ze stosu (zgodnie z<br />

formatem zadeklarowanym w prototypie Funkcji()).<br />

* Funkcja() zadziała zgodnie ze swoją definicją i jeśli ma coś<br />

do pozostawienia (np. return (wynik); ) pozostawi wynik.<br />

Jak widać:<br />

* funkcja "nie wie", gdzie w pamięci umieszczony był przekazany<br />

jej argument;<br />

* funkcja komunikuje się "ze światem zewnętrznym" (czyli własnym<br />

programem, bądź funkcją wyższego rzędu - wywołującą) tylko za<br />

pośrednictwem stosu;<br />

* funkcja dostaje swoją "kopię" argumentu z którym działa;<br />

* funkcja nie ma wpływu na "oryginał" argumentu, który pozostaje<br />

bez zmian.<br />

REFERENCJA - CO TO TAKIEGO ?<br />

Zastanówmy się, czym właściwie jest referencja zmiennej w <strong>C++</strong>.<br />

Pewne jest, że jest to alternatywny sposób odwołania się do<br />

zmiennej. Zacznijmy od trywialnego przykładu odwołania się do<br />

tej samej zmiennej mającej swoją właściwą nazwę "zmienna" i<br />

referencję "ksywa".<br />

# include "iostream.h"<br />

main()<br />

- 314-


{<br />

int zmienna;<br />

int& ksywa;<br />

...<br />

Aby "ksywa" oznaczała tę samą zmienną, referencję należy<br />

zainicjować:<br />

int& ksywa = zmienna;<br />

Zainicjujemy naszą zmienną "zmienna" i będziemy robić z nią<br />

cokolwiek (np. inkrementować). Równocześnie będziemy sprawdzać,<br />

czy odwołania do zmiennej przy pomocy nazwy i referencji będą<br />

pozostawać równoważne.<br />

[P111.CPP]<br />

/* UWAGA: Program moze potrzebowac modelu wiekszego niz<br />

domyslnie ustawiany MODEL SMALL */<br />

# include "iostream.h"<br />

main()<br />

{<br />

int zmienna = 6666;<br />

int& ksywa = zmienna;<br />

cout


zastosowania: określenie adresu w pamęci oraz tworzenie<br />

wskazania. Aby rozróżnić te dwie sytuacje zwróć uwagę na<br />

"gramatykę" zapisu. Jeśli identyfikator zminnej jest poprzedzony<br />

określeniem typu zminnej:<br />

int &zmienna; /* lub */ int &zmienna = ... ;<br />

to zmienną nazywamy "zmienną referencyjną". Jeśli natomiast<br />

identyfikator nie został poprzedzony określeniem typu:<br />

p = &zmienna;<br />

to mówimy wtedy o adresie zmiennej.<br />

Przekazanie argumentu do funkcji poprzez referencję jest w<br />

istocie zbliżone do przekazania wskaźnika do argumentu. Zwróć<br />

uwagę, że przekazanie wskaźnika do obiektu może zwykle odbyć się<br />

szybciej niż sporządzenie kopii obiektu i przekazanie tej kopii<br />

do funkcji. Zastosowanie w deklaracji funkcji operatora<br />

adresowego & pozwala nam stosować syntaktykę zapisu taką "jak<br />

zwykle" - przy przekazaniu przez wartość. Jeśli nie chcemy<br />

ryzykować zmian wprowadzonych do oryginalnego parametru<br />

przekazanego funkcji poprzez wskazanie, możemy zadeklarować<br />

oryginalny parametr jako stałą (kompilator "dopilnuje" i<br />

uniemożliwi zmianę wartości):<br />

nazwa_funkcji(const &nazwa_obiektu);<br />

________________________________________________________________<br />

Poprosimy <strong>C++</strong> by pokazał nam konkretne fizyczne adresy<br />

skojarzone z identyfikatorami "zmienna" i "ksywa". Operator &<br />

oznacza dla <strong>C++</strong><br />

&X --> adres w pamięci zmiennej X<br />

[P112.CPP]<br />

/* UWAGA: Program moze potrzebowac modelu wiekszego niz<br />

domyslnie ustawiany MODEL SMALL */<br />

# include "iostream.h"<br />

main()<br />

{<br />

int zmienna = 6666;<br />

int& ksywa = zmienna;<br />

cout


Zmienna (ADR-hex) Ksywa (ADR-hex):<br />

0x287efff4<br />

0x287efff4<br />

Fizyczny adres pamięci, który "kojarzy się" <strong>C++</strong> ze zmienną i<br />

ksywą jest identyczny. Referencja nie oznacza zatem ani<br />

sporządzania dodatkowej kopii zmiennej, ani wskazania do<br />

zmiennej w rozumieniu wskaźnika (pointer). Jest to inna metoda<br />

odwołania się do tej samej pojedynczej zmiennej.<br />

- 317-


LEKCJA 32. WSKAŹNIKI DO OBIEKTÓW.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak posługiwać się obiektami<br />

za pośrednictwem wskaźników.<br />

________________________________________________________________<br />

Wskaźniki do obiektów funkcjonują podobnie jak wskaźniki do<br />

struktur. Operator -> pozwala na dostęp zarówno do danych jak i<br />

do funkcji. Dla przykładu wykorzystamy obiekt naszej prywatnej<br />

klasy Licznik.<br />

class Licznik<br />

{<br />

public:<br />

char moja_litera;<br />

int ile;<br />

Licznik(char znak) { moja_litera = z; ile = 0; }<br />

void Skok_licznika(void) { ile++; }<br />

};<br />

Aby w programie można było odwołać się do obiektu nie poprzez<br />

nazwę a przy pomocy wskaźnika, zadeklarujemy wskaźnik do<br />

obiektów klasy Licznik:<br />

Licznik *p;<br />

Wskaźnik w programie możemy zastosować np. tak:<br />

p->Skok_licznika();<br />

(czytaj: Wywołaj metodę "Skok_licznika()" w stosunku do obiektu<br />

wskazywanego w danym momencie przez wskaźnik p)<br />

Trzeba pamiętać, że sama deklaracja w przypadku referencji i<br />

wskaźników nie wystarcza. Przed użyciem należy jeszcze<br />

zainicjować wskaźnik w taki sposób, by wskazywał na nasz<br />

obiekt-licznik. Wskaźnik do obiektu inicjujemy w taki sam sposób<br />

jak każdy inny pointer:<br />

p = &Obiekt;<br />

Możemy przystąpić do utworzenia programu przykładowego.<br />

[P119.CPP]<br />

# include "ctype.h"<br />

# include "iostream.h"<br />

class Licznik<br />

{<br />

public:<br />

char moja_litera;<br />

int ile;<br />

Licznik(char z) { moja_litera = z; ile = 0; }<br />

- 318-


void Skok_licznika(void) { ile++; }<br />

};<br />

void main()<br />

{<br />

char znak;<br />

cout > znak;<br />

Licznik Obiekt1(znak), Obiekt2('a'), *p1, *p2;<br />

p1 = &Obiekt1;<br />

p2 = &Obiekt2;<br />

cout znak;<br />

if(znak == '.') break;<br />

if(znak == p1->moja_litera) p1->Skok_licznika();<br />

if(znak == p2->moja_litera) p2->Skok_licznika();<br />

}<br />

cout


wątpliwości zidentyfikować właśnie ten obiekt, z którym pracuje<br />

a nie obiekt przypadkowy.<br />

[!!!] FUNKCJE KATEGORII static NIE OTRZYMUJĄ POINTERA this.<br />

Należy pamiętać, że wskaźnik this istnieje wyłącznie podczas<br />

wykonywania metod (ang. class member function execution), za<br />

wyjątkiem funkcji statycznych.<br />

Jeśli w programie zadeklarujemy klasę Klasa:<br />

class Klasa<br />

{<br />

int dane;<br />

...<br />

}<br />

a wewnątrz tej klasy metodę Pokazuj():<br />

class Klasa<br />

{<br />

int dane;<br />

public:<br />

void Pokazuj();<br />

...<br />

}<br />

void Klasa::Pokazuj(void)<br />

{<br />

cout ), jak poniżej, będzie równoważne:<br />

void Klasa::Pokazuj(void)<br />

{<br />

cout dane;<br />

}<br />

Przypomnijmy, że taka notacja wskaźnikowa oznacza:<br />

"Wyprowadź zawartość pola "dane" obiektu, na który wskazuje<br />

wskaźnik" (ponieważ jest to wskaźnik this, więc chodzi o własny<br />

obiekt).<br />

- 320-


LEKCJA 33. OVERLOADING OPERATORÓW.<br />

________________________________________________________________<br />

Podczas tej lekcji poznasz możliwości dostosowania operatorów<br />

<strong>C++</strong> do własnego "widzimisię" i do potrzeb własnych obiektów.<br />

________________________________________________________________<br />

Niemal od początku niniejszej książki korzystamy z operatorów<br />

poddanych overloadingowi. Są to operatory > , które<br />

pierwotnie wykonywały bitowe przesunięcie w lewo i w prawo.<br />

Owerloading tych operatorów "załatwił" za nas producent<br />

(<strong>Borland</strong>, Microsoft, czy inny). Jak widzisz, nie powoduje to w<br />

dalszym użytkowaniu tych operatorów żadnych zauważalnych<br />

komplikacji, a często ułatwia tworzenie programów. Zwróć uwagę,<br />

że overloading operatorów (jak i definicje klas) może znajdować<br />

się w dołączonych plikach nagłówkowych i po jednorazowym<br />

wykonaniu może być "niewidoczny" dla programistów tworzących<br />

programy aplikacyjne.<br />

Jeśli projektujemy (definiujemy) nową klasę, dodajemy do <strong>C++</strong><br />

nowy, lecz pełnoprawny typ danych. Autorzy <strong>C++</strong> nie byli w stanie<br />

przewidzieć jakie klasy i jakie obiekty mogą wymyślić kolejne<br />

pokolenia programistów w ramach swojej radosnej twórczości.<br />

Wprowadzili zatem do <strong>C++</strong> jasne i jednoznaczne algorytmy<br />

postępowania z typami "typowymi". <strong>C++</strong> doskonale wie jak dodawać,<br />

mnożyć, czy odejmować np. liczby int, long, float itp., nie wie<br />

jednak jak dodać do siebie obiekty klas CString (CString = Class<br />

String = klasa "łańcuch znaków"), TOdcinek (to taki kawałek<br />

prostej) itp.. A przecież miło byłoby, gdyby rozbudować<br />

działanie operatorów tak, by było możliwe ich typowe<br />

zastosowanie w stosunku do naszych własnych, "nietypowych"<br />

obiektów:<br />

int x, y; int z = x + y; //To operator + załatwia sam<br />

float x, y; float z = x + y;<br />

Zanim jednak stanie się możliwe postępowanie takie:<br />

class CString x, y, z; z = x + y;<br />

class Nasza_Klasa obiekt1, obiekt2, obiekt3;<br />

obiekt3 = obiekt1 + obiekt2;<br />

itp., itd. ...<br />

musimy "uzupełnić" <strong>C++</strong> i "wyjaśnić" operatorom, co właściwie ma<br />

w praktyce oznaczać operacja obiekt1 = obiekt2 + obiekt3; .<br />

Jest wyczuwalne intuicyjnie, że działanie operatorów w stosunku<br />

do różnych obiektów może być różne. Dla przykładu - wiesz<br />

zapewne, że inaczej wygląda algorytm mnożenia liczb zespolonych,<br />

a inaczej liczb całkowitych rzeczywistych. Dlatego też wykonanie<br />

- 321-


operacji mnożenia wymaga od operatora * podjęcia różnych<br />

działań:<br />

class Liczba_zespolona x, y, z; z = x * y;<br />

int x, y, z; z = x * y;<br />

Czasem może się zdarzyć, że dla dwu różnych klas działanie<br />

jakiegoś operatora jest identyczne, częściej jednak (i tak<br />

należy się spodziewać) działanie operatora dla każdej klasy<br />

będzie odrębne i unikalne.<br />

Pójdźmy w tym rozumowaniu o krok dalej. Skoro rozszerzenie<br />

obszaru zastosowań jakiegoś operatora na obiekty nowej<br />

(nieznanej wcześniej klasy) wymaga zdefiniowania nowego<br />

algorytmu działania operatora, <strong>C++</strong> będzie potrzebował do tego<br />

celu specjalnych środków, które powinny być łatwo rozpoznawalne.<br />

Do opisu algorytmów służą generalnie w <strong>C++</strong> funkcje i tu Autorzy<br />

nie wprowadzili wyjątku. Zastrzegli jednak dla tych specjalnych<br />

funkcji specjalną nazwę: operator ...();<br />

I tak funkcja precyzująca nowy algorytm dodawania (nowy sposób<br />

działania operatora + ) będzie się nazywać:<br />

operator+();<br />

a np. funkcja określająca nowy algorytm mnożenia (nowy sposób<br />

działania operatora * ) będzie się nazywać:<br />

operator*();<br />

Spróbujmy zastosować taką filozofię w praktyce programowania.<br />

[!!!] NIESTETY NIE WSZYSTKIE OPERATORY MOŻNA ROZBUDOWAĆ.<br />

________________________________________________________________<br />

Są w <strong>C++</strong> operatory, których nie możemy poddać overloadingowi. Są<br />

to:<br />

. :: .* ?:<br />

. operator kropki umożliwia dostęp do pól struktur i obiektów;<br />

:: operator "widoczności-przesłaniania" (ang. scope);<br />

.* wskazanie członka klasy (ang. pointer-to-member);<br />

?: operator warunkowy.<br />

________________________________________________________________<br />

Wszystkie pozostałe operatory możemy poddać overloadingowi i<br />

przypisywać im potrzebne nam działanie.<br />

OVERLOADING OPERATORA [+] (DWUARGUMENTOWEGO).<br />

- 322-


Zaczniemy od operatora + należącego do grupy "dwuargumentowych<br />

operatorów arytmetycznych" (ang. binary arithmetic operator).<br />

Zwracamy tu już na początku rozważań uwagę na przynależność<br />

operatora do określonej grupy, ponieważ overloading różnych<br />

opertorów należących do tej samej grupy przebiega podobnie.<br />

Ponieważ znak + może być także operatorem jednoargumentowym<br />

(ang. unary plus, o czym za chwilę), podkreślamy, że tym razem<br />

chodzi o plus jako operator dodawania. Overloading operatora<br />

przeprowadzimy w stosunku do obiektów prostej, znanej Ci już z<br />

poprzednich przykładów klasy Data, którą (w celu upodobnienia<br />

się do maniery stosowanej w Windows i bibliotekach klas)<br />

nazwiemy tym razem CData. "Namówimy" operator + do<br />

przeprowadzenia operacji na obiektach (dokładniej na polach<br />

obiektów):<br />

CData nowadata = staradata + 7;<br />

// W tydzien pozniej<br />

Operator + musi oczywiście "wiedzieć", na którym polu obiekty<br />

klasy CData przechowują liczbę dni i jak związane są (logicznie)<br />

pola obiektu dz, mc, rok. Jest rzeczą zrozumiałą, że samo<br />

dodanie dni do pola dz może nie wystarczyć, ponieważ data<br />

37.11.93 jest niedopuszczalna.<br />

Jeśli staradata jest obiektem klasy CData z zawartymi wewnątrz<br />

danymi, to w wyniku działania "nowego" operatora + powinien<br />

powstać obiekt nowadata klasy CData, którego pola zostaną w<br />

sensowny sposób powiększone o dodaną liczbę dni. Rozważ<br />

działanie programu (najlepiej skompiluj i uruchom).<br />

[P120.CPP]<br />

/* Overloading operatora dwuargumentowego + */<br />

# include <br />

class CData<br />

{<br />

int dz, mc, rok;<br />

public:<br />

CData() {} //Konstruktor domyslny (pusty)<br />

CData(int d, int m, int y) { mc = m; dz = d; rok = y; }<br />

void Pokazuj() { cout


{<br />

n -= TAB[kopia_obiektu.mc-1];<br />

if (++kopia_obiektu.mc == 13)<br />

{ kopia_obiektu.mc = 1; kopia_obiektu.rok++; }<br />

}<br />

kopia_obiektu.dz = n;<br />

return (kopia_obiektu);<br />

}<br />

main()<br />

{<br />

CData staradata(31, 1, 94); //Kostruktor z argumentami<br />

CData nowadata; //Pusty konstruktor<br />

cout > n;<br />

nowadata = staradata + n;<br />

cout


nowadata = staradata + 14;<br />

zostanie wykonana poprawnie.<br />

Ale to nie wszystko. Jeśli wystąpi układ odwrotny - np.:<br />

nowadata = 14 + staradata;<br />

nasz operator "zgłupieje". Doszedłszy do operatora + <strong>C++</strong> "nie<br />

będzie jeszcze wiedział" (analizuje wyrażenia arytmetyczne od<br />

lewej do prawej), KTÓRY obiekt wystąpi za chwilę. Jedno jest<br />

pewne, nie zawsze musi być to "własny" obiekt funkcji, do<br />

którego mamy pointer this. Aby uzyskać jednoznaczność sytuacji,<br />

funkcja operatorowa powinna tu w jawny sposób pobierać przed<br />

zadziałaniem dwa argumenty:<br />

CData operator+(int n, CData obiekt);<br />

aby działanie:<br />

CData obiekt_wynik;<br />

obiekt_wynik = n + obiekt;<br />

stało się wykonalne. Pojawia się tu wszakże pewien problem.<br />

Wskaźnik this wskazuje własny obiekt funkcji-metody, a tym razem<br />

funkcja potrzebuje dostępu nie do pola własnego obiektu, lecz do<br />

pola "obcego" obiektu przekazanego jej jako argument. Ale w <strong>C++</strong><br />

możemy:<br />

* zdefiniować dwie (i więcej) funkcji o tej samej nazwie (każda<br />

na inną ewentualność);<br />

* możemy nadać funkcji status friend (wtedy nie będąc metodą też<br />

uzyska dostęp do danych obiektu).<br />

Definicja naszej klasy CData zawierająca deklaracje dwu funkcji<br />

operatorowych operator+() różniących się zastosowaniem i (po<br />

czym rozpozna je <strong>C++</strong>) liczbą argumentów, będzie wyglądać tak:<br />

class CData<br />

{<br />

int dz, mc, rok;<br />

public:<br />

CData() {}<br />

CData(int d, int m, int y) { mc = m; dz = d; rok = y; }<br />

void Pokazuj() { cout


zawiera:<br />

* prywatne dane;<br />

* dwa konstruktory;<br />

* własną metodę - funkcję operatorową operator+();<br />

* deklarację zaprzyjaźnionej z klasą funkcji kategorii friend<br />

(choć jest to funkcja o tej samej nazwie, jej status i<br />

uprawnienia są nieco inne).<br />

[!!!] NIE WSZYSTKO, CO WEWNĄTRZ JEST METODĄ.<br />

________________________________________________________________<br />

Nawet, jeśli wewnątrz definicji klasy zdefiniujemy w pełni<br />

funkcję (nadając jej status inline), nie stanie się ona metodą!<br />

Słowo kluczowe friend określa status funkcji jednoznacznie, bez<br />

względu na to, w którym miejscu w tekście programu umieścimy<br />

definicję ciała funkcji.<br />

________________________________________________________________<br />

W zasadzie ciało funkcji jest na tyle proste (wymagamy od niej<br />

tylko zwrotu obiektu ze zmodyfikowanym polem danych), że możemy<br />

skorzystać z rozbudowanego wcześniej operatora + i całe ciało<br />

zdefiniować tak:<br />

class CData<br />

{<br />

int dz, mc, rok;<br />

public:<br />

...<br />

CData operator+(int);<br />

friend CData operator+(int n, CData& x) { return (x + n); }<br />

};<br />

Jeśli w operacji dodawania argumenty zastosujemy we<br />

wcześniejszej kolejności:<br />

return (obiekt + liczba);<br />

to zostanie tu wykorzystany operator + rozbudowany poprzednio<br />

przez metodę CData::operator+(int). Program w całości może<br />

zatem wyglądać tak:<br />

[P121.CPP]<br />

# include "iostream.h"<br />

class CData<br />

{<br />

int dz, mc, rok;<br />

public:<br />

CData() {}<br />

CData(int d, int m, int y) { mc = m; dz = d; rok = y; }<br />

void Pokazuj() { cout


static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31};<br />

CData CData::operator+(int n)<br />

{<br />

CData kopia_obiektu = *this;<br />

n += kopia_obiektu.dz;<br />

while (n > TAB[kopia_obiektu.mc-1])<br />

{<br />

n -= TAB[kopia_obiektu.mc-1];<br />

if (++kopia_obiektu.mc == 13)<br />

{ kopia_obiektu.mc = 1; kopia_obiektu.rok++; }<br />

}<br />

kopia_obiektu.dz = n;<br />

return (kopia_obiektu);<br />

}<br />

main()<br />

{<br />

CData staradata(31, 1, 94); //Kostruktor z argumentami<br />

CData nowadata, jeszczejednadata;<br />

cout > n;<br />

nowadata = staradata + n;<br />

cout -10<br />

Jest zatem --> 21.1.94<br />

Testuje nowy operator: 22.1.94<br />

lub tak:<br />

C:\>program<br />

Stara data: 31.1.94<br />

Podaj ile minelo dni --> -150<br />

- 327-


Jest zatem --> -119.1.94<br />

Testuje nowy operator: -118.1.94<br />

Funkcja operatorowa została napisana w taki sposób, że po<br />

przekroczeniu wartości -31 program będzie wypisywał bzdury. Jako<br />

zadanie domowe - spróbuj zmodyfikować algorytm w taki sposób, by<br />

rozszerzyć zakres poprawnych wartości.<br />

[!!!] Możesz dodawać obiekty minusem.<br />

________________________________________________________________<br />

* Należy tu zwrócić uwagę, że dodawanie obiektów może wykonywać<br />

nie tylko i nie koniecznie operator + . Jeśli zechcesz, możesz<br />

do tego celu zastosować dowolnie wybrany operator (np. -, *<br />

itp.). W celu ułatwienia zrozumienia zapisu (i tylko dlatego)<br />

większość programistów rozbudowuje działanie operatorów zgodnie<br />

z ich pierwotnym zastosowaniem.<br />

* DOWOLNOŚĆ, ALE NIE PEŁNA!<br />

O tyle, o ile działanie operatora może być zmienione, to ilość<br />

argumentów potrzebnych operatorowi pozostaje w <strong>C++</strong> "sztywna"<br />

(patrz przykład z n!).<br />

________________________________________________________________<br />

W bardzo podobny sposób możesz rozbudowywać inne arytmetyczne<br />

operatory dwuargumentowe (*, /, -, itp.) w stosunku także do<br />

innych klas.<br />

OVERLOADING OPERATORÓW JEDNOARGUMENTOWYCH ++ I -- .<br />

Typowe operatory jednoargumentowe to ++ i --. Jako przykładem<br />

posłużymy się problemem zlicznia znaków pobieranych ze<br />

strumienia wejściowego.<br />

Zaczniemy od redefinicji postinkrementacji licznika. Musimy<br />

zastosować funkcję operatorową. Funkcja, chcąc operować na<br />

obiektach musi w stosunku do tych obiektów posiadać status<br />

friend, lub być metodą. Prototyp funkcji operatorowej potrzebnej<br />

do wykonania overloadingu operatora jednoargumentowego ++<br />

wygląda w postaci ogólnej tak:<br />

typ_zwracany nazwa_klasy::operator++(lista argumentów);<br />

Funkcje operatorowe zwracają zwykle wartość zgodną co do typu z<br />

typem obiektów z którymi współpracują. Jeśli identyfikatory b, c<br />

i d reprezentują obiekty, nic nie stoi na przeszkodzie, by stał<br />

się możliwy zapis:<br />

class Klasa<br />

{<br />

...<br />

} x, y, z;<br />

...<br />

- 328-


z = x + y;<br />

Dodajemy dwa obiekty x i y tego samego typu (tej samej klasy), a<br />

wynik przypisujemy obiektowi z, który także jest obiektem tego<br />

samego typu. Jeśli możnaby jeszcze zastosować operator<br />

przypisania tak:<br />

z = q = x + y;<br />

operator przypisania = zwracałby nam w efekcie obiekt tego<br />

samego typu. Funkcje operatorowe muszą przestrzegać tych samych<br />

zasad, które obowiązują wyrażenia: typ argumentów x, y, z, q,<br />

... powinien być zgodny, rezultat operacji (x + y) powinien być<br />

obiektem tego samego typu, co obiekty x, y, z, q. Dokonując<br />

overloadingu operatorów powinniśmy precyzyjnie określić typ<br />

wartości zwracanej w wyniku działania operatora.<br />

Stosowaną poprzednio do inkrementacji liczników metodę<br />

Skok_licznika() zastąpimy w definicji klasy funkcją operatorową:<br />

class Licznik<br />

{<br />

public:<br />

char moja_litera;<br />

int ile;<br />

Licznik(char);<br />

Licznik operator++();<br />

};<br />

Powinniśmy teraz zdefiniować funkcję operatorową. Ponieważ pole<br />

obiektu, które zamierzamy inkrementować nazywa się:<br />

obiekt.ile<br />

// Licznik::ile;<br />

funkcja powinna zadziałać tak:<br />

Licznik Licznik::operator++(void)<br />

{<br />

this->ile++;<br />

return (*this);<br />

}<br />

Przetłumaczmy tę notację na "ludzki język". Funkcja operatorowa:<br />

* nie pobiera żadnych jawnych argumentów (void);<br />

* jest metodą, zatem w momencie wywołania otrzymuje w niejawny<br />

sposób wskaźnik *this do "własnego" obiektu;<br />

* posługując się wsakźnikiem this inkrementuje zawartość pola<br />

int ile własnego obiektu;<br />

* zwraca obiekt (zmodyfikowany) klasy Licznik (tj. dokładniej -<br />

zwraca wskaźnik this do własnego-zmodyfikowanego obiektu.<br />

- 329-


Ponieważ funkcja operatorowa jest metodą zadeklarowaną wewnątrz<br />

klasy, bez problemu uzyska dostęp do wewnętrznych pól obiektów<br />

tej klasy i wykona inkrementację licznika. Możemy zatem<br />

zastosować wyrażenie typu:<br />

Licznik licznik_a;<br />

licznik_a++;<br />

Funkcja jest metodą wraz ze wszystkimi właściwymi metodom<br />

przywilejami. Zapis możemy zatem uprościć do postaci:<br />

Licznik Licznik::operator++(void)<br />

{<br />

ile++;<br />

return (*this);<br />

}<br />

a tak skrócone ciało funkcji umieścić w definicji klasy obok<br />

definicji konstruktora:<br />

class Licznik<br />

{<br />

public:<br />

char moja_litera;<br />

int ile;<br />

Licznik(char z) { ile = 0; moja_litera = z; }<br />

Licznik operator++() { ile++; return (this); }<br />

};<br />

Aby nie zaciemniać obrazu, przy pomocy licznika będziemy tym<br />

razem zliczać wszystkie znaki za wyjątkiem kropki. Ponieważ<br />

licznik nie będzie miał swojej ulubionej litery, możemy<br />

zastosować pusty konstruktor.<br />

[P121.CPP]<br />

/* --------------------- POST - inkrementacja ----------- */<br />

# include "iostream.h"<br />

class Licznik<br />

{<br />

public:<br />

int ile;<br />

Licznik() { ile = 0;}<br />

Licznik operator++() { ile++; return (*this); }<br />

} obiekt;<br />

void main()<br />

{<br />

cout > znak;<br />

if(znak == '.') break;<br />

obiekt++;<br />

}<br />

- 330-


cout


class Licznik<br />

{<br />

public:<br />

char moja_litera;<br />

int ile;<br />

Licznik() { ile = 0; } //Pusty konstruktor<br />

Licznik(char);<br />

Licznik operator++(); //Funkcja pre/post-inkrementacji<br />

Licznik operator--(); //Funkcja pre/post-dekrementacji<br />

};<br />

Licznik::Licznik(char z) { moja_litera = z; ile = 10; }<br />

Licznik Licznik::operator++(void) { ile++; return *this; }<br />

Licznik Licznik::operator--(void) { ile--; return *this; }<br />

void main()<br />

{<br />

Licznik obiekt1('A'), obiekt2;<br />

//obiekt2 - "pusty"<br />

cout


{<br />

public:<br />

long wartosc;<br />

Liczba(int x) { wartosc = (long) x; }<br />

friend void operator!(Liczba&);<br />

};<br />

void operator!(Liczba& obiekt)<br />

{<br />

long wynik = 1;<br />

for(int i = 1; i


cout


overloadingowi operator


LEKCJA 34. O ZASTOSOWANIU DZIEDZICZENIA.<br />

________________________________________________________________<br />

Z tej lekcji dowiesz się, do czego w praktyce programowania<br />

szczególnie przydaje się dziedziczenie.<br />

________________________________________________________________<br />

Dzięki dziedziczeniu programista może w pełni wykorzystać gotowe<br />

biblioteki klas, tworząc własne klasy i obiekty, jako klasy<br />

pochodne wazględem "fabrycznych" klas bazowych. Jeśli bazowy<br />

zestw danych i funkcji nie jest adekwatny do potrzeb, można np.<br />

przesłonić, rozbudować, bądź przebudować bazową metodę dzięki<br />

elastyczności <strong>C++</strong>. Zdecydowana większość standardowych klas<br />

bazowych wyposażana jest w konstruktory. Tworząc klasę pochodną<br />

powinniśmy pamiętać o istnieniu konstruktorów i rozumieć sposoby<br />

przekazywania argumentów obowiązujące konstruktory w przypadku<br />

bardziej złożonej struktury klas bazowych-pochodnych.<br />

PRZEKAZANIE PARAMETRÓW DO WIELU KONSTRUKTORÓW.<br />

Klasy bazowe mogą być wyposażone w kilka wersji konstruktora.<br />

Dopóki nie przekażemy konstruktorowi klasy bazowej żadnych<br />

argumentów - zostanie wywołany (domyślny) pusty konstruktor i<br />

klasa bazowa będzie utworzona z parametrami domyślnymi. Nie<br />

zawsze jest to dla nas najwygodniejsza sytuacja.<br />

Jeżeli wszystkie, bądź choćby niektóre z parametrów, które<br />

przekazujemy konstruktorowi obiektu klasy pochodnej powinny<br />

zostać przekazane także konstruktorowi (konstruktorom) klas<br />

bazowych, powinniśmy wytłumaczyć to <strong>C++</strong>. Z tego też powodu,<br />

jeśli konstruktor jakiejś klasy ma jeden, bądź więcej<br />

parametrów, to wszystkie klasy pochodne względem tej klasy<br />

bazowej muszą posiadać konstruktory. Dla przykładu dodajmy<br />

konstruktor do naszej klasy pochodnej Cpochodna:<br />

class CBazowa1<br />

{<br />

public:<br />

CBazowa1(...);<br />

};<br />

class CBazowa2<br />

{<br />

public:<br />

CBazowa2(...);<br />

};<br />

//Konstruktor<br />

//Konstruktor<br />

class Cpochodna : public CBazowa1, CBazowa2<br />

{<br />

public:<br />

Cpochodna(...); //Konstruktor<br />

};<br />

//Lista klas<br />

- 336-


main()<br />

{<br />

Cpochodna Obiekt(...);<br />

...<br />

//Wywolanie konstruktora<br />

W momencie wywołania kostruktora obiektu klasy pochodnej<br />

Cpochodna() przekazujemy kostruktorowi argumenty. Możemy (jeśli<br />

chcemy, nie koniecznie) przekazać te argumenty konstruktorom<br />

"wcześniejszym" - konstruktorom klas bazowych. Ta możliwość<br />

okazuje się bardzo przydatna (niezbędna) w środowisku obiektowym<br />

- np. OWL i TVL. Oto prosty przykład definiowania konstruktora w<br />

przypadku dziedziczenia. Rola konstruktorów będzie polegać na<br />

trywialnej operacji przekazania pojedynczego znaku.<br />

class CBazowa1<br />

{<br />

public:<br />

CBazowa1(char znak) { cout


gdzie:<br />

lista - oznacza listę parametrów odpowiedniego konstruktora.<br />

W takiej sytuacji na liście argumentów konstruktorów klas<br />

bazowych mogą znajdować się także wyrażenia, przy założeniu, że<br />

elementy tych wyrażeń są widoczne i dostępne (np. globalne<br />

stałe, globalne zmienne, dynamicznie inicjowane zmienne globalne<br />

itp.). Konstruktory będą wykonywane w kolejności:<br />

CBazowa1 --> CBazowa2 --> Cpochodna<br />

Dzięki tym mechanizmom możemy łatwo przekazywać argumenty<br />

"wstecz" od konstruktorów klas pochodnych do konstruktorów klas<br />

bazowych.<br />

FUNKCJE WIRTUALNE.<br />

Działanie funkcji wirtualnych przypomina rozbudowę funkcji<br />

dzięki mechanizmowi overloadingu. Jeśli, zdefiniowaliśmy w<br />

klasie bazowej funkcję wirtualną, to w klasie pochodnej możemy<br />

definicję tej funkcji zastąpić nową definicją. Przekonajmy się o<br />

tym na przykładzie. Zacznijmy od zadeklarowania funkcji<br />

wirtualnej (przy pomocy słowa kluczowego virtual) w klasie<br />

bazowej. Zadeklarujemy jako funkcję wirtualną funkcję oddychaj()<br />

w klasie CZwierzak:<br />

class CZwierzak<br />

{<br />

public:<br />

void Jedz();<br />

virtual void Oddychaj();<br />

};<br />

Wyobraźmy sobie, że chcemy zdefiniować klasę pochodną CRybka<br />

Rybki nie oddychają w taki sam sposób, jak inne obiekty klasy<br />

CZwierzak. Funkcję Oddychaj() trzeba zatem będzie napisać w dwu<br />

różnych wariantach. Obiekt Ciapek może tę funkcję odziedziczyć<br />

bez zmian i sapać spokojnie, z Sardynką gorzej:<br />

class CZwierzak<br />

{<br />

public:<br />

void Jedz();<br />

virtual void Oddychaj() { cout


char imie[30];<br />

public:<br />

void Oddychaj() { cout


wirtualnej w taki sposób, funkcja we wszystkich "pokoleniach"<br />

musi mieć taki sam prototyp, tj. pobierać taką samą liczbę<br />

parametrów tych samych typów oraz zwracać wartość tego samego<br />

typu. Jeśli tak się nie stanie, <strong>C++</strong> potraktuje różne prototypy<br />

tej samej funkcji w kolejnych pokoleniach zgodnie z zasadami<br />

overloadingu funkcji. Zwróćmy tu uwagę, że w przypadku funkcji<br />

wirtualnych o wyborze wersji funkcji decyduje to, wobec którego<br />

obiektu (której klasy) funkcja została wywołana. Jeśli wywołamy<br />

funkcję dla obiektu Ciapek, <strong>C++</strong> wybierze wersję<br />

CZwierzak::Oddychaj(), natomiast wobec obiektu Sardynka zostanie<br />

zastosowana wersja CRybka::Oddychaj().<br />

W <strong>C++</strong> wskaźnik do klasy bazowej może także wskazywać na klasy<br />

pochodne, więc zastosowanie funkcji wirtualnych może dać pewne<br />

ciekawe efekty "uboczne". Jeśli zadeklarujemy wskaźnik *p do<br />

obiektów klasy bazowej CZwierzak *p; a następnie zastosujemy ten<br />

sam wskaźnik do wskazania na obiekt klasy pochodnej:<br />

p = &Ciapek; p->Oddychaj();<br />

...<br />

p = &Sardynka; p->Oddychaj();<br />

zarządamy w taki sposób od <strong>C++</strong> rozpoznania właściwej wersji<br />

wirtualnej metody Oddychaj() i jej wywołania we właściwym<br />

momencie. <strong>C++</strong> może rozpoznać, którą wersję funkcji należałoby<br />

zastosować tylko na podstawie typu obiektu, wobec którego<br />

funkcja została wywołana. I tu pojawia się pewien problem.<br />

Kompilator wykonując kompilcję programu nie wie, co będzie<br />

wskazywał pointer. Ustawienie pointera na konkretny adres<br />

nastąpi dopiero w czasie wykonania programu (run-time).<br />

Kompilator "wie" zatem tylko tyle:<br />

p->Oddychaj()(); //która wersja Oddychaj() ???<br />

Aby mieć pewność, co w tym momencie będzie wskazywał pointer,<br />

kompilator musiałby wiedzieć w jaki sposób będzie przebiegać<br />

wykonanie programu. Takie wyrażenie może zostać wykonane "w<br />

ruchu programu" dwojako: raz, gdy pointer będzie wskazywał<br />

Ciapka (inaczej), a drugi raz - Sardynkę (inaczej):<br />

CZwierzak *p;<br />

...<br />

for(p = &Ciapek, int i = 0; i < 2; i++)<br />

{<br />

p->Oddychaj();<br />

p = &Sardynka;<br />

}<br />

lub inaczej:<br />

if(p == &Ciapek) CZwierzak::Oddychaj();<br />

else CRybka::Oddychaj();<br />

- 340-


Taki efekt nazywa się polimorfizmem uruchomieniowym (ang.<br />

run-time polymorphism).<br />

Overloading funkcji i operatorów daje efekt tzw. polimorfizmu<br />

kompilacji (ang. compile-time), to funkcje wirtualne dają efekt<br />

polimorfizmu uruchomieniowego (run-time). Ponieważ wszystkie<br />

wersje funkcji wirtualnej mają taki sam prototyp, nie ma innej<br />

metody stwierdzenia, którą wersję funkcji należy zastosować.<br />

Wybór właściwej wersji funkcji może być dokonany tylko na<br />

podstawie typu obiektu, do którego należy wersja funkcji-metody.<br />

Różnica pomiędzy polimorfizmem przejawiającym się na etapie<br />

kompilacji i poliformizmem przejawiającym się na etapie<br />

uruchomienia programu jest nazywana również wszesnym albo póżnym<br />

polimorfizmem (ang. early/late binding). W przypadku wystąpienia<br />

wczesnego polimorfizmu (compile-time, early binding) <strong>C++</strong> wybiera<br />

wersję funkcji (poddanej overloadingowi) do zastosowania już<br />

tworząc plik .OBJ. W przypadku późnego polimorfizmu (run-time,<br />

late binding) <strong>C++</strong> wybiera wersję funkcji (poddanej przesłanianiu<br />

- overriding) do zastosowania po sprawdzeniu bieżącego kontekstu<br />

i zgodnie z bieżącym wskazaniem pointera.<br />

Przyjrzyjmy się dokładniej zastosowaniu wskaźników do obiektów w<br />

przykładowym programie. Utworzymy hierarchię złożoną z klasy<br />

bazowej i pochodnej w taki sposób, by klasa pochodna zawierała<br />

jakiś unikalny element - np. nie występującą w klasie bazowej<br />

funkcję.<br />

class CZwierzak<br />

{<br />

public:<br />

void Jedz();<br />

virtual void Oddychaj() {cout


taki element klasy pochodnej, który nie został odziedziczony i<br />

którego nie ma w klasie bazowej? Rozwiązanie jest proste -<br />

wystarczy zarządać od <strong>C++</strong>, by chwilowo zmienił typ wskaźnika z<br />

obiektów klasy bazowej na obiekty klasy pochodnej. W przypadku<br />

funkcji Szczekaj() w naszym programie wyglądałoby to tak:<br />

CZwierzak *p;<br />

...<br />

p->Oddychaj();<br />

p->Szczekaj(); //ŹLE !<br />

(CPiesek*)p->Szczekaj(); //Poprawnie<br />

...<br />

Dzięki funkcjom wirtualnym tworząc klasy bazowe pozwalamy<br />

późniejszym użytkownikom na rozbudowę funkcji-metod w<br />

najwłaściwszy ich zdaniem sposób. Dzięki tej "nieokreśloności"<br />

dziedzicząc możemy przejmować z klasy bazowej tylko to, co nam<br />

odpowiada. Funkcje w <strong>C++</strong> mogą być jeszcze bardziej<br />

"nieokreślone" i rozbudowywalne. Nazywają się wtedy funkcjami w<br />

pełni wirtualnymi.<br />

- 342-


LEKCJA 35. FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, co mawia żona programisty, gdy<br />

nie chce być obiektem klasy abstrakcyjnej.<br />

________________________________________________________________<br />

FUNKCJE W PEŁNI WIRTUALNE (PURE VIRTUAL).<br />

W skrajnych przypadkach wolno nam umieścić funkcję wirtualną w<br />

klasie bazowej nie definiując jej wcale. W klasie bazowej<br />

umieszczamy wtedy tylko deklarację-prototyp funkcji. W<br />

następnych pokoleniach klas pochodnych mamy wtedy pełną swobodę<br />

i możemy zdefiniować funkcję wirtualną w dowolny sposób -<br />

adekwatny dla potrzeb danej klasy pochodnej. Możemy np. do klasy<br />

bazowej (ang. generic class) dodać prototyp funkcji wirtualnej<br />

funkcja_eksperymentalna() nie definiując jej w (ani wobec)<br />

klasie bazowej. Sens umieszczenia takiej funkcji w klasie<br />

bazowej polege na uzyskaniu pewności, iż wszystkie klasy<br />

pochodne odziedziczą funkcję funkcja_eksperymentalna(), ale<br />

każda z klas pochodnych wyposaży tę funkcję we własną definicję.<br />

Takie postępowanie może okazać się szczególnie uzasadnione przy<br />

tworzeniu biblioteki klas (class library) przeznaczonej dla<br />

innych użytkowników. <strong>C++</strong> w wersji instalacyjnej posiada już<br />

kilka gotowych bibliotek klas. Funkcje wirtuale, które nie<br />

zostają zdefiniowane - nie posiadają zatem ciała funkcji -<br />

nazywane są funkcjami w pełni wirtualnymi (ang. pure virtual<br />

function).<br />

O KLASACH ABSTRAKCYJNYCH.<br />

Jeśli zadeklarujemy funkcję CZwierzak::Oddychaj() jako funkcję w<br />

pełni wirtualną, oprócz słowa kluczowego virtual, trzeba tę<br />

informację w jakiś sposób przekazać kompilatorowi <strong>C++</strong>. Aby <strong>C++</strong><br />

wiedział, że naszą intencją jest funkcja w pełni wirtalna, nie<br />

możemy zadeklarować jej tak:<br />

class CZwierzak<br />

{<br />

...<br />

public:<br />

virtual void Oddychaj();<br />

...<br />

};<br />

a następnie pominąć definicję (ciało) funkcji. Takie<br />

postępowanie <strong>C++</strong> uznałby za błąd, a funkcję - za zwykłą funkcję<br />

wirtualną, tyle, że "niedorobioną" przez programistę. Naszą<br />

intencję musimy zaznaczyć już w definicji klasy w taki sposób:<br />

class CZwierzak<br />

{<br />

- 343-


...<br />

public:<br />

virtual void Oddychaj() = 0;<br />

...<br />

};<br />

Informacją dla kompilatora, że chodzi nam o funkcję w pełni<br />

wirtualną, jest dodanie po prototypie funkcji "= 0". Definiując<br />

klasę pochodną możemy rozbudować funkcję wirtualną np.:<br />

class CZwierzak<br />

{<br />

...<br />

public:<br />

virtual void Oddychaj() = 0;<br />

...<br />

};<br />

class CPiesek : public CZwierzak<br />

{<br />

...<br />

public:<br />

void Oddychaj() { cout


}<br />

W tym pokoleniu definicja wirtualnej metody Mow() mogłaby<br />

wyglądać np. tak:<br />

void Zona::Mow(void)<br />

{<br />

cout


eturn 0;<br />

}<br />

Przykładowa klasa CZŁOWIEK jest klasą ABSTRAKCYJNĄ. Jeśli<br />

spróbujesz dodać do powyższego programu np.:<br />

CZLOWIEK Facet;<br />

Facet.Jedz();<br />

uzyskasz komunikat o błędzie:<br />

Cannot create a variable for abstract class "CZLOWIEK"<br />

(Nie mogę utworzyć zmiennych dla klasy abstrakcyjnej "CZLOWIEK"<br />

[???] KLASY ABSTRAKCYJNE.<br />

________________________________________________________________<br />

* Po klasach abstrakcyjnych MOŻNA dziedziczyć!<br />

* Obiektów klas abstrakcyjnych NIE MOŻNA stosować bezpośrednio!<br />

________________________________________________________________<br />

Ponieważ wyjaśniliśmy, dlaczego klasy są nowymi typami danych,<br />

więc logika (i sens) innej rozpowszechnionej nazwy klas<br />

abstrakcyjnych - ADT - Abstract Data Type (Abstrakcyjne Typy<br />

Danych) jest chyba zrozumiała i oczywista.<br />

ZAGNIEŻDŻANIE KLAS I OBIEKTÓW.<br />

Może się np. zdarzyć, że klasa stanie się wewnętrznym elementem<br />

(ang. member) innej klasy i odpowiednio - obiekt - elementem<br />

innego obiektu. Nazywa się to fachowo "zagnieżdżaniem" (ang.<br />

nesting). Jeśli, dla przykładu klasa CB będzie zawierać obiekt<br />

klasy CA:<br />

class CA<br />

{<br />

int liczba;<br />

public:<br />

CA() { liczba = 0; }<br />

CA(int x) { liczba = x; }<br />

//Konstruktor domyslny<br />

void operator=(int n) { liczba = n }<br />

};<br />

class CB<br />

{<br />

CA obiekt;<br />

public:<br />

CB() { obiekt = 1; }<br />

};<br />

Nasze klasy wyposażyliśmy w konstruktory i od razu poddaliśmy<br />

overloadingowi operator przypisania = . Aby prześledzić<br />

kolejność wywoływania funkcji i sposób przekazywania parametrów<br />

pomiędzy tak powiązanymi obiektami rozbudujemy każdą funkcję o<br />

- 346-


zgłoszenie na ekranie.<br />

class CA<br />

{<br />

int liczba;<br />

public:<br />

CA() { liczba = 0; cout program<br />

-> CA(), CA_O::liczba = 0 ->operator ->Konstruktor CB()<br />

Skoro oprócz zainicjowania obiektu klasy pochodnej nie robimy w<br />

programie dokładnie nic, nie dziwmy się ostrzeżeniu<br />

- 347-


Warning: Obiekt is never used...<br />

Jest to sytuacja trochę podobna do komunikacji pomiędzy<br />

konstruktorami klas bazowych i pochodnych. Jeśli zaprojektujemy<br />

prostą strukturę klas:<br />

class CBazowa<br />

{<br />

private:<br />

int liczba;<br />

public:<br />

CBazowa() { liczba = 0}<br />

CBazowa(int n) { liczba = n; }<br />

};<br />

class CPochodna : public CBazowa<br />

{<br />

public:<br />

CPochodna() { liczba = 0; }<br />

CPochodna(int x) { liczba = x; }<br />

};<br />

problem przekazywania parametrów między konstruktorami klas<br />

możemy w <strong>C++</strong> rozstrzygnąć i tak:<br />

class CPochodna : public CBazowa<br />

{<br />

public:<br />

CPochodna() : CBazowa(0) { liczba = 0; }<br />

CPochodna(int x) { liczba = x; }<br />

};<br />

Będzie to w praktyce oznaczać wywołanie konstruktora klasy<br />

bazowej z przekazanym mu argumentem 0. Podobnie możemy postąpić<br />

w stosunku do klas zagnieżdżonych:<br />

[P130.CPP]<br />

#include "iostream.h"<br />

class CA<br />

{<br />

int liczba;<br />

public:<br />

CA() { liczba = 0; cout


};<br />

main()<br />

{<br />

CB Obiekt;<br />

return 0;<br />

}<br />

Eksperymentując z dwoma powyższymi programami możesz przekonać<br />

się, jak przebiega przekazywanie parametrów pomiędzy<br />

konstruktorami i obiektami klas bazowych i pochodnych.<br />

JESZCZE RAZ O WSKAŹNIKU *this.<br />

Szczególnie ważnym wskaźnikiem przy tworzeniu klas pochodnych i<br />

funkcji operatorowych może okazać się pointer *this. Oto<br />

przykład listy.<br />

[P131.CPP]<br />

# include "string.h"<br />

# include "iostream.h"<br />

class CLista<br />

{<br />

private:<br />

char *poz_listy;<br />

CLista *poprzednia;<br />

public:<br />

CLista(char*);<br />

CLista* Poprzednia() { return (poprzednia); };<br />

void Pokazuj() { cout


char TAB[70];<br />

cin >> TAB;<br />

if (strncmp(TAB, ".", 1) == 0) break;<br />

CLista *lista = new CLista(TAB);<br />

if (ostatni != NULL)<br />

ostatni->Dodaj(*lista);<br />

ostatni = lista;<br />

}<br />

for(; ostatni != NULL;)<br />

{<br />

ostatni->Pokazuj();<br />

CLista *temp = ostatni;<br />

ostatni = ostatni->Poprzednia();<br />

delete (temp);<br />

}<br />

return 0;<br />

}<br />

Z reguły to kompilator nadaje wartość wskaźnikowi this i to on<br />

automatycznie dba o przyporządkowanie pamięci obiektom. Pointer<br />

this jest zwykle inicjowany w trakcie działania konstruktora<br />

obiektu.<br />

- 350-


LEKCJA 36. KAŹDY DYSK JEST ZA MAŁY, A KAŹDY PROCESOR ZBYT<br />

WOLNY...<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak komputer dysponuje swoimi<br />

zasobami w środowisku tekstowym (DOS).<br />

________________________________________________________________<br />

Truizmy użyte w tytule mają znaczyć, że "zasoby najlepszego<br />

nawet komputera są ograniczone" i zwykle okazują się<br />

wystarczające tylko do pewnego momentu. Najbardziej newralgiczne<br />

zasoby to:<br />

* czas mikroprocesora i<br />

* miejsce w pamięci operacyjnej.<br />

Tworzone przez nas programy powinny wystrzegać się zatem<br />

najcięższych grzechów:<br />

* nie pozwalać mikroprocesorowi na słodkie nieróbstwo;<br />

Rzadko uzmysławiamy sobie, że oczekiwanie na naciśnięcie<br />

klawisza przez użytkownika (czasem po przeczytaniu napisu na<br />

ekranie) trwa sekundy (1, 2, .... czasem 20), a każda sekunda<br />

lenistwa PC to stracone miliony cykli mikroprocesora.<br />

* oszczędnie korzystać z pamięci dyskowej, a szczególnie<br />

oszczędnie z pamięci operacyjnej RAM.<br />

MODELE PAMIĘCI IBM PC.<br />

Jak zapewne wiesz, Twój PC może mieć:<br />

* pamięć ROM (tylko do odczytu),<br />

* konwencjonalną pamięć RAM (640 KB),<br />

* pamięć rozszerzoną EMS i XMS,<br />

* pamięć karty sterownika graficznego ekranu (np. SVGA-RAM),<br />

* pamięć Cache dla buforowania operacji dyskowych.<br />

Najczęściej stosowane modele pamięci to:<br />

* Small - mały,<br />

* Medium - średni,<br />

* Compact - niewielki (tu mam wątpliwość, może "taki sobie" ?),<br />

* Large - duży,<br />

* Huge - jeszcze większy, odległy.<br />

Dodatkowo może wystąpić<br />

* Tiny - najmniejszy.<br />

Taki podział został spowodowany segmentacją pamięci komputera<br />

przez procesory Intel 8086 i podziałem pamięci na bloki o<br />

wielkości 64 KB. Model Small (Tiny, jeśli jest) jest najszybszy,<br />

- 351-


ale najmniej pojemny. Model Huge - odwrotnie - najpojemniejszy,<br />

za to najwolniejszy. Model Tiny powoduje ustawienia wszystkich<br />

rejestrów segmentowych mikroprocesora na tę samą wartość<br />

(początek tej samej stronicy pamięci) i umieszczenie wszystkich<br />

zasobów programu wewnątrz wspólnego obszaru pamięci o wielkości<br />

nie przekraczającej 64 KB. Wszystkie skoki są wtedy "krótkie", a<br />

wszystkie pointery (adresy) 16-bitowe. Kompilacja z<br />

zastosowaniem modelu Tiny pozwala uzyskać program wykonywalny w<br />

wersji *.COM (a nie *.EXE). Ale niestety nie wszystkie programy<br />

mieszczą się w 64 KB. W modelu Small segment kodu jest jeden<br />

(kod max. 64 K) i segment danych też tylko jeden (dane max. 64<br />

K), ale są to już dwa różne segmenty. Zestawienia<br />

najważniejszych parametrów poszczególnych modeli pamięci<br />

przedstawia tabelka poniżej:<br />

Modele pamięci komputera IBM PC.<br />

________________________________________________________________<br />

Model Segment kodu Segment danych *dp *cp<br />

________________________________________________________________<br />

Tiny 1 1 (CS = DS) 16 bit 16 bit<br />

Small 1 1 16 bit 16 bit<br />

Medium wiele 1 16 bit 32 bit<br />

Compact 1 wiele 32 bit 16 bit<br />

Large wiele wiele 32 bit 32 bit<br />

Huge wiele wiele 32 bit 32 bit<br />

________________________________________________________________<br />

*dp - data pointer - wskaźnik do danych (near/far)<br />

*cp - code pointer - wskaźnik do kodu.<br />

Large - kod + dane = max. 1 MB.<br />

Huge - kod = max. 1 MB, wiele segmentów danych po 64 K każdy.<br />

Wynikające z takich modeli pamięci kwalifikatory near, far, huge<br />

dotyczące pointerów w <strong>C++</strong> nie są akceptowane przez standard ANSI<br />

C (ponieważ odnoszą się tylko do IBM PC i nie mają charakteru<br />

uniwersalnego). Trzeba tu zaznaczyć, że typ wskaźnika jest przez<br />

kompilator przyjmowany domyślnie (automatycznie) zgodnie z<br />

wybranym do kompilacji modelem pamięci. Jeśli poruszamy się<br />

wewnątrz niewielkiego obszaru pamięci, możesz "forsować" bliższy<br />

typ pointera, przyspieszając tym samym działanie programów:<br />

huge *p;<br />

...<br />

near *ptr;<br />

...<br />

near int Funkcja(...)<br />

{<br />

...<br />

//Bliski pointer<br />

//Bliska funkcja<br />

- 352-


}<br />

#define ILE (1024*640)<br />

near unsigned int Funkcja(void)<br />

{<br />

huge char *ptr; // tu długi pointer jest niezbędny<br />

long suma = 0;<br />

for (p = 0; p < ILE; p++) suma += *p;<br />

return (suma);<br />

}<br />

Zarówno zadeklarowanie funkcji jako bliskiej (near), jak i jako<br />

statycznej (static) powoduje wygenerowanie uproszczonej<br />

sekwencji wywołania funkcji przez kompilator. Daje to w efekcie<br />

mniejszy i szybszy kod wynikowy.<br />

IDENTYFIKACJA KLAWISZY.<br />

Znane Ci z pliku i "klasyczne" funkcje<br />

obsługi konsoli mają pewne zalety. Korzystanie z klasycznych,<br />

nieobiektowych mechanizmów powoduje z reguły wygenerowanie<br />

znacznie krótszego kodu wynikowego. Funkcje scanf() i gets()<br />

wymagają wciśnięcia klawisza [Enter]. Dla szybkiego dialogu z<br />

komputerem znacznie bardziej nadają się szybsze getch() i<br />

kbhit(). Ponieważ klawiatura zawiera także klawisze specjalne<br />

(F1 ... F10, [Shift], [Del], itp.), pełną informację o stanie<br />

klawiatury można uzyskać za pośrednictwem funkcji bioskey(),<br />

korzystającej z przerywania BIOS Nr 16. Oto krótki przykład<br />

zastosowania funkcji bioskey():<br />

#include "bios.h"<br />

#include "ctype.h"<br />

#include "stdio.h"<br />

#include "conio.h"<br />

# define CTRL 0x04<br />

# define ALT 0x08<br />

# define RIGHT 0x01<br />

# define LEFT 0x02<br />

int klawisz, modyfikatory;<br />

void main()<br />

{<br />

clrscr();<br />

printf("Funkcja zwraca : %d", bioskey(1));<br />

printf("\n Nacisnij klawisz ! \n");<br />

while (!bioskey(1));<br />

printf("Funkcja zwrocila: %c", bioskey(1));<br />

printf("\nKod: %d", (char)bioskey(1));<br />

...<br />

A to jeszcze inny sposób korzystania z tej bardzo przydatnej<br />

funkcji, tym razem z innymi parametrami:<br />

/* Funkcja z parametrem (0) zwraca kod klawisza: ------ */<br />

- 353-


klawisz = bioskey(0);<br />

/* Funkcja sprawdza stan klawiszy specjalnych --------- */<br />

modyfikatory = bioskey(2);<br />

if (modyfikatory)<br />

{<br />

printf("\n");<br />

if (modyfikatory & RIGHT) printf("RIGHT");<br />

if (modyfikatory & LEFT) printf("LEFT");<br />

if (modyfikatory & CTRL) printf("CTRL");<br />

if (modyfikatory & ALT) printf("ALT");<br />

printf("\n");<br />

}<br />

/* drukujemy pobrany klawisz */<br />

if (isalnum(klawisz & 0xFF))<br />

printf("'%c'\n", klawisz);<br />

else<br />

printf("%#02x\n", klawisz);<br />

}<br />

Należy tu zwrócić uwagę, że funkcje kbhit() i bioskey() nie<br />

dokonują czyszczenia bufora klawiatury. Identyfikują znak<br />

(znaki) w buforze, ale pozostawiają bufor w stanie niezmienionym<br />

do dalszej obróbki. Zwróć uwagę, że funkcja getch() może<br />

oczekiwać na klawisz w nieskończoność. Sprawdzić szybciej, czy<br />

użytkownik nacisnął już cokolwiek możesz np. tak:<br />

if (kbhit()) ...; if (!kbhit()) ...;<br />

while (!bioskey(1)) ... if (bioskey(1)) ...;<br />

Inną wielce przydatną "szybką" funkcją jest getch(). Oto<br />

praktyczny przykład pobierania i testowania naciśniętych<br />

klawiszy klawiatury.<br />

[P131.CPP]<br />

# include "stdio.h"<br />

# include "conio.h"<br />

char z1, z2;<br />

void Odczyt(void)<br />

{<br />

z2 = '\0';<br />

z1 = getch();<br />

if (z1 == '\0') z2 = getch();<br />

}<br />

main()<br />

{<br />

clrscr();<br />

- 354-


printf("\nKropka [.] = Quit");<br />

printf("\nRozpoznaje klawisze [F1] ... [F3] \n\n");<br />

for (;;)<br />

{<br />

while(!kbhit());<br />

Odczyt();<br />

if (z1 == '.') break;<br />

if (z1 != '\0') printf("\nZnak: %c", z1);<br />

else<br />

switch (z2)<br />

{<br />

case ';' : printf("\n F1"); break;<br />

case '


RightArrow M 77<br />

UpArrow H 72<br />

DownArrow P 80<br />

Ctrl + PgDn v 118<br />

Ctrl + PgUp Ń (?) 132<br />

Ctrl + Home w 119<br />

Ctrl + End u 117<br />

________________________________________________________________<br />

Wyprowadzanie znaków na ekran można przeprowadzić szybciej<br />

posługując się przerywaniem DOS INT 29H. Drukowanie na ekranie w<br />

trybie tekstowym przebiega wtedy szybciej niż robią to<br />

standardowe funkcje , , czy .<br />

Poniżej prosty przykład praktyczny wykorzystania przerywania<br />

29H:<br />

[P132.CPP]<br />

# include <br />

# include <br />

# pragma inline<br />

void SpeedBox(int, int, int, int, char);<br />

main()<br />

{<br />

clrscr();<br />

for (; !kbhit(); )<br />

{<br />

int x = rand() % 40;<br />

int y = rand() % 12;<br />

SpeedBox(x, y, (80 - x), (24 - y), ('€' + x % 50));<br />

}<br />

return 0;<br />

}<br />

void SpeedBox(int x1, int y1, int x2, int y2, char znak)<br />

{<br />

int k;<br />

for (; y1 < y2; y1++) { gotoxy(x1, y1);<br />

for (k = x1; k < x2; k++)<br />

{<br />

asm MOV AL, znak<br />

asm INT 29H<br />

}<br />

}<br />

}<br />

[Z]<br />

________________________________________________________________<br />

1. Opracuj program pozwalający porównać szybkość wyprowadzania<br />

- 356-


danych na ekran monitora różnymi technikami (cout, puts(),<br />

printf(), asm).<br />

2. Porównaj wielkość plików wynikowych .EXE powstających w<br />

różnych wariantach z poprzedniego zadania.<br />

________________________________________________________________<br />

- 357-


LEKCJA 37. O <strong>C++</strong>, Windows i małym Chińczyku.<br />

czyli:<br />

KTO POWIEDZIAŁ, ŻE PROGRAMOWANIE DLA WINDOWS JEST TRUDNE?!!!<br />

Jak świat światem ludzie przekazują sobie sądy, opinie,<br />

poglądy... W ciągu naszej nowożytnej ery wymyślono już wiele<br />

opinii, które krążyły przez dziesięcio- i stulecia gwarantując<br />

jednym komfort psychiczny (- Ja przecież mam swoje zdanie na ten<br />

temat!), innym dając pozory wiedzy (- Tak, ja coś o tym wiem,<br />

słyszałem, że...). Żywotność takich ćwierćprawd, uproszczeń,<br />

uogólnień, czy wręcz kompletnie bzdurnych mitów była i jest<br />

zadziwiająca.<br />

Podejmę tu próbę obalenia funkcjonującego powszechnie przesądu,<br />

że<br />

- <strong>Programowanie</strong> dla Windows jest trudne. (BZDURA!!!)<br />

Aby nie zostać całkowicie posądzonym o herezję, przyznaję na<br />

wstępie dwa bezsporne fakty.<br />

Po pierwsze, wielu powszechnie szanowanych ludzi zrobiło wiele,<br />

by już pierwszymi przykładami (zwykle na co najmniej dwie<br />

strony) skutecznie odstraszyć adeptów programowania dla Windows.<br />

No bo jak tu nie stracić zapału, gdy program piszący tradycyjne<br />

"Hello World." w okienku ma 2 - 3 stronice i jeszcze zawiera<br />

kilkadziesiąt zupełnie nieznanych i niezrozumiałych słów<br />

(skrótów? szyfrów?).<br />

Po drugie, wszystko jest trudne, gdy brak odpowiednich narzędzi.<br />

Nawet odkręcenie małej śrubki bywa niezwykle trudne, gdy do<br />

dyspozycji mamy tylko młotek... Napisanie aplikacji okienkowej<br />

przy pomocy Turbo Pascal 6, Turbo C, Quick C, czy QBASIC<br />

rzeczywiście BYŁO nadwyraz trudne.<br />

I tu właśnie dochodzimy do sedna sprawy:<br />

(!!!) <strong>Programowanie</strong> dla Windows BYŁO trudne (!!!)<br />

UWAGA!<br />

Pierwsza typowa aplikacja dla Windows napisana w BORLAND <strong>C++</strong> 3/4<br />

może wyglądać np. tak:<br />

#include <br />

void main()<br />

{<br />

cout


TAK!.<br />

W BORLAND <strong>C++</strong> 3+ ... 4+ wystarczy dobrać parametry pracy<br />

kompilatora i zamiast aplikacji DOS-owskiej otrzymamy program<br />

wyposażony we własne okienko, paski przewijania w okienku,<br />

klawisze, menu, ikonkę, itp., itd.!<br />

O MAŁYM CHIŃCZYKU, czyli - NAJLEPIEJ ZACZĄĆ OD POCZĄTKU...<br />

Istnieje jedyny sprawdzony sposób rozwiązywania zagadnień<br />

takiego typu - tzw. METODA MAŁEGO CHIŃCZYKA.<br />

WSZYSCY DOSKONALE WIEDZĄ, że język chiński jest szalenie trudny.<br />

Dlatego też mimo ogromnego wysiłku prawie NIKOMU nie udaje się<br />

biegle nauczyć chińskiego - z jednym wyjątkiem - wyjątkiem<br />

małego Chińczyka. Dlaczego? To proste. Mały Chińczyk po prostu o<br />

tym nie wie! I dlatego już po kilku latach doskonale swobodnie<br />

włada tym bodaj najtrudniejszym językiem świata!<br />

Jeśli zatem komuś udało się przekonać Cię, szanowny Czytelniku,<br />

że programowanie dla Windows jest trudne, namawiam Cię na<br />

dokonanie pewnego eksperymentu intelektualnego. Spróbuj<br />

zapomnieć, że masz już na ten temat jakieś zdanie i wczuj się w<br />

rolę małego Chińczyka. Co roku udaje się to wielu milionom<br />

przyszłych ekspertów od wszystkich możliwych języków świata (<strong>C++</strong><br />

jest chyba znacznie łatwiejszy do chińskiego).<br />

BORLAND <strong>C++</strong> aby dopomóc programiście w jego ciężkiej pracy<br />

tworzy (często automatycznie) wiele plików pomocniczych. Krótkie<br />

zestawienie plików pomocniczych zawiera tabela poniżej.<br />

Najważniejsze pliki pomocnicze w kompilatorach <strong>Borland</strong>/Turbo<br />

<strong>C++</strong>.<br />

________________________________________________________________<br />

Rozszerzenie Przeznaczenie Gdzie/Uwagi<br />

________________________________________________________________<br />

.C .CPP Teksty żródłowe \EXAMPLES \SOURCE<br />

(ASCII) (przykłady) (kod żródł.)<br />

.H .HPP .CAS Pliki nagłówkowe \INCLUDE<br />

(ASCII)<br />

.PRJ .DPR .IDE Projekty \EXAMPLES \SOURCE<br />

.TAH .TCH .TDH<br />

.TFH .HLP .HPJ<br />

.RTF<br />

.DSK .TC .CFG<br />

.DSW .BCW<br />

.DEF .RC .RES<br />

Help<br />

Konfiguracyjne<br />

Zasoby i definicje<br />

- 359-


.RH .ICO .BMP<br />

.BGI .CHR .RTF<br />

Grafika DOS, fonty<br />

.MAK .NMK .GEN Pliki instruktażowe dla<br />

MAKEFILE MAKE.EXE<br />

.ASM .INC .ASI<br />

Do asemblacji (ASCII)<br />

.RSP<br />

.LIB .DLL<br />

.TOK<br />

.DRV<br />

.OVL<br />

Instruktażowy dla TLINK<br />

Biblioteki<br />

Lista słów zastrzeżonych (reserved words)<br />

(ASCII)<br />

Sterowniki (drivery)<br />

Nakładki (overlay)<br />

.SYM Plik ze skompilowanymi (Pre - compiled)<br />

plikami nagłówkowymi.<br />

________________________________________________________________<br />

Świadome i umiejętne wykorzystanie tych plików może znacznie<br />

ułatwić i przyspieszyć pracę.<br />

Po wprowadzeniu na rynek polskiej wersji Windows 3.1 okienka<br />

zaczęły coraz częściej pojawiać się w biurach i domach, i<br />

stanowią coraz częściej naturalne (właśnie tak, jak chiński dla<br />

Chińczyków) środowisko pracy dla polskich użytkowników PC. Nie<br />

pozostaje nam nic innego, jak po prostu zauważyć i uznać ten<br />

fakt.<br />

Po uruchomieniu <strong>Borland</strong> <strong>C++</strong> (2 * klik myszką, lub rozkaz Uruchom<br />

z menu Plik) zobaczymy tradycyjny pulpit (desktop)<br />

zintegrowanego środowiska IDE - podobny do Turbo Pascala, z<br />

tradycyjnym układem głównego menu i okien roboczych.<br />

Skoro mamy zająć się tworzeniem aplikacji dla Windows- zaczynamy<br />

od rozwinięcia menu Options i wybieramy z menu rozkaz<br />

Application... . Rozwinie się okienko dialogowe. Przy pomocy<br />

klawiszy możemy wybrać sposób generowania aplikacji - dla DOS,<br />

dla Windows lub tworzenie bibliotek statycznych .LIB, czy też<br />

dynamicznych .DLL. Wybieramy oczywiście wariant [Windows EXE].<br />

[!!!]UWAGA!<br />

________________________________________________________________<br />

Struktura podkatalogów i wewnętrzna organizacja pakietów 3.0,<br />

3.1, 4 i 4.5 ZNACZNIE SIĘ RÓŻNI.<br />

________________________________________________________________<br />

- 360-


Skoro ustawiliśmy już poprawnie najważniejsze dla nas parametry<br />

konfiguracyjne - możemy przystąpić do uruchomienia pierwszej<br />

aplikacji dla Windows.<br />

PIERWSZA APLIKACJA "specjalnie dla Windows".<br />

Tryb postępowania z kompilatorem BORLAND <strong>C++</strong> 3.0/3.1 będzie w<br />

tym przypadku dokładnie taki sam, jak np. z Turbo Pascalem.<br />

Wszystkich niezbędnych zmian w konfiguracji kompilatora już<br />

dokonaliśmy. Kompilator "wie" już, że chcemy uzyskać w efekcie<br />

aplikację dla Windows w postaci programu .EXE. Możemy zatem<br />

* Wydać rozkaz File | New<br />

Pojawi się nowe okienko robocze. Zwróć uwagę, że domyślne<br />

rozszerzenie jest .CPP, co powoduje domyślne zastosowanie<br />

kompilatora <strong>C++</strong> (a nie kompilatora C - jak w przypadku plików z<br />

rozszerzeniem .C). Możesz to oczywiście zmienić, jeśli zechcesz,<br />

posługując się menu Options | Compiler | <strong>C++</strong> options... (Opcje |<br />

Kompilator | Kompilator C albo <strong>C++</strong>). W tym okienku dialogowym<br />

masz sekcję:<br />

Use <strong>C++</strong> Compiler: Zastosuj Kompilator <strong>C++</strong><br />

(zamiast kompilatora C)<br />

(.) CPP extention - tylko dla rozszerzenia .CPP<br />

( ) <strong>C++</strong> always - zawsze<br />

* Wybierz rozkaz Save as... z menu File<br />

Pojawi się okienko dialogowe "Save File As" (zapis pliku pod<br />

wybraną nazwą i w wybranym miejscu).<br />

* Do okienka edycyjnego wpisz nazwę pliku i pełną ścieżkę<br />

dostępu - np. A:\WIN1.CPP lub C:\C-BELFER\WIN1.CPP<br />

Zmieni się tytuł roboczego okna z NONAME00 na wybraną nazwę<br />

Możemy wpisać tekst pierwszego programu:<br />

[P133.CPP]<br />

#include <br />

void main()<br />

{<br />

cout


W okienku komunikatów (Messages) powinien pojawić się w trakcie<br />

konsolidacji komunikat ostrzegawczy:<br />

*Linker Warning: No module definition file specified:<br />

using defaults<br />

Oznacza to: Konsolidator ostrzega, że brak specjalnego<br />

stowarzyszonego z plikiem .CPP tzw. pliku definicji sposobu<br />

wykorzystania zasobów Windows - .DEF. Program linkujący<br />

zastosuje wartości domyślne.<br />

Jeśli w IDE wersji kompilatora przeznaczonej dla środowiska DOS<br />

spróbujesz uruchomić program WIN1.EXE w tradycyjny sposób -<br />

rozkazem Run z menu Run - na ekranie pojawi się okienko z<br />

komunikatem o błędzie (Error message box):<br />

Can't run a Windows EXE file<br />

D:\WIN1.EXE<br />

[ OK ]<br />

czyli: "Nie mogę uruchomić pliku EXE dla Windows".<br />

Jak już napisałem wcześniej, kompilatory <strong>C++</strong> w pakietach 3.0/3.1<br />

mają swoje ulubione specjalności:<br />

<strong>Borland</strong> <strong>C++</strong><br />

Turbo <strong>C++</strong><br />

- jest zorientowany na współpracę z DOS<br />

- jest zorientowany na współpracę z Windows<br />

w wersji 3.1:<br />

BCW -<br />

BC -<br />

dla Windows<br />

dla DOS<br />

nie oznacza to jednak, że będą kłopoty z pracą naszego programu!<br />

Wyjdź z IDE BC/BCW.<br />

Z poziomu Menedżera Programów możesz uruchomić swój program<br />

rozkazem Plik | Uruchom. Do okienka musisz oczywiście wpisać<br />

poprawną ścieżkę do pliku WIN1.EXE (czyli katalog wyjściowy<br />

kompilatora <strong>Borland</strong> <strong>C++</strong>).<br />

*** Wybierz z menu głównego Menedżera Programów (pasek w górnej<br />

części ekranu) rozkaz Plik. Rozwinie się menu Plik.<br />

*** Wybierz z menu Plik rozkaz Uruchom. Pojawi się okienko<br />

dialogowe uruchamiania programów. Wpisz pełną ścieżkę<br />

dostępu do programu - np.:<br />

D:\KATALOG\WIN1.EXE<br />

i "kliknij" myszką na klawiszu [OK] w okienku.<br />

Na ekranie pojawi się okno naszej aplikacji. Okno jest<br />

- 362-


wyposażone w:<br />

- Pasek z tytułem (Caption) - np.: A:\WIN1.EXE ;<br />

- Klawisz zamykania okna i rozwinięcia standardowego menu (tzw.<br />

menu systemowego Windows) - [-] ;<br />

- Paski przewijania poziomego i pionowego;<br />

- Klawisze MINIMIZE i MAXIMIZE (zmniejsz do ikonki | powiększ na<br />

cały ekran) w prawym górnym narożniku okna;<br />

Program znajduje się w wersji .EXE na dyskietce dołączonej do<br />

książki. Możesz uruchomić go z poziomu Menedżera Plików (Windows<br />

File Manager), Menedżera Programów (Windows Program Manager) lub<br />

z DOS-owskiego wiersza rozkazów (DOS Command Line):<br />

C\>WIN A:\WIN1.EXE[Enter]<br />

Co może nasza pierwsza aplikacja?<br />

- Typową dla Windows techniką drag and drop - pociągnij i upuść<br />

możesz przy pomocy myszki przesuwać okno naszej pierwszej<br />

aplikacji po ekranie ("ciągnąc" okno za pasek tytułowy).<br />

- Ciągnąc ramki bądź narożniki możesz zmieniać wymiary okna w<br />

sposób dowolny.<br />

- Posługując się paskami przewijania możesz przewijać tekst w<br />

oknie w pionie i w poziomie.<br />

- Miżesz zredukować okno do ikonki.<br />

- Możesz uruchomić naszą aplikację wielokrotnie i mieć na<br />

ekranie kilka okien programu WIN1.EXE.<br />

- Nasza aplikacja wyposażona jest w menu systemowe. Możesz<br />

rozwinąć menu i wybrać z menu jeden z kilku rozkazów.<br />

Jeśli nie pisałeś jeszcze programów dla Windows - możesz być<br />

trochę zaskoczony. Gdzie w naszym programie jest napisane np. -<br />

co powinno znaleść się w menu??? Odpowiedź jest prosta -<br />

nigdzie. Podobnie jak programy tworzone dla DOS korzystają w<br />

niejawny sposób z zasobów systemu - standardowych funkcji DOS,<br />

standardowych funkcji BIOS, przerywań, itp - tak programy<br />

tworzone dla Windows mogą w niejawny sposób korzystać z zasobów<br />

środowiska Windows - standardowego menu, standardowych okien,<br />

standardowych klawiszy, itp.. Takie zasoby udostępniane przez<br />

środowisko programom aplikacyjnym nazywają się interfejsem API<br />

(Application Program Interface). Poznałeś już API DOS'a - jego<br />

przerywania i funkcje. Interfejs Windows nazywa się "Windows<br />

API" i to z jego gotowych funkcji właśnie korzystamy.<br />

Uruchom program wielokrotnie (min. 4 razy). Wykonaj 4 - 6 razy<br />

czynności oznaczone powyżej trzema gwiazdkami *** . Ponieważ nie<br />

- 363-


zażądaliśmy, by okno programu było zawsze "na wierzchu" (on top)<br />

- po każdym kolejnym uruchomieniu (nie musisz nic robić -<br />

nastąpi to automatycznie - zadba o to Menedżer Windows)<br />

poprzednie okno programu zniknie. Jeśli po czwartym (piątym)<br />

uruchomieniu programu zredukujesz okno Menedżera Programów do<br />

ikony (np. [-] i "do ikony" z menu systemowego) - okaże się, że<br />

"pod spodem" stale widać kaskadę okien naszej aplikacji WIN1.EXE<br />

(patrz rys. poniżej). Na rysunkach poniżej przedstawiono kolejne<br />

stadia pracy z naszą PIERWSZĄ APLIKACJĄ.<br />

Aplikacja WIN1.EXE została wyposażona w ikonkę systemową (znane<br />

Ci okienko). Ikonka jest transparentna (półprzezroczysta) i<br />

możemy ją także metodą drag and drop przenieść w dowolne miejsce<br />

- np. do roboczego okna naszej aplikacji. Zwróć uwagę także na<br />

towarzyszący nazwie programu napis "inactive" (nieaktywna).<br />

Chodzi o to, że program zrobił już wszystko, co miał do<br />

zrobienia i zakończył się. DOS dołożyłby standardowo funkcję<br />

zwolnienia pamięci i zakończył program. W Windows niestety<br />

okienko nie zamknie się samo w sposób standardowy. W Windows,<br />

jak wiesz, możemy mieć otwarte jednocześnie wiele okien<br />

programów a aktywne jest (tylko jedno) zawsze to okno, do<br />

którego przekażemy tzw. focus. Okno to można rozpoznać po<br />

ciemnym pasku tytułowym. Właśnie z przyjęcia takiego sposobu<br />

podziału zasobów Windows pomiędzy aplikacje wynika skutek<br />

praktyczny - okno nie zamknie się automatycznie po zakończeniu<br />

programu - lecz wyłącznie na wyrażne życzenie użytkownika. API<br />

Windows zawiera wiele gotowych funkcji (np. CloseWindow() -<br />

zamknij okno, DestroyWindow() - skasuj okno i in.), z których<br />

może skorzystać programista pisząc aplikację. Nie jesteśmy więc<br />

całkiem bezradni.<br />

Spróbuj skompilować w opisany wyżej sposób i uruchomić pierwszą<br />

aplikację w takiej wersji:<br />

#include <br />

void main()<br />

{<br />

printf(" Pierwsza Aplikacja \n Dla MS Windows ");<br />

}<br />

Jak łatwo się przekonać, całkowicie klasyczny, w pełni<br />

nieobiektowy program WIN1.C będzie w Windows działać dokładnie<br />

tak samo. Nasze aplikacje nie muszą bynajmniej być całkowicie<br />

obiektowe, chociaż zastosowanie obiektowej techniki<br />

programowania pozwala zmusić nasz komputer do zdecydowanie<br />

wydajniejszego działania.<br />

PODSUMUJMY:<br />

- 364-


* Jeśli korzystamy wyłącznie ze standardowych zasobów środowiska<br />

Windows, tworzenie aplikacji dla Windows nie musi być wcale<br />

trudniejsze od tworzenia aplikacji DOS'owskich.<br />

* Czy aplikacja ma być przeznaczona dla DOS, czy dla Windows<br />

możemy zdecydować "w ostatniej chwili" ustawiając odpowiednio<br />

robocze parametry kompilatora <strong>C++</strong>:<br />

Options | Applications... | DOS standard<br />

albo<br />

Options | Applications... | Windows EXE<br />

* Aplikacje skompilowane do wersji DOS'owskiej możemy uruchamiać<br />

wewnątrz kompilatora DOS'owskiego rozkazem Run | Run.<br />

* Aplikacje skompilowane (ściślej - skonsolidowane) do wersji<br />

okienkowej możemy uruchamiać wewnątrz Windows z poziomu<br />

Menedżera Plików bądź Menedżera Programów rozkazem Uruchom z<br />

menu Plik.<br />

* Dodatkowe pliki nagłówkowe .H i biblioteki .LIB .DLL znajdują<br />

się w katalogach<br />

\BORLANDC\INCLUDE<br />

\BORLANDC\OWL\INCLUDE<br />

\BORLANDC\LIB<br />

\BORLANDC\OWL\LIB<br />

Ścieżki dostępu do tych katalogów należy dodać do roboczych<br />

katalogów kompilatora w okienku Options | Directories...<br />

* Aplikacje nie korzystające z funkcji Windows API nie muszą<br />

dołączać okienkowych plików nagłówkowych. Jeśli jednak<br />

zechcemy zastosować funkcje i dane (stałe, struktury,<br />

obiekty, itp.) wchodzące w skład:<br />

- Windows API<br />

- Windows Stock Objects - obiekty "ze składu Windows"<br />

- biblioteki klas Object Windows Library<br />

należy dołączyć odpowiedni plik nagłówkowy:<br />

#include <br />

#include <br />

#include <br />

TYPOWE BŁĘDY I KŁOPOTLIWE SYTUACJE:<br />

* Należy pamiętać o ustawieniu właściwych katalogów roboczych<br />

kompilatora Options | Directories...<br />

* Przy bardziej skomplikowanych aplikacjach może wystąpić<br />

potrzeba dobrania innego (zwykle większego) modelu pamięci.<br />

Modelem domyślnym jest model Small. Inne parametry pracy<br />

kompilatora ustawia się podobnie za pomocą menu Options.<br />

________________________________________________________________<br />

- 365-


LEKCJA 38. KORZYSTAMY ZE STANDARDOWYCH ZASOBÓW Windows.<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak korzystać z zasobów<br />

Windows bez potrzeby wnikania w wiele szczególów technicznych<br />

interfejsu aplikacji - Windows API.<br />

________________________________________________________________<br />

Ponieważ nasze programy mogą korzystać ze standardowych zasobów<br />

Windows, na początku będziemy posługiwać się okienkami<br />

standardowymi. Począwszy od aplikacji WIN3.EXE "rozszerzymy<br />

ofertę" do dwu podstawowych typów:<br />

* Standardowe główne okno programu (Default main window).<br />

To takie właśnie okno, jakie dostały nasze pierwsze aplikacje<br />

WIN1.EXE.<br />

* Okienkiem dialogowym (Dialog box),<br />

a dokładniej najprostszym rodzajem okienek dialogowych - tzw.<br />

okienkami komunikatów - Message Box.<br />

Zastosowanie okienka dialogowego pozwoli nam na wprowadzenie do<br />

akcji klawiszy (buttons).<br />

________________________________________________________________<br />

UWAGA:<br />

Niestety nie wszystkie tradycyjne funkcje typu printf(),<br />

scanf(), gets() itp. zostały zaimplementowane dla Windows!<br />

Pisząc własne programy możesz przekonać się o tym dzięki opisowi<br />

funkcji w Help. Funkcję należy odszukać w Help | Index. Oprócz<br />

przykładu zastosowania znajdziesz tam tabelkę typu:<br />

DOS Unix Windows ANSI C <strong>C++</strong> Only<br />

cscanf Yes<br />

fscanf Yes Yes Yes Yes<br />

scanf Yes Yes Yes<br />

sscanf Yes Yes Yes Yes<br />

[Yes] oznacza "zaimplementowana". Dlatego właśnie w dalszych<br />

programach przykładowych dla wersji 3.0 należy np. stosować np.<br />

makro getchar() zamiast tradycyjnego getch() zaimplementowane<br />

dla Windows już w wersji B<strong>C++</strong> 3.0.<br />

________________________________________________________________<br />

Dla przykładu spróbujmy skompilować i uruchomić w środowisku<br />

Windows jeden z wcześniejszych programów - tabliczkę mnożenia.<br />

Zwróć uwagę na dołączony dodatkowy plik WINDOWS.H i nowy typ<br />

wskaźnika. Zamiast zwykłego<br />

char *p ...<br />

LPSTR p ...<br />

LPSTR - to Long Pointer to STRing - daleki wskaźnik do łańcucha<br />

tekstowego. Jest to jeden z "ulubionych" typów Windows.<br />

- 366-


* WIN2.CPP: */<br />

/* - Tablica dwuwymiarowa<br />

- Wskazniki do elementów tablicy */<br />

#include <br />

#include <br />

#include <br />

int T[10][10], *pT, i, j, k;<br />

char spacja = ' ';<br />

LPSTR p1 = " TABLICZKA MNOZENIA (ineksy)\n";<br />

LPSTR p2 = " Inicjujemy i INKREMENTUJEMY wskaznik:\n";<br />

LPSTR p3 = "... nacisnij cokolwiek (koniec)...";<br />

void main()<br />

{<br />

printf(p1);<br />

for (i = 0; i < 10; i++)<br />

{<br />

for (j = 0; j < 10; j++)<br />

{ T[i][j] = (i + 1)*(j + 1);<br />

if (T[i][j] < 10) cout


Zastosujemy teraz najprostszy typ okienka dialogowego - okienko<br />

kamunikatów (Message Box), nasze następne aplikacje mogą być już<br />

nie jedno- a dwupoziomowe. Typowe postępowanie okienkowych<br />

aplikacji bywa takie:<br />

* program wyświetla w głównym oknie to, co ma do powiedzenia;<br />

* aby zadawać pytania stosuje okienka dialogowe, bądź okienka<br />

komunikatów;<br />

* funkcja okienkowa (u nas MessageBox()) zwraca do programu<br />

decyzję użytkownika;<br />

* program główny analizuje odpowiedź i podejmuje w głównym oknie<br />

stosowne działania.<br />

Prześledźmy ewolucję powstającej w taki sposób aplikacji.<br />

STADIUM 1. Tekst w głównym oknie.<br />

Zaczniemy tworzenie naszej aplikacji tak:<br />

/* WINR1.CPP: */<br />

/* Stadium 1: Dwa okienka w jednym programie */<br />

# include <br />

# include <br />

char *p1 = "Teraz dziala \n funkcja \n MessageBox()";<br />

char *p2 = "START";<br />

int wynik;<br />

void main()<br />

{<br />

printf(" Start: Piszemy w glownym oknie \n");<br />

printf(" ...nacisnij cosik...");<br />

getchar();<br />

MessageBox(0, p1, p2, 0);<br />

printf("\n\n\n Hello World dla WINDOWS!");<br />

printf("\n\t...dowolny klawisz... ");<br />

getchar();<br />

}<br />

Moglibyśmy zrezygnować z metod typowych dla aplikacji DOSowskich<br />

i zatrzymania (i zapytania) makrem getchar() (odpowiednik<br />

getch() dla Windows). To działanie możemy z powodzeniem<br />

powierzyć funkcji okienkowej MessageBox(). Funkcja MessageBox()<br />

pobiera cztery parametry:<br />

int Message Box(hwndParent, lpszText, lpszTitle, Style)<br />

HWND hwndParent - identyfikator macieżystego okna (głównego okna<br />

aplikacji). Ponieważ nie wiemy póki co pod jakim numerem<br />

(identyfikatorem) Windows zarejestrują naszą aplikację -<br />

- 368-


wpisujemy 0<br />

LPCSTR lpszText - daleki wskaźnik do łańcucha tekstowego<br />

wewnątrz okienka.<br />

LPCSTR lpszTitle - daleki wskażnik do łańcucha tekstowego -<br />

tytułu okienka komunikatu.<br />

UINT Style - UINT = unsigned int; numer określający zawartość<br />

okienka.<br />

int Return Value - identyfikator klawisza, który wybrał<br />

użytkownik w okienku komunikatu.<br />

[!!!] UWAGA<br />

________________________________________________________________<br />

Deklaracje wskaźników do tekstów powinny wyglądać tak:<br />

LPCSTR p1 = "Napis1", p2 = "Tekst2";<br />

ale <strong>C++</strong> może samodzielnie dokonać forsowania typów i zamienić<br />

typ char* na typ LPCSTR (lub LPSTR).<br />

________________________________________________________________<br />

/* WINR2.CPP: */<br />

/* Stadium 2: Dwa okienka ze zmienną zawarością */<br />

# include <br />

# include <br />

char *p2, *p1 = "Dopisywanie:";<br />

char napisy[4][20] = { "<strong>Borland</strong> ", "<strong>C++</strong> ", "dla ", "Windows" };<br />

void main()<br />

{<br />

printf("\n\n\n Hello World dla WINDOWS!");<br />

printf("\n AUTOR: ...................");<br />

for( int i = 0; i < 4; i++)<br />

{<br />

p2 = &napisy[i][0];<br />

MessageBox(0, p2, p1, MB_OK);<br />

printf("\n %s", napisy[i]);<br />

}<br />

MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK);<br />

}<br />

W tym stadium stosujemy:<br />

- główne okno aplikacji<br />

- dwa okienka komunikatów (Dopisywanie i KONIEC)<br />

- jeden klawisz - [OK]<br />

Łańcuchy tekstowe przeznaczone do pola tekstowego okienka<br />

pobierane są z tablicy napisy[4][20] (cztery napisy po max. 20<br />

znaków) przy pomocy wskaźnika p2. MB_OK to predefiniowana stała<br />

(Message Box OK - key identifier - identyfikator klawisza [OK]<br />

dla okienek komunikatów).<br />

/* WINR3.CPP: */<br />

/* Stadium 3: Dwa okienka sterują pętlą */<br />

- 369-


# include <br />

# include <br />

char *p2, *p1 = "Dopisywanie:";<br />

char napisy[4][20] = { "<strong>Borland</strong> ", "<strong>C++</strong> ", "dla ", "Windows" };<br />

void main()<br />

{<br />

printf("\n\n\n Hello World dla WINDOWS!");<br />

printf("\n AUTOR: ...................");<br />

for( int i = 0; i < 4; i++)<br />

{<br />

p2 = &napisy[i][0];<br />

if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK)<br />

printf("\n %s", napisy[i]);<br />

else<br />

printf("\n ...?");<br />

}<br />

MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK);<br />

}<br />

W tym stadium stosujemy:<br />

- główne okno aplikacji<br />

- dwa okienka komunikatów (Dopisywanie i KONIEC)<br />

- dwa klawisze - [OK] i [Anuluj] (OK/Cancel)<br />

- jedną ikonę [STOP]<br />

Zwróć uwagę, że tym razem sprawdzamy, który klawisz wybrał<br />

użytkownik w okienku. Odbywa się to tak:<br />

if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK)<br />

IDOK jest predefiniowaną stałą - kodem klawisza [OK] (ang.<br />

OK-key IDentifier - identyfikator klawisza OK). Identyfikatory<br />

różnych zasobów Windows są liczbami całkowitymi. Jeśli jesteś<br />

dociekliwy Czytelniku, możesz sprawdzić - jaki numer ma klawisz<br />

[OK] rozbudowując tekst aplikacji np. tak:<br />

int Numer;<br />

...<br />

Numer = MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL);<br />

printf("\nKlawisz [OK] ma numer: %d", Numer);<br />

if(Numer == IDOK) ...<br />

Zwróć uwagę na sposób wykorzystania zasobów w funkcji<br />

MessageBox(). Identyfikatory zasobów, które chcemy umieścić w<br />

okienku są wpisywane jako ostatni czwarty argument funkcji i<br />

mogą być sumowane przy pomocy znaku | (ang. ORing), np.:<br />

MessageBox(0,..,.., MB_ICONSTOP | MB_OKCANCEL);<br />

- 370-


oznacza umieszczenie ikony STOP i klawiszy [OK] i [Anuluj]. Kod<br />

zwracany przez funkcję może być wykorzystywany we wszelkich<br />

konstrukcjach warunkowych (switch, case, for, while, if-else,<br />

itp.).<br />

/* WINR4.CPP: */<br />

/* Stadium 4: Okienka sterują 2 pętlami, przybywa zasobów. */<br />

# include <br />

# include <br />

char *p2, *p1 = "Dopisywanie:";<br />

char *p3 = "I to by bylo na tyle...\n Konczymy ???";<br />

char *p4 = "UWAGA: KONIEC ?";<br />

char napisy[5][20] = { "<strong>Borland</strong> ", "<strong>C++</strong> ", "dla ", "Microsoft",<br />

"Windows" };<br />

main()<br />

{<br />

printf("\n\n\n Grafoman dla WINDOWS!");<br />

printf("\n AUTOR: (jak wyzej)");<br />

puts("_____________________________\n");<br />

do<br />

{<br />

for( int i = 0; i < 5; i++)<br />

{<br />

p2 = &napisy[i][0];<br />

if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK)<br />

printf("\n %s", napisy[i]);<br />

else<br />

printf("\n ...?");<br />

}<br />

} while<br />

(MessageBox(0,p3,p4,MB_ICONQUESTION | MB_OKCANCEL)==IDCANCEL);<br />

return 0;<br />

}<br />

W tym stadium stosujemy:<br />

- główne okno aplikacji<br />

- dwa okienka komunikatów (Dopisywanie i KONIEC)<br />

- dwa klawisze - [OK] i [Anuluj] (OK/Cancel)<br />

- dwie ikonki [STOP] i [PYTAJNIK]<br />

Tekst jest przewijany w głównym oknie programu i po zakończeniu<br />

roboczej części programu i przejściu w stan nieaktywny<br />

(inactive) możesz przy pomocy paska przewijania pionowego<br />

obejrzeć napisy - historię Twoich zmagań z programem. Zwróć<br />

uwagę, że pojemność głównego okna jest ograniczona. Jeśli<br />

napisów będzie zbyt dużo, tekst przewinięty poza okno może<br />

ulegać obcięciu (ang clip on). Zwróć również uwagę na<br />

naprzemienne przekazywanie aktywności (focus) pomiędzy oknami<br />

aplikacji:<br />

- 371-


MainWindow MessageBox<br />

Warto w tym momencie zwrócić uwagę na kilka typowych dla<br />

okienkowych aplikacji mechanizmów.<br />

* Jeśli naciśniemy klawisz na klawiaturze, bądź klawisz myszki,<br />

obsługa takiego zdarzenia może następować na dwa sposoby.<br />

Najpierw Windows pobierają kod klawisza i dokonują<br />

"kolejkowania" (podobnie jak DOS-owski bufor klawiatury).<br />

Następnie przekazują kod klawisza aplikacji do obsługi. Jeśli<br />

aplikacja czeka na klawisz i potrafi obsłużyć takie zdarzenie<br />

(np. funkcja MessageBox(), bądź makro getchar(), czy operator<br />

cin >> w programie głównym), obsługa zdarzenia zostaje<br />

zakończona. Jeśli aplikacja nie potrafi obsłużyć zdarzenia -<br />

obsługa przekazywaba jest stadardowym funkcjom obsługi (Event<br />

Handler) Windows.<br />

* Kiedy na ekranie pojawia się okienko dialogowe (tu:<br />

komunikatów) zostaje mu przekazany tzw. focus - czyli aktywność.<br />

Naciśnięcie [Entera] spowoduje zadziałanie tego klawisza w<br />

okienku, który właśnie ten focus otrzymał (tu zwykle pierwszego<br />

z lewej).<br />

* jeśli naciśniemy klawisz specjalny, którego obsługę w sposób<br />

standardowy powinny załatwiać Windows - obsługa takiego<br />

zdarzenia zostaje przekazana domyślnej funkcji Windows (ang.<br />

Default Event Handler). Tak jest w przypadku klawiszy ze<br />

strzałkami (przewijanie w oknie), [Tab], [Alt]+[F4], itp.<br />

/* WINR5.CPP: */<br />

/* Stadium 5: Zmiana wielkości i nazwy okienka. */<br />

# include <br />

# include <br />

# include <br />

char tytul[80] = "Dopisywanie: ";<br />

char *p0, *p2;<br />

char *p1 = "UWAGA: Ponawianie proby \n oznacza: WYDRUKUJE I<br />

ZAPYTAM";<br />

char *p3 = "I to by bylo na tyle...\n Konczymy ???";<br />

char *p4 = "UWAGA: KONIEC ?";<br />

char napisy[5][20] = { "<strong>Borland</strong> ", "<strong>C++</strong> ", "dla ", "Microsoft",<br />

"Windows" };<br />

main()<br />

{<br />

cout


{<br />

p2 = &napisy[i][0];<br />

strcat(p0, p2);<br />

int decyzja = MessageBox(0, p1, p0, MB_ICONHAND |<br />

MB_ABORTRETRYIGNORE);<br />

if (decyzja == IDABORT) break;<br />

else<br />

if (decyzja == IDRETRY)<br />

{<br />

cout


LEKCJA 39. STRUKTURA PROGRAMU PROCEDURALNO - ZDARZENIOWEGO<br />

PRZEZNACZONEGO DLA WINDOWS.<br />

________________________________________________________________<br />

W trakcie tej lekcji poznasz ogólną budowę interfejsu API<br />

Windows i dowiesz się, co z tego wynika dla nas - autorów<br />

programów przeznaczonych dla Windows.<br />

________________________________________________________________<br />

W przeciwieństwie do długich liniowych programów przeznaczonych<br />

dla DOS, w naszych programach dla Windows będziemy pisać coś<br />

na kształt krótkich odcinków programu i przekazywać sterowanie<br />

Windows. Jest to bardzo ważna cecha - kod programu jest zwykle<br />

silnie związany z Windows w taki sposób, że użytkownik może w<br />

dużym stopniu decydować o sposobie (kolejności) wykonywania<br />

programu. Praktycznie robi to poprzez wybór opcji-klawiszy w<br />

dowolnej kolejności. Przy takiej filozofii w dowolnym momencie<br />

powinniśmy mieć możliwość przełączenia się do innego programu<br />

(innego okna) i nasz program powinien (bez zauważalnej zwłoki)<br />

przekazać sterowanie, nie zagarniając i nie marnując czasu CPU.<br />

Z tego powodu kod programu powinien być bardzo<br />

"zmodularyzowany". Każda sekcja kodu powinna być odseparowana i<br />

każda, po wykonaniu powinna przekazywać sterowanie do Windows.<br />

NOTACJA WĘGIERSKA I NOWE TYPY DANYCH.<br />

Tworzenie zdarzeniowych programów dla Windows wymaga kilku<br />

wstępnych uwag na temat nowych typów danych. Okienkowe typy są<br />

definiowane w plikach nagłówkowych (WINDOWS.H, WINDOWSX.H, OWL.H<br />

itp) i mają postać najczęściej struktury, bądź klasy. Typowe<br />

sposoby deklaracji w programach okienkowych są następujące:<br />

HWND hWnd - WiNDow Handle - identyfikator okna<br />

HWND hWnd - typ (predefiniowany), hWnd - zmienna<br />

HINSTANCE hInstance - Instance Handle - identyfikator danego<br />

wystąpienia (uruchomienia) programu<br />

PAINTSTRUCT - struktura graficzna typu PAINTSTRUCT<br />

ps - nasza robocza struktura (zmienna)<br />

WNDCLASS - struktura (a nie klasa wbrew mylącej nazwie)<br />

POINT - struktura (współrzędne punktu - piksela na ekranie)<br />

RECT - struktura (współrzędne prostokąta)<br />

BOOL - typ signed int wykorzystywany jako flaga (TRUE/FALSE)<br />

WORD - unsigned int<br />

DWORD - unsigned long int<br />

LONG - long int<br />

HANDLE, HWND, HINSTANCE - unsigned int (jako nr - identyfikator)<br />

UINT - j. w. - unsigned int.<br />

W programach okienkowych stosuje się wiele predefiniowanych<br />

stałych, których znaczenie sugeruje przedrostek i nazwa, np:<br />

WM_CREATE - Windows Message: Create! - Komunikat Windows:<br />

Utworzyć! (np. okno)<br />

WS_VISIBLE - Window Style: Visible - Styl Okna: Widoczne<br />

- 374-


ID_...<br />

MB_...<br />

- IDentifier - IDentyfikator<br />

- Message Box - elementy okienka komunikatów<br />

W środowisku Windows stosuje się specjalną notację nazwaną od<br />

narodowości swojego wynalazcy Karoja Szimoni - notacją<br />

węgierską. Sens notacji węgierskiej polega na dodaniu do nazwy<br />

zmiennej określonych liter jako przedrostka (prefix).<br />

Litery-przedrostki stosowane w notacji węgierskiej zebrano w<br />

Tabeli poniżej. Pomiędzy nazewnictwem Microsofta a <strong>Borland</strong>a<br />

istnieją wprawdzie drobne rozbieżności, ale ogólne zasady można<br />

odnieść zarówno do BORLAND <strong>C++</strong> 3+...4+, jak i Microsoft <strong>C++</strong><br />

6...7, czy Visual <strong>C++</strong>.<br />

Notacja węgierska<br />

________________________________________________________________<br />

Prefix Skrót ang. Znaczenie<br />

________________________________________________________________<br />

a array tablica<br />

b bool zmienna logiczna (0 lub 1)<br />

by unsigned char znak (bajt)<br />

c char znak<br />

cb count of bytes liczba bajtów<br />

cr color reference value określenie koloru<br />

cx, cy short (count x, y len.) x-ilość, y-długość (short)<br />

dw unsigned long liczba długa bez znaku<br />

double word podwójne słowo<br />

fn function funkcja<br />

pfn pointer to function wsk. do funkcji<br />

h handle "uchwyt" - identyfikator<br />

i integer całkowity<br />

id identifier identyfikator<br />

n short or int krótki lub całkowity<br />

np near pointer wskaźnik bliski<br />

p pointer wskaźnik<br />

l long długi<br />

lp long pointer wskaźnik typu long int<br />

lpfn l. p. to function daleki wskaźn. do funkcji<br />

s string łańcuch znaków<br />

sz string terminated '\0' łańcuch ASCIIZ<br />

tm text metric miara tekstowa<br />

w unsigned int (word) słowo<br />

x,y short x,y coordinate współrzędne x,y (typ: short)<br />

________________________________________________________________<br />

O PROGRAMOWANIU PROCEDURALNO - ZDARZENIOWYM DLA WINDOWS.<br />

W proceduralno-sekwencyjnych programach DOS'owskich sterowanie<br />

jest przekazywane mniej lub bardziej kolejno kolejnym<br />

instrukcjom w taki sposób, jak życzył sobie tego programista. W<br />

Windows program-aplikacja prezentuje użytkownikowi wszystkie<br />

dostępne opcje w formie widocznych na ekranie obiektów (visual<br />

objects) do wyboru przez użytkownika. Program funkcjonuje zatem<br />

według zupełnie innej koncepcji nazywanej "programowaniem<br />

zdarzeniowym" (ang. event-driven programming). Można powiedzieć,<br />

- 375-


że za przebieg wykonania programu nie jest odpowiedzialny tylko<br />

programista lecz część tej odpowiedzialności przejmuje<br />

użytkownik i to on decyduje w jaki sposób przebiega wykonanie<br />

programu. Użytkownik może wybrać w dowolnym momencie dowolną<br />

spośród wszystkich oferowanych mu opcji a program powinien<br />

zawsze zareagować poprawnie i równie szybko. Jest oczywiste, że<br />

pisząc program nie możemy przewidzieć w jakiej kolejności<br />

użytkownik będzie wybierał opcje/rozkazy z menu. Przeciwnie<br />

powiniśmy napisać program w taki sposób by dla każdego rozkazu<br />

istniał oddzielny kod. Jest to ogólna koncepcja, na której<br />

opiera się programowanie zdarzeniowe.<br />

W przeciwieństwie do programów proceduralno - sekwencyjnych,<br />

które należy czytać od początku do końca, programy dla Windows<br />

muszą zostać pocięte na na mniejsze fragmenty - sekcje - na<br />

zasadzie jedna sekcja - obsługa jednego zdarzenia. Jeśli<br />

zechcesz wyświetlić napis "Hello, World", sekcja zdarzeniowego<br />

programu obsługująca takie zdarzenie może wyglądać np. tak:<br />

Funkcja_Obsługi_Komunikatów_o_Zdarzeniach(komunikat)<br />

{<br />

switch (komunikat_od_Windows)<br />

{<br />

case WM_CREATE:<br />

...<br />

TextOut(0, 0, "Napis: np. Hello world.", dlugosc_tekstu);<br />

break;<br />

...<br />

case WM_CLOSE:<br />

.... break;<br />

..................... itd.<br />

}<br />

// CLOSE - zamknąć okno<br />

a w przypadku obiektowego stylu programowania - metoda<br />

obsługująca to zdarzenie (należąca np. do obiektu<br />

Obiekt_Główne_Okno - TMainWindow) może wyglądać np. tak:<br />

void TMainWindow::RysujOkno()<br />

{<br />

TString Obiekt_napis = "Hello, World";<br />

int dlugosc_tekstu = sizeof(Obiekt_napis);<br />

TextOut(DC, 10, 10, Obiekt-napis, dlugosc_tekstu);<br />

}<br />

Taki fragment kodu programu jest specjalnie przeznaczony do<br />

obsługi jednego zdarzenia (ewent-ualności). W okienku wykonuje<br />

się operacja PAINT (maluj). "Malowanie" okna może się odbywać<br />

albo po raz pierwszy, albo na skutek przesunięcia. Programy<br />

zdarzeniowe tworzone w <strong>C++</strong> dla Windows będą zbiorem podobnych<br />

"kawałków" następujących w tekście programu sekcja za sekcją.<br />

Oto jak działa program zdarzeniowy: kod programu, podzielony na<br />

sekcje obsługujące poszczególne zdarzenia koncentruje się wokół<br />

interfejsu.<br />

- 376-


FUNKCJE WinMain() i WindowProc().<br />

W programach pisanych w standardowym C dla Windows używane są<br />

dwie najważniejsze funkcje: WinMain() i WindowProc().<br />

________________________________________________________________<br />

UWAGA:<br />

Funkcji WindowProc() można nadać dowolną nazwę, ale WinMain()<br />

musi się zawsze nazywać WinMain(). Jest to nazwa zastrzeżona<br />

podobnie jak main() dla aplikacji DOSowskich.<br />

________________________________________________________________<br />

Funkcja WinMain() powoduje utworzenie okna programu umożliwiając<br />

zdefiniowanie i zarejestrowanie struktury "okno" (struct<br />

WNDCLASS) a następnie powoduje wyświetlenie okna na ekranie. Od<br />

tego momentu zarządzanie przejmuje funkcja WindowProc(). W<br />

typowej proceduralno - zdarzeniowej aplikacji dla Windows to<br />

właśnie funkcja WindowProc() obsługuje pobieranie informacji od<br />

użytkownika (np. naciśnięcie klawisza lub wybór z menu). Funkcja<br />

WindowProc() robi to dzięki otrzymywaniu tzw. komunikatów (ang.<br />

Windows message).<br />

W Windows zawsze po wystąpieniu jakiegoś zdarzenia (event)<br />

następuje przesłanie komunikatu (message) o tym zdarzeniu do<br />

bieżącego aktywnego w danym momencie programu w celu<br />

poinformowania go, co się stało. Jeśli został naciśnięty<br />

klawisz, komunikat o tym zdarzeniu zostanie przesłany do funkcji<br />

WindowProc(). Tak funkcjonuje interfejs pomiędzy aplikacją a<br />

Windows. W programach tworzonych w C prototyp funkcji<br />

WindowProc() wygląda następująco:<br />

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message,<br />

WORD wParam, LONG lParam);<br />

Słowa FAR i PASCAL oznaczają, że:<br />

FAR - kod funkcji znajduje się w innym segmencie niż kod<br />

programu;<br />

PASCAL - kolejność odkładania argumentów na stos - odwrotna (jak<br />

w Pascalu).<br />

________________________________________________________________<br />

UWAGA:<br />

Prototyp funkcji może zostać podany również tak:<br />

LONG FAR PASCAL WndProc(HWND, unsigned, WORD, LONG);<br />

________________________________________________________________<br />

- 377-


Pierwszy parametr hWnd jest to tzw. identyfikator okna (ang.<br />

window handle). Ten parametr zawiera informację, dla którego<br />

okna przeznaczony jest komunikat. Zastosowanie takiego<br />

identyfikatora jest celowe, ponieważ funkcje typu WindowProc()<br />

mogą obsługiwać przesyłanie komunikatów do wielu okien. Jeśli<br />

okien jest wiele, okno jest identyfikowane przy pomocy tego<br />

właśnie identyfikatora (numeru).<br />

Następny parametr to sam komunikat o długości jednego słowa<br />

(word). Ten parametr przechowuje wartość z zakresu<br />

zdefiniowanego w pliku nagłówkowym WINDOWS.H. W zależności od<br />

tego co się zdarzyło, Windows mogą nam przekazać ok. 150 różnych<br />

komunikatów a w tym np.:<br />

WM_CREATE Utworzono okno<br />

WM_KEYDOWN Naciśnięto klawisz<br />

WM_SIZE Zostały zmienione wymiary okna<br />

WM_MOVE Okno zostało przesunięte<br />

WM_PAINT Okno należy narysować (powtórnie) - (re)draw<br />

WM_QUIT Koniec pracy aplikacji<br />

itp.<br />

Przedrostek WM_ to skrót od Windows Message - komunikat Windows.<br />

Wymiana komunikatów w środowisku Windows może przebiegać w różny<br />

sposób - zależnie od źródła wywołującego generację komunikatu i<br />

od charakteru zdarzenia. Ze względu na źródło można komuniakty<br />

umownie podzielić na następujące grupy:<br />

1. Działanie użytkownika (np. naciśnięcie klawisza) powoduje<br />

wygenerowanie komunikatu.<br />

2. Program - aplikacja wywołuje funkcję Windows i powoduje<br />

przesłanie komunikatu do aplikacji.<br />

3. Środowisko Windows przesyła komunikat do programu.<br />

4. Dwie aplikacje związane mechanizmem dynamicznej wymiany<br />

danych (Dinamic Data Exchange - DDE) wymieniają komunikaty.<br />

Komunikaty Windows można także podzielić umownie na następujące<br />

kategorie:<br />

1. Komunikaty dotyczące zarządzania oknami (Windows Managenent<br />

Msg.):<br />

WM_ACTIVATE (zaktywizuj lub zdezaktywizuj okno), WM_PAINT,<br />

WM_MOVE, WM_SIZE, WM_CLOSE, WM_QUIT.<br />

Bardzo istotnym szczegółem technicznym jest problem<br />

przekazywania aktywności pomiędzy oknami. Szczególnie często<br />

występuje potrzeba przekazania aktywności do elementu<br />

sterującego. Jeśli hEditWnd będzie identyfikatorem (window<br />

handle) okienka edycyjnego:<br />

- 378-


case WM_SETFOCUS:<br />

SetFocus(hEditWnd);<br />

break;<br />

funkcja SetFocus() spowoduje, że wszystkie komunikaty dotyczące<br />

zdarzeń klawiatury będą kierowane do okna sterującego, jeżeli<br />

okno macieżyste jest aktywne. Ponieważ zmiana rozmiaru okna<br />

głównego nie pociąga za sobą automatycznej zmiany rozmiaru okna<br />

sterującego, potrzebna jest dodatkowo obsługa wiadomości<br />

WM_SIZE wobec okna elementu sterującego.<br />

2. Komunikaty inicjacyjne dotyczące konstrukcji np. menu<br />

aplikacji:<br />

WM_INITMENU - zainicjuj menu (wysyłany przed zainicjowaniem),<br />

WM_INITDIALOG - zainicjuj okienko dialogowe.<br />

3. Komunikaty generowane przez Windows w odpowiedzi na wybór<br />

rozkazu z menu, zegar, bądź naciśnięcie klawisza:<br />

WM_COMMAND - wybrano rozkaz z menu,<br />

WM_KEYDOWN - naciśnięto klawisz,<br />

WM_MOUSEMOVE - przesunięto myszkę,<br />

WM_TIMER - czas minął.<br />

4. Komunikaty systemowe. Aplikacja nie musi odpowiadać na<br />

rozkazy obsługiwane przez domyślną procedurę Windows -<br />

DefWindowProc(). Szczególnie dotyczy to rozkazów nie odnoszących<br />

się do roboczego obszaru okna - Non Client Area Messages.<br />

5. Komunikaty schowka (Clipborad Messages).<br />

Sens działania funkcji WindowProc() w C/<strong>C++</strong> polega na<br />

przeprowadzeniu analizy, co się stało i podjęciu stosownej<br />

akcji. Można to realizować przy pomocy drabinki if-else-if, ale<br />

najwygodniejsze jest stosowanie instrukcji switch.<br />

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message,<br />

WORD wParam, LONG lParam)<br />

{<br />

switch (Message)<br />

{<br />

case WM_CREATE:<br />

.....<br />

break; /* Koniec obsługi komunikatu WM_CREATE */<br />

case WM_MOVE:<br />

.... /* Kod obsługi komunikatu WM_MOVE */<br />

break; /* Koniec obsługi WM_MOVE. */<br />

case WM_SIZE:<br />

.... /* Kod obsługi sytuacji WM_SIZE */<br />

break; /* Koniec obsługi WM_SIZE */<br />

.......... /* Inne, pozostałe możliwe sytuacje */<br />

case WM_CLOSE: /* Zamknięcie okna */<br />

....<br />

- 379-


eak;<br />

default: /* wariant domyślny: standardowa obsługa<br />

.... przez standardową funkcję Windows */<br />

}<br />

}<br />

________________________________________________________________<br />

UWAGA:<br />

Ponieważ komunikatów "interesujących" daną aplikację może być<br />

ponad 100 a sposobów reakcji użytkownika jeszcze więcej, w<br />

"poważnych" aplikacjach tworzone są często struktury decyzyjne o<br />

większym stopniu złożoności. Jeśli istnieje potrzeba<br />

optymalizacji działania programów stosuje się struktury dwu<br />

typów:<br />

* hierarchia wartości (Value Tree) i<br />

* drzewo analizy zdarzeń (Event Tree).<br />

Utworzone w taki sposób tzw. "Drzewo decyzyjne" nazywane także<br />

"Drzewem analizy zdarzeń" może być wielopoziomowe. Widoczny<br />

powyżej pierwszy poziom drzewa (pierwszy przesiew) realizowany<br />

jest zwykle przy pomocy instrukcji switch a następne przy pomocy<br />

drabinek typu if-else-if-break. Schemat if-else-if-break często<br />

bywa zastępowany okienkami dialogowymi.<br />

________________________________________________________________<br />

Parametry wParam i lParam przechowują parametry istotne dla<br />

danego komunikatu. wParam ma długość pojedynczego słowa (word) a<br />

lParam ma długość podwójnego słowa (long). Jeśli, dla przykładu,<br />

okno zostało przesunięte, te parametry zawierają nowe<br />

współrzędne okna.<br />

Jeżeli program ma być programem zdarzeniowym, powinniśmy przed<br />

podjęciem jakiejkolwiek akcji zaczekać aż Windows przyślą nam<br />

komunikat o tym, jakie zdarzenie nastąpiło. Wewnątrz Windows<br />

tworzona jest dla komunikatów kolejka (ang message queue).<br />

Dzięki istnieniu kolejkowania otrzymujemy komunikaty pobierane z<br />

kolejki pojedynczo. Jeśli użytkownik przesunie okno a następnie<br />

przyciśnie klawisz, to Windows wywołają funkcję WindowProc()<br />

najpierw z parametrem WM_MOVE a następnie z parametrem<br />

WM_KEYDOWN.<br />

Jednym z najważniejszych zadań funkcji WinMain() jest utworzenie<br />

kolejki dla komunikatów i poinformowanie Windows, że komunikaty<br />

do naszego programu należy kierować pod adresem funkcji<br />

WindowProc(). W tym celu stosuje się daleki wskaźnik do<br />

procedury okienkowej lpfn (Long Pointer to Function). Poza tym<br />

funkcja WinMain() tworzy okno (okna) i wyświetla je na ekranie w<br />

pozycji początkowej. Kiedy program zostaje po raz pierwszy<br />

załadowany i uruchomiony - Windows najpierw wywołują funkcję<br />

- 380-


WinMain().<br />

Windows manipulują komunikatami posługując się strukturą MSG (od<br />

messages - komunikaty). Struktura MSG jest zdefiniowana w pliku<br />

WINDOWS.H w następujący sposób:<br />

typedef struct tagMSG<br />

{<br />

HWND hwnd;<br />

WORD message;<br />

WORD wParam;<br />

LONG lParam;<br />

DWORD time;<br />

POINT pt;<br />

} MSG;<br />

Na pierwszym polu tej struktury znajduje się "identyfikator"<br />

(kod) okna, dla którego przeznaczony jest komunikat (każdy<br />

komunikat może być przesłany tylko do jednego okna). Na drugim<br />

polu struktury przechowywany jest sam komunikat. Komunikat jest<br />

zakodowany przy pomocy predefiniowanych stałych w rodzaju<br />

WM_SIZE, WM_PAINT czy WM_MOUSEMOVE. Kolejne dwa pola służą do<br />

przechowania danych-parametrów towarzyszących każdemu<br />

komunikatowi: wParam i lParam. Na następnym polu przechowywany<br />

jest w zakodowanej postaci czas - moment, w którym wystąpiło<br />

zdarzenie. Na polu pt przechowywane są współrzędne kursora<br />

myszki na ekranie w momencie w którym został wygenerowany<br />

komunikat o wystąpieniu zdarzenia. Należy zwrócić tu uwagę, że<br />

typ POINT oznacza strukturę. Struktura POINT (punkt) w Windows<br />

wygląda tak:<br />

typedef struct tagPOINT<br />

{<br />

int x;<br />

int y;<br />

} POINT;<br />

Aby mieć pewność, że otrzymaliśmy wszystkie komunikaty, które<br />

zostały do nas skierowane, w programie wykonywana jest pętla<br />

pobierania komunikatów (message loop) wewnątrz funkcji<br />

WinMain(). Na początek wywoływana jest zwykle okienkowa (czyli<br />

należącą do Windows API) funkcja GetMessage(). Ta funkcja<br />

wypełnia strukturę komunikatów i zwraca wartość. Zwracana przez<br />

funkcję wartość jest różna od zera, jeżeli otrzymany właśnie<br />

komunikat był czymkolwiek za wyjątkiem WM_QUIT. Komunikat<br />

WM_QUIT jest komunikatem kończącym pracę każdej aplikacji dla<br />

Windows. Jeśli otrzymamy komunikat WM_QUIT powinniśmy przerwać<br />

pętlę pobierania komunikatów i zakończyć pracę funkcji<br />

WinMain(). Taka sytuacja oznacza, że więcej komunikatów nie<br />

będzie. Po uwzględnieniu tych warunków pętla może wyglądać tak:<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \<br />

LPSTR lpszCmdLine, int nCmdShow)<br />

....<br />

- 381-


while(GetMessage(&msg,NULL,0,0)) //Poki nie otrzymamy WM_QUIT<br />

{<br />

....<br />

}<br />

Po naciśnięciu przez użytkownika klawisza generowany jest<br />

komunikat WM_KEYDOWN. Jednakże z faktu otrzymania komunikatu<br />

WM_KEYDOWN nie wynika, który klawisz został przyciśnięty, czy<br />

była to duża, czy mała litera. Funkcję TranslateMessage()<br />

(PrzetłumaczKomunikat) stosuje się do przetłumaczenia komunikatu<br />

WM_KEYDOWN na komunikat WM_CHAR. Komunikat WM_CHAR przekazuje<br />

przy pomocy parametru wParam kod ASCII naciśniętego klawisza.<br />

Funkcję TranslateMessage() stosujemy w pętli pobierania<br />

komunikatów tak:<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \<br />

LPSTR lpszCmdLine, int nCmdShow)<br />

....<br />

while(GetMessage(&msg, 0, 0, 0))<br />

{<br />

TranslateMessage(&msg);<br />

....<br />

}<br />

W tym stadium program jest gotów do przesłania komunikatu do<br />

funkcji - procedury okienkowej WindowProc(). Posłużymy się w tym<br />

celu funkcją DispatchMessage() (ang. dispatch - odpraw, przekaż,<br />

DispatchMessage = OtprawKomunikat). Funkcja WinMain()<br />

poinformowała wcześniej Windows, że odprawiane komunikaty<br />

powinny trafić właśnie do WindowProc().<br />

while(GetMessage(&msg, NULL, NULL, NULL))<br />

{<br />

TranslateMessage(&msg);<br />

DispatchMessage(&msg);<br />

}<br />

Tak funkcjonuje pętla pobierająca komunikaty od Windows i<br />

przekazująca je funkcji WindowProc(). Pętla działa do momentu<br />

pobrania komunikatu WM_QUIT (Koniec!). Otrzymanie komunikatu<br />

WM_QUIT powoduje przerwanie pętli i zakończenie pracy programu.<br />

Komunikaty systemowe (system messages), które są kierowane do<br />

Windows także trafiają do tej pętli i są przekazywane do<br />

WindowProc(), ale ich obsługą powinna się zająć specjalna<br />

funkcja DefWindowProc() - Default Window Procedure, umieszczona<br />

na końcu (wariant default).<br />

Jest to standardowa dla aplikacji okienkowych postać pętli<br />

pobierania komunikatów.<br />

Jak widać, wymiana informacji pomiędzy użytkownikiem,<br />

środowiskiem a aplikacją przebiega tu trochę inaczej niż w DOS.<br />

Program pracujący w środowisku tekstowym DOS nie musi np.<br />

- 382-


ysować własnego okna.<br />

[Z]<br />

________________________________________________________________<br />

1. Uruchom Windows i popatrz świadomym, fachowym okiem, jak<br />

przebiega przekazywanie aktywności (focus) między okienkami<br />

aplikacji.<br />

________________________________________________________________<br />

- 383-


LEKCJA 40. JAK TWORZY SIĘ APLIKACJĘ DLA Windows?<br />

________________________________________________________________<br />

W trakcie tej lekcji dowiesz się, jak "poskładać" aplikację dla<br />

Windows z podstawowych funkcji interfejsu API i jakie komunikaty<br />

są najważniejsze dla naszych aplikacji.<br />

________________________________________________________________<br />

Przy tworzeniu programu zwróćmy szczególną uwagę na to, co<br />

dzieje się w programie po otrzymaniu komunikatu WM_PAINT (należy<br />

narysować okno). Jest to żądanie ze strony Windows, by program<br />

narysował obszar roboczy (client area) swojego okna. Program<br />

otrzyma komunikat WM_PAINT zawsze na początku, kiedy powinien<br />

narysować swoje okno po raz pierwszy i później powtórnie, za<br />

każdym razem, gdy trzeba będzie odtworzyć okno na ekranie. Jeśli<br />

inne okno przesuwane po ekranie przysłoni okno naszego programu,<br />

po odsłonięciu naszego okna Windows prześlą do programu<br />

komunikat WM_PAINT - odtwórz swoje okno - narysuj go powtórnie<br />

(redraw, repaint). Jeśli zechcemy wyprowadzić na ekran napis<br />

"Hello World" także będziemy musieli narysować okno od nowa. Nie<br />

zawsze "odświeżenia" wymaga całe okno. W każdej z sytuacji:<br />

- całe okno zostało przysłonięte i odsłonięte<br />

- część okna wymaga odświeżenia<br />

- okno jest rysowane po raz pierwszy<br />

Windows prześlą do programu ten sam komunikat - WM_PAINT.<br />

Jeśli odtworzenia wymaga tylko część okna, taka część okna<br />

nazywa się nieważną-nieaktualną (ang. invalid). W Windows takie<br />

nieaktualne fragmenty okna zawsze mają kształt prostokątów.<br />

Wyobraźmy sobie, że jakieś inne okno przesłoniło narożnik okna<br />

naszego programu. Jeśli użytkownik usunie to przesłaniające<br />

okno, odsłonięty obszar będzie potraktowany przez Windows jako<br />

nieaktualny. Windows prześlą do aplikacji komunikat WM_PAINT<br />

żądający odtworzenia okna. Żądając odtworzenia okna Windows<br />

powinny nas poinformować która część naszego okna została na<br />

ekranie "zepsuta". Współrzędne prostokąta na ekranie Windows<br />

przekażą przy pomocy specjalnej struktury nazywanej strukturą<br />

rysunku (ang. paint structure - PAINTSTRUCT).<br />

Strukturę rysunku możemy nazwać w programie np.:<br />

PAINSTRUCT ps;<br />

W funkcji WindowProc() obsługa komunikatu WM_PAINT rozpoczyna<br />

się od wyczyszczenia pól struktury rysunku ps. Struktura<br />

predefiniowanego typu PAINTSTRUCT (w WINDOWS.H) zawiera<br />

informacje o rysunku.<br />

PAINTSTRUCT ps;<br />

{<br />

switch (Message)<br />

{<br />

case WM_CREATE:<br />

..... break;<br />

case WM_MOVE:<br />

- 384-


.... break;<br />

case WM_SIZE:<br />

.... break;<br />

case WM_PAINT: /* Obsługa rysowania okna */<br />

memset(&ps, 0x00, sizeof(PAINTSTRUCT);<br />

....<br />

break; //Koniec obsługi WM_PAINT<br />

case WM_CLOSE:<br />

.... break;<br />

default: .....<br />

}<br />

}<br />

Następnie pola struktury rysunku zostają wypełnione poprzez<br />

okienkową funkcją BeginPaint() - RozpocznijRysowanie. Zwróć<br />

uwagę, że do poprawnego działania funkcji potrzebne są<br />

informacje o tym, które okno trzeba odświeżyć (Windows powinny<br />

wiedzieć wobec którego okna żądamy informacji o "zepsutym"<br />

prostokącie) i adres naszej struktury rysunku. Aby przekazać te<br />

informacje postępujemy tak:<br />

case WM_PAINT:<br />

memset(&ps, 0x00, sizeof(PAINTSTRUCT));<br />

hDC = BeginPaint(hWnd, &ps);<br />

....<br />

Teraz funkcja BeginPaint() może wypełnić naszą strukturę rysunku<br />

ps danymi. Pola struktury typu PAINTSTRUCT wyglądają<br />

następująco:<br />

typedef struct tagPAINTSTRUCT<br />

{<br />

HDC hdc;<br />

BOOL fErase;<br />

RECT rcPaint;<br />

BOOL fRestore;<br />

BYTE rgbReserved[16];<br />

} PAINTSTRUCT;<br />

Przy pomocy pola typu RECT (ang. rectangle - prostokąt) Windows<br />

przekazują do programu współrzędne wymiary (ang. dimensions)<br />

"zepsutego" na ekranie prostokąta. Typ RECT oznacza następującą<br />

strukturę:<br />

typedef struct tagRECT<br />

{<br />

int left; //współrzędna lewa - x<br />

int top; //współrzędna górna - y<br />

int right; //współrzędna prawa - x<br />

int bottom; //współrzędna dolna - y<br />

} RECT;<br />

- 385-


Górny lewy róg nieaktualnego prostokąta (invalid rectangle) ma<br />

dwie współrzędne (left, top) a dolny prawy róg prostokąta ma<br />

współrzędne (right, bottom). Te współrzędne ekranowe mierzone są<br />

w pikselach i są to współrzędne względne - względem lewego<br />

górnego narożnika okna aplikacji. Lewy górny narożnik okna<br />

aplikacji ma więc współrzędne (0,0).<br />

Zwróćmy uwagę na wartość zwracaną przez funkcję BeginPaint() -<br />

zmienną hDC:<br />

case WM_PAINT:<br />

memset(&ps, 0x00, sizeof(PAINTSTRUCT));<br />

hDC = BeginPaint(hWnd, &ps);<br />

....<br />

Wszystnie operacje graficzne będą wymagać nie kodu okna hWnd a<br />

właśnie kodu-identyfikatora kontekstowego hDC.<br />

Na początku pracy programu, gdy okno jest rysowane po raz<br />

pierwszy, Windows generują komunikat WM_PAINT i cały obszar<br />

roboczy okna jest uznawany za nieaktualny. Kiedy program otrzyma<br />

ten pierwszy komunikat, możemy wykorzystać to do umieszczenia w<br />

oknie np. napisu. Jeśli tekst ma rozpoczynać się od lewego<br />

górnego narożnika okna aplikacji, funkcja TextOut() używana w<br />

Windows do wykreślania tekstu (w trybie graficznym) powinna<br />

rozpoczynać wyprowadzanie tekstu od punktu o (pikselowych)<br />

współrzędnych (0,0).<br />

case WM_PAINT:<br />

...<br />

TextOut(hDC, 0, 0, (LPSTR) "Tekst", strlen("Tekst"));<br />

EndPaint(hWnd, &ps);<br />

break;<br />

Funkcja TextOut() (wyprowadź tekst) pobiera pięć parametrów:<br />

hDC - identyfikator-kod prostokąta, który należy narysować<br />

x - współrzędna pozioma (w pikselach)<br />

y - współrzędna pionowa początku naszego napisu<br />

W tym przypadku współrzędne wynoszą (0,0).<br />

LPSTR - wskaźnik do łańcucha znaków "Hello world."<br />

LPSTR = long pointer to string (wskaźnik typu far).<br />

Wskaźnk ten przekazujemy do funkcji poprzez forsowanie typu:<br />

... (LPSTR) "Tekst";<br />

Zgodnie z definicją typu w pliku WINDOWS.H spowoduje to zamianę<br />

wskaźnika do łańcucha typu near char* (bliski) na wskaźnik typu<br />

far (daleki). Ostatni parametr funkcji to długość wyprowadzanego<br />

tekstu - tu obliczana przez funkcję strlen().<br />

Prześledźmy etapy powstawania aplikacji.<br />

- 386-


Funkcja MainWin() rejestruje i tworzy główne okno programu oraz<br />

inicjuje globalne zmienne i struktury. Funkcja WinMain() zawiera<br />

pętlę pobierania komunikatów. Każdy komunikat przeznaczony dla<br />

głównego okna (lub ewentualnych nastepnych okien potomnych) jest<br />

pobierany, ewentualnie poddawany translacji i przekazywany do<br />

funkcji obsługującej dialog z Windows. Przed zakończeniem<br />

programu funkcja WinMain() kasuje utworzone wcześniej obiekty,<br />

zwalnia pamięć i pozostałe zasoby.<br />

UWAGA: "Obiekty" nie są tu użyte w sensie stosowanym w OOP.<br />

"Obiekt" oznacza tu np. strukturę.<br />

int PASCAL WinMain(HANDLE hInstance, hPrevInstance,<br />

LPSTR lpszCmLine, int nCmdShow)<br />

{ ...<br />

HANDLE hInstance - identyfikator bieżącego pojawienia się danej<br />

aplikacji. Ponieważ w Windows program może być uruchamiany<br />

wielokrotnie, stosuje sie pojecie tzw. "Instancji" - wystąpienia<br />

- uruchomienia programu.<br />

HANDLE hPrevInstance - identyfikator poprzedniego wystąpienia<br />

danej aplikacji<br />

LPSTR lpszCmdLine - daleki wskaźnik do parametrów wywołania<br />

programu z linii rozkazu<br />

int nCmdShow - sposób początkowego wyświetlenia okna<br />

(pełne okno, bądź ikona)<br />

Deklaracja struktury typu MSG (Message) do przechowywania<br />

komunikatów.<br />

MSG<br />

msg;<br />

Nadanie nazwy aplikacji:<br />

strcpy(szAppName, "Nazwa Aplikacji");<br />

Rejestrujemy struktury okien jeśli jest to pierwsze uruchomienie<br />

danej aplikacji i sprawdzamy, czy rejestracja powiodła się:<br />

if(!PrevInstance)<br />

{<br />

if((int nRc = RegisterClass() ...<br />

Utworzenie głównego okna programu (może się nie udać):<br />

hWndMain = CreateWindow(....);<br />

if(hWndMain == NULL)<br />

- 387-


{<br />

MessageBox(0, "Klops", "Koniec", MB_OK);<br />

return (FALSE);<br />

}<br />

Wyświetlenie głównego okna na ekranie:<br />

ShowWindow(hWndMain, nCmdShow);<br />

Pętla komunikatów wykrywająca komunikat WM_QUIT:<br />

while(GetMessage(&msg, 0, 0, 0))<br />

{<br />

TranslateMessage(&msg);<br />

DispatchMessage(&msg);<br />

}<br />

Główna procedura obsługi okna WindowProc().<br />

Instrukcja switch przełącza do odpowiedniego wariantu działania<br />

- obsługi odpowiedniego komunikatu. Muszą tu znajdować sie<br />

procedury obsługi wszystkich interesujacych nas działań<br />

uzytkownika i ogólnych komunikatow Windows (np. WM_CLOSE). Jeśli<br />

wystąpi taki komunikat, którego obsługa nie została<br />

przewidziana, obsługa jest przekazywana, do funkcji okienkowej<br />

DefWindowProc() - obsługę przejmują Windows.<br />

Komunikaty inicjowane przez użytkownika są rozpatrywane<br />

zasadniczo jako WM_COMMAND. Rozkaz wybrany z menu lub<br />

odpowiadająca mu kombinacja klawiszy jest przekazywana przy<br />

pomocy pierwszego parametru komunikatu - wParam. Kod<br />

odpowiadający rozkazowi z menu nazywa sie "control menu ID", a<br />

identyfikator kombinacji klawiszy - "accelerator ID". Procedura<br />

obsługi komunikatów powinna zawierać<br />

case (WM_COMMAND): ..... break;<br />

Wewnątrz przy pomocy instrukcji switch{...} należałoby<br />

rozpatrywać kolejne warianty, wykorzystując identyfikator<br />

wybranego z menu rozkazu - ID. Obsługa komunikatow świadczących<br />

o wyborze przez użytkownika rozkazu z menu stanowi zwykle główną<br />

roboczą cześć programu.<br />

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message, WORD wParam,<br />

LONG lParam)<br />

{<br />

HMENU hMenu=0; /* Identyfikator menu */<br />

HBITMAP hBitmap=0; /* Identyfikator mapy bitowej */<br />

HDC hDC; /* Identyfikator kontekstowy */<br />

PAINSTRUCT ps; /* Struktura rysunku */<br />

int nRc=0; /* Zwrot kodu przez funkcje */<br />

switch (message)<br />

- 388-


{<br />

case WM_CREATE:<br />

Gdy okno jest tworzone Windows przesyłają jeden raz komunikat<br />

WM_CREATE do okna. Procedura obsługi nowego okna (new window<br />

procedure) otrzymuje ten komunikat po utworzeniu okna, ale<br />

jeszcze zanim okno pojawi sie na ekranie.<br />

lParam - Wskaźnik do struktury CREATESTRUCT o postaci:<br />

typedef struct {<br />

LPSTR lpCreateParams;<br />

HANDLE hInst;<br />

HANDLE hMenu;<br />

HWND hwndParent;<br />

int cy;<br />

int cx;<br />

int y;<br />

int x;<br />

LONG style;<br />

LPSTR lpszName;<br />

LPSTR lpszClass;<br />

DWORD dwExStyle;<br />

} CREATESTRUCT; */<br />

Kod obsługi powiekszania/zmniejszania case WM_SIZE.<br />

wParam zawiera kod operacji - zmniejsz/powiększ<br />

lParam zawiera nową wysokość i szerokość okna<br />

case WM_PAINT:<br />

Pobranie kontekstowego identyfikatora urządzenia. Funkcja<br />

BeginPaint() spowoduje w razie potrzeby wysłanie komunikatu<br />

WM_ERASEBKGND (Erase Background - skasuj tło).<br />

memset(&ps, 0x00, sizeof(PAINTSTRUCT));<br />

hDC = BeginPaint(hWnd, &ps);<br />

Set Background Mode - ustaw rodzaj tła (tu: przezroczyste):<br />

SetBkMode(hDC, TRANSPARENT);<br />

Aplikacja powinna wykreślić obszar roboczy okna posługując sie<br />

grafiką GDI i (Graficzny Interfejs Urządzenia - analogia do<br />

graficznego standardu BGI w środowisku DOS). Struktura ps typu<br />

PAINSTRUCT zwrócona przez BeginPaint() wskazuje prostokąt do<br />

zamalowania.<br />

Wypisanie tekstu w głównym oknie aplikacji:<br />

TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello,<br />

world."));<br />

- 389-


Funkcja TextOut() pracuje w trybie graficznym, więc (podobnie<br />

jak inne funkcje graficzne Windows API) otrzymuje jako argument<br />

tzw. "kontekst urządzenia" - hDC.<br />

Zamykanie okna:<br />

case WM_CLOSE:<br />

DestroyWindow(hWnd);<br />

if (hWnd == hWndMain)<br />

PostQuitMessage(0);<br />

Jeśli zamknięte zostało główne okno aplikacji, funkcja<br />

PostQuitMessage() wysyła do Windows komunikat, że aplikacja<br />

zakończyła działanie i okno aplikacji zostało usunięte. W tym<br />

stadium stosuje się funkcje PostQuitMessage() i<br />

PostAppMessage(). Pozostale przypadki są obsługiwane przez<br />

wariant domyślny - default. Przekazanie komunikatu do obsługi<br />

przez Windows.<br />

default:<br />

return (DefWindowProc(hWnd, Message, wParam, lParam));<br />

Funkcja rejestrująca wszystkie klasy wszystkich okien związanych<br />

z bieżącą aplikacja (nazwiemy ją roboczo FRegisterClasses()).<br />

Jesli operacja sie powiodła - funkcja zwraca kod błędu.<br />

int FRegisterClasses(void)<br />

{<br />

WNDCLASS wndclass; /* Struktura do definiowania klas okien. */<br />

memset(&wndclass, 0x00, sizeof(WNDCLASS));<br />

Ustawienie parametrów okna w strukturze:<br />

wndclass.style = CS_HRDRAW | CS_VRDRAW;<br />

wndclass.lpfnWindowProc = WindowProc;<br />

Dodatkowa pamięć dla klasy Window i obiektów klasy Window.<br />

Dołączanie innych zasobów odbywa się przy pomocy funkcji:<br />

LoadBitmap() - załaduj mapę bitową<br />

LoadIcon() - załaduj ikonkę<br />

LoadCurcor(), LoadMenu(), itp. ...<br />

wndclass.cbClsExtra = 0;<br />

wndclass.cbWndExtra = 0;<br />

wndclass.hInstance = hInst;<br />

wndclass.hIcon = LoadIcon(NULL, ID_ICON);<br />

wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);<br />

Utworzenie pędzla (brush) dla tła:<br />

wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);<br />

wndclass.lpszMenuName = szAppName;<br />

wndclass.lpszClassName = szAppName;<br />

- 390-


if (!RegisterClass(&wndclass)) return -1;<br />

}<br />

Typowe obiekty ze składu Windows to<br />

HBRUSH Pędzel; i<br />

HPEN Ołówek;<br />

Należy tu zwrócić uwagę jeszcze na dwa szczegóły techniczne. DC<br />

i GDI - Device Context, Graphics Device Interface - to tzw.<br />

kontekst urządzenia i graficzny interfejs urządzenia. Pozwala to<br />

Windows działać skutecznie w trybie "Device Independent"<br />

(niezależnym od sprzętu).<br />

- 391-


LEKCJA 41. KOMPILATORY "SPECJALNIE DLA Windows".<br />

________________________________________________________________<br />

Z tej lekcji dowiesz się, czym różnią się kompilatory<br />

przeznaczone dla pracy w środowisku Windows.<br />

________________________________________________________________<br />

W IDE i w sposobie zachowania zaszły istotne zmiany. Posługując<br />

się Turbo <strong>C++</strong> z pakietu BORLAND <strong>C++</strong> 3.0 lub BCW z pakietu 3.1<br />

możemy korzystać z uroków i usług Windows szerzej niż do tej<br />

pory. Możemy otwierać wiele okien i uruchamiać bezpośrednio z<br />

poziomu IDE okienkowe aplikacje. W głównym menu kompilatora<br />

zaszły pewne zmiany (sygnalizujące obiektowo- i okienkowo -<br />

zorientowaną ewolucję pakietów <strong>Borland</strong>a), na które warto zwrócić<br />

uwagę.<br />

Zniknęło menu Debug (co wcale nie oznacza, że nie możemy<br />

korzystać z Debuggera), pojawiło się natomiast nowe menu Browse<br />

(przeglądanie). Rozkazy, których tradycyjnie szukaliśmy w menu<br />

Debug zostały rozrzucone do innych menu. I tak:<br />

Menu Compile zawiera:<br />

Compile (kompilacja do *.OBJ),<br />

Make (kompilacja i konsolidacja do *.EXE),<br />

Link (konsolidacja bez powtórnej kompilacji),<br />

Build all (konsolidacja wszystkich modułów),<br />

Information... (informacja o przebiegu kompilacji),<br />

Remove messages (usuwanie komunikatów z pliku wynikowego)<br />

Menu Run zawiera:<br />

Run (uruchomienie i ewentualna rekompilcja),<br />

Arguments... (argumenty uruchomieniowe z wiersza rozkazu),<br />

Debugger (zamiast w Debug - TU!)<br />

Debugger arguments... (argumenty dla Debuggera)<br />

Menu Project zawiera:<br />

Open project<br />

- otwórz (nowy lub istniejący) plik projektu,<br />

Close project - zamknij projekt,<br />

Add item... - dodaj element (plik) do projektu,<br />

Delete item - usuń element (plik) z projektu,<br />

Include ˙˙files... ˙˙- ˙˙podaj ˙katalog ˙zawierający ˙dodatkowe<br />

dołączane do programu pliki nagłówkowe *.H<br />

W menu Options (zestaw znany już z <strong>Borland</strong> <strong>C++</strong>) warto zwrócić<br />

uwagę na pewną dodatkową możliwość. Jak wiemy z doświadczenia,<br />

uruchamiając program często dokonujemy zmian i korekt w pliku<br />

żródłowym *.C, czy *.CPP. Znacznie rzadziej jednak zmieniamy<br />

zestaw dołączanych do programu plików nagłówkowych *.H. Wiemy<br />

również, że kompilacja tych właśnie plików nagłówkowych zajmuje<br />

często lwią część czasu całej kompilacji i konsolidacji<br />

programu. <strong>Borland</strong> zauważył to i w okienku dialogowym:<br />

- 392-


Options | Compiler | Code generation --> Code Generation Options<br />

umieścił opcję Pre-compiled headers (pliki nagłówkowe wstępnie<br />

skompilowane wcześniej - i tylko jeden raz). Szczególnie w<br />

przypadku aplikacji okienkowych może to znacznie przyspieszyć<br />

proces uruchamiania i "szlifowania" naszych programów. Nie ma<br />

jednak nic za darmo. <strong>Borland</strong>/Turbo <strong>C++</strong> po skompilowaniu plików<br />

nagłówkowych tworzy na dysku roboczy plik *.SYM nadając mu nazwę<br />

zgodną z nazwą bieżącego projektu (jest to zwykle nazwa głównego<br />

modułu *.CPP) i do poprawnego działania wymaga kilkadziesiąt lub<br />

nawet kilkaset kilobajtów dodatkowej przestrzeni na dysku.<br />

[!!!]UWAGA<br />

________________________________________________________________<br />

Jeśli przenosisz projekt na dyskietkę i tam kontynuujesz pracę<br />

nad projektem, pamiętaj, że może zabraknąć miejsca na<br />

prekompilowany plik .SYM.<br />

________________________________________________________________<br />

Czytelnik zechce sam sprawdzić w jakim stopniu przyspieszy to<br />

kompilację naszego własnego programu proceduralno -<br />

zdarzeniowego WINPZ1.CPP:<br />

WINZ1.CPP. Jednomodułowa aplikacja proceduralno - zdarzeniowa<br />

dla Windows.<br />

________________________________________________________________<br />

#include <br />

#pragma argused<br />

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpszCmdLine, int nCmdShow )<br />

{<br />

WNDCLASS Okno1;<br />

MSG komunikaty;<br />

HWND NrOkna;<br />

LPSTR LongPtr1 = "Okno 1";<br />

LPSTR lp2 = "AM: B<strong>C++</strong> 3..4/Reczne sterowanie (1)";<br />

if (hPrevInstance == 0)<br />

{<br />

Okno1.style= CS_HREDRAW | CS_VREDRAW ;<br />

Okno1.lpfnWndProc= WndProc;<br />

Okno1.cbClsExtra = 0;<br />

Okno1.cbWndExtra= 0;<br />

Okno1.hInstance = hInstance;<br />

Okno1.hCursor = LoadCursor(0, IDC_CROSS );<br />

- 393-


Okno1.hbrBackground= GetStockObject(WHITE_BRUSH );<br />

Okno1.lpszMenuName= 0;<br />

Okno1.lpszClassName= LongPtr1;<br />

if (!RegisterClass(&Okno1))<br />

return 0;<br />

}<br />

NrOkna = CreateWindow(LongPtr1, lp2, WS_VISIBLE |<br />

WS_SYSMENU |<br />

WS_MINIMIZEBOX | WS_VSCROLL | WS_MAXIMIZEBOX,<br />

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,<br />

0, 0, hInstance, 0);<br />

ShowWindow(NrOkna, nCmdShow);<br />

UpdateWindow(NrOkna);<br />

while (GetMessage(&komunikaty, 0, 0, 0))<br />

{<br />

TranslateMessage(&komunikaty );<br />

DispatchMessage(&komunikaty );<br />

}<br />

return 0;<br />

}<br />

long FAR PASCAL WndProc (HWND NrOkna, unsigned KomunikatWindows,<br />

WORD wParam, LONG lParam)<br />

{<br />

HDC NrKontekstu;<br />

PAINTSTRUCT struktura_graficzna;<br />

RECT prostokat;<br />

switch(KomunikatWindows)<br />

{<br />

case WM_PAINT:<br />

{<br />

NrKontekstu = BeginPaint(NrOkna, &struktura_graficzna);<br />

GetClientRect(NrOkna, &prostokat);<br />

TextOut(NrKontekstu,80,50, ": Reczne sterowanie:", 20 );<br />

TextOut(NrKontekstu, 5,70, "Tu -->", 6);<br />

TextOut(NrKontekstu, 5, 85, "Blad:", 5);<br />

TextOut(NrKontekstu,75,70, "-----------------------------",<br />

40);<br />

TextOut(NrKontekstu,30,110, "<strong>Programowanie</strong> proceduralno -<br />

zdarzeniowe.", 41 );<br />

TextOut(NrKontekstu,30,135, "Szablon moze zostac rozbudowany<br />

o inne funkcje.", 47 );<br />

TextOut(NrKontekstu,30,180, "RECZNIE panujemy np. nad:", 25<br />

);<br />

TextOut(NrKontekstu,20,220, "paskiem tytulowym okna, tytulem<br />

ikonki...", 41);<br />

- 394-


TextOut(NrKontekstu, 100, 250, "!KONIEC - [Alt]+[F4]", 20);<br />

EndPaint(NrOkna,&struktura_graficzna);<br />

break;<br />

}<br />

case WM_DESTROY:<br />

{<br />

PostQuitMessage(0);<br />

break;<br />

}<br />

default:<br />

return DefWindowProc(NrOkna,KomunikatWindows,wParam,lParam);<br />

}<br />

}<br />

return 0;<br />

Program demonstruje opisane wyżej mechanizmy, może być<br />

uruchamiany wielokrotnie i sprowadzony do ikony. Z uwagi na brak<br />

zdefiniowanych dodatkowych zasobów (brak w projekcie plików:<br />

.RC - resources - zasoby<br />

.ICO - ikona<br />

.DEF - definicji<br />

.PRJ lub .IDE - projektu<br />

.DSK - konfiguracyjnego<br />

itp.)<br />

podczas kompilacji programu wystąpią dwa komunikaty<br />

ostrzegawcze. Komunikaty te można zignorować.<br />

A oto druga przykładowa aplikacja w tym samym stylu. Tym razem<br />

funkcja okienkowa reaguje na naciśnięcie lewego klawisza myszki,<br />

co powoduje wygenerowanie komunikatu WM_LEFTBUTTONDOWN.<br />

Program WINZ-2.CPP<br />

________________________________________________________________<br />

#include <br />

#include <br />

#pragma argused<br />

char napis[10];<br />

int X, Y;<br />

LONG FAR PASCAL WndProc (HWND, WORD, WORD, LONG);<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpszCmdLine, int nCmdShow )<br />

{<br />

WNDCLASSwndClass;<br />

MSGmsg;<br />

HWNDhWnd;<br />

LPSTR Lp1 = "Mysza1";<br />

LPSTR lp2 = "WINPZ2: Wykrywanie Lewego Klawisza<br />

- 395-


Myszki";<br />

if (!hPrevInstance)<br />

{<br />

wndClass.style= CS_HREDRAW | CS_VREDRAW ;<br />

wndClass.lpfnWndProc= WndProc;<br />

wndClass.cbClsExtra = 0;<br />

wndClass.cbWndExtra= 0;<br />

wndClass.hInstance = hInstance;<br />

wndClass.hIcon = 0;<br />

wndClass.hCursor= LoadCursor(0, IDC_ARROW );<br />

wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );<br />

wndClass.lpszMenuName= 0;<br />

wndClass.lpszClassName= Lp1;<br />

if (!RegisterClass(&wndClass))<br />

exit(1);<br />

}<br />

hWnd = CreateWindow(Lp1, lp2, WS_OVERLAPPEDWINDOW,<br />

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,<br />

0, 0, hInstance, 0);<br />

ShowWindow(hWnd, nCmdShow);<br />

UpdateWindow(hWnd);<br />

while (GetMessage(&msg, 0, 0, 0))<br />

{<br />

TranslateMessage(&msg );<br />

DispatchMessage(&msg );<br />

}<br />

return 0;<br />

}<br />

LONG FAR PASCAL WndProc (HWND hWnd, WORD Message,<br />

WORD wParam, LONG lParam)<br />

{<br />

HDC hDC;<br />

PAINTSTRUCT ps;<br />

RECT rect;<br />

switch(Message)<br />

{<br />

case WM_SIZE:<br />

hDC = GetDC( hWnd );<br />

TextOut(hDC, 50, 100, "Wykrywanie nacisniecia", 22);<br />

TextOut(hDC, 50, 120, "lewego klawisza myszki.", 23);<br />

TextOut(hDC, 20, 140, "Komunikat o zdarzeniu: ", 22);<br />

TextOut(hDC, 20, 156, "Left Button Down - LBUTTONDOWN", 31);<br />

TextOut(hDC, 50, 170, "Po wcisnieciu klawisza,", 23);<br />

TextOut(hDC, 50, 190,"w biezacej pozycji kursora, pojawi sie<br />

napis


case WM_PAINT:<br />

hDC = BeginPaint(hWnd, &ps);<br />

TextOut(hDC, X,Y, napis, strlen(napis));<br />

EndPaint(hWnd, &ps);<br />

break;<br />

case WM_LBUTTONDOWN:<br />

strcpy(napis,"


Kompilacja przebiegnie poprawnie (pamiętaj o Opcjach i<br />

Katalogach), mimo to pojawią się jednak dwa komunikaty<br />

ostrzegawcze. W okienku "Compile Status" (stan/przebieg<br />

kompilacji) pojawi się zawartość:<br />

Lines 3832 (znakomita większość to WINDOWS.H,<br />

prekompilacja byłaby celowa)<br />

Warnings: 1<br />

Errors: 0<br />

Jeśli wybierzesz klawisz [OK] w okienku "focus" (aktywność)<br />

zostanie przekazana do okienka komunikatów "Message" a tam<br />

pojawi się napis:<br />

Warning: Parameter 'lspzCmdLine' is never used.<br />

Wskaźnik do parametrów uruchomieniowych programu (Arguments)<br />

pobieranych z wiersza rozkazu nie został ani raz użyty w<br />

programie. Na to nic nie możemy poradzić. Po prostu argumenty<br />

uruchomieniowe nie są nam potrzebne. Wykonujemy więc "klik"<br />

(przekazanie "focusa") w okienku edycyjnym i możemy przejść do<br />

następnej czynności:<br />

4. Konsolidacja: Compile | Link.<br />

W okienku "Message" znów pojawi się ostrzeżenie:<br />

Linker Warning: No module definition file specified:<br />

using defaults.<br />

(brak wyspecyfikowanego pliku definicji .DEF; stosuję wartości<br />

domyślne)<br />

I tu już możemy coś zaradzić. Możemy zatem pokusić się o<br />

stworzenie naszego pierwszego pliku definicji (nazwa jest trochę<br />

myląca - chodzi o zdefiniowanie sposobu wykorzystania zasobów<br />

środowiska Windows).<br />

Aby utworzyć plik .DEF (jest to plik ASCII) należy:<br />

1. Otworzyć nowe okienko edycyjne (nie wychodząc z IDE):<br />

File | New<br />

Otworzy się okienko NONAMExx.CPP. Ta nazwa nie jest oczywiście<br />

najodpowiedniejsza, więc umieszczamy plik we właściwym katalogu<br />

(tym samym, co główny program *.CPP) przy pomocy rozkazu File |<br />

Save as...<br />

i nadajemy plikowi stosowną nazwę i rozszerzenie *.DEF. Okieno<br />

pozostaje puste, ma jednak "focus" i nową nazwę, np.<br />

C:\..\PR.DEF.<br />

3. Redagujemy nasz pierwszy plik definicji, np. tak:<br />

NAME JAKAKOLWIEK //


EXETYPE WINDOWS //


7. Wpisujemy nazwę kolejnego pliku wchodzącego w skład projektu<br />

(w tym przypadku WINZ2.DEF).<br />

8. Wybieramy klawisz [+Add] w okienku.<br />

UWAGA: Czynności 7) i 8) w przypadku bardziej złożonych<br />

projektów będą powtarzane wielokrotnie.<br />

9. Wybieramy klawisz [Done] w okienku (zrobione/gotowe).<br />

Konfigurowanie projektu zostało zakończone.<br />

10. Przy pomocy rozkazów Compile, Link, Make, Build all, Run<br />

możemy teraz skompilować, skonsolidować i uruchomić nasz program<br />

w postaci projektu. Ostrzeżenie Linkera zniknie.<br />

[!!!]UWAGA<br />

________________________________________________________________<br />

W dolnej części ekranu w stadium tworzenia projektów ( i póżniej<br />

po załadowaniu pliku projektu [Open Project] pojawi się lista<br />

plików. Do trybu edycji pliku możesz przjść poprzez dwukrotne<br />

klinięcie pliku na tej liście.<br />

Zwróć uwagę, że pliki projektów .PRJ ( w <strong>Borland</strong> 4+ .IDE)<br />

przechowują również informacje o konfiguracji. Najważniejsza z<br />

nich to informacja o katalogach, z których korzysta kompilator:<br />

Options | Directories... | Include<br />

Options | Directories... | Library<br />

Options | Directories... | Output<br />

________________________________________________________________<br />

Najwygodniej przechowywać wszystkie pliki wchodzące w skład<br />

jednego projektu w odrębnym katalogu dyskowym. Dla wprawy załóż<br />

odrębny katalog i zapisz tam pliki:<br />

*.CPP<br />

*.DEF<br />

*.PRJ<br />

(lub *.IDE)<br />

dla swoich pierwszych dwóch projektów, które właśnie powstały.<br />

[!!!] UWAGA<br />

________________________________________________________________<br />

Ten sam plik definicji możesz wykorzystywać do tworzenia<br />

następnych przykładowych aplikacji typu Windows EXE.<br />

________________________________________________________________<br />

- 400-


LEKCJA 42. Elementy sterujące i zarządzanie programem.<br />

________________________________________________________________<br />

Jak sterować pracą aplikacji. Jak umieszczać elementy<br />

graficzne-sterujące w oknie aplikacji. Najczęściej stosowane<br />

funkcje API Windows.<br />

________________________________________________________________<br />

Elementy sterujące pracą aplikacji w Windows (ang. controls) są<br />

również swoistymi okienkami (tyle, że potomnymi - Child Window<br />

wobec głównego okna aplikacji - Parent Window).<br />

Do utworzenia takiego specjalnego okna również można użyć<br />

funkcji CreateWindow(). Jeśli okno ma stać się nie głównym oknem<br />

aplikacji, lecz oknem sterującym przebiegiem programu, funkcja<br />

wymaga podania następujących argumentów:<br />

- rodzaj klasy sterującej (ang. control class)<br />

- rodzaj elementu sterującego (ang. control style)<br />

Typowe rodzaje elementów (obiektów) starujących w środowisku<br />

Windows:<br />

BUTTON<br />

- klawisz rozkazu, prostokątne okno typu<br />

Child, reprezentujące przycisk, który<br />

użytkownik może włączyć; przycisk może<br />

być opatrzony etykietą (text label).<br />

COMBOBOX<br />

- okienko dialogowe kombinowane. Jest<br />

złożeniem klasy EDIT i LISTBOX;<br />

LISTBOX<br />

STATIC<br />

- oknienko z listą (zwykle element<br />

składowy okienka dialogowego typu<br />

Combo Box.<br />

- pole statyczne (bez prawa edycji).<br />

Niewielkie okno zawierające tekst lub<br />

grafikę; służy z reguły do oznaczania<br />

innych okien sterujących.<br />

SCROLLBAR<br />

- pasek przewijania (pionowy - Vertical<br />

Scroll Bar; poziomy - Horizontal<br />

Scroll Bar).<br />

Style klawiszy sterujących (Button Styles):<br />

BS_PUSHBUTTON - Klawisz. Okno sterujące wysyła, po<br />

każdym wyborze klawisza<br />

(kliknięcie), wiadomość do okna<br />

macieżystego (Parent Window).<br />

BS_RADIOBUTTON - Okrągły przełącznik działający<br />

zwykle na zasadzie @tylko jeden<br />

z grupy".<br />

- 401-


BS_CHECKBOX -<br />

- prostokątny przełącznik [X]<br />

włączający (aktywna) lub<br />

wyłączający (nieaktywna)<br />

opcję. Działa niezależnie od<br />

pozostałych.<br />

Inne style określają np. sposób edycji tekstu (ES_LEFT,<br />

ES_MULTILINE, itp.) Szczegóły - patrz system Help - Windows API.<br />

Oto przykład utworzenia okna elementu sterującego typu "Klawisz"<br />

(BUTTON to nazwa typu):<br />

hControlWnd = CreateWindow ("BUTTON", " Napis_na_Klawiszu ",<br />

BS_PUSHBUTTON |WS_CHILD | WS_VISIBLE,<br />

10, 20, 30, 40,<br />

hWnd, ID_Elem, hInstance, 0);<br />

Identyfikator ID_Elem jest potrzebny, gdy w jednym oknie<br />

znajduje się kilka elementów sterujących - pozwala na ich<br />

rozpoznawanie w programie. Sposób przekazywania informacji o<br />

kliknięciu klawisza przypomnę na przykładzie okienka<br />

komunikatów:<br />

if(IDOK==MessageBox(0, "", "", MB_OK)) ...<br />

IDOK to predefiniowany w Windows identyfikator klawisza [OK].<br />

Oto krótkie wyjaśnienie pozostałych elementów:<br />

10, 10, 30, 20, - współrzędne. x, y, szerokość, wysokość<br />

hWnd, - oznacznik okna macieżystego<br />

Przesuwanie i zmiana wielkości elementu sterującego.<br />

Funkcja MoveWindow() przesuwa okno we wskazane miejsce:<br />

MoveWindow(hKlawisz, 10, 10, 20, 30, TRUE);<br />

Ponieważ okno elementu sterującego ma zadane względne<br />

współrzędne w oknie macieżystym, gdy okno macierzyste zostanie<br />

przesunięte - element sterujący będzie przesunięty<br />

automatycznie. Również po zmianie rozmiarów okna macieżystego<br />

okno elementu sterującego zmienia położenie, zawsze jednakowe<br />

względem lewego górnego rogu.<br />

Usuwanie okna sterującego<br />

Okienko elementu sterującego możemy usunąć (jak i każde inne<br />

okna) przy pomocy funkcji:<br />

DestroyWindow(hKlawisz);<br />

Przekazywanie informacji do- i z- okna elementu sterującego<br />

- 402-


Zdarzenie w oknie elementu sterującego - np. kliknięcie klawisza<br />

- powoduje wygenerowanie komunikatu WM_COMMAND. Towarzyszące<br />

komunikatowi parametry przenoszą istotne informacje:<br />

wParam<br />

lParam<br />

- identyfikator elementu sterującego,<br />

- dla wciśniętego klawisza będzie to BN_CLICKED.<br />

Niektóre komunikaty Windows mogą być kierowane do okna elementu<br />

sterującego i wymuszać pewne operacje. Dla przykładu komunikat<br />

WM_GETTEXTLENGTH przesłany do okienka edycyjnego typu Text Edit<br />

Box (element sterujący klasy EDIT) jest żądaniem podania<br />

długości tekstu wpisanego właśnie do okienka. Aby Windows<br />

wygenerowały komunikat i przesłały go do naszego elementu<br />

sterującego - musimy "poprosić" przy pomocy funkcji<br />

SendMessage() (WyślijKomunikat):<br />

DlugTekstu = SendMessage(hEditWnd, WM_GETTEXTLENGHT, 0, 0);<br />

gdzie:<br />

hEditWnd jest identyfikatorem elementu - okienka edycyjnego<br />

[???]Robi na "szaro'?<br />

________________________________________________________________<br />

Podobnie jak opcje w menu - klawisze także mogą zostać<br />

udostępnione (ang. enable), bądź zablokowane (ang. disable).<br />

Jeśli hKlawisz będzie identyfikatorem elementu sterującego,<br />

można go udostępnić (1), bądź zablokować (0) przy pomocy<br />

funkcji:<br />

EnableWindow(hKlawisz, 0);<br />

EnableWindow(hKlawisz, 1);<br />

________________________________________________________________<br />

Typowy projekt dla środowiska Windows składa się z kilku (czasem<br />

kilkunastu) plików: .H, .MNU, .DLG, .RC, .DEF, .PRJ, .ICO, .BMP,<br />

itp. Kompilator zasobów generuje na podstawie tego "składu"<br />

końcowy plik aplikacji.<br />

------------------Plik MEDYT-01.H-------------------------------<br />

#define szAppName "MEDYT-01"<br />

#define ID_EDIT 200<br />

------------------Plik główny: MEDYT-01.CPP---------------------<br />

#include <br />

#include "EDIT.H"<br />

#pragma argused<br />

HWND hEditWnd;<br />

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;<br />

- 403-


int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpszCmdLine, int nCmdShow)<br />

{<br />

WNDCLASS wndClass;<br />

MSG msg;<br />

HWND hWnd;<br />

RECT rect;<br />

if ( !hPrevInstance )<br />

{<br />

wndClass.style= CS_HREDRAW | CS_VREDRAW ;<br />

wndClass.lpfnWndProc= WndProc;<br />

wndClass.cbClsExtra = 0;<br />

wndClass.cbWndExtra= 0;<br />

wndClass.hInstance = hInstance;<br />

wndClass.hIcon = LoadIcon(NULL, szAppName);<br />

wndClass.hCursor= LoadCursor(NULL, IDC_CROSS);<br />

wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );<br />

wndClass.lpszMenuName= NULL;<br />

wndClass.lpszClassName= szAppName;<br />

if (!RegisterClass(&wndClass))<br />

return 0;<br />

}<br />

hWnd = CreateWindow(szAppName,<br />

"MEDYT-01", WS_OVERLAPPEDWINDOW,<br />

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,<br />

0, 0, hInstance, 0);<br />

GetClientRect(hWnd, (LPRECT) &rect);<br />

hEditWnd = CreateWindow ("Edit",NULL, WS_CHILD | WS_VISIBLE |<br />

ES_MULTILINE | WS_VSCROLL |<br />

WS_HSCROLL | ES_AUTOHSCROLL |<br />

ES_AUTOVSCROLL, 0, 0,(rect. right -<br />

rect. left),<br />

(rect. bottom - rect.<br />

top),hWnd,IDC_EDIT, hIstance,NULL);<br />

if( ! hEditWnd )<br />

{<br />

DestroyWindow(hWnd);<br />

return 0;<br />

}<br />

ShowWindow(hWnd, nCmdShow);<br />

UpdateWindow(hWnd);<br />

while (GetMessage(&msg, NULL, 0, 0))<br />

{<br />

TranslateMessage(&msg );<br />

DispatchMessage(&msg );<br />

}<br />

return 0;<br />

}<br />

long FAR PASCAL WndProc (HWND hWnd, unsigned Message,<br />

WORD wParam, LONG lParam)<br />

- 404-


{<br />

switch(Message)<br />

{<br />

case ID_EDIT:<br />

if(HIWORD(lParam)==EN_ERRSPACE)<br />

/* starsze słowo lParam zawiera właściwe dla okna edycyjnego<br />

wiadomości, jeżeli jest to EN_ERRSPACE - okno sterujące nie<br />

może alokować dodatkowego obszaru pamięci */<br />

{<br />

MessageBox (GetFocus(), "Brak pamieci", "MEDYT-01",<br />

MB_ICONSTOP | MB_OK);<br />

}<br />

break;<br />

case WM_SETFOCUS:<br />

SetFocus(hEditWnd);<br />

break;<br />

/* Pierwsze dwa parametry funkcji MoveWindow są ustawione na<br />

zero, dzięki temu po zastosowaniu tej funkcji nie zmieni się<br />

wzajemne położenie obu okien, a jedynie uaktualnianiu<br />

ulegnie okno sterujące. */<br />

case WM_SIZE:<br />

MoveWindows(hEditWnd, 0, 0, LOWORD(lParam));<br />

HIWORD(lParam), TRUE);<br />

break;<br />

case WM_DESTROY:<br />

PostQuitMessage(0);<br />

break;<br />

default:<br />

return (DefWindowProc(hWnd,Message,wParam,lParam));<br />

}<br />

return 0;<br />

}<br />

Jak sterować pracą aplikacji. Jak umieszczać elementy<br />

graficzne-sterujące w oknie aplikacji. Najczęściej stosowane<br />

funkcje API Windows.<br />

________________________________________________________________<br />

Elementy sterujące pracą aplikacji w Windows (ang. controls) są<br />

również swoistymi okienkami (tyle, że potomnymi - Child Window<br />

wobec głównego okna aplikacji - Parent Window).<br />

Do utworzenia takiego specjalnego okna również można użyć<br />

funkcji CreateWindow(). Jeśli okno ma stać się nie głównym oknem<br />

aplikacji, lecz oknem sterującym przebiegiem programu, funkcja<br />

wymaga podania następujących argumentów:<br />

- rodzaj klasy sterującej (ang. control class)<br />

- rodzaj elementu sterującego (ang. control style)<br />

- 405-


Typowe rodzaje elementów (obiektów) starujących w środowisku<br />

Windows:<br />

BUTTON<br />

- klawisz rozkazu, prostokątne okno typu<br />

Child, reprezentujące przycisk, który<br />

użytkownik może włączyć; przycisk może<br />

być opatrzony etykietą (text label).<br />

COMBOBOX<br />

- okienko dialogowe kombinowane. Jest<br />

złożeniem klasy EDIT i LISTBOX;<br />

LISTBOX<br />

STATIC<br />

- oknienko z listą (zwykle element<br />

składowy okienka dialogowego typu<br />

Combo Box.<br />

- pole statyczne (bez prawa edycji).<br />

Niewielkie okno zawierające tekst lub<br />

grafikę; służy z reguły do oznaczania<br />

innych okien sterujących.<br />

SCROLLBAR<br />

- pasek przewijania (pionowy - Vertical<br />

Scroll Bar; poziomy - Horizontal<br />

Scroll Bar).<br />

Style klawiszy sterujących (Button Styles):<br />

BS_PUSHBUTTON - Klawisz. Okno sterujące wysyła, po<br />

każdym wyborze klawisza<br />

(kliknięcie), wiadomość do okna<br />

macieżystego (Parent Window).<br />

BS_RADIOBUTTON - Okrągły przełącznik działający<br />

zwykle na zasadzie @tylko jeden<br />

z grupy".<br />

BS_CHECKBOX -<br />

- prostokątny przełącznik [X]<br />

włączający (aktywna) lub<br />

wyłączający (nieaktywna)<br />

opcję. Działa niezależnie od<br />

pozostałych.<br />

Inne style określają np. sposób edycji tekstu (ES_LEFT,<br />

ES_MULTILINE, itp.) Szczegóły - patrz system Help - Windows API.<br />

Oto przykład utworzenia okna elementu sterującego typu "Klawisz"<br />

(BUTTON to nazwa typu):<br />

hControlWnd = CreateWindow ("BUTTON", " Napis_na_Klawiszu ",<br />

BS_PUSHBUTTON |WS_CHILD | WS_VISIBLE,<br />

10, 20, 30, 40,<br />

- 406-


hWnd, ID_Elem, hInstance, 0);<br />

Identyfikator ID_Elem jest potrzebny, gdy w jednym oknie<br />

znajduje się kilka elementów sterujących - pozwala na ich<br />

rozpoznawanie w programie. Sposób przekazywania informacji o<br />

kliknięciu klawisza przypomnę na przykładzie okienka<br />

komunikatów:<br />

if(IDOK==MessageBox(0, "", "", MB_OK)) ...<br />

IDOK to predefiniowany w Windows identyfikator klawisza [OK].<br />

Oto krótkie wyjaśnienie pozostałych elementów:<br />

10, 10, 30, 20, - współrzędne. x, y, szerokość, wysokość<br />

hWnd, - oznacznik okna macieżystego<br />

Przesuwanie i zmiana wielkości elementu sterującego.<br />

Funkcja MoveWindow() przesuwa okno we wskazane miejsce:<br />

MoveWindow(hKlawisz, 10, 10, 20, 30, TRUE);<br />

Ponieważ okno elementu sterującego ma zadane względne<br />

współrzędne w oknie macieżystym, gdy okno macierzyste zostanie<br />

przesunięte - element sterujący będzie przesunięty<br />

automatycznie. Również po zmianie rozmiarów okna macieżystego<br />

okno elementu sterującego zmienia położenie, zawsze jednakowe<br />

względem lewego górnego rogu.<br />

Usuwanie okna sterującego<br />

Okienko elementu sterującego możemy usunąć (jak i każde inne<br />

okna) przy pomocy funkcji:<br />

DestroyWindow(hKlawisz);<br />

Przekazywanie informacji do- i z- okna elementu sterującego<br />

Zdarzenie w oknie elementu sterującego - np. kliknięcie klawisza<br />

- powoduje wygenerowanie komunikatu WM_COMMAND. Towarzyszące<br />

komunikatowi parametry przenoszą istotne informacje:<br />

wParam<br />

lParam<br />

- identyfikator elementu sterującego,<br />

- dla wciśniętego klawisza będzie to BN_CLICKED.<br />

Niektóre komunikaty Windows mogą być kierowane do okna elementu<br />

sterującego i wymuszać pewne operacje. Dla przykładu komunikat<br />

WM_GETTEXTLENGTH przesłany do okienka edycyjnego typu Text Edit<br />

Box (element sterujący klasy EDIT) jest żądaniem podania<br />

długości tekstu wpisanego właśnie do okienka. Aby Windows<br />

wygenerowały komunikat i przesłały go do naszego elementu<br />

sterującego - musimy "poprosić" przy pomocy funkcji<br />

SendMessage() (WyślijKomunikat):<br />

- 407-


DlugTekstu = SendMessage(hEditWnd, WM_GETTEXTLENGHT, 0, 0);<br />

gdzie:<br />

hEditWnd jest identyfikatorem elementu - okienka edycyjnego<br />

[???]Robi na "szaro'?<br />

________________________________________________________________<br />

Podobnie jak opcje w menu - klawisze także mogą zostać<br />

udostępnione (ang. enable), bądź zablokowane (ang. disable).<br />

Jeśli hKlawisz będzie identyfikatorem elementu sterującego,<br />

można go udostępnić (1), bądź zablokować (0) przy pomocy<br />

funkcji:<br />

EnableWindow(hKlawisz, 0);<br />

EnableWindow(hKlawisz, 1);<br />

________________________________________________________________<br />

Typowy projekt dla środowiska Windows składa się z kilku (czasem<br />

kilkunastu) plików: .H, .MNU, .DLG, .RC, .DEF, .PRJ, .ICO, .BMP,<br />

itp. Kompilator zasobów generuje na podstawie tego "składu"<br />

końcowy plik aplikacji.<br />

------------------Plik MEDYT-01.H-------------------------------<br />

#define szAppName "MEDYT-01"<br />

#define ID_EDIT 200<br />

------------------Plik główny: MEDYT-01.CPP---------------------<br />

#include <br />

#include "EDIT.H"<br />

#pragma argused<br />

HWND hEditWnd;<br />

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpszCmdLine, int nCmdShow)<br />

{<br />

WNDCLASS wndClass;<br />

MSG msg;<br />

HWND hWnd;<br />

RECT rect;<br />

if ( !hPrevInstance )<br />

{<br />

wndClass.style= CS_HREDRAW | CS_VREDRAW ;<br />

wndClass.lpfnWndProc= WndProc;<br />

wndClass.cbClsExtra = 0;<br />

wndClass.cbWndExtra= 0;<br />

wndClass.hInstance = hInstance;<br />

wndClass.hIcon = LoadIcon(NULL, szAppName);<br />

- 408-


wndClass.hCursor= LoadCursor(NULL, IDC_CROSS);<br />

wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );<br />

wndClass.lpszMenuName= NULL;<br />

wndClass.lpszClassName= szAppName;<br />

if (!RegisterClass(&wndClass))<br />

return 0;<br />

}<br />

hWnd = CreateWindow(szAppName,<br />

"MEDYT-01", WS_OVERLAPPEDWINDOW,<br />

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,<br />

0, 0, hInstance, 0);<br />

GetClientRect(hWnd, (LPRECT) &rect);<br />

hEditWnd = CreateWindow ("Edit",NULL, WS_CHILD | WS_VISIBLE |<br />

ES_MULTILINE | WS_VSCROLL |<br />

WS_HSCROLL | ES_AUTOHSCROLL |<br />

ES_AUTOVSCROLL, 0, 0,(rect. right -<br />

rect. left),<br />

(rect. bottom - rect.<br />

top),hWnd,IDC_EDIT, hIstance,NULL);<br />

if( ! hEditWnd )<br />

{<br />

DestroyWindow(hWnd);<br />

return 0;<br />

}<br />

ShowWindow(hWnd, nCmdShow);<br />

UpdateWindow(hWnd);<br />

while (GetMessage(&msg, NULL, 0, 0))<br />

{<br />

TranslateMessage(&msg );<br />

DispatchMessage(&msg );<br />

}<br />

return 0;<br />

}<br />

long FAR PASCAL WndProc (HWND hWnd, unsigned Message,<br />

WORD wParam, LONG lParam)<br />

{<br />

switch(Message)<br />

{<br />

case ID_EDIT:<br />

if(HIWORD(lParam)==EN_ERRSPACE)<br />

/* starsze słowo lParam zawiera właściwe dla okna edycyjnego<br />

wiadomości, jeżeli jest to EN_ERRSPACE - okno sterujące nie<br />

może alokować dodatkowego obszaru pamięci */<br />

{<br />

MessageBox (GetFocus(), "Brak pamieci", "MEDYT-01",<br />

MB_ICONSTOP | MB_OK);<br />

}<br />

break;<br />

case WM_SETFOCUS:<br />

- 409-


SetFocus(hEditWnd);<br />

break;<br />

/* Pierwsze dwa parametry funkcji MoveWindow są ustawione na<br />

zero, dzięki temu po zastosowaniu tej funkcji nie zmieni się<br />

wzajemne położenie obu okien, a jedynie uaktualnianiu<br />

ulegnie okno sterujące. */<br />

case WM_SIZE:<br />

MoveWindows(hEditWnd, 0, 0, LOWORD(lParam));<br />

HIWORD(lParam), TRUE);<br />

break;<br />

case WM_DESTROY:<br />

PostQuitMessage(0);<br />

break;<br />

default:<br />

return (DefWindowProc(hWnd,Message,wParam,lParam));<br />

}<br />

return 0;<br />

}<br />

- 410-


LEKCJA 43. O Okienkach dialogowych.<br />

________________________________________________________________<br />

O tym, jak konstruuje się okienka dialogowe.<br />

________________________________________________________________<br />

Do wyświetlania okienek dialogowych w Windows API służy funkcja<br />

DialogBox(), a do zakończenia ich "życia na ekranie" -<br />

EndDialog(). Podobnie jak każde okno, również okno dialogowe<br />

musi mieć swoją funkcję, obsługi komunikatów Windows. Zamiast<br />

WindowProc() nazywa się ją tradycyjnie DlgProc():<br />

BOOL FAR PASCAL DlgProc(HWND hDLG, unsigned Message, WORD<br />

wParam, LONG lParam);<br />

{<br />

switch (message)<br />

{<br />

...<br />

default: return (0);<br />

}<br />

}<br />

Za wyjątkiem braku domyślnego handlera Windows -<br />

DefWindowProc(), który jest zbędny, w związku z wewnętrznie<br />

przyjmowanymi wartościami domyślnymi, funkcja podobna jest<br />

bardzo w swojej konstrukcji do WindowProc(). Funkcja zwraca<br />

wartość FALSE (czyli 0), jeśli przesłany komunikat nie został<br />

obsłużony. Typowymi komunikatami, które rozpatruje większość<br />

okienek dialogowych, są WM_INITDIALOG oraz WM_COMMAND.<br />

Przykład okienka dialogowego:<br />

------------------Plik: DLGBOX1.H-------------------------------<br />

#define szAppName "DLGBOX1"<br />

#define IDM_DLG1 100<br />

------------------Plik zasobów: DLGBOX1.RC----------------------<br />

#include "DLGBOX1.H"<br />

#include <br />

IDI_ICON ICON CONTROL.ICO<br />

DLGBOX1 MENU<br />

BEGIN<br />

MENUITEM "&O DlgBox" IDM_DLG1<br />

/* to menu pojawi się w oknie macieżystym */<br />

END<br />

DLGBOX1 DIALOG 30,30,200,100<br />

/* Pierwsze liczby to współrzędne lewego-górnego rogu okna, dwie<br />

następne - to szerokość i długość. Współrzędne są względne.<br />

Punkt (0,0) to narożnik okna macieżystego */<br />

- 411-


STYLE WS_POPUP | WS_DLGFRAME<br />

BEGIN<br />

LTEXT "Przyklad" -1, 0, 12, 160, 8<br />

CTEXT "DLGBOX1 - Przyklad" -1, 0, 36, 160, 8<br />

DEFPUSHBUTTON "OK" IDOK, 64, 60, 32,14, WS_GROUP<br />

END<br />

----------------------------------------------------------------<br />

Pomiędzy parą słów kluczowych BEGIN-END można umieszczać różne<br />

instrukcje sterujące. Definiują one, jaki rodzaj okna<br />

sterującego ukaże się w okienku dialogowym. Instrukcje te można<br />

stosować w następującym formacie:<br />

typ_okna "tekst" ID, x, y, szerokość, wysokość [styl]<br />

Parametr styl jest opcjonalny. Styl okna określają<br />

identyfikatory predefiniowane w API Windows (WS_...). Parametr<br />

ID jest odpowiednikiem identyfikatora dla okien potomnych typu<br />

Child Window; dla okien sterujących, które nie zwracają<br />

komunikatów do okna macierzystego, ma wartość -1. IDOK<br />

wykorzystaliśmy jako identyfikator dla okna sterującego typu<br />

BUTTON. Zostanie on wysłany do funkcji okienkowej jako wartość<br />

parametru wParam, gdy użytkownik kliknie klawisz.<br />

------------------Plik główny: DLGBOX1.CPP----------------------<br />

#include <br />

#include <br />

#include <br />

#include "DLGBOX1.H"<br />

#pragma argused<br />

HANDLE<br />

hInst;<br />

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;<br />

BOOL FAR PASCAL ControlProc (HWND, unsigned, WORD, LONG) ;<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpszCmdLine, int nCmdShow )<br />

{<br />

WNDCLASS wndClass;<br />

MSG msg;<br />

HWND hWnd;<br />

if ( !hPrevInstance )<br />

{<br />

wndClass.style= CS_HREDRAW | CS_VREDRAW ;<br />

wndClass.lpfnWndProc= WndProc;<br />

wndClass.cbClsExtra = 0;<br />

wndClass.cbWndExtra= 0;<br />

wndClass.hInstance = hInstance;<br />

wndClass.hIcon = LoadIcon(NULL, szAppName);<br />

wndClass.hCursor= LoadCursor(NULL, IDC_ARROW );<br />

- 412-


wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );<br />

wndClass.lpszMenuName= szAppName;<br />

wndClass.lpszClassName= szAppName;<br />

if (!RegisterClass(&wndClass))<br />

return 0;<br />

}<br />

hInst = hInstance;<br />

hWnd = CreateWindow(szAppName, "DLGBOX1", WS_OVERLAPPEDWINDOW,<br />

CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0);<br />

ShowWindow(hWnd, nCmdShow);<br />

UpdateWindow(hWnd);<br />

while (GetMessage(&msg, 0, 0, 0))<br />

{<br />

TranslateMessage(&msg );<br />

DispatchMessage(&msg );<br />

}<br />

return 0;<br />

}<br />

BOOL FAR PASCAL ControlProc (HWND hDlg, unsigned Message,<br />

WORD wParam, LONG lParam)<br />

{<br />

switch(msg)<br />

{<br />

case WM_INITDIALOG:<br />

return TRUE;<br />

break;<br />

case WM_COMMAND:<br />

switch(wParam)<br />

{<br />

case IDOK:<br />

case IDCANCEL:<br />

EndDialog(hDlg,0);<br />

return TRUE;<br />

}<br />

break;<br />

}<br />

return (0);<br />

}<br />

long FAR PASCAL WndProc (HWND hWnd, unsigned msg,<br />

WORD wParam, LONG lParam)<br />

{<br />

FARPROC lpControlProc;<br />

switch(Message)<br />

{<br />

case WM_COMMAND:<br />

switch(wParam)<br />

{<br />

case IDM_ABOUT:<br />

lpControlProc = MakeProcInstance((FARPROC) ControlProc, hInst);<br />

- 413-


DialogBox(hInst, "DLGBOX1", hWnd, lpControlProc);<br />

return 0;<br />

}<br />

break;<br />

case WM_DESTROY:<br />

hDC = BeginPaint(hWnd , &ps);<br />

TextOut(hDC, 30, 50,"Demo okienka dialogowego", 25);<br />

TextOut(hDC, 30, 70,"Zastosuj menu...", 17);<br />

EndPaint(hWnd, &ps);<br />

break;<br />

case WM_DESTROY:<br />

PostQuitMessage(0);<br />

break;<br />

default:<br />

return (DefWindowProc(hWnd,Message,wParam,lParam));<br />

}<br />

return 0;<br />

}<br />

Stosując okienko edycyjne możemy użyć następujących<br />

predefiniowanych parametrów:<br />

CONTROL<br />

- określa okno elementu sterującego zdefiniowane<br />

przez użytkownika.<br />

CONTROL "tekst", klasa, styl, x, y, szerokość, wysokość<br />

LTEXT - element sterujący: okienko tekstowe<br />

Wyrównywanie tesktu: do lewej.<br />

RTEXT - j. w. Wyrównywanie tesktu: do prawej<br />

CTEXT - j. w. Wyrównywanie tesktu: centrowanie w okienku<br />

CHECKBOX - pole tekstowe po prawej stronie przełącznika typu<br />

Check Box.<br />

PUSHBUTTON - Klawisz z napisem.<br />

LISTBOX - okienko z listą<br />

GROUPBOX - grupa elementów sterujących typu BUTTON; zgrupowanie<br />

kilku elementów sterujących i otoczenie ramką. Tekst<br />

zostanie umieszczony w lewym górnym rogu.<br />

DEFPUSHBUTTON - Klawisz domyślny w stylu BS_DEFPUSHBUTTON.<br />

RADIOBUTTON - analogicznie jak dla stylu BS_RADIOBUTTON.<br />

EDITTEXT - tworzy okno oparte na klasie EDIT.<br />

COMBOBOX - tworz okno oparte na klasie COMBOBOX.<br />

ICON - definiuje ikonę opartą na klasie STATIC; w<br />

okienku dialogowym.<br />

SCROLLBAR - tworzy okno oparte na klasie SCROLLBAR.<br />

[!!!]UWAGA<br />

________________________________________________________________<br />

W niektórych przypadkach okienko dialogowe może być głównym<br />

oknem aplikacji.<br />

- 414-


________________________________________________________________<br />

- 415-


LEKCJA 44. Dołączanie zasobów - menu i okienka dialogowe.<br />

________________________________________________________________<br />

Jak dodać menu i okienka dialogowe do aplikacji.<br />

________________________________________________________________<br />

Aby dodać do aplikacji menu należy utworzyć plik (ASCII) zasobów<br />

*.RC, który zostanie użyty w projekcie. Pierwszą instrukcją jest<br />

MENU, "NazwaMenu",<br />

MENU i para słów kluczowych (znanych z Pascala) BEGIN oraz END,<br />

między którymi znajdzie się kombinacja instrukcji MENUITEM oraz<br />

POPUP.<br />

MENUITEM definiuje pozycję na głównym pasku menu - określa - jak<br />

będzie wyglądać i jaki identyfikator będzie ją reprezentował.<br />

Instrukcja POPUP pozwala, rozwinąć listę pozycji wchodzących w<br />

skład danego menu. Nazwa menu może być użyta podczas rejestracji<br />

klasy danego okna jako wpisana w odpowiednie pole struktury na<br />

której oparte jest okno. W ten sposób uzyskamy menu dla<br />

wszystkich okien danej klasy.<br />

BEGIN<br />

POPUP "Rozkaz"<br />

BEGIN<br />

MENUITEM "Rozkaz 1", IDM_R1<br />

MENUITEM "Rozkaz 2", IDM_R2<br />

MENUITEM "Rozkaz 3", IDM_R3<br />

END<br />

POPUP "Kolor"<br />

BEGIN<br />

MENUITEM "Czarny", IDM_BLACK<br />

MENUITEM "Niebieski", IDM_BLUE<br />

MENUITEM "Zielony", IDM_GREEN<br />

END<br />

MENUITEM "Koniec", IDM_EXIT<br />

END<br />

Każda pozycja ma własny identyfikator, np. IDM_EXIT, IDM_BLUE,<br />

który Windows przekazują do aplikacji, gdy zostaje ona wybrana<br />

przez użytkownika z systemu menu. Ponieważ każdy identyfikator<br />

powinien być unikalny, najlepiej jest go zdefiniować w pliku<br />

zasobów .RC lub własnym pliku nagłówkowym .H:<br />

#define IDM_EXIT 100<br />

#define IDM_BLUE 101<br />

#define IDM_R1 102<br />

...<br />

Mamy już zdefiniowane menu w pliku zasobów, należy je teraz<br />

dołączyć do aplikacji na jeden z dwóch sposobów:<br />

- Można określić menu jako menu danej klasy okien, gdy klasa ta<br />

jest rejestrowana. W ten sposób dołączymy menu do każdego<br />

okna opartego na tej klasie. Aby to wykonać, wystarczy<br />

- 416-


przypisać odpowiedniemu polu struktury nazwę naszego menu.<br />

Jeżeli obiekt klasy WNDCLASS nazwiemy Window1, to:<br />

Window1.lpszMenuName = "NazwaMenu";<br />

Gdy klasa zostanie zarejestrowana, każde okno tej klasy będzie<br />

miało to samo menu, chyba że dostarczymy odpowiedni<br />

identyfikator menu w momencie tworzenia okna funkcją<br />

CreateWindow().<br />

- Drugim sposobem jest dołączenie menu w momencie tworzenia<br />

okna, wtedy tylko tworzone okno będzie miało dane menu.<br />

Należy najpierw załadować menu przy użyciu funkcji LoadMenu(),<br />

która zwraca jego identyfikator:<br />

HMENU hMenu = LoadMenu(hInstance, "NazwaMenu");<br />

hWnd = CreateWindow(szAppName,<br />

"Nazwa Aplikacji",<br />

WS_OVERLAPPEDWINDOW,<br />

CW_USEDEFAULT,<br />

CW_USEDEFAULT,<br />

CW_USEDEFAULT,<br />

CW_USEDEFAULT,<br />

NULL,<br />

hMenu,


kopii zmiennej do funkcji).<br />

Nie wszystkie pozycje w menu są w danym stadium pracy aplikacji<br />

sensowne (możliwe do wykonania). Zaraz przekonasz się, jak to<br />

się dzieje, że niektóre pozycje "robi się na szaro". W API<br />

Windows służy do tego funkcja:<br />

EnableMenuItem (hMenu, IDM_R1, MF_DISABLED);<br />

EnableMenuItem (hMenu, IDM_R1, MF_GRAYED);<br />

EnableMenuItem (hMenu, IDM_R1, MF_ENABLED);<br />

Rozkaz R1 skojarzony z identyfikatorem IDM_R1 i znajdujący się w<br />

systemie menu o oznaczniku hMenu stanie się kolejno zablokowany,<br />

widoczny-lecz-niedostępny, dostępny.<br />

Dodawanie i usuwanie pozycji w menu<br />

Dodawanie pozycji do menu może być wykonane dwoma sposobami:<br />

przez wstawienie pomiędzy istniejące pozycje lub na końcu listy.<br />

W pierwszym przypadku należy użyć funkcji InsertMenu(). Funkcja<br />

ta pozwala jednocześnie określić status pozycji, między innymi<br />

czy będzie umieszczone nowe pole można określić dwoma sposobami:<br />

przez identyfikator pozycji mającej być przed nową lub przez<br />

numerację poszczególnych, licząc id lewej skrajnej pozycji (<strong>C++</strong><br />

tradycyjnie liczy od zera). Sposób "odliczania" pozycji w<br />

systemie menu określa tryb (BYCOMMAND lub BYPOSITION - rozkaz,<br />

bądź pozycja):<br />

InsertMenu(hMenu, IDM_R1, MF_BYCOMMAND |MF_DISABLED, IDM_R5,<br />

"Rozkaz 5");<br />

InsertMenu(hMenu, 1, MF_ENABLED, IDM_R5, "Rozkaz 5");<br />

Funkcja wstawi za pozycją "Rozkaz 1" nową pozycję "Rozkaz 5",<br />

jednocześnie ustawia jej status. Drugą funkcją dodającą pozycję<br />

do utworzonego systemu menu jest:<br />

AppendMenu(hMenu, MF_ENABLED, IDM_R4, "Rozkaz 4");<br />

Poniżej przykład zdefiniowania menu aplikacji w taki właśnie<br />

sposób:<br />

case WM_CREATE:<br />

hMenu = CreateMenu(); //Utworzenie menu<br />

AppendMenu(hMenu, MF_ENABLED, IDM_R1, "Rozkaz 1");<br />

AppendMenu(hMenu, MF_ENABLED, IDM_R2, "Rozkaz 2");<br />

AppendMenu(hMenu, MF_ENABLED, IDM_R3, "Rozkaz 3");<br />

SetMenu(hWnd, hMenu); //Wyświetlenie menu<br />

...<br />

break;<br />

- 418-


Usuwanie pozycji z menu można przeprowadzić dwoma sposobami:<br />

- poprzez wskazanie numeru pozycji w systemie menu:<br />

DeleteMenu(hMenu, 1, MF_BYPOSITION); //usunięta zostanie druga<br />

//pozycja z systemu menu<br />

- przez wyszczególnienie identyfikatorem pozycji<br />

DeleteMenu(hMenu, IDM_R3, MF_BYCOMMAND);<br />

Po usunięciu pozycji z menu Window usunie również wszystkie<br />

związane z nią submenu.<br />

Zaznaczanie pozycji w menu (mark).<br />

Obok pozycji w menu można umieścić znak markujący ("ptaszek").<br />

Znak markujący można zainicjować w pliku zasobów .RC. Dzięki<br />

temu, użytkownik w momencie otwarcia okna dowie się z wyglądu<br />

menu o początkowym ustawieniu opcji.<br />

MENUITEM "Rozkaz 2", IDM_R2, CHECKED<br />

W trakcie pracy aplikacji należy posłużyć się funkcją<br />

CheckMenuItem(). Zwykle najpierw kasujemy "ptaszka" przy<br />

poprzedniej pozycji:<br />

CheckMenuItem( hMenu, IDM_R2, MF_UNCHECKED);<br />

CheckMenuItem(hMenu, IDM_R3, MF_CHECKED);<br />

Zmiany pozycji menu<br />

Funkcja ModyfyMenu() pozwala na zmianę nazwy pozycji i jej<br />

atrybutów. Oto przykłady użycia tej funkcji:<br />

ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND, IDM_R2, "Polecenie 2");<br />

Identyfikator pozycji nie ulegnie zmianie, jedynie nazwa pola z<br />

"Rozkaz 2" na "Polecenie 2". Możemy zmienić jednocześnie i<br />

identyfikator, by nie pomylić się w programie:<br />

ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND, IDM_P2, "Polecenie 2");<br />

Dodatkowo można ustawić za jednym zamachem i atrybuty:<br />

ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND | MF_CHECKED | MF_GRAYED,<br />

IDM_R2, "Polecenie 2");<br />

Użycie grafiki w systemie menu.<br />

W systemie menu aplikacji możemy zamiast łańcucha znaków "Rozkaz<br />

2" umieścić element graficzny - np. w postaci mapy bitowej.<br />

Zamiast pola o nazwie "Pole", wprowadza mapę bitową:<br />

- 419-


HMENU hMenu = GetMenu(hWnd);<br />

HBITMAP hBitmap = LoadBitmap (hIstance, "Pole");<br />

ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND | MF_BITMAP, IDM_R2,<br />

(LPSTR) MAKELONG (hBitmap, 0));<br />

GetMenu() zwraca oznacznik aktualnego menu, potrzebny jako<br />

pierwszy parametr funkcji ModifyMenu(). Drugim parametrem tej<br />

funkcji jest identyfikator pozycji, którą chcemy zmienić.<br />

Trzecia określa, że zmiana ma być wykonana przez wyszukanie<br />

pozycji za pośrednictwem jej identyfikatora oraz że nową pozycję<br />

ma reprezentować mapa bitowa. Czwarty parametr określa<br />

identyfikator nowej pozycji. Ponieważ ostatnim parametrem nie<br />

jest już wskaźnik do łańcucha znakowego, należy przesłać<br />

oznacznik mapy bitowej jako mniej znaczące słowo tego parametru.<br />

W tym celu 16-bitowy oznacznik jest łączony z 16-bitową stałą, a<br />

następnie poddawany konwersji do typu Long Pointer to STRing.<br />

Zmiana menu aplikacji na kolejne.<br />

Aplikacja w różnych stadiach pracy może mieć na ekranie różne<br />

(kilka czasem kilkanaście) menu. Wymiany menu w oknie aplikacji<br />

można dokonać, załadowując nowe menu funkcją LoadMenu() i<br />

ustawiając je jako aktualne funkcją SetMenu():<br />

...<br />

hMenu2 = LoadMenu (hIstance, "Menu2");<br />

SetMenu (hWnd, hMenu2);<br />

DrawMenuBar(...);<br />

...<br />

Menu i Menu2 powinny być zdefiniowane w pliku zasobów *.RC.<br />

Po każdej zmianie menu należy użyć funkcji DrawMenuBar(), aby<br />

wprowadzone zmiany pojawiły się na ekranie. Oto przykład<br />

stosownego pliku zasobów:<br />

Menu1 MENU<br />

BEGIN<br />

POPUP "&File"<br />

BEGIN<br />

MENUITEM "&New" , IDM_NEW<br />

MENUITEM "&Save", IDM_SAVE<br />

MENUITEM "E&xit", IDM_EXIT<br />

END<br />

POPUP "&Options"<br />

BEGIN<br />

MENUITEM "Menu&1", IDM_M1,CHECKED<br />

MENUITEM "Menu&2" , IDM_M2<br />

END<br />

END<br />

Menu2 MENU<br />

- 420-


BEGIN<br />

POPUP "&File"<br />

BEGIN<br />

MENUITEM "&Open", IDM_OPEN<br />

MENUITEM "&New" , IDM_NEW<br />

MENUITEM "&Save", IDM_SAVE<br />

MENUITEM "Save &As", IDM_SAVEAS<br />

MENUITEM "&DOS shell", IDM_DOSSHELL<br />

MENUITEM "E&xit", IDM_EXIT<br />

END<br />

POPUP "&Options"<br />

BEGIN<br />

MENUITEM "Menu&1", IDM_M1,<br />

MENUITEM "Menu&2" , IDM_M2, CHECKED<br />

END<br />

END<br />

ZASTOSOWANIE Resource Worshop<br />

Takie pliki zasobów w <strong>Borland</strong> <strong>C++</strong> mało kto tworzy dziś "na<br />

piechotę". BORLAND <strong>C++</strong> oferuje do tego celu dwa<br />

narzędzia:<br />

Edytor zasobów - Resource Workshop<br />

Automatyczny generator - DialogExpert (wersje 4+)<br />

Najwygodniejszym sposobem jest zastosowanie edytora zasobów<br />

Resource Workshop. Jest to tym wygodniejsze, że Resource<br />

Workshop pozwala jednocześnie obserwować i źródłowy plik *.RC<br />

(ASCII) i efekt - menu w ruchu.<br />

W środowisku <strong>Borland</strong> <strong>C++</strong> okienka dialogowe tworzy się także<br />

zwykle przy pomocy Resource Worshop.<br />

Tworzenie okienek dialogowych przy pomocy Resource Workshop<br />

przypomina składanie budowli z gotowych klocków.<br />

Kolejne elementy sterujące możemy umieszczać w okienku<br />

dialogowym poprzez wybranie ich z palety narzędzi i<br />

przeniesienie do projektowanego okienka techniką "pociągnij i<br />

upuść" (drag & drop).<br />

Po skróconym omówieniu najważniejszych funkcji z API Windows<br />

przejdźmy to niemniej krótkiej prezentacji zasad tworzenia<br />

aplikacji przy pomocy biblioteki obiektów OWL.<br />

[Z]<br />

________________________________________________________________<br />

1. Przeanalizuj program w pełnej wersji (na dyskietce).<br />

2. Zmodyfikuj dowolną aplikację przykładową tak, by dołączyć do<br />

niej inną ikonę.<br />

3. Opracuj własne menu i własną ikonę przy pomocy Resource<br />

Workshop.<br />

________________________________________________________________<br />

Krótka instrukcja do Resource Workshop.<br />

________________________________________________________________<br />

- 421-


1. Uruchomienie: Ikonka Worshop w oknie grupowym <strong>Borland</strong> <strong>C++</strong>.<br />

2. Początek pracy: File | New Project...<br />

3. Rodzaje zasobów do wyboru w okienku dialogowym "New project":<br />

[ ] RC - plik zasobów<br />

[ ] CUR - kursor<br />

[ ] BMP - mapa bitowa<br />

[ ] RES - plik zasobów w formie skompilowanej<br />

[ ] ICO - ikonka<br />

[ ] FNT - czcionki (Fonts)<br />

Wybieramy odpowiednio: RC<br />

4. Zmieni się menu w głównym oknie Resource Workshop. Z menu<br />

wybieramy Resource | New<br />

W okienku dialogowym z listy Resource Type (rodzaj zasobów):<br />

ACCELERATORS, BITMAP, CURSOR, DIALOG, FONT, ICON, MENU, RCDATA,<br />

STRINGTABLE, VERSINFO<br />

wybieramy odpowiednio MENU lub DILOG.<br />

Kolejny raz zmieni się menu. W przypadku menu wybieramy:<br />

Menu:<br />

New pop-up - nowa pozycja POPUP<br />

New menu item - nowa pozycja MENUITEM<br />

Zwróć uwagę, że typowe menu File, Edit, Help jesy już gotowe do<br />

wstawienia (ukryte pod pozycjami New file pop-up, New edit<br />

pop-up...).<br />

W przypadku okienka dialogowego najważniejsze jest menu Control.<br />

Są tam wszyskie rodzaje podstawowych elementów sterujących (Push<br />

button, Radio button, scroll bar, List box, Combo box, Edit box,<br />

itd.). Projektując okienko możesz również wyświetlić siatkę<br />

(Grid).<br />

Przy pomocy Resource Workshop możesz poddawać edycji i<br />

modyfikować pliki zasobów zarówno należące do programów<br />

przykładowych zawartoch na dyskietce, jak i zasoby "firmowych"<br />

przykładów <strong>Borland</strong>a. W katalogach \SOURCE (kody źródłowe .CPP) i<br />

\EXAMPLES (przykłady - projekty) znajdziesz wiele rozmaitych<br />

przykładów. Możesz także poddawać edycji pliki .BMP, .ICO i inne<br />

niekoniecznie należące do pakietu <strong>Borland</strong> <strong>C++</strong>.<br />

________________________________________________________________<br />

- 422-


LEKCJA 45. O PROGRAMACH OBIEKTOWO - ZDARZENIOWYCH.<br />

________________________________________________________________<br />

Po aplikacjach sekwencyjnych, proceduralno-zdarzeniowych, jedno-<br />

i dwupoziomowych, pora rozważyć dokładniej stosowanie technik<br />

obiektowych.<br />

________________________________________________________________<br />

Programy pracujące w środowisku Windows tworzone są w oparciu o<br />

tzw. model trójwarstwowy. Pierwsza warstwa to warstwa<br />

wizualizacji, druga - interfejs, a trzecia - to właściwa<br />

maszyneria programu. W tej lekcji zajmiemy się "anatomią"<br />

aplikacji wielowarstwowych a następnie sposobami wykorzystania<br />

bogatego instrumentarium oferowanego przez <strong>Borland</strong>a wraz z<br />

kompilatorami B<strong>C++</strong> 3+...4+.<br />

Biblioteka OWL w wersjach BORLAND <strong>C++</strong> 3, 3.1, 4 i 4.5 zawiera<br />

definicje klas potrzebnych do tworzenia aplikacji dla Windows.<br />

Fundamentalne znaczenie dla większości typowych aplikacji mają<br />

następujące klasy:<br />

TModule (moduł - program lub biblioteka DLL)<br />

TApplication (program - aplikacja)<br />

TWindow (Okno)<br />

Rozpocznę od krótkiego opisu dwu podstawowych klas.<br />

KLASA TApplication.<br />

Tworząc obiekt klasy TNaszProgram będziemy wykorzystywać<br />

dziedziczenie od tej właśnie klasy bazowej:<br />

class TNaszProgram : public TApplication<br />

Podstawowym celem zastosowania tej właśnie klasy bazowej jest<br />

odziedziczenie gotowej funkcji - metody virtual InitMainWindow()<br />

(zainicjuj główne okno programu). Utworzenie obiektu klasy<br />

TNaszProgram następuje zwykle w czterech etapach:<br />

* Windows uruchamiają program wywołując główną funkcję WinMain()<br />

lub OwlMain() wchodzącą w skład każdej aplikacji.<br />

* Funkcja WinMain() tworzy przy pomocy operatora new nowy obiekt<br />

- aplikację.<br />

* Obiekt - aplikacja zaczyna funkcjonować. Konstruktor obiektu<br />

(własny, bądź odziedziczony po klasie TApplication) wywołuje<br />

funkcję - wirtualną metodę InitMainWindow().<br />

* Funkcja przy pomocy operatora new tworzy obiekt - okno<br />

aplikacji.<br />

Wskaźnik do utworzonego obiektu zwraca funkcja GetApplication().<br />

Dla zobrazowania mechanizmów poniżej przedstawiamy uproszczony<br />

- 423-


"wyciąg" z dwu opisywanych klas. Nie jest to dokładna kopia kodu<br />

źródłowego <strong>Borland</strong>a, lecz skrót tego kodu pozwalający na<br />

zrozumienie metod implementacji okienkowych mechanizmów wewnątrz<br />

klas biblioteki OWL i tym samym wewnątrz obiektów obiektowo -<br />

zdarzeniowych aplikacji.<br />

A oto najważniejsze elementy implementacji klasy TApplication:<br />

- Konstruktor obiektu "Aplikacja":<br />

TApplication::TApplication(const char far* name,<br />

HINSTANCE Instance,<br />

HINSTANCE prevInstance,<br />

const char far* CmdLine,<br />

int CmdShow,<br />

TModule*& gModule)<br />

{<br />

hPrevInstance = prevInstance;<br />

nCmdShow = CmdShow;<br />

MainWindow = 0;<br />

HAccTable = 0; //Accelerator Keys Table Handle<br />

BreakMessageLoop = FALSE;<br />

AddApplicationObject(this); //this to wskaźnik do własnego<br />

gModule = this; //obiektu, czyli do bież. aplikacji<br />

}<br />

Funkcja - metoda "Zainicjuj Instancję":<br />

void TApplication::InitInstance()<br />

{<br />

InitMainWindow();<br />

if (MainWindow)<br />

{<br />

MainWindow->SetFlag(wfMainWindow);<br />

MainWindow->Create();<br />

MainWindow->Show(nCmdShow);<br />

}<br />

Metoda "Zainicjuj główne okno aplikacji":<br />

void TApplication::InitMainWindow()<br />

{<br />

SetMainWindow(new TFrameWindow(0, GetName()));<br />

}<br />

Metoda Run() - "Uruchom program":<br />

int TApplication::Run()<br />

{<br />

int status;<br />

{<br />

if (!hPrevInstance) InitApplication();<br />

InitInstance();<br />

status = MessageLoop();<br />

- 424-


}<br />

A oto pętla pobierania komunikatów w uproszczeniu. "Pump" to po<br />

prostu "pompowanie" komunikatów (message) oczekujących (waiting)<br />

w kolejce. PeekMessage() to sprawdzenie, czy w kolejce oczekuje<br />

komunikat. PM_REMOWE to "brak komunikatu".<br />

BOOL TApplication::PumpWaitingMessages()<br />

{<br />

MSG msg;<br />

BOOL foundOne = FALSE;<br />

while (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))<br />

{<br />

foundOne = TRUE;<br />

if (msg.message == WM_QUIT)<br />

{<br />

BreakMessageLoop = TRUE;<br />

MessageLoopResult = msg.wParam;<br />

::PostQuitMessage(msg.wParam);<br />

break;<br />

}<br />

if (!ProcessAppMsg(msg))<br />

{<br />

::TranslateMessage(&msg);<br />

::DispatchMessage(&msg);<br />

}<br />

}<br />

return foundOne;<br />

}<br />

int TApplication::MessageLoop()<br />

{<br />

long idleCount = 0;<br />

MessageLoopResult = 0;<br />

while (!BreakMessageLoop) {<br />

TRY {<br />

if (!IdleAction(idleCount++))<br />

::WaitMessage();<br />

if (PumpWaitingMessages())<br />

idleCount = 0;<br />

}<br />

if (MessageLoopResult != 0) {<br />

::PostQuitMessage(MessageLoopResult);<br />

break;<br />

}<br />

})<br />

}<br />

BreakMessageLoop = FALSE;<br />

return MessageLoopResult;<br />

}<br />

else if (::IsWindowEnabled(wnd)) {<br />

- 425-


*(info->Wnds++) = wnd;<br />

::EnableWindow(wnd, FALSE);<br />

}<br />

}<br />

return TRUE;<br />

}<br />

KLASA TWindow.<br />

Klasa TWindow (Okno) zawiera implementację wielu przydatnych<br />

przy tworzeniu aplikacji "cegiełek". Poniżej przedstawiono<br />

fragment pliku źródłowego (patrz \SOURCE\OWL\WINDOW.CPP). Łatwo<br />

można rozpoznać pewne znane już elementy.<br />

...<br />

extern LRESULT FAR PASCAL _export InitWndProc(HWND, UINT,<br />

WPARAM, LPARAM);<br />

...<br />

struct TCurrentEvent //Struktura BieżąceZdarzenie<br />

{<br />

TWindow* win; //Wskażnik do okna<br />

UINT message; //Komunikat<br />

WPARAM wParam;<br />

LPARAM lParam;<br />

};<br />

...<br />

DEFINE_RESPONSE_TABLE(TWindow)<br />

//Makro: Zdefiniuj tablicę odpowiedzi na zdarzenia<br />

//EV_WM_SIZE - Zdarzenie (EVent)-nadszedł komunikat WM_SIZE<br />

...<br />

EV_WM_SETCURSOR,<br />

EV_WM_SIZE,<br />

EV_WM_MOVE,<br />

EV_WM_PAINT,<br />

EV_WM_LBUTTONDOWN,<br />

EV_WM_KILLFOCUS,<br />

EV_WM_CREATE,<br />

EV_WM_CLOSE,<br />

EV_WM_DESTROY,<br />

EV_COMMAND(CM_EXIT, CmExit),<br />

...<br />

END_RESPONSE_TABLE;<br />

Funkcje - metody obsługujące komunikaty zaimplementowane zostały<br />

wewnątrz klasy TWindow tak:<br />

TWindow::EvCreate(CREATESTRUCT far&)<br />

{<br />

SetupWindow();<br />

return (int)DefaultProcessing();<br />

}<br />

void TWindow::EvSize(UINT sizeType, TSize&)<br />

{<br />

- 426-


if (Scroller && sizeType != SIZE_MINIMIZED)<br />

{<br />

Scroller->SetPageSize();<br />

Scroller->SetSBarRange();<br />

}<br />

}<br />

Metoda GetWindowClass() bardzo przypomina klasyczne<br />

zainicjowanie zanej już struktury WNDCLASS:<br />

void TWindow::GetWindowClass(WNDCLASS& wndClass)<br />

{<br />

wndClass.cbClsExtra = 0;<br />

wndClass.cbWndExtra = 0;<br />

wndClass.hInstance = *GetModule();<br />

wndClass.hIcon = 0;<br />

wndClass.hCursor = ::LoadCursor(0, IDC_ARROW);<br />

wndClass.hbrBackground = HBRUSH(COLOR_WINDOW + 1);<br />

wndClass.lpszMenuName = 0;<br />

wndClass.lpszClassName = GetClassName();<br />

wndClass.style = CS_DBLCLKS;<br />

wndClass.lpfnWndProc = InitWndProc;<br />

}<br />

Skoro te wszystkie "klocki" zostały już zaimplementowane<br />

wewnątrz definicji klas, nasze programy powinny tylko umiejętnie<br />

z nich korzystać a teksty źródłowe programów powinny ulec<br />

skróceniu i uproszczeniu.<br />

STADIA TWORZENIA OBIEKTOWEJ APLIKACJI.<br />

Ponieważ znakomita większość dzisiejszych użytkowników pracuje z<br />

Windows 3.1, 3.11, i NT - zaczniemy tworzenie aplikacji od<br />

umieszczenia na początku informacji dla OWL, że nasz docelowy<br />

program ma być przeznaczony właśnie dla tego środowiska:<br />

#define WIN31<br />

Jak już wiemy dzięki krótkiemu przeglądowi struktury bazowych<br />

klas przeprowadzonemu powyżej - funkcje API Windows są w istocie<br />

klasycznymi funkcjami posługującymi się mechanizmami języka C.<br />

<strong>C++</strong> jest "pedantem typologicznym" i przeprowadza dodatkowe<br />

testowanie typów parametrów przekazywanych do funkcji (patrz<br />

"Technika programowania w <strong>C++</strong>"). Aby ułatwić współpracę,<br />

zwiększyć poziom bezpieczeństwa i "uregulować" potencjalne<br />

konflikty - dodamy do programu:<br />

#define STRICT<br />

Chcąc korzystać z biblioteki OWL wypada dołączyć właściwy plik<br />

nagłówkowy:<br />

- 427-


#include <br />

Plik OWL.H zawiera już wewnątrz dołączony WINDOWS.H, który<br />

występował we wcześniejszych aplikacjach proceduralno -<br />

zdarzeniowych i jeszcze parę innych plików.<br />

Ponieważ chcemy skorzystać z gotowych zasobów - odziedziczymy<br />

pewne cechy po klasie bazowej TApplication. Zgodnie z zasadami<br />

programowania obiektowego chcąc utworzyć obiekt musimy najpierw<br />

zdefiniować klasę:<br />

class TOkno ...<br />

i wskazać po której klasie bazowej chcemy dziedziczyć:<br />

class TOkno : public TApplication<br />

{<br />

...<br />

Konstruktor obiektu klasy TOkno powinien tylko przekazać<br />

parametry konstruktorowi klasy bazowej - i już.<br />

class TOkno : public TApplication<br />

{<br />

public:<br />

TOkno(LPSTR name, HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpCmdLine, int nShow) : TApplication(name,<br />

hInstance, hPrevInstance, lpCmdLine, nShow)<br />

{<br />

return;<br />

}<br />

virtual void InitMainWindow();<br />

};<br />

Umieściliśmy w definicji klasy jeszcze jedną funkcję inicjującą<br />

główne okno aplikacji. Możemy ją zdefiniować np. tak:<br />

void TOkno::InitMainWindow(void)<br />

{<br />

MainWindow = new (TWindow(0, "Napis - Tytul Okna"));<br />

}<br />

Działanie funkcji polega na utworzeniu nowego obiektu (operator<br />

new) klasy bazowej TWindow. Główne okno stanie się zatem<br />

obiektem klasy TWindow (Niektóre specyficzne aplikacje posługują<br />

się okienkiem dialogowym jako głównym oknem programu. W takiej<br />

sytuacji dziedziczenie powinno następować po klasie TDialog).<br />

Konstruktorowi tego obiektu przekazujemy jako parametr napis,<br />

który zostanie umieszczony w nagłówku głównego okna aplikacji.<br />

Pierwszy argument (tu ZERO) to wskażnik do macieżystego okna,<br />

ponieważ w bardziej złożonych aplikacjach występują okna<br />

macieżyste (parent) i okna potomne (child). Okno macieżyste to<br />

zwykle obiekt klasy "główne okno" a okno potomne to najczęściej<br />

okienko dialogowe, bądź okienko komunikatów. W tym przypadku<br />

- 428-


wpisujemy zero, ponieważ program nie posiada w tym stadium<br />

wcześniejszego okna macieżystego.<br />

Pozostało nam jeszcze dodać funkcję WinMain() i pierwszy program<br />

obiektowy w wersji "Maszyna do robienia nic" jest gotów.<br />

Listing . Obiektowa "Maszyna do robienia nic"<br />

________________________________________________________________<br />

#define STRICT<br />

#define WIN31<br />

#include <br />

class TOkno : public TApplication<br />

{<br />

public:<br />

TOkno(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

: TApplication(AName, hInstance, hPrevInstance, lpCmdLine,<br />

nCmdShow) {};<br />

void InitMainWindow(){MainWindow = new TWindow(NULL, Name);};<br />

};<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

{<br />

TOkno OBIEKT("Windows - Program PW1", hInstance,<br />

hPrevInstance, lpCmdLine, nCmdShow);<br />

OBIEKT.Run();<br />

return 0;<br />

}<br />

________________________________________________________________<br />

Wykonanie takiej aplikacji przebiega następująco. Windows<br />

wywołują główną funkcję WinMain(), która przekazuje swoje<br />

parametry do konstruktora klasy TOkno::TOkno(). Konstruktor<br />

przekazuje parametry do konstruktora klasy bazowej<br />

TApplication(). Po skonstruowaniu obiektu w pamięci funkcja<br />

wywołuje odziedziczoną metodę Run(). Funkcja Run() wywołuje<br />

metody InitApplication() (zainicjuj aplikację) i InitInstance()<br />

(zainicjuj dane wystąpienie programu). Metoda InitInstance()<br />

wywołuje funkcję InitMainWindow(), która buduje główne okno<br />

aplikacji na ekranie. Po pojawieniu się okna rozpoczyna<br />

działanie pętla pobierania komunikatów (message loop). Pętla<br />

komunikatów działa aż do otrzymania komunikatu WM_QUIT.<br />

Rozbudujmy aplikację o okienko komunikatów. Zastosujemy do tego<br />

funkcję MessageBox(). Funkcja zostanie użyta nie jako metoda<br />

(składnik obiektu), lecz jako "wolny strzelec" (stand alone<br />

function).<br />

Listing B. Maszyna rozszerzona o okienka komunikatów.<br />

________________________________________________________________<br />

#define WIN31<br />

#define STRICT<br />

- 429-


#include <br />

class TOkno : public TApplication<br />

{<br />

public:<br />

TOkno(LPSTR Nazwa, HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

: TApplication(Nazwa, hInstance, hPrevInstance, lpCmdLine,<br />

nCmdShow) {};<br />

void InitMainWindow(){MainWindow = new TWindow(NULL, "Okno<br />

PW2" );};<br />

};<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

{<br />

TOkno OBIEKT("Okno PW2", hInstance, hPrevInstance,<br />

lpCmdLine, nCmdShow);<br />

LPSTR p1 = "Jesli wybierzesz [Anuluj]\n- aplikacja nie<br />

ruszy!";<br />

LPSTR p2 = "START";<br />

if (MessageBox(NULL, p1, p2, MB_OKCANCEL) == IDCANCEL)<br />

MessageBox(NULL, "I juz..." , "KONIEC" , MB_OK);<br />

else<br />

OBIEKT.Run();<br />

return 0;<br />

}<br />

________________________________________________________________<br />

Uwagi techniczne.<br />

Ścieżki do katalogów:<br />

..\INCLUDE;..\CLASSLIB\INCLUDE;..\OWL\INCLUDE;<br />

..\LIB;..\CLASSLIB\LIB;..\OWL\LIB;<br />

Konsolidacja:<br />

Options | Linker | Settings: Windows EXE (typ aplikacji)<br />

Options | Linker | Libraries:<br />

- Container class Libraries: Static (bibl. klas CLASSLIB)<br />

- OWL: Static (bibl. OWL statycze .LIB)<br />

- Standard Run-time Lib: Static (bibl. uruchomieniowe .LIB)<br />

(.) None - oznacza żadne (nie zostaną dołączone);<br />

(.) Static - oznacza statyczne .LIB<br />

(.) Dinamic - oznacza dynamiczne .DLL<br />

________________________________________________________________<br />

JAK ROZBUDOWYWAĆ OBIEKTOWE APLIKACJE?<br />

Mimo całego uroku obiektowych aplikacji pojawia się tu wszakże<br />

drobny problem. Skoro komunikacja klawiatura/myszka -> program<br />

-> ekran nie odbywa się wprost, lecz przy pomocy wymiany danych<br />

pomiędzy obiektami różnych warstw - w jaki sposób (w którym<br />

miejscu programu) umieścić "zwyczajne" funkcje i procedury i jak<br />

zorganizować wymianę informacji. "Zwyczajne" funkcje będą<br />

przecież wchodzić w skład roboczych części naszych programów<br />

- 430-


(Engine). Rozważmy to na przykładzie aplikacji reagującej na<br />

naciśnięcie klawisza myszki. Najbardziej istotny -<br />

"newralgiczny" punkt programu został zaznaczony w tekście "


Konstruktor tradycyjnie wykorzystujemy do przekazania parametrów<br />

do konstruktora klasy bazowej. PTWindowsObject AParent to<br />

wskażnik (PoinTer) do obiektu "okno" a ATitle to string - tytuł.<br />

Obsługa komunikatów kierowanych do tego okna może być<br />

realizowana przy pomocy metod zaimplementowanych jako elemeny<br />

składowe klasy Główne Okno - TGOkno.<br />

Program graficzny powinien reagować raczej na myszkę niż na<br />

klawiaturę. Windows rozpoznają zdarzenia związane z myszką i<br />

generują komunikaty o tych zdarzeniach.<br />

Zdarzenia myszki (mouse events).<br />

________________________________________________________________<br />

Komunikat Zdarzenie<br />

________________________________________________________________<br />

WM_MOUSEMOWE - przesunięto myszkę (wewnątrz obszaru<br />

roboczego - inside the client area -<br />

ICA)<br />

WM_LBUTTONDOWN - naciśnięto LEWY klawisz myszki (ICA)<br />

WM_LBUTTONDBLCLK - naciśnięto dwukrotnie LEWY klaw. (ICA)<br />

WM_LBUTTONUP - puszczono LEWY klawisz (ICA)<br />

WM_RBUTTONDOWN - naciśnięto PRAWY klawisz myszki (ICA)<br />

WM_RBUTTONDBLCLK - naciśnięto dwukrotnie PRAWY klaw. (ICA)<br />

WM_RBUTTONUP - puszczono PRAWY klawisz (ICA)<br />

WM_MBUTTONDOWN - naciśnięto ŚRODK. klawisz myszki (ICA)<br />

WM_MBUTTONDBLCLK - naciśnięto dwukrotnie ŚROD. klaw. (ICA)<br />

WM_MBUTTONUP - puszczono ŚRODKOWY klawisz (ICA)<br />

WM_NCMOUSEMOVE - ruch myszki poza client area (NCA)<br />

WM_NLBUTTONDOWN - naciśnięto LEWY klawisz myszki poza<br />

obszarem roboczym - non-client area<br />

(NCA)<br />

WM_NCLBUTTONDBLCLK - naciśnięto dwukrotnie LEWY klaw. (NCA)<br />

WM_NCLBUTTONUP - puszczono LEWY klawisz (NCA)<br />

WM_NCRBUTTONDOWN - naciśnięto PRAWY klawisz myszki (NCA)<br />

WM_NCRBUTTONDBLCLK - naciśnięto dwukrotnie PRAWY klaw. (NCA)<br />

WM_NCRBUTTONUP - puszczono PRAWY klawisz (NCA)<br />

WM_NCMBUTTONDOWN - naciśnięto ŚR. klawisz myszki (NCA)<br />

WM_NCMBUTTONDBLCLK - naciśnięto dwukrotnie ŚRODK. klaw. (NCA)<br />

WM_LBUTTONUP - puszczono ŚRODKOWY klawisz (NCA)<br />

________________________________________________________________<br />

Następna tabelka zawiera (znacznie skromniejszy) zestaw<br />

komunikatów generowanych pod wpływem zdarzeń związanych z<br />

klawiaturą. Choćby z wizualnego porównaia wielkości tych tabel<br />

wyrażnie widać, że Windows znacznie bardziej "lubią" współpracę<br />

z myszką.<br />

Komunikaty Windows w odpowiedzi na zdarzenia związane z<br />

klawiaturą.<br />

_______________________________________________________________<br />

Komunikat Zdarzenie<br />

- 432-


_______________________________________________________________<br />

WM_KEYDOWN Naciśnięto (jakiś) klawisz.<br />

WM_KEYUP Puszczono klawisz.<br />

WM_SYSKEYDOWN Naciśnięto klawisz "systemowy".<br />

WM_SYSKEYUP Puszczono klawisz "systemowy".<br />

WM_CHAR Kod ASCII klawisza.<br />

________________________________________________________________<br />

Klawisz systemowy to np. [Alt]+[Esc], [Alt]+[F4] itp.<br />

________________________________________________________________<br />

Komunikaty Windows możemy wykorzystać w programie.<br />

...<br />

BOOL CanClose();<br />

void WMLButtonDown(RTMessage Msg)= [WM_FIRST + WM_LBUTTONDOWN];<br />

void WMRButtonDown(RTMessage Msg)= [WM_FIRST + WM_RBUTTONDOWN];<br />

};<br />

Nasze Główne_Okno potrafi obsługiwać następujące zdarzenia:<br />

* Funkcja CanClose() zwróciła wynik TRUE/FALSE,<br />

* Naciśnięto lewy/prawy klawisz myszki.<br />

Komunikat Msg zadeklarowany jako zmienna typu RTMessage jest w<br />

klasie macieżystej TWindow wykorzystywany tak:<br />

_CLASSDEF(TWindow)<br />

class _EXPORT TWindow : public TWindowsObject<br />

{<br />

...<br />

protected:<br />

virtual LPSTR GetClassName()<br />

{ return "OWLWindow"; }<br />

virtual void GetWindowClass(WNDCLASS _FAR & AWndClass);<br />

virtual void SetupWindow();<br />

virtual void WMCreate(RTMessage Msg) = [WM_FIRST +<br />

WM_CREATE];<br />

virtual void WMMDIActivate(RTMessage Msg) =<br />

[WM_FIRST + WM_MDIACTIVATE];<br />

...<br />

virtual void WMSize(RTMessage Msg) = [WM_FIRST + WM_SIZE];<br />

virtual void WMMove(RTMessage Msg) = [WM_FIRST + WM_MOVE];<br />

virtual void WMLButtonDown(RTMessage Msg) = [WM_FIRST +<br />

WM_LBUTTONDOWN];<br />

Zwróć uwagę na notację. Zamiast WM_CREATE pojawiło się [WM_FIRST<br />

+ WM_CREATE]. Komunikat WM_FIRST jest predefiniowany w OWLDEF.H<br />

i musi wystąpić w obiektowych aplikacjach w dowolnej klasie<br />

okienkowej, bądź sterującej (window class/controll class), która<br />

winna odpowiadać na określony komunikat. Oto fragment pliku<br />

OWLDEF.H zawierający definicje stałych tej grupy:<br />

- 433-


#define WM_FIRST 0x0000<br />

/* 0x0000- 0x7FFF window messages */<br />

#define WM_INTERNAL 0x7F00<br />

/* 0x7F00- 0x7FFF reserved for internal use */<br />

#define ID_FIRST 0x8000<br />

/* 0x8000- 0x8FFF child id messages */<br />

#define NF_FIRST 0x9000<br />

/* 0x9000- 0x9FFF notification messages */<br />

#define CM_FIRST 0xA000<br />

/* 0xA000- 0xFFFF command messages */<br />

#define WM_RESERVED WM_INTERNAL - WM_FIRST<br />

#define ID_RESERVED ID_INTERNAL - ID_FIRST<br />

#define ID_FIRSTMDICHILD ID_RESERVED + 1<br />

#define ID_MDICLIENT ID_RESERVED + 2<br />

#define CM_RESERVED CM_INTERNAL - CM_FIRST<br />

W tym momencie zwróćmy jeszcze uwagę, że funkcje z grupy<br />

MessageHandlers są typu void i zwykle są metodami wirtualnymi -<br />

przeznaczonymi "z definicji" do nadpisywania przez programistów<br />

w klasach potomnych. Wszystkie te metody mają zawsze jedyny<br />

argument - referencję do struktury TMessage zdefiniowanej<br />

następująco:<br />

struct TMessage<br />

{<br />

HWND Receiver; //Identyfikator okna - odbiorcy<br />

WORD Message; //sam komunikat<br />

union<br />

{<br />

WORD WParam; //Parametr WParam stowarzyszony z<br />

//komunikatem; ALBO (dlatego unia!)<br />

struct tagWP<br />

{<br />

BYTE Lo;<br />

BYTE Hi;<br />

} WP;<br />

union<br />

{<br />

DWORD lParam;<br />

struct tagLP<br />

{<br />

WORD Lo;<br />

WORD Hi;<br />

} LP;<br />

};<br />

long Result;<br />

};<br />

Po tych wyjaśnieniach możemy zaimplementować poszczególne<br />

funkcje.<br />

void TAplikacja::InitMainWindow()<br />

{<br />

MainWindow = new (0, Name);<br />

- 434-


}<br />

Jeśli wybrano klawisz [Yes] funkcja zwróci IDYES. Jeśli funkcja<br />

zwróciła IDYES - operator porównania zwróci TRUE (prawda) i ta<br />

też wartość zostanie zwrócona przez metodę CanClose:<br />

BOOL TMyWindow::CanClose()<br />

{<br />

return (MessageBox(HWindow, "Wychodzimy?",<br />

"Koniec", MB_YESNO | MB_ICONQUESTION) == IDYES);<br />

}<br />

Stosunkowo najciekawsza kombinacja odbywa się wewnątrz handlera<br />

komunikatu WM_LBUTTONDOWN. Ze struktury komunikatów pobierana<br />

jest zawartość młodszego słowa parametru lParam - Msg.LP.Lo i<br />

starszego słowa Msg.LP.Hi. Są to względne współrzędne graficzne<br />

kursora myszki (względem narożnika okna) w momencie naciśnięcia<br />

lewego klawisza myszki. Funkcja sprintf() zapisuje je w postaci<br />

dwu liczb dziesiętnych %d, %d do bufora znakowego char<br />

string[20]. Funkcja GetDC() (Get Device Context) określa<br />

kontekst urządzenia (warstwa sterownika urządzenia) i dalej<br />

obiekt może już stosując funkcję kontekstową "czuć się"<br />

niezależny od sprzętu. Dane te w postaci znakowej są pobierane<br />

przez funkcję kontekstową OutText() jako string a równocześnie<br />

pobierane są w formie liczbowej: Msg.LP.Hi. Msg.LP.Lo, aby<br />

wyznaczyć współrzędne tekstu na ekranie. Funkcja strlen()<br />

oblicza długość łańcucha znakowego - i to już ostatni potrzebny<br />

nam parametr.<br />

void TMyWindow::WMLButtonDown(RTMessage Msg)<br />

{<br />

HDC DC;<br />

char string[20];<br />

sprintf(string, "(%d, %d)", Msg.LP.Lo, Msg.LP.Hi);


LPSTR lpCmdLine, int nCmdShow)<br />

{<br />

TNAplikacja OBIEKT("Wspolrzedne w oknie", hInstance,<br />

hPrevInstance, lpCmdLine, nCmdShow);<br />

OBIEKT.Run();<br />

return (OBIEKT.Status);<br />

}<br />

Wyświetlanie współrzędnych jakkolwiek wartościowe z<br />

dydaktycznego punktu widzenia jest mało interesujące. Pokusimy<br />

się o obiektową aplikację umożliwiającą odręczne rysowanie w<br />

oknie (freehand drawing).<br />

[!!!]UWAGA<br />

________________________________________________________________<br />

Pakiety <strong>Borland</strong> <strong>C++</strong> 3..4.5 zawierają wiele gotowych "klocków" do<br />

wykorzystania. Oto przykład wykorzystania w pliku zasobów .RC<br />

standardowego okienka wejściowego (Input Dialog Box) i<br />

standardowego okienka typu Plik (File Dialog Box):<br />

#include <br />

#include <br />

rcinclude INPUTDIA.DLG<br />

rcinclude FILEDIAL.DLG<br />

ROZKAZY MENU LOADONCALL MOVEABLE PURE DISCARDABLE<br />

BEGIN<br />

POPUP "&File"<br />

BEGIN<br />

MENUITEM "&New" CM_FILENEW<br />

MENUITEM "&Open" CM_FILEOPEN<br />

MENUITEM "&Save" CM_FILESAVE<br />

END<br />

END<br />

Takie menu można zastosować w programie obiektowym umieszcając<br />

je w konstruktorze i dokonując nadpisania metody AssignMenu()<br />

(przypisz menu):<br />

TGOkno::TGOkno(PTWindowsObject AParent, LPSTR ATitle) :<br />

TWindow(AParent, ATitle)<br />

{<br />

AssignMenu("ROZKAZY");<br />

...<br />

}<br />

[S]<br />

rcinclude - dołącz zasoby<br />

LOADONCALL - załaduj po wywołaniu<br />

owlrc - zasoby biblioteki klas OWL<br />

- 436-


Gotowe "klocki" można wykorzystać nawet wtedy, gdy nie pasują w<br />

100%. Inne niż typowe odpowiedzi na wybór rozkazu implementujemy<br />

w programie głównym poprzez nadpisanie wirtualnej metody<br />

virtual void CMFileOpen(RTMessage msg) =<br />

[CM_FIRST + CM_FILEOPEN]<br />

TGOkno GOkno;<br />

void TGOkno::CMFileOpen(RTMessage)<br />

{<br />

... obsługa zdarzenia ...<br />

}<br />

________________________________________________________________<br />

[Z]<br />

________________________________________________________________<br />

1. Przeanalizuj gotowe zasoby dołączone do Twojej wersji <strong>Borland</strong><br />

<strong>C++</strong>.<br />

2. Uruchom kilka projektów "firmowych" dołączonych w katalogu<br />

\EXAMPLES. Zwróć szczególną uwagę na projekty STEPS (kolejne<br />

kroki w tworzeniu aplikacji obiektowej).<br />

________________________________________________________________<br />

- 437-


LEKCJA 46. APLIKACJA OBIEKTOWA - RYSOWANIE W OKNIE.<br />

________________________________________________________________<br />

W trakcie tej lekcji opracujemy obiektową aplikację psoługując<br />

się biblioteką klas Object Windows Library.<br />

________________________________________________________________<br />

Zaczniemy oczywiście od standardowych "klocków". Definicja klasy<br />

Nasza_Aplikacja i moduł prezentacyjno - uruchomieniowy będą<br />

wyglądać standardowo, nie musimy im zatem poświęcać zbytniej<br />

uwagi. Przytoczymy je jedynie. Pointer do napisu inicjujemy po<br />

to, by okienko komunikatu zawierało jakąś bardziej konkretną<br />

informację dla użytkownika. Rysunki z wnętrza tej aplikacji<br />

można przy pomocy Schowka przenieść jako pliki .CLP, bądź za<br />

pomocą PAINTBRUSH - jako .BMP, .PCX i drukować.<br />

#include <br />

LPSTR Ptr = "Jesli chcesz zapamietac rysunek, \<br />

powinienes przeniesc go do Clipboard'u \<br />

klawiszami [Print Screen] \<br />

lub [Alt]+[PrtScr].";<br />

class TNAplikacja : public TApplication<br />

{<br />

public:<br />

TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE<br />

hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

: TApplication(AName, hInstance, hPrevInstance, lpCmdLine,<br />

nCmdShow) {};<br />

virtual void InitMainWindow();<br />

};<br />

...<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

{<br />

TNAplikacja OBIEKT("Rysownik. Prawy klawisz umozliwia wyjscie.",<br />

hInstance, hPrevInstance, lpCmdLine, nCmdShow);<br />

OBIEKT.Run();<br />

return (OBIEKT.Status);<br />

}<br />

Nic specjalnie ciekawego nie dzieje się w funkcji inicjującej<br />

główne okno, ani w funkcji zamykającej aplikację. Zmieniły się<br />

tylko napisy w okienku komunikatów.<br />

void TNAplikacja::InitMainWindow()<br />

{<br />

MainWindow = new TGOkno(0, Name);<br />

}<br />

BOOL TGOkno::CanClose()<br />

- 438-


{<br />

return (MessageBox(HWindow, Ptr, "KONIEC",<br />

MB_YESNO | MB_ICONQUESTION) == IDYES);<br />

}<br />

Zajmiemy się teraz główną "maszynerią" programu. Rozbudujemy<br />

obsługę komunikatów przez handlery zaimplenmentowane w klasie<br />

Główne_Okno.<br />

_CLASSDEF(TGOkno)<br />

class TGOkno : public TWindow<br />

{<br />

public:<br />

HDC dc;<br />

BOOL ButtonDown;<br />

BOOL Flaga_Start;<br />

TGOkno(PTWindowsObject AParent, LPSTR ATitle);<br />

//Konstruktor<br />

virtual void WMLButtonDown(RTMessage Msg)<br />

= [WM_FIRST + WM_LBUTTONDOWN];<br />

virtual void WMLButtonUp(RTMessage Msg)<br />

= [WM_FIRST + WM_LBUTTONUP];<br />

virtual void WMMouseMove(RTMessage Msg)<br />

= [WM_FIRST + WM_MOUSEMOVE];<br />

virtual void WMRButtonDown(RTMessage Msg)<br />

= [WM_FIRST + WM_RBUTTONDOWN];<br />

virtual BOOL CanClose();<br />

};<br />

Konstruktor przekazuje parametry do konstruktora klasy bazowej i<br />

zeruje flagę ButtonDown - lewy klawisz myszki przyciśnięty.<br />

TGOkno::TGOkno(PTWindowsObject AParent, LPSTR ATitle)<br />

: TWindow(AParent, ATitle)<br />

{<br />

ButtonDown = FALSE;<br />

}<br />

Funkcja obsługująca zdarzenie WM_LBUTTONDOWN jeden raz inicjuje<br />

obsługę myszki i ustawia flagę. Funkcje SetCapture() i GetDC()<br />

załatwiją problem relacji kontekstowych i określają obszar<br />

roboczy (client area). Jeśli umieścimy te funkcje w<br />

konstruktorze za obszar client area uznany zostanie cały ekran.<br />

Po zadziałaniu tych funkcji komunikaty od myszki będą dotyczyć<br />

wyłącznie obszaru roboczego. Do naciśnięcia prawego klawisza nie<br />

będzie dostępu do "ramki" okna.<br />

void TGOkno::WMLButtonDown(RTMessage Msg)<br />

{<br />

if (!Flaga_Start)<br />

{<br />

- 439-


Flaga_Start = TRUE; //UWAGA:<br />

SetCapture(HWindow); //Jesli zainicjujemy SetCapture()<br />

dc = GetDC(HWindow); //w konstruktorze - mamy caly ekran<br />

}<br />

MoveTo(dc, Msg.LP.Lo, Msg.LP.Hi);<br />

ButtonDown = TRUE;<br />

}<br />

Funkcja MoweTo() powoduje przesunięcie kursora graficznego do<br />

aktualnej pozycji myszki (już względnej - z uwzględnieniem dc)<br />

bez rysowania linii. Flaga ButtnDown została ustawiona.<br />

Rysowanie scedujemy na metodę obsługującą WM_MOUSEMOVE -<br />

przesunięcie myszki.<br />

void TGOkno::WMMouseMove(RTMessage Msg)<br />

{<br />

if (ButtonDown)<br />

LineTo(dc, Msg.LP.Lo, Msg.LP.Hi);<br />

}<br />

Jeśli lewy klawisz jest naciśnięty - funkcja LineTo() będzie<br />

kreślić linię do kolejnych punktów "śledząc" ruch myszki. Jeśli<br />

użytkownik puści lewy klawisz - zerujemy flagę stanu klawisza<br />

ButtonDown


klawiszami [Print Screen] \<br />

lub [Alt]+[PrtScr].";<br />

class TNAplikacja : public TApplication<br />

{<br />

public:<br />

TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE<br />

hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

: TApplication(AName, hInstance, hPrevInstance, lpCmdLine,<br />

nCmdShow) {};<br />

virtual void InitMainWindow();<br />

};<br />

_CLASSDEF(TMyWindow)<br />

class TMyWindow : public TWindow<br />

{<br />

public:<br />

HDC dc;<br />

BOOL ButtonDown;<br />

BOOL Flaga_Start;<br />

TMyWindow(PTWindowsObject AParent, LPSTR ATitle);<br />

//Konstruktor<br />

virtual void WMLButtonDown(RTMessage Msg)<br />

= [WM_FIRST + WM_LBUTTONDOWN];<br />

virtual void WMLButtonUp(RTMessage Msg)<br />

= [WM_FIRST + WM_LBUTTONUP];<br />

virtual void WMMouseMove(RTMessage Msg)<br />

= [WM_FIRST + WM_MOUSEMOVE];<br />

virtual void WMRButtonDown(RTMessage Msg)<br />

= [WM_FIRST + WM_RBUTTONDOWN];<br />

virtual BOOL CanClose();<br />

};<br />

TMyWindow::TMyWindow(PTWindowsObject AParent, LPSTR ATitle)<br />

: TWindow(AParent, ATitle)<br />

{<br />

ButtonDown = FALSE;<br />

}<br />

void TMyWindow::WMLButtonDown(RTMessage Msg)<br />

{<br />

if ( !Flaga_Start )<br />

{<br />

Flaga_Start = TRUE; //UWAGA:<br />

SetCapture(HWindow); //Jesli zainicjujemy SetCapture()<br />

dc = GetDC(HWindow); //w konstruktorze - mamy caly ekran<br />

}<br />

MoveTo(dc, Msg.LP.Lo, Msg.LP.Hi);<br />

ButtonDown = TRUE;<br />

}<br />

void TMyWindow::WMMouseMove(RTMessage Msg)<br />

- 441-


{<br />

if ( ButtonDown )<br />

LineTo(dc, Msg.LP.Lo, Msg.LP.Hi);<br />

}<br />

void TMyWindow::WMLButtonUp(RTMessage)<br />

{<br />

if (ButtonDown) ButtonDown = FALSE;<br />

}<br />

void TMyWindow::WMRButtonDown(RTMessage)<br />

{<br />

InvalidateRect(HWindow, NULL, TRUE);<br />

ReleaseCapture();<br />

ReleaseDC(HWindow, dc);<br />

Flaga_Start = FALSE;<br />

}<br />

void TNAplikacja::InitMainWindow()<br />

{<br />

MainWindow = new TMyWindow(0, Name);<br />

}<br />

BOOL TMyWindow::CanClose()<br />

{<br />

return (MessageBox(HWindow, Ptr, "KONIEC",<br />

MB_YESNO | MB_ICONQUESTION) == IDYES);<br />

}<br />

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,<br />

LPSTR lpCmdLine, int nCmdShow)<br />

{<br />

TNAplikacja OBIEKT("Rysownik. Prawy klawisz umozliwia<br />

wyjscie.", hInstance, hPrevInstance,<br />

lpCmdLine, nCmdShow);<br />

OBIEKT.Run();<br />

return (OBIEKT.Status);<br />

}<br />

________________________________________________________________<br />

- 442-


LEKCJA 47. O PAKIETACH BORLAND <strong>C++</strong> 4/4.5.<br />

________________________________________________________________<br />

Z tej lekcji dowiesz się, czy warto kupić nowszą wersję <strong>Borland</strong><br />

<strong>C++</strong> 4/4.5 i jakie niespodzianki czekają Cię po zamianie<br />

kompilatora na nowszy.<br />

________________________________________________________________<br />

Czy warto sprawić sobie BORLAND <strong>C++</strong> 4/4.5 ?<br />

Kilka słów o tym, co oferuje <strong>Borland</strong> w pakietach "<strong>Borland</strong> <strong>C++</strong><br />

4/4.5" i jakie niespodzianki czekają nowych użytkowników przy<br />

instalacji i uruchamianiu.<br />

Wymagania sprzętowe i instalacja<br />

Aby instalacja i użytkowanie pakietu przebiegało poprawnie,<br />

zaleca się następującą konfigurację sprzętu:<br />

Wymagania sprzętowe <strong>Borland</strong> <strong>C++</strong> 4.<br />

________________________________________________________________<br />

Parametr minimum zalecane (pełna konfig.)<br />

________________________________________________________________<br />

* procesor 80386/33 MHZ 486 DX (lub 386 + 387)<br />

* miejsce na dysku 8 MB 80 MB (bez kompresji)<br />

* pamięć RAM 4 MB 8 MB i więcej<br />

* system DOS 4.01 DOS 6.0...6.22<br />

* MS Windows 3.1 Windows NT<br />

________________________________________________________________<br />

Częściowa instalacja <strong>Borland</strong> <strong>C++</strong> 4.<br />

________________________________________________________________<br />

Konfiguracja<br />

Dysk<br />

________________________________________________________________<br />

1. Kompilator BCC 16 bitowy (D+W) 9 MB<br />

2. Kompilator BCC 32 bitowy (D+W) 13 MB<br />

3. Środowisko IDE 16 bitowe 26 MB<br />

4. Środowisko IDE 32 bitowe 30 MB<br />

5. Tylko dla DOS (minimum) 8 MB<br />

________________________________________________________________<br />

* D+W - dla DOS i Windows<br />

Można próbować zainstalować <strong>Borland</strong> <strong>C++</strong> 4 na małym dysku, można<br />

także ograniczyć się do 4 MB RAM, ale generowanie 32-bitowych<br />

aplikacji będzie wtedy znacznie utrudnione a praca kompilatora<br />

wolniejsza. W przypadku stosowania kompresorów (np. SUPERSTOR,<br />

DOUBLE SPACE) należy pamiętać, że wtórna kompresja plików jest<br />

mało skuteczna i dysk zgłaszany jako 80 MB może okazać się<br />

"ciasny".<br />

<strong>Borland</strong> <strong>C++</strong> 4 można instalować z dyskietek, bądź z CD-ROM.<br />

Ponieważ pakiet B<strong>C++</strong> 4 jest "okienkowo - zorientowany", nawet<br />

- 443-


program instalacyjny wymaga obecności Windows. Uruchomienie<br />

programu instalacyjnego następuje zatem z poziomu Menedżera<br />

programów rozkazem File | Run... (w spolszczonej wersji Windows<br />

- Plik | Uruchom...) lub z DOS-owskiego wiersza rozkazu:<br />

C:\>WIN X:INSTALL<br />

Opcji jest trochę więcej - o najciekawszych z nich - kilka słów<br />

poniżej.<br />

Warto zwrócić uwagę na tzw. "rozszerzenie dla Windows"<br />

(extention to MS Windows) - Win32s. W programie INSTALL.EXE do<br />

zainstalowania tego pakietu (pakiet jest oryginalnym produktem<br />

Microsofta i wymaga 8 MB przestrzeni dyskowej) służy opcja<br />

[Install Win32s]. Najważniejszy plik-driver instaluje się w<br />

pliku SYSTEM.INI:<br />

device=X:\WINDOWS\SYSTEM\WIN32S\W32S.386<br />

Pozwala to na uruchamianie 32 - bitowych aplikacji pod Windows<br />

3.1. Jeśli masz Windows NT - jest to zbędne - o ten "drobiazg"<br />

zadbał już Microsoft.<br />

W przypadku instalacji w sieci, gdzie Windows zainstalowane są<br />

na serwerze należy pamiętać, że B<strong>C++</strong> 4 w trakcie instalacji<br />

modyfikuje nie tylko klasyczne parametry systemu:<br />

FILES=40<br />

BUFFERS=40<br />

PATH=...;X:\BC4\BIN;<br />

ale także pliki konfiguracyjne i inicjacyjne w katalogu WINDOWS:<br />

WIN.INI, PROGMAN.INI, SYSTEM.INI,<br />

oraz tworzy nowe własne pliki, które będzie próbował zapisać w<br />

katalogach \WINDOWS i \WINDOWS\SYSTEM, np. BCW.INI, TDW.INI,<br />

HELP.ICO, OWL.INI, BWCC.DLL, itp. (łącznie 18 nowych plików).<br />

Brak prawa zapisu na dysk serwera może uniemożliwić poprawną<br />

instalację i skonfigurowanie B<strong>C++</strong> 4/4.5 w sieci.<br />

<strong>Borland</strong> wraz z wersjami bibliotek dostarcza komplet kodów<br />

źródłowych. Jeśli chcesz - możesz sam wygenerować sobie całą<br />

bibliotekę, jeśli chcesz - możesz na własne oczy przekonać się<br />

jak to wszystko działa i jak jest zbudowane. Oprócz<br />

teoretycznych możliwości poznawczych daje to praktyczną<br />

możliwość dostosowania bibliotek do nowej wersji kompilatora, co<br />

w przypadku "czwórki" może okazać się dla wielu użytkowników<br />

bardzo przydatne (o czy dokładniej za chwilę).<br />

Oprócz klasycznego paska głównego menu zintegrowane środowisko<br />

(IDE) zostało wyposażone w rozbudowaną listwę narzędziową.<br />

- 444-


W skład pakietu wchodzą między innymi:<br />

* BCW - zintegrowane środowisko (IDE) dla środowiska Windows<br />

* TDW - Turbo Debugger for Windows<br />

* BCC - kompilator uruchamiany z DOS'owskiego wiersza rozkazu<br />

* BCC32 - kompilator 32 - bitowy (odpowiednik BCC)<br />

* BRCC - kompilator zasobów do kompilacji plików *.RC z zasobami<br />

do postaci binarnej *.RES<br />

* RLINK - konsolidator służący do dołączania plików z zasobami<br />

przy tworzeniu plików wykonywalnych *.EXE<br />

* TLINK - "zwykły" konsolidator<br />

* MAKE - program narzędziowy do automatyzacji kompilacji i<br />

konsolidacji, korzystający z tzw. plików instruktażowych<br />

(emulujący NMAKE Microsofta)<br />

* WINSIGHT - przeglądanie informacji o okienkach (dla Windows) i<br />

komunikatach<br />

* TDUMP - bezpośrednie przeglądanie informacji zawartych w<br />

plikach *.EXE i *.OBJ<br />

* TDSTRIP - narzędzie do usuwania tablicy symboli z plików<br />

wykonywalnych<br />

* IMPLIB - importer bibliotek z DLL<br />

* TDMEM - wyświetlanie informacji o zajętości pamięci<br />

* MAKESWAP - zarządzanie swapowaniem (tworzenie plików<br />

tymczasowych EDPMI.SWP o zadanej wielkości)<br />

i jeszcze parę narzędzi (np. tradycyjny bibliotekarz TLIB,<br />

TOUCH, GREP, itp.), o których tu nie wspominam.<br />

Czego robić nie należy?<br />

Przede wszystkim nie należy traktować <strong>Borland</strong> <strong>C++</strong> 4/4.5 jako<br />

"upgrade" do wcześniejszych wersji (3, czy 3.1). W kompilatorze<br />

dokonano sporych zmian (np. inaczej działa operator new). Nie<br />

wolno zatem "nadpisać" zawartości poprzednich katalogów i plików<br />

o tych samych nazwach. Szczególnie dotyczy to plików<br />

konfiguracyjnych BCCONFIG.BCW i TDCONFIG.TDW. Jeśli stare wersje<br />

tych plików nie zostaną przemianowane, bądź usunięte z pola<br />

widzenia (PATH) - pojawią się konflikty przy uruchamianiu B<strong>C++</strong>.<br />

Ze względu na wprowadzone zmiany pliki .OBJ tworzone przez<br />

wcześniejsze kompilatory C będą w zasadzie przenośne, natomiast<br />

pliki .OBJ i biblioteki utworzone przez wcześniejsze wersje<br />

kompilatorów <strong>C++</strong> (szczególnie <strong>Borland</strong> <strong>C++</strong> 3.1) będą sprawiać<br />

kłopoty (nie będą np. poprawnie wywoływane destruktory). Przy<br />

konsolidacji "starych" plików można stosować opcję -K2<br />

konsolidatora, co pozwoli zmniejszyć do minimum ryzyko<br />

konfliktów.<br />

Jeśli jest już <strong>Borland</strong> Pascal 7...<br />

Jeśli masz już zainstalowany <strong>Borland</strong> Pascal 7 należy pamiętać,<br />

- 445-


że poprawna praca obu kompilatorów w jednym systemie wymaga<br />

"uregulowania stosunków":<br />

1. Każdy kompilator musi mieć własną kopię debuggera TDW. Aby<br />

uniknąć konfliktu pascalowski debugger można przemianować np.:<br />

TDW.EXE --> PASTDW.EXE<br />

2. Należy usunąć stare pliki inicjujące TDW.INI. Można tu<br />

posłużyć się narzędziem TDWINI.EXE.<br />

3. Należy sprawdzić poprawność instalacji driverów w pliku<br />

SYSTEM.INI:<br />

DEVICE=X:\BC4\BIN\WINDPMI.386<br />

DEVICE=X:\BC4\BIN\TDDEBUG.386


- inną bibliotekę Runtime Library<br />

Kod żródłowy biblioteki znajduje się w katalogu:<br />

\BIN\TVISION\SOURCE<br />

Po (Uwaga!) wprowadzeniu kilku niewielkich zmian<br />

- do plików żródłowych .CPP<br />

- do pliku instruktażowego MAKEFILE<br />

oraz po skompilowaniu przy pomocy BCC 4 w DWU WERSJACH: TVO.LIB<br />

(z nakładką - Overlay) i TVNO.LIB (bez nakładki - No Overlay)<br />

biblioteka TVL może być nadal z powodzeniem stosowana z <strong>Borland</strong><br />

<strong>C++</strong> 4. Podobnie rekompilacji wymaga bibiloteka klas dołączona w<br />

wersji żródłowej w katalogu X:\BC4\SOURCE\CLASSLIB.<br />

O AUTOMATYZACJI - CASE.<br />

Prócz znanego już od dość dawna (w komputerologii kilka lat to<br />

cała epoka) tradycyjnego narzędzia Resource Worshop, w wersji<br />

BC4 występują jeszcze inne narzędzia CASE kategorii "wizard"<br />

(kreator aplikacji):<br />

- ClassExpert<br />

- ApplicationExpert<br />

- DialogExpert<br />

- TargetExpert<br />

Nazwa TargetExpert pochodzi od ang. "Target platform" - docelowa<br />

platforma pracy aplikacji (DOS, Win16, Win32).<br />

Biblioteka OWL 2.0 została wzbogacona o dodatkowe klasy VBX<br />

umożliwiające współpracę z Visual Basic i wykorzystanie<br />

elementów utworzonych przy pomocy VB.<br />

Wspomaganie tworzenie programu przy pomocy tych narzędzi<br />

(AppExpert podobnie jak inne narzędzie typu Wizard jest<br />

automatycznym generatorem aplikacji) wymaga od użytkownika<br />

wyboru z listy "zagadnienia" a z okienek docelowych cech<br />

programu. Przytoczę tu dla przykładu listę opcji z pojedynczego<br />

okienka AppExperta z krótkim wyjaśnieniem:<br />

________________________________________________________________<br />

Topics:<br />

(okienko z listą: Zagadnienia)<br />

Application (program docelowy)<br />

-- Basic Opttions (wybór opcji podstawowych)<br />

-- Advanced Options (opcje zaawansowane)<br />

-- Code Gen Control (sposób generacji kodu)<br />

-- Admin Options (opcje "administracyjne")<br />

Main Window (główne okno programu)<br />

-- Basic Options (podstawowe opcje)<br />

-- SDI Client (interf. jednego dokumentu)<br />

-- MDI Client (interf. wielu dokumentów)<br />

MDI Child/View (okna potomne, widok/edycja)<br />

-- Basic Options (opcje podstawowe)<br />

- 447-


Model:<br />

(Szkielet programu)<br />

[X] Multiple document interface - interfejs MDI<br />

[ ] Single document interface - interfejs SDI<br />

Features:<br />

(cechy)<br />

[.] SpeedBar (ma pasek narzędzi)<br />

[.] Status line (ma wiersz statusowy)<br />

[.] Drag/drop (obsługuje ciągnij/upuść)<br />

[.] Printing (obsługuje drukarkę)<br />

________________________________________________________________<br />

Po wybraniu w okienku klawisza [Generate] (wygeneruj) AppExpert<br />

generuje szkielet programu aplikacji o podanych własnościach.<br />

Wygenerowane zostaje od sześciu do dziewięciu (zależnie od<br />

ustawienia opcji i Twoich życzeń) plików projektu:<br />

*.IDE<br />

*.APX<br />

*.RC<br />

*.RH<br />

*.H<br />

*.CPP<br />

*.HPJ<br />

*.RTF<br />

*.ICO<br />

- plik projektu (lub .PRJ)<br />

- plik roboczy AppExpert'a (odpowiednik<br />

.PRJ)<br />

- plik zasobów<br />

- plik nagłówkowy zasobów<br />

- plik nagłówkowy, źródłowy<br />

- moduł główny źródłowy<br />

- plik pomocy<br />

- źródłowy pomocy kontekstowej<br />

- ikonka projektu<br />

Przy pomocy rozkazu Generate makefile można również<br />

automatycznie utworzyć plik instruktażowy MAKEFILE dla<br />

generatora MAKE.EXE.<br />

Uzyskany plik szkieletowy *.CPP należy tylko uzupełnić o obsługę<br />

interesujących nas zdarzeń/komunikatów. Przyspiesza to znacznie<br />

tworzenie typowych aplikacji.<br />

Programiści wszystkich krajów...<br />

B<strong>C++</strong> 4 zawiera bibliotekę LOCALE.DLL umożliwiającą obsługę<br />

angielsko- francusko- i niemiecko- języczną. <strong>Borland</strong> zapowiada,<br />

że następne wersje będą coraz bogatsze. Doczekaliśmy się<br />

spolszczenia Windows - może i <strong>Borland</strong> <strong>C++</strong> po polsku już tuż tuż?<br />

Póki co, najwygodniej podmienić czcionki.<br />

________________________________________________________________<br />

ZAKOŃCZENIE.<br />

I to już niestety koniec. Po przeanalizowaniu historii:<br />

programowania sekwencyjnego i strukturalnego<br />

oraz nowoczesnych styli programowania:<br />

obiektowego i zdarzeniowego<br />

- 448-


pozostał Ci już tylko wykonanie trzech rzeczy. Powinieneś teraz:<br />

1. Pisać własne aplikacje<br />

2. Pisać własne aplikacje<br />

3. Pisać własne aplikacje<br />

Tak naprawdę - jest to jedyny sposób, by zostać dobrym<br />

programistą.<br />

Przez pewien czas okaże Ci się zapewne przydatna dyskietka<br />

dołączona do książki. Znajdziesz tam jeszcze sporo programów<br />

przykładowych, które nie zmieściły się w książce.<br />

Przyjemnej pracy z programem MEDYT.<br />

- 449-

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!