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

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

<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!