P. 1
C++ - lekcje

C++ - lekcje

4.5

|Views: 3,120|Likes:
Wydawca: api-3723532

More info:

Published by: api-3723532 on Oct 18, 2008
Prawo autorskie:Attribution Non-commercial

Availability:

Read on Scribd mobile: iPhone, iPad and Android.
download as PDF, TXT or read online from Scribd
See more
See less

03/18/2014

pdf

text

original

1

SPIS


LEKCJA 1. Co o C i C++ kazdy wiedziec powinien.
LEKCJA 2. Jak korzystac z kompilatora BORLAND C++?
LEKCJA 3. Główne menu i inne elementy IDE.
LEKCJA 4. Jeszcze o IDE C++ .
LEKCJA 5 - DZIAŁANIA PRZY POMOCY MYSZKI I BŁEDY W PROGRAMIE
LEKCJA 6 - NASTEPNY PROGRAM - KOMPUTEROWA ARYTMETYKA
LEKCJA 7. Z czego składa sie program
LEKCJA 8. Jakich słów kluczowych uzywa C++.
LEKCJA 9: O SPOSOBACH ODWOŁYWANIA SIE DO DANYCH
LEKCJA 10 Jakie operatory stosuje C++.
LEKCJA 11. Jak deklarowac zmienne. Co to jest wskaznik
LEKCJA 12. Wskazniki i tablice w C i C++.
LEKCJA 13. Jak tworzyc w programie petle i rozgałezienia
LEKCJA 14. Jak tworzyc i stosowac struktury
LEKCJA 15. Jak posługiwac sie funkcjami.
LEKCJA 16 - ASEMBLER TASM i BASM.
LEKCJA 17: TROCHE SZCZEGÓLÓW TECHNICZNYCH
LEKCJA 18 - O ŁANCUCHACH TEKSTOWYCH
LEKCJA 19: KILKA INNYCH PRZYDATNYCH FUNKCJI
LEKCJA 20 - JESLI PROGRAM POWINIEN URUCHOMIC INNY PROGRAM...
LEKCJA 21: KILKA PROCESÓW JEDNOCZESNIE.
LEKCJA 22. NA ZDROWY CHŁOPSKI ROZUM PROGRAMISTY.
LEKCJA 23 - Co nowego w C++?
LEKCJA 24 : SKAD WZIEŁY SIE KLASY I OBIEKTY W C++.
LEKCJA 25: PRZYKŁAD OBIEKTU.
LEKCJA 26: CO TO JEST KONSTRUKTOR.
LEKCJA 27: O DZIEDZICZENIU.
LEKCJA 28: DZIEDZICZENIE ZŁOZONE.
LEKCJA 29: FUNKCJE I OVERLOADING.
LEKCJA 30: WYMIANA DANYCH MIEDZY OBIEKTAMI.
LEKCJA 31: PRZEKAZANIE OBIEKTÓW JAKO ARGUMENTÓW DO FUNKCJI
LEKCJA 33: WSKAZNIKI DO OBIEKTÓW.
LEKCJA 34 OVERLOADING OPERATORÓW.
LEKCJA 35: O ZASTOSOWANIU DZIEDZICZENIA
LEKCJA 36: FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE.
LEKCJA 37: KAZDY DYSK JEST ZA MAŁY, A KAZDY PROCESOR ZBYT WOLNY...
LEKCJA 38: O C++, Windows i małym Chinczyku. czyli: KTO POWIEDZIAŁ, ZE PROGRAMOWANIE DLA WINDOWS JEST TRUDNE?!!!
LEKCJA 39: KORZYSTAMY ZE STANDARDOWYCH ZASOBÓW Windows.
LEKCJA 40: STRUKTURA PROGRAMU PROCEDURALNO - ZDARZENIOWEGO PRZEZNACZONEGO DLA WINDOWS.
LEKCJA 41: JAK TWORZY SIE APLIKACJE DLA Windows?
LEKCJA 42: KOMPILATORY "SPECJALNIE DLA Windows".
LEKCJA 43: Elementy sterujace i zarzadzanie programem.
LEKCJA 44: O Okienkach dialogowych.
LEKCJA 45: Dołaczanie zasobów - menu i okienka dialogowe.
LEKCJA 46: O PROGRAMACH OBIEKTOWO - ZDARZENIOWYCH.
LEKCJA 47: APLIKACJA OBIEKTOWA - RYSOWANIE W OKNIE.
LEKCJA 48: O PAKIETACH BORLAND C++ 4/4.5.
2

LEKCJA 1. Co o C i C++ kazdy wiedziec powinien.



W trakcie tej lekcji dowiesz sie, dlaczego pora na C++.
____________________________________________________


Jezyk C++ jest uniwersalnym, nowoczesnym jezykiem programowania.
Stosowane przez USA i inne kraje wobec Polski wieloletnie
embargo COCOM'u (przeszkody w dostepie do nowoczesnej
technologii) sprawiły m. in., ze popularnosc OS2, UNIXa i C/C++
jest w Polsce do dzis nieproporcjonalnie mała, a Basica, Pascala
i DOSa nieproporcjonalnie duza. W USA C++ juz od kilku lat
stanowi podstawowe narzedzie programistów.

Juz słysze oburzenie (A co mnie obchodzi historia
"komputerologii" i koligacyjki!). Otóz obchodzi, bo wynikaja z
niej pewne "grzechy pierworodne" jezyka C/C++, a dla Ciebie,
szanowny Czytelniku - pewne wnioski praktyczne.

Grzech Pierwszy:
* Kompilator jezyka C/C++ jest standardowym wyposazeniem systemu
operacyjnego UNIX.

Skutki praktyczne:

Kazdy PC jest w momencie zakupu (co czesto wchodzi w cene zakupu
komputera) wyposazany w system operacyjny DOS - np. DR DOS, PC
DOS, PTS DOS lub MS DOS. Standardowo w zestaw systemu MS DOS
wchodzi interpreter jezyka BASIC (w MS-DOS - QBasic.EXE). Mozesz
wiec byc pewien, ze jesli jest DOS, to musi byc i BASIC.
Podobnie rzecz ma sie z C/C++. Jesli jest na komputerze system
UNIX (za wyjatkiem najubozszych wersji systemu XENIX), masz tam
do dyspozycji kompilator C/C++, za to BASICA ani Pascala prawie
na pewno tam nie ma. Podobnie coraz popularniejszy OS/2
wyposazony jest w kompilator (całkiem niezły) C++ i dodatkowo
jeszcze w pewne gotowe-firmowe biblioteki.

Grzech drugi:
* Jezyk C/C++ powstał jeszcze zanim wymyslono PC, DOS, GUI
(Graficzny Interfejs Uzytkownika), Windows i inne tym podobne.

Dwa najwazniejsze skutki praktyczne:

I. W załozeniach twórców jezyk C++ miał byc szybki (i jest) i
zajmowac mało miejsca w pamieci (bo ówczesne komputery miały jej
bardzo mało!). Zawiera wiec rózne, niezrozumiałe dla nas z
dzisiejszego punktu widzenia skróty. Np. to co w Pascalu czy
Basicu wyglada zrozumiale:

i:=i+1; (Pascal)
10 I=I+1 lub inaczej NEXT I (Basic)

to w jezyku C++ wyglada dziwacznie:

i++; albo jeszcze dziwniej ++i;

Tym niemniej zwróc uwage, ze w Pascalu zajmuje to 7 znaków, w
Basicu - 8 znaków (spacja to tez znak!), a w C++ tylko 4.

Inny przykład:

X=X+5 (Basic, 5 znaków),
X:=X+5 (Pascal, 6 znaków),
X+=5 (C++, tylko 4 znaki).

Z takiej własnie filozofii wynika i sama nazwa - najkrótsza z
mozliwych. Jesli bowiem i++ miało znaczyc mniej wiecej tyle samo
co NEXT I (nastepne I) to C++ znaczy mniej wiecej tyle samo co
"NASTEPNA WERSJA C".

II. Nie ma nic za darmo. W jezyku C/C++, podobnie jak w
samochodzie wyscigowym formuły I, za szybkosc i skutecznosc
płaci sie komfortem. Konstrukcje stosowane w jezyku C/C++ sa
maksymalnie dostosowane do "wygody" komputera. Pozwala to na
uzyskiwanie ˙niezwykle szybkich "maszynowo-zorientowanych" kodów
wykonywalnych programu, ale od programisty wymaga
przyzwyczajenia sie do "komputerowo-zorientowanego sposobu
myslenia".

Grzech Trzeci (i chyba najciezszy):
* Jest najlepszy. Ostrozniej - jest najchetniej stosowanym
narzedziem profesjonalnych programistów.

Ma oczywiscie konkurentów. Visual Basic (do małych aplikacji
okienkowych), Turbo Pascal (do nauki podstaw - elementów
programowania sekwencyjnego, proceduralno-strukturalnego),
QuickBasic (programowanie strukturalne w srodowisku DOS),
Fortran 90, ADA, SmallTalk, itp, itd.

Sam wielki Peter Norton przyznaje, ze poczatkowe wersje swojego
słynnego pakietu Norton Utilities pisał w Pascalu, ale dopiero
przesiadka na C/C++ pozwoliła mu doprowadzic NU do dzisiejszej
doskonałosci. Jakie sa programy Petera Nortona - kazdy widzi...

Zapewne masz na swoim komputerze kilka róznych aplikacji (np.
TAG, QR-Tekst, Word, itp.) - jesli zajrzysz do nich do srodka
(View), mozesz sam przekonac sie, ze wiekszosc z nich została
napisana własnie w C++ (Kompilatory C++ pozostawiaja w kodzie
wynikowym .EXE swoja wizytówke zwykle czytelna przy pomocy
przegladarki; przekonasz sie o tym takze zagladajac przez [View]
do własnych programów); stosowane narzedzia mozesz rozpoznac
takze po obecnosci dynamicznych bibliotek - np. BWCC.DLL -
biblioteka elementów sterujacych - klawiszy, itp - Borland
Custom Controls for Windows).

Skutki praktyczne:

Nauczywszy sie jezyka C/C++ mozesz nie bac sie ani systemu
UNIX/XENIX a ich srodowiska okienkowego - X Windows, ani OS2,
ani Windows 95 (dotychczasowe testy starych 16-bitowych
aplikacji wykazały b. wysoki stopien kompatibilnosci), ani
stacji roboczych, ani duzych komputerów klasy mainframe. Jezyk
C/C++ dosłuzył sie bowiem ogromnej ilosci tzw. implementacji
czyli swoich odmian, przeznaczonych dla róznych komputerów i dla
róznych systemów operacyjnych. Windows NT i Windows 95 równiez
zostały napisane w C++.
Czytajac prase (np. Computer World, PC-Kurier i in.) zwróc
uwage, ze najwiecej ofert pracy jest własnie dla programistów
posługujacych sie C++ (i tak zapewne bedzie jeszcze przez kilka
lat, póki nie wymysla czegos lepszego - np. jakiegos C+++).
Z Grzechu Trzeciego (choc nie tylko) wynika takze posrednio
Grzech Czwarty.

Jezyka C++ Grzech Czwarty - ANSI C, C++, czy Turbo C++, Visual
C++, czyli mała wieza BABEL.

Nie jestem pewien, czy "wieza BABEL" jest okresleniem
trafniejszym niz "kamien filozoficzny", badz "perpetuum mobile".
To co w ciagu ostatnich lat stało sie z jezykiem C++ ma cos
wspólnego z kazdym z tych utopijnych symboli. A w duzym
uproszczeniu było to tak.

Podobnie, jak mechanikom od zarania dziejów marzyło sie
perpetuum mobile, tak informatykom zawsze marzyło sie stworzenie
jednego SUPER-UNIWERSALNEGO jezyka programowania. Takiego, który
byłby zupełnie niezalezny od sprzetu tzn., aby program napisany
w takim jezyku mógł byc przeniesiony BEZ ZADNYCH ZMIAN na
dowolny komputer I DZIAŁAŁ. Do takiej roli pretendowały kolejno
FORTRAN, Algol a potem przyszła pora na C/C++. Gdyby informatycy
nie okazali sie zbyt zachłanni, moze cos by z tego wyszło. Ale,
jak to w zyciu, programisci (podobnie jak zona rybaka z bajki "O
rybaku i złotej rybce") chcieli wszystkiego naraz:

* zeby program dał sie przeniesc na komputer innego typu i
działał,
* zeby działał szybko i optymalnie wykorzystywał sprzet,
* zeby umiał wszystko, co w informatyce tylko wymyslono (tj. i
grafika, i obiekty, i obsługa peryferii i...).

I stało sie. W pomyslanym jako uniwersalny jezyku zaczeły
powstawac odmiany, dialekty, mutacje, wersje itp. itd.

Jesli C++ nie jest Twoim pierwszym jezykiem, z pewnoscia
zauwazyłes Czytelniku, ze pomiedzy GW Basic a Quick Basic sa
pewne drobne róznice. Podobnie Turbo Pascal 7.0 troche rózni sie
od Turbo Pascala 5.0. Mimo to przykład ponizej pewnie Cie troche
zaskoczy. Dla zilustrowania skali problemu przedstawiam ponizej
dwie wersje TEGO SAMEGO PROGRAMU napisanego w dwu róznych
wersjach TEGO SAMEGO JEZYKA C++. . Obydwa programy robia
dokładnie to samo. Maja za zadanie wypisac na ekranie napis
"Hello World" (czyli "Czesc swiecie!").

3
Program (1)

main()
{
printf("Hello World\n");
}


Program (2)

#include <windows.h>
#include <iostream.h>

LPSTR p = "Hello World\n";

main(void)
{
cout << "Hello World" << endl;
MessageBox(0, p, "Aplikacja dla Windows", MB_OK);
return (0);
}

Cóz za uderzajace podobienstwo, prawda? Ale zarty na bok. Jesli
zaistnieje jakis problem, zawsze mamy co najmniej trzy wyjscia.
Mozemy:

1. Udawac, ze go nie ma.
Tak postepuje wielu autorów podreczników na temat C/C++.
2. Krzyczec, ze nam sie to nie podoba.
Mamy pełne prawo obrazic sie i wrócic do Basica lub Pascala.
3. Spróbowac poruszac sie w tym gaszczu.

Wyjscie trzecie ma jedna wade - jest najtrudniejsze, ale i
efekty takiego wyboru sa najbardziej obiecujace.

Jesli chcesz zaryzykowac i wybrac wyjscie trzecie, spróbujmy
zrobic pierwszy krok w tej "dzungli". Wyjasnijmy kilka nazw,
pojec i zasad gry obowiazujacych w tym obszarze.

Jezyki programowania posługuja sie pewnymi specyficznymi grupami
słów i symboli. Sa to m. in.:

* słowa kluczowe
(tu pomiedzy wersjami C++ rozbieznosci sa niewielkie),

* operatory (po prostu znaki operacji - np. +),
(tu zgodnosc jest niemal 100 %-owa)

* dyrektywy
(polecenia dla kompilatora JAK tworzyc program wynikowy;
tu juz jest gorzej, szczególnie dyrektywa #pragma w kazdej
wersji kompilatora C++ jest inna)

* nazwy funkcji
(z tym gorzej, bo kazdy producent ma własne funkcje i własne
upodobania)

* nazwy stałych
(gdyby chodziło tylko o PI i e - wszystko byłoby proste)

* nazy zasobów (FILE, PRN, CONSOLE, SCREEN itp. itd)
(tu jest lepiej, ale tez rozbieznosci sa zauwazalne)

Autor programu moze jeszcze nadawac zmiennym (liczbom, zmiennym
napisom, obiektom, itp.) własne nazwy, wiec czasem nawet
wytrawny programista ma kłopoty ze zrozumieniem tekstu
zródłowego programu...

W jezyku C a nastepnie C++ przyjeto pewne maniery nadawania nazw


- identyfikatorów ułatwiajace rozpoznawanie tych grup słów:

* nazwa() - funkcja
* słowa kluczowe i nazwy zmiennych - małymi literami
* STAŁE - nazwy stałych najczesciej duzymi literami
* long/LONG - typy danych podstawowe/predefiniowane dla Windows
_NAZWA - nazwy stałych predefiniowanych przez producenta
__nazwa lub __nazwa__ - identyfikatory charakterystyczne dla
danej wersji kompilatora

itp., których to zwyczajów i ja postaram sie przestrzegac w
tekscie ksiazki.

Amerykanski Instytut Standardów ANSI od lat prowadzi walke z
wiatrakami. Stoi na strazy jednolitego standardu jezyka, który
nazywa sie standardem ANSI C i ANSI C++. Wielcy producenci od
czasu do czasu organizuja konferencje i spotkania gdzies w
ciepłych krajach i uzgadniaja niektóre standardy - czyli wspólne
dla nich i zalecane dla innych normy, ale niektórzy bywaja
zazdrosni o własne tajemnice i nie publikuja wszystkich
informacji o swoich produktach. Dlatego wszelkie "słuszne i
uniwersalne" standardy typu ODBC, Latin 2, Mazovia, LIM, OLE,
DDE, BGI, itp., itd. maja niestety do dzis ograniczony zakres
stosowalnosci a wszelkie zapewnienia producentów o całkowitej
zgodnosci ich produktu z... (tu wpisac odpowiednie) nalezy
niestety nadal traktowac z pewna rezerwa.

W niniejszej ksiazce zajmiemy sie kompilatorem Borland C++ w
jego wersjach 3.0 do 4.5, jest to bowiem najpopularniejszy w
Polsce kompilator jezyka C/C++ przeznaczony dla komputerów IBM
PC. Nie bez znaczenia dla tej decyzji był takze fakt, ze Borland
C++ i Turbo C++ bez konfliktów współpracuje z:

* Turbo Pascal i Borland Pascal;
* Assemblerami: TASM, BASM i MASM;
* Turbo Debuggerem i Turbo Profilerem;
* bibliotekami Turbo Vision, ObjectVision, Object Windows
Library, Database Tools, itp.
* pakietami innych producentów - np. Win/Sys Library, Object
Professional, CA-Visual Objects, Clipper, itp.

i in. produktami "ze stajni" Borlanda popularnymi wsród
programistów. Programy TASM/BASM, Debugger, Profiler a takze
niektóre biblioteki (np. Object Windows Library, Turbo Vision
Library, itp. wchodza w skład pakietów instalacyjnych BORLANDA,
ale UWAGA - niestety nie wszystkich). Borland C++ 4+ pozwala,
dzieki obecnosci specjalnych klas VBX w bibliotece klas i
obiektów Object Windows Library na wykorzystanie programów i
zasobów tworzonych w srodowisku Visual Basic'a. Podobnie
kompilatory C++ firmy Microsoft (szczególnie Visual C++)
bezkonfliktowo współpracuja z zasobami innych aplikacji - np.
Access, Excel, itp..

Warto tu zwrócic uwage na jeszcze jeden czynnik, który moze stac
sie Twoim, Czytelniku atutem. Jesli znasz juz kompilatory Turbo
Pascal, badz Borland Pascal, zwróc uwage, ze wiele funkcji
zaimplementowanych w Turbo Pascal 6.0. czy 7.0 ma swoje
odpowiedniki w BORLAND C++ i Turbo C++. Odpowiedniki te zwykle
działaja dokładnie tak samo, a róznia sie najczesciej
nieznacznie pisownia nazwy funkcji. Wynika to z błogosławienstwa
"lenistwa" (ponoc homo sapiens najwiecej wynalazków popełniał
własnie ze strachu, badz z lenistwa...). Firmie Borland "nie
chciało sie" wymyslac od nowa tego, co juz sprawdziło sie
wczesniej i do czego przyzwyczaili sie klienci! I odwrotnie.
Poznawszy Borland/Turbo C++ z łatwoscia zauwazysz te same
funkcje w Borland/Turbo Pascalu.

[!!!]UWAGA!
________________________________________________________________

O Kompilatorach BORLAND C++ 4 i 4.5 napisze nieco pózniej,
poniewaz sa bardziej skomplikowane i wymagaja troche wiekszej
znajomosci zasad tworzenia i uruchamiania programów (projekty).

To prawda, ze zawieraja narzedzia klasy CASE do automatycznego
generowania aplikacji i jeszcze kilka innych ułatwien, ale miej
troche cierpliwosci...
________________________________________________________________

[???] C.A.S.E.
________________________________________________________________

CASE - Computer Aided Software Engineering - inzynieria
programowa wspomagana komputerowo. Najnowsze kompilatory C++
wyposazone sa w narzedzia nowej generacji. W róznych wersjach
nazywaja sie one AppExpert, ClassExpert, AppWizard, VBX
Generator, itp. itd, które pozwalaja w duzym stopniu
zautomatyzowac proces tworzenia aplikacji. Nie mozna jednak
zaczynac kursu pilotazu od programowania autopilota - a kursu
programowania od automatycznych generatorów aplikacji dla
Windows...
________________________________________________________________


Zaczynamy zatem od rzeczy najprostszych, majac jedynie te
4
krzepiaca swiadomosc, ze gdy juz przystapimy do pisania
aplikacji konkurencyjnej wobec Worda, QR-Tekst'a, czy Power
Point'a - moze nas wspomagac system wspomaganina CASE dołaczony
do najnowszych wersji BORLAND C++ 4 i 4.5. Jesli mamy juz gotowe
aplikacje w Visual Basic'u - Borland C++ 4+ pozwoli nam
skorzystac z elementów tych programów (ale pracowac te aplikacje
po przetransponowaniu do C++ beda od kilku do kilkuset razy
szybciej).
_______________________________________________________________


LEKCJA 2. Jak korzystac z kompilatora BORLAND C++?

________________________________________________________________
W trakcie tej lekcji poznasz sposoby rozwiazania typowych
problemów wystepujacych przy uruchomieniu kompilatora Borland
C++.
________________________________________________________________

UWAGA:
Z A N I M rozpoczniesz prace z dyskietka dołaczona do niniejszej
ksiazki radzimy Ci SPORZADZIC ZAPASOWA KOPIE DYSKIETKI przy
pomocy rozkazu DISKCOPY, np.

DISKCOPY A: A: lub DISKCOPY B: B:

Unikniesz dzieki temu byc moze wielu kłopotów, których moze Ci
narobic np. przypadkowy wirus lub kropelka kawy.

INSTALACJA DYSKIETKI.

Na dyskietce dołaczonej do niniejszej ksiazki, która najlepiej
zainstalowac na dysku stałym (z dyskiem pracuje sie znacznie
szybciej, a prócz tego jest tam znacznie wiecej miejsca), w jej
katalogu głównym znajduje sie programik instalacyjny o nazwie:

INSTALUJ.BAT

napisany jako krótki plik wsadowy w jezyku BPL (Batch
Programming Language - jezyk programowania wsadowego). Aby
zainstalowac programy z dyskietki na własnym dysku powinienes:

* sprawdzic, czy na dysku (C:, D:, H: lub innym) jest co
najmniej 2 MB wolnego miejsca,
* włozyc dyskietke do napedu i wydac rozkaz:

<-- patrz tekst ksiazki

* po nacisnieciu [Entera] rozpocznie sie nstalacja. O
zakonczeniu instalacji zostaniesz poinformowany napisem na
ekranie.

UWAGI:
* Jesli korzystasz z napedu dyskietek B:, lub chcesz
zainstalowac programy z dyskietki na innym dysku niz C: -
wystarczy napisac rozkaz - np. B:\INSTALUJ AMC48 D: i nacisnac
[Enter].
* Program instalacyjny zadziała poprawnie tylko wtedy, gdy masz
system operacyjny DOS 6+ (6.0 lub nowszy) na dysku C: w katalogu

C:\DOS.
* Mozesz zainstalowac programy z dyskietki z poziomu srodowiska
Windows. W oknie Menedzera Programów:
- rozwin menu Plik
- wybierz rozkaz Uruchom...
- do okienka wpisz <-- patrz tekst ksiazki

Program instalacyjny utworzy na wskazanym dysku katalog
\C-BELFER
i tam skopiuje cała zawartosc dyskietki oraz dokona dekompresji
(rozpakowania) plików. Jesli chcesz skopiwac zawartosc dyskietki
do własnego katalogu roboczego, wystarczy "wskazac" programowi
instalacyjnemu własciwy adres:

<-- patrz tekst ksiazki

Zostanie utworzony katalog: F:\USERS\ADAM\TEKSTY\C-BELFER

UWAGA:
Prócz przykładów opisanych w ksiazce dyskietka zawiera dodatkowo
kilka przykładowych aplikacji, na które zabrakło miejsca, miedzy
innymi:

WYBORY95 - prosta gra zrecznosciowa (dla Windows)
FOR*.CPP - przykłady zastosowania petli
BGI*.CPP - przykłady grafiki DOS/BGI
oraz programik ułatwiajacy kurs - MEDYT.EXE wyposazony w
dodatkowe pliki tekstowe.

I. URUCHOMIENIE KOMPILATORA.

Aby uruchomic kompilator, powinienes w linii rozkazu po
DOS'owskim znaku zachety (zwykle C> lub C:\>) wydac polecenie:

BC

i nacisnac [Enter].
(UWAGA: w róznych wersjach kompilatorów moze to byc np.:
BC, TC, a dla Windows np. BCW - sprawdz swoja wersje)

Jesli Twój komputer odpowiedział na to:

Bad command or file name

* na Twoim komputerze nie ma kompilatora BORLAND C++:
ROZWIAZANIE: Zainstaluj C++.

* w pliku AUTOEXEC.BAT nie ma sciezki dostepu do katalogu, w
którym zainstalowany jest kompilator C++.

ROZWIAZANIE:

1. Zmienic biezacy katalog (i ewentualnie dysk) na odpowiedni,
np.:
D:[Enter]
CD D:\BORLANDC\BIN[Enter]. //UWAGA: Podkatalog \BIN
Albo

2. Ustawic sciezke dostepu przy pomocy rozkazu np:
PATH C:\BORLANDC\BIN
(lub D:\TURBOC\BIN stosownie do rozmieszczenia plików na Twoim
komputerze; najlepiej zasiegnij rady lokalnego eksperta).

[???] NIE CHCE USTAWIC SCIEZKI ?
________________________________________________________________
Tak czasem sie zdarza - zwykle wtedy, gdy pracujesz w DOS-ie z
programem Norton Commander. Musisz pozbyc sie "na chwile"
programu NC. Nacisnij [F10] - Quit i potwierdz przez [Y] lub
[Enter]. Po ustawieniu sciezek mozesz powtórnie uruchomic NC.
________________________________________________________________

Albo

3. Dodac do pliku AUTOEXEC.BAT dodatkowa sciezke. Jest to
wyjscie najlepsze. Na koncu linii ustawiajacej sciezki - np.:

PATH C:\; C:\DOS; C:\NC; C:\WINDOWS

dodaj sciezke do kompilatora C++, np.:

PATH C:\; C:\DOS; C:\NC; D:\BORLANDC\BIN;

Załatwi to problem "raz na zawsze". Po uruchomieniu komputera
sciezka bedzie odtad zawsze ustawiana automatycznie.
Poniewaz kompilator C++ wymaga w trakcie pracy otwierania i
łaczenia wielu plików, rózne wersje (program instalacyjny
INSTALL.EXE podaje te informacje w okienku pod koniec
instalacji) wymagaja dodania do pliku konfiguracyjnego
CONFIG.SYS wiersza:

FILES = 20

(dla róznych wersji wartosc ta wacha sie w granicach od 20 do
50). Najbezpieczniej, jesli nie masz pewnosci dodac 50. Jesli
wybrałes wariant trzeci i ewentualnie zmodyfikowałes swój
CONFIG.SYS, wykonaj przeładowanie systemu [Ctrl]-[Alt]-[Del].
Teraz mozesz wydac rozkaz

BC[Enter]

Mam nadzieje, ze tym razem sie udało i oto jestesmy w IDE
Borland C++. Jesli nie jestes jedynym uzytkownikiem, na ekranie
rozwinie sie cała kaskada okienek roboczych. Skonsultuj z
włascicielem, które z nich mozna pozamykac a które pliki mozna
skasowac lub przeniesc. Pamietaj "primo non nocere" - przede
wszystkim nie szkodzic!
5

t[S!] IDE = Integrated Development Environment,

IDE, czyli Zintegrowane Srodowisko Uruchomieniowe. Bardziej
prozaicznie - połaczony EDYTOR i KOMPILATOR. Zapewne znasz juz
cos podobnego z Pascala lub Quick Basica. Od dzis bedzie to
Twoje srodowisko pracy, w którym bedziesz pisac, uruchamiac i
modyfikowac swoje programy.

t[???] DISK FULL!
________________________________________________________________
Co robic, jesli przy próbie uruchomienia kompilator C++
odpowiedział Ci:

Disk full! Not enough swap space.

Program BC.EXE (TC.EXE) jest bardzo długi. Jesli wydasz rozkaz
(wariant 1: Turbo C++ 1.0, nizej BORLAND C++ 3.1):

DIR TC.EXE
uzyskasz odpowiedz, jak ponizej:

C:>DIR TC.EXE
Directory of D:\TC\BIN

TC EXE 876480 05-04-90 1:00a
1 file(s) 876480 bytes
17658880 bytes free

C:>DIR BC.EXE
Directory of C:\BORLANDC\BIN

BC EXE 1410992 06-10-92 3:10a
1 file(s) 1410992 bytes
18926976 bytes free


Poniewaz plik kompilatora nie miesci sie w 640 K pamieci musi
dokonywac tzw. SWAPOWANIA i tworzy na dysku dodatkowy plik
tymczasowy (ang. swap file). Na dysku roboczym powinno
pozostawac najmniej 500 KB wolnego miejsca. Jesli mozesz,
pozostaw na tym dysku wolne nie mniej niz 1 MB. Ułatwi to i
przyspieszy prace.
________________________________________________________________

Tworzony tymczasowo plik roboczy wyglada tak:

Volume in drive D has no label
Directory of D:\SIERRA

TC000A SWP 262144 12-13-94 5:42p (13-XII to dzis!)
1 file(s) 262144 bytes
11696320 bytes free

t[!!!] UWAGA:

Kompilator C++ bedzie próbował tworzyc plik tymczasowy zawsze w
biezacym katalogu, tzn. tym, z którego wydałes rozkaz
TC lub BC.

II. WNIOSKI PRAKTYCZNE.

* Lepiej nie uruchamiac C++ "siedzac" na dyskietce, poniewaz
moze mu tam zabraknac miejsca na plik tymczasowy.
* Dla uzytkowników Novella: Uruchamiajcie kompilator C++ tylko
we własnych katalogach - do innych mozecie nie miec praw zapisu.

Plik .SWP jest tworzony tylko podczas sesji z kompilatorem C++ i

usuwany natychmiast po jej zakonczeniu. Mozesz go zobaczyc tylko

wychodzac "na chwile" do systemu DOS przy pomocy rozkazu DOS
Shell (menu File).

t[S!] SWAP - Zamiana.
________________________________________________________________
Jesli wszystkie dane, potrzebne do pracy programu nie mieszcza
sie jednoczesnie w pamieci operacyjnej komputera, to program -
"własciciel", (lub system operacyjny - DOS, OS2, Windows) moze
dokonac tzw. SWAPOWANIA. Polega to na usunieciu z pamieci
operacyjnej i zapisaniu na dysk zbednej w tym momencie czesci
danych, a na ich miejsce wpisaniu odczytanej z dysku innej
czesci danych, zwykle takich, które sa programowi pilnie
potrzebne do pracy własnie teraz.
________________________________________________________________


t[Z] - Propozycje zadan do samodzielnego wykonania.
----------------------------------------------------------------

1.1 Sprawdz ile bajtów ma plik .EXE w tej wersji kompilatora
C++, której uzywasz.
1.2. Posługujac sie rozkazem DOS Shell z menu File sprawdz gdzie
znajduje sie i jakiej jest wielkosci plik tymczasowy .SWP. Ile
masz wolnego miejsca na dysku ?
________________________________________________________________
EOF



LEKCJA 3. Główne menu i inne elementy IDE.
________________________________________________________________
W trakcie tej lekcji dowiesz sie jak poruszac sie w
zintegrowanym srodowisku (IDE) Turbo C++.
________________________________________________________________

Najwazniejsza rzecza w srodowisku IDE jest GŁÓWNE MENU (ang.
MENU BAR), czyli pasek, który widzisz w górnej czesci ekranu.
Działa to podobnie, jak główne menu w programie Norton Commander
(dostepne tam przez klawisz [F9]).

KRÓTKI PRZEGLAD GŁÓWNEGO MENU.

Przycisnij klawisz [F10].
Główne menu stało sie aktywne. Teraz przy pomocy klawiszy
kursora (ze strzałkami [<-], [->]) mozesz poruszac sie po menu i

wybrac te grupe polecen, która jest Ci potrzebna. A oto nazwy
poszczególnych grup:

[S!]tGRUPY POLECEN - NAZWY POSZCZEGÓLNYCH
"ROZWIJANYCH" MENU.

= Bez nazwy (menu systemowe).
File Operacje na plikach.
Edit Edycja plików z tekstami zródłowymi programów.
Search Przeszukiwanie.
Run Uruchomienie programu.
Compile Kompilacja programu.
Debug "Odpluskwianie", czyli wyszukiwanie błedów w
programie.
Project Tworzenie duzych, wielomodułowych programów.
Options Opcje, warianty IDE i kompilatora.
Window Okna (te na ekranie).
Help Pomoc, niestety po angielsku.

UWAGA:
__________________________________________________________
W niektórych wersjach kompilatora na pasku głównego menu pojawi
sie jeszcze Browse - przegladanie (funkcji, struktury klas i
obiektów). Zwróc uwage, ze w okienkowych wersjach niektóre
rozkazy "zmieniaja" menu i trafiaja do
Browse, Debug, Project.
W BC++ 4 menu Run brak (!). Tworzenie aplikacji sprowadza sie
tam do nastepujacych kroków:

Project | Open project lub | AppExpert
Debug | Run


ROZWIJAMY MENU.

Z takiego krecenia sie w kółko po pasku (a propos, czy
zauwazyłes, ze pasek podswietlenia moze byc "przewijany w
kółko"?) jeszcze niewiele wynika. Robimy wiec nastepny krok.

Wskaz w menu głównym nazwe "File" i nacisnij [Enter].
Rozwineło sie menu File zawierajace liste rozkazów dotyczacych
operacji na plikach. Po tym menu tez mozesz sie poruszac przy
pomocy klawiszy kursora ze strzałkami góre lub w dół. Masz do
wyboru dwie grupy rozkazów rozdzielone pozioma linia:

t[S!]
______________________________________________________________
Open - Otwórz istniejacy juz plik z programem (np. w celu
dopisania czegos nowego).
6
New - Utwórz nowy plik (zaczynamy tworzyc nowy program).
Save - Zapisz biezacy program na dysk. Pamietaj: Pliki z
dysku nie znikaja po wyłaczeniu komputera. Zawsze
lepiej miec o jedna kopie za duzo niz o jedna za mało.

oraz
Print - Wydrukuj program.
Get Infott - Wyswietl informacje o stanie IDE.
Dos Shell - Wyjscie "na chwile" do systemu DOS z mozliwoscia
powrotu do IDE przez rozkaz EXIT.
Quit - Wyjscie z IDE Turbo C++ i powrót do DOSa. Inaczej -
KONIEC PRACY.
_______________________________________________________________

Skoro juz wiemy jak rozpoczac prace nad nowym programem,
zacznijmy przygotowanie do uruchomienia naszego pierwszego
programu.

Wybierz z menu File rozkaz Open... (otwórz plik). Poniewaz
rozkaz taki jest niejednoznaczny, wymaga przed wykonaniem
podania dodatkowych informacji. Gdyby Twój komputer mówił,
zapytałby w tym momencie "który plik mam otworzyc?". Pytanie
zadac musi, bedzie wiec prowadził dialog z Toba przy pomocy
OKIENEK DIALOGOWYCH. Jesli wybrałes z menu rozkaz OPEN i
nacisnałes [Enter], to masz własnie na ekranie takie okienko
dialogowe. Okienko składa sie z kilku charakterystycznych
elementów:

OKIENKO TEKSTOWE - (ang. Text Box lub Input Box) w którym mozesz

pisac (klawisz BackSpace [<-] pozwoli Ci
skasowac wprowadzony tekst, jesli sie
rozmyslisz). Okienko to zawiera tekst "*.C".

OKIENKO Z LISTA - (ang. List Box) zawiera liste plików, z której
mozesz wybrac plik z programem.

KLAWISZE OPCJI/POLECEN - (ang. Command Button) kiedy juz
dokonasz wyboru, to mozesz wskazujac
taki klawisz np. potwierdzic [OK],
zrezygnowac [Cancel], otworzyc plik
[Open] itp..

Pomiedzy elementami okienka dialogowego mozesz poruszac sie przy
pomocy klawiszy kursora i klawisza [Tab] lub kombinacji klawiszy

[Shift]-[Tab] (spróbuj!).

Mozesz takze posługiwac sie myszka.
Wiecej o okienkach i menu dowiesz sie z nastepnych lekcji, a na
razie wrócmy do naszego podstawowego zadania - tworzenia
pierwszego programu.

Zanim zaczniemy tworzyc program włóz do kieszeni napedu A: (lub
B:) dyskietke dołaczona do niniejszej ksiazki. Moze ona stac sie
Twoja dyskietka robocza i pomocnicza zarazem na okres tego
kursu.

Jezeli zainstalowałes zawartosc dyskietki na dysku - przejdz do
stosownego katalogu - C:\C-BELFER (D:\C-BELFER) i odszukaj tam
programy przykładowe. Jesli nie - mozesz nadal korzystac z
dyskietki (jest na niej troche miejsca).

Wpisz do okienka tekstowego nazwe A:\PIERWSZY (lub odpowiednio
np. C:\C-BELFER\PIERWSZY). Rozszerzeniem mozesz sie nie
przejmowac - zostanie nadane automatycznie. Plik roboczy z Twoim
programem zostanie utworzony na dyskietce w napedzie A:.

Wskaz klawisz [Open] w okienku dialogowym i nacisnij [Enter] na
klawiaturze.

UWAGA!
________________________________________________________________
_
Dopóki manipulujesz okienkiem tekstowym i okienkiem z lista
klawisz polecenia [Open] jest wyrózniony (podswietlony) i
traktowany jako tzw. OPCJA DOMYSLNA (ang. default). W tym
stadium aby wybrac [Open] WYSTARCZY NACISNAC [Enter].
________________________________________________________________
__


Wrócilismy do IDE. zmieniło sie tyle, ze w nagłówku okna edytora
zamiast napisu

"NONAME00.CPP" (ang. no mame - bez nazwy)

jest teraz nazwa Twojego programu - PIERWSZY.CPP. Kursor miga w lewym
górnym rogu okna edytora. Mozemy zaczynac.

Pierwsze podejscie do programu zrobimy troche "intuicyjnie".
Zamiast wyjasniac wszystkie szczegóły posłuzymy sie analogia do
konstrukcji w Pascalu i Basicu (zakładam, ze napisałes juz
chocby jeden program w któryms z tych jezyków). Szczegóły te
wyjasnie dokładniej poczawszy od nastepnej lekcji.


WPISUJEMY PROGRAM "PIERWSZY.CPP".

Wpisz nastepujacy tekst programu:

/* Program przykładowy - [P-1] */

#include <stdio.h>
main()
{
printf("Autor: ..........."); /*tu wpisz imie Twoje!*/
printf(" TO JA, TWOJ PROGRAM - PIERWSZY.CPP");
printf("...achoj !!!");
}

I juz. Jak widzisz nie jest to az takie straszne. Gdyby nie to,
ze zamiast znajomego PRINT"TO JA...", albo writeln(".."); jest
printf("...");, byłoby prawie całkiem zrozumiałe. Podobny
program w Pascalu mógłby wygladac np. tak:

# include <stdio.h> uses Crt;
main() /* poczatek */ program AHOJ; {poczatek}
{ Begin
printf("Autor"); write('Autor');
printf("TO JA"); write('TO JA');
printf("ahoj"); write('ahoj');
} end.


a w BASICU:

10 PRINT "Autor" : REM Poczatek
20 PRINT "TO JA"
30 PRINT "ahoj"
40 END


t[!!!]UWAGA
______________________________________________________________
Zwróc uwage, ze działanie funkcji:
PRINT (Basic),
printf() (C++),
Write i Writeln (Pascal)
nie jest identyczne, a TYLKO PODOBNE.
________________________________________________________________

Sprawdzimy, czy program działa. Tam, gdzie sa kropki wpisz Twoje
imie - np. Ewa, Marian, Marcin. Pamietaj o postawieniu na koncu
znaków cudzysłowu ("), zamknieciu nawiasu i sredniku (;) na
koncu linii (wiersza).

Nacisnij kombinacje klawiszy [Alt]-[R]. Jest to inny, niz
opisano poprzednio sposób dostepu do menu. Kombinacja klawiszy
[Alt]-[Litera] powoduje uaktywnienie tego menu, którego nazwa
zaczyna sie na podana litere. Przy takiej konwencji litera nie
musi byc zawsze pierwsza litera nazwy opcji. Moze to byc takze
litera wyrózniona w nazwie przez podkreslenie lub wyswietlenie
np. w innym kolorze. I tak:

[Alt]+[F] menu File (Plik)
[Alt]+[C] menu Compile (Kompilacja
[Alt]+[R] menu Run (Uruchamianie)
[Alt]+[W] menu Window (Okna)
itd., itd..

Kombinacja [Alt]+[R] wybiera wiec menu Run (uruchomienie
programu). Menu Run daje Ci do wyboru nastepujace polecenia:

[S!]
________________________________________________________________
7
Run - Uruchomienie programu (Utwórz plik .EXE i Wykonaj).
Program Reset - "Wyzerowanie" zmiennych programu.
Go to Cursor - Wykonanie programu do miejsca wskazanego kursorem
w tekscie.
Trace Into - Uruchom sledzenie programu.
Step Over - Sledzenie programu z mozliwoscia pominiecia funkcji.

(dosł. tzw. "Przekraczanie" funkcji).
Arguments - Uruchom program z zadanymi argumentami.
________________________________________________________________

Wybierz "Run". Jesli nie zrobiłes zadnego błedu, program
powinien sie skompilowac z komentarzem "Success" i wykonac
(kompilacja zakonczona sukcesem; napis mignie tak szybko, ze
mozesz tego nie zauwazyc). Jesli chcesz spokojnie obejrzec
wyniki działania swojego programu powinienes wykonac
nastepujace czynnosci:

1. Rozwin menu Window naciskajac klawisze [Alt]-[W].
2. Wybierz z menu rozkaz User screen (ekran uzytkownika).
Mozesz wykonac to samo bez rozwijania menu naciskajac kombinacje

klawiszy [Alt]-[F5].
3. Po przejrzeniu wydruku nacisnij [Enter]. Wrócisz do okna
edytora.

Jesli zrobiłes błedy - kompilacja sie nie uda i program nie
zostanie wykonany, w okienku natomiast pojawi sie napis "Errors"

(czyli "Błedy"). Jesli tak sie stało nacisnij [Enter]
dwukrotnie. Popraw ewentualne niezgodnosci i spróbuj jeszcze
raz.

Błedów zwykle bywa nie wiecej niz dwa. Najczesciej jest to brak
lub przekłamanie którejs litery (w słowie main lub printf) i
brak srednika na koncu linii. W okienku komunikatów (Message)
moga pojawic sie napisy - np.:

Error: Statement missing ;
(Bład: Zgubiony znak ;)

t[S] Error Messages - Komunikaty o błedach.
________________________________________________________________
Najczesciej w komunikatach o błedach beda na poczatku pojawiac
sie nastepujace słowa:

Error - bład
Warning - ostrzezenie
Syntax - składnia (składniowy)
Expression - wyrazenie
never used - nie uzyte (nie zastosowane)
assign - przypisywac, nadawac wartosc/znaczenie
value - wartosc
statement - operator, operacja, wyrazenie
________________________________________________________________

t[???] Co z tym srednikiem?

________________________________________________________________
Zwróc uwage, ze po pdswietleniu komunikatu o błedzie (pasek
wyróznienia podswietlenia mozesz przesuwac po liscie przy pomocy
klawiszy ze strzałkami w góre i w dół) i po nacisnieciu [Entera]
kompilator pokaze ten wiersz programu, w którym jego zdaniem
jest cos nie w porzadku. Brak srednika zauwaza zwykle dopiero po
przejsciu do nastepnego wiersza (i tenze wiersz pokaze), co bywa
na poczatku troche mylace.
________________________________________________________________


[???] CZEGO ON JESZCZE CHCE ?
________________________________________________________________
Nawet po usunieciu wszystkich błedów C++ nie "uspokoi sie"
całkiem i bedzie wyswietlał ciagle komunikat ostrzegawczy:

* w OKIENKU KOMPILACJI: (bardzo typowa sytuacja)

Errors: 0 (Błedy: 0)
Warnings: 1 (Ostrzezenia: 1)


* W OKIENKU KOMUNIKATÓW - (Messages - tym w dolnej czesci
ekranu):

*WARNING A:\PIERWSZY.C 4: Function should return a value in
function main

(Uwaga: Funkcja main powinna zwrócic wartosc.)

Na razie zadowolimy sie spostrzezeniem, ze:
* Błedy UNIEMOZLIWIAJA KOMPILACJE i powoduja komunikat
ERRORS.
* Ostrzezenia NIE WSTRZYMUJA KOMPILACJI i powoduja komunikat
WARNINGS.

Jaki jest sens powyzszego ostrzezenia i jak go uniknac dowiesz
sie z nastepnych lekcji.
________________________________________________________________

Pozostaje nam w ramach tej lekcji:
* Zapisac Twój pierwszy program na dysku i
* Wyjsc z IDE C++.

JAK STAD WYJSC ?

Aby zapisac plik PIERWSZY.CPP z Twoim programem (koncowa
ostateczna wersja) na dysk nalezy wykonac nastepujace czynnosci:

1. Nacisnij klawisz [F10].
W głównym menu pojawi sie pasek wyróznienia sygnalizujac, ze
menu stało sie aktywne.

2. Nacisnij klawisz [F].
Pasek wyróznienia przesunie sie podswietlajac menu File
(operacje na plikach). Rozwinie sie menu File.

3. Nacisnij klawisz [S] - wybierz polecenie Save (jesli chcesz
zapisac program w biezacym katalogu i pod biezaca nazwa) lub
rozkaz Save As... (zapisz jako...), podaj nowy dysk/katalog i
nowa nazwe pliku.

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

mozemy wyjsc z C++.

Aby to zrobic, wykonaj nastepujace czynnosci:

1. Nacisnij klawisz [F10]. Uaktywni sie główne menu.
2. Rozwin menu File naciskajac klawisz [F].
3. Wybierz z menu polecenie "Exit/Quit" i nacisnij [Enter].

t[!!!] SAVE szybciej.
________________________________________________________________
Zwróc uwage, ze zamiast rozwijac kolejne menu, mozesz korzystac
z kombinacji klawiszy, które pozwalaja Ci wydac rozkaz bez
rozwijania menu. Takie kombinacje klawiszy (ang. hot keys lub
shortcut keys) znajdziesz w menu obok rozkazu, np.:

[Alt]-[X] - Quit/Exit
[F2] - Save
[F3] - Open
[Alt]-[F5] - User screen (Podgladanie działania programu) itp.
________________________________________________________________

t[Z]
________________________________________________________________
1. Spróbuj napisac i uruchomic kilka własnych programów
wypisujacych rózne napisy. W swoich programach zastosuj funkcje
printf() według nastepujacego wzoru:

printf("....tu wpisz napis do wydrukowania...");

zastosuj znaki przejscia do nowego wiersza według wzoru:

printf("...napis...\n");

porównaj działanie.

Swoim programom staraj sie nadawac łatwe do rozpoznania nazwy
typu PIERWSZY, DRUGI, ADAM1, PRZYKLAD itp.

t[???] NIE CHCE DZIAŁAC ?
________________________________________________________________
Pamietaj, ze dla jezyka C i C++ (w przeciwienstwie np. do
Basica) PRINTF i printf to nie to samo! Słowa kluczowe i nazwy
standardowych funkcji
MUSZA BYC PISANE MAŁYMI LITERAMI !!!
8
________________________________________________________________

t[???] GDZIE MOJE PROGRAMY ?
________________________________________________________________
Badz spokojny. Zapisz wersje zródłowe programów na dyskietke
(dysk). Swoje programy skompilowane do wykonywalnej wersji *.EXE
znajdziesz najprawdopodobniej w katalogu głównym tego dysku, na
którym zainstalowany został C++ lub w katalogu
\BORLANDC\BIN\.... Jesli ich tam nie ma, zachowaj zimna krew i
przeczytaj uwaznie kilka nastepnych stron.
________________________________________________________________


t PAMIETAJ:
________________________________________________________________
Jesli masz oryginalny tekst programu, nazywany WERSJA ZRÓDŁOWA
PROGRAMU, zawsze mozesz uzyskac ten program w wersji "roboczej",
tzn. skompilowac go na plik wykonywalny typu *.EXE (ang.
EXEcutable - wykonywalny).
________________________________________________________________


t[S!] printf() - PRINTing Function - Funkcja DRUKujaca
________________________________________________________________
na ekranie (dokładniej - na standardowym urzadzeniu wyjscia).
Odpowiednik PRINT w Basicu lub write w Pascalu. Dla ułatwienia
rozpoznawania nazw funkcji w tekscie wiekszosc autorów piszaca o
jezyku C++ umieszcza zawsze po nazwie funkcji pare nawiasów (tak
tez musi ja stosowac programista w programach w C++). Ja takze
bede stosowac dalej te zasade.
________________________________________________________________


t[???] A JESLI NIE MA C++ ???
________________________________________________________________
W przeciwienstwie do INTERPRETERÓW (np. QBasic), które musza byc
obecne, by program zadziałał, KOMPILATORY tworza wersje
wykonywalne programów, które moga pracowac niezaleznie. W
katalogu głównym tego dysku, na którym jest zainstalowany Twój
BORLAND/Turbo C++ znajdziesz swoje programy PIERWSZY.EXE,
DRUGI.EXE itp. Aby te programy uruchomic nie musisz uruchamiac
kompilatora C++. Wystarczy:

1. Przejsc na odpowiedni dysk przy pomocy polecenia:
D: (E: lub F:)

2. Przejsc do odpowiedniego katalogu - np. głównego:
CD \

3. Wydac polecenie:
PIERWSZY[Enter]
________________________________________________________________

t[!!!]UWAGA:
________________________________________________________________
Jesli nie jestes jedynym uzytkownikiem kompilatora C++ i na tym
samym komputerze pracuje jeszcze ktos inny, sprawdz, czy inny
uzytkownik nie ustawił inaczej katalogu wyjsciowego (Options |
Directories | Output Directory). Katalog wyjsciowy (ang. output
directory) to ten katalog, w którym C++ zapisuje pliki *.EXE po
wykonaniu kompilacji. Jesli jestes skazany na własne siły -
patrz - nastepne lekcje.
________________________________________________________________


SPECJALNE KLAWISZE, które warto poznac.

Oto skrócona tabela z najwazniejszymi kombinacjami klawiszy
słuzacymi do "nawigacji" (czyli prosciej - poruszania sie) w
srodowisku IDE kompilatorów BORLAND C++ i Turbo C++.

Przydatne w Borland C++ i Turbo C++ kombinacje klawiszy.
________________________________________________________________
Wybór rozkazów z menu:
Alt+F Rozwiniecie menu File (operacje na plikach)
Alt+E Rozwiniecie menu Edit (edycja tekstu programu)
Alt+S Rozwiniecie menu Search (przeszukiwanie)
Alt+R Rozwiniecie menu Run (uruchamianie programu)
Alt+C Rozwiniecie menu Compile (kompilacja)
Alt+D Rozwiniecie menu Debug (diagnostyka i błedy)
Alt+P Rozwiniecie menu Project (program wielomodułowy)
Alt+O Rozwiniecie menu Option (opcje, konfiguracja)
Alt+W Rozwiniecie menu Window (zarzadzanie oknami)
Alt+H Rozwiniecie menu Help (pomoc)
Alt+B Rozwiniecie menu przegladarki - Browse (Win)
Alt+X Wyjscie z kompilatora DOS'owskiego - Exit
Alt+F4 Wyjscie z kompilatora dla Windows
________________________________________________________________


Rozkazy w trybie edycji tekstu programu:
________________________________________________________________
Shift+Delete Wytnij wybrany blok tekstu (Cut) i umiesc w
przechowalni (Clipboard)
Shift+Insert Wstaw blok tekstu z przechowalni (Paste)
Ctrl+Insert Skopiuj zaznaczony blok tekstu do przechowalni
(Copy)
Ctrl+Y Skasuj wiersz tekstu (Delete a line)
Ctrl+Delete Skasuj zaznaczony blok tekstu
Shift+[-->] Zaznaczanie bloku tekstu w prawo
Shift+[<--] Zaznaczanie bloku tekstu w lewo
Shift+[Down Arrow] Zaznaczanie bloku tekstu w dół (strzałka w
dół)
Shift+[Up Arrow] Zaznaczanie bloku tekstu w góre (strzałka w
góre)
Alt+Backspace Anuluj ostatnia operacje (Undo)
Ctrl+L Powtórz przeszukiwanie (Repeat search)
________________________________________________________________


Rozkazy ogólnego przeznaczenia:
________________________________________________________________
F1 Wyswietl pomoc - Help screen
F2 Zapisz biezacy stan tekstu na dysk (Save)
F3 Otwórz nowy plik (Open)
F4 Uruchom i wykonaj program do pozycji wskazanej
kursorem
F5 Powieksz (maximize) biezace aktywne okno
F6 Przejdz do nastepnego okna (next window)
F7 Wykonaj program krok-po-kroku
F8 Wykonaj program krok-po-kroku z pominieciem
sledzenia funkcji
F9 Skompiluj i skonsoliduj program (Compile/Make)
F10 Uaktywnij pasek głównego menu
Shift+F1 Wyswietl spis tresci Help - tzw. Help index
Shift+F2 Wybierz rozkaz Arguments... z menu Run
(uruchamianie programu z parametrami w
DOS'owskim wierszu rozkazu)
Ctrl+F1 Podpowiedzi kontekstowe (help topic search)
Ctrl+F2 Wyzeruj biezacy program
Ctrl+F5 Zmien pozycje aktywnego okna
Ctrl+F7 Wyswietl okienko dialogowe "Add Watch"
Ctrl+F8 Zaznacz punkt krytyczny (Toggle breakpoint)
Ctrl+F9 Uruchom program (Run)
Ctrl+PgUp Skocz na poczatek pliku
Ctrl+PgDn Skocz na koniec pliku
Alt+F1 Pokaz poprzedni ekran Help
Alt+F2 Zmniejsz okno
Alt+F3 Zamknij aktywne okno
Alt+F4 Dokonaj inspekcji (inspect)
Alt+F5 Pokaz DOS'owski ekran roboczy (User screen)
Alt+F7 Przejdz do poprzedniego błedu (previous error)
Alt+F8 Przejdz do nastepnego błedu (next error)
________________________________________________________________

________________________________________________________________
EOF


LEKCJA 4. Jeszcze o IDE C++ .

_______________________________________________________________
W trakcie tej lekcji:
1. Dowiesz sie wiecej o menu i okienkach w srodowisku IDE.
2. Poznasz troche technik "myszologicznych".
3. Napiszesz i uruchomisz swój drugi program.
________________________________________________________________


W dolnej czesci ekranu jest podobny pasek do paska menu,
niemniej wazny, choc o innym przeznaczeniu. Pasek ten jest to
tzw. WIERSZ STATUSOWY (ang. Status Line). Jak wynika z nazwy w
tym wierszu wyswietlane sa informacje dotyczace biezacego stanu
(i biezacych mozliwosci) srodowiska IDE. Zaryzykuje teze, ze
czesto jeden prosty, własny eksperyment moze byc wiecej wart niz

9
wiele stron opisów. Poeksperymentujmy zatem chwile z wierszem
statusowym.

[???] NIE CHCE SIE URUCHOMIC ???
________________________________________________________________
Jesli przy starcie kompilatora C++ nastapi komunikat:

System Message
Disk is not ready in drive A
[Retry] [Cancel]

(Komunikat systemu C++: Dyskietka w napedzie A nie gotowa do
odczytu; Jeszcze raz? Zrezygnowac?)

to znaczy, ze C++ nie moze odtworzyc ostatniego ekranu
roboczego, poniewaz nie udostepniłes mu dyskietki z programami,
nad którymi ostatnio pracowałes.
________________________________________________________________


W wierszu statusowym wyjasnione jest działanie klawiszy
funkcyjnych F1, F2, itd. Moga tam równiez pojawiac sie
krótkie napisy-wyjasnienia dotyczace np. rozkazu wyróznionego
własnie w menu. Powinien tam byc napis:

F1 Help F2 Save F3 Load AltF9 Compile F9 Make F10 Menu

znaczy to:

[F1] - Pomoc
[F2] - Zapamietanie biezacego pliku na dysku pod biezaca nazwa
(nawet jesli ta nazwa jest NONAME01.CPP, tzn. została nadana
automatycznie i znaczy - o ironio - "BEZNAZWY01.CPP") i w
biezacym katalogu.
[F3] - Załadowanie do okienka edycyjnego nowego pliku tekstowego
(np. nowego programu).
[Alt]-[F9] - Kompilacja w trybie "Compile".
[F9] - Kompilacja w trybie "Make" (jednoczesnej kompilacji i
konsolidacji).
[F10] - Uaktywnienie głównego menu.


JAK ZROBIC PORZADEK?

W trakcie uruchamiania kompilator korzysta z plików
zewnetrznych. C++ stara sie byc USER FRIENDLY (przyjazny wobec
uzytkownika) i odtworzyc taki stan ekranu, w jakim ostatnio
przerwałes prace, co nie zawsze jednak jest korzystne. W wierszu

statusowym pojawiaja sie napisy informujace o tym (np:

Loading Desktop File . . .

- ładuje plik zawierajacy konfiguracje ostatniego ekranu
roboczego...). Jesli chcesz by na poczatku
sesji z C++ ekran był "dziewiczo" czysty, powinienes:

* zmienic nazwe pliku [D:]\BORLANDC\BIN\TCDEF.DSK

na dowolna inna, np. STARY.DSK lub STARY1.DSK, stosujac
polecenie systemu DOS RENAME. [D:] oznacza odpowiedni dla
Twojego komputera dysk. C++ wystartuje wtedy z czystym ekranem i

utworzy nowy plik TCDEF.DSK.

* Plików TCDEF nie nalezy usuwac. Kiedy nabierzesz troche wprawy

pliki te znacznie przyspiesza i ułatwia Ci prace z C++.

Aby zamknac zbedne okna mozesz zastosowac równiez rozkaz Close
(ang. Close - zamknij) z menu Window (okna). Zwróc uwage, ze
polecenie Close odnosi sie do biezacego okna wyróznionego przy
pomocy podwójnej ramki. Aby zamknac biezace okno, powinienes:

1. Nacisnac klawisze [Alt]-[W]
Rozwinie sie menu Windows.
2. Wybrac z menu rozkaz Close - [C].

Moze pojawic sie okienko z ostrzezeniem:

WARNING: A:\PIERWSZY.CPP not saved. Save?
(UWAGA: plik A:\PIERWSZY.CPP nie zapisany na dysku. Zapisac ?).

[???] ZNIKNAŁ PROGRAM ???
________________________________________________________________
C++ chce Cie uchronic przed utrata programu, ale uwazaj! Jesli
odpowiesz Yes - Tak ([Y] lub [Enter]), to nowa wersja programu
zostanie nadpisana na stara!
________________________________________________________________

[!!!] ZAMYKANIE OKIEN.
________________________________________________________________
Mozesz szybciej zamknac okno naciskajac kombinacje klawiszy
[Alt]-[F3].
________________________________________________________________

[!!!]UWAGA
________________________________________________________________
Badz ostrozny podejmujac decyzje o zapisie wersji programu na
dysk. Okienko z ostrzezeniem pojawi sie za kazdym razem przed
zamknieciem okna edycyjnego z tekstem programu. Jesli przy
zamykaniu okna nie pojawi sie ostrzezenie, to znaczy, ze program
w tej wersji, która widzisz na ekranie został juz zapisany na
dysk.
________________________________________________________________


A JESLI NIE CHCE ZAMYKAC OKIEN?

W porzadku, nie musisz. W menu Window ([Alt]-[W]) masz do
dyspozycji rozkaz Next (nastepne okno). Mozesz go wybrac albo
naciskajac klawisz [N], albo przy pomocy klawiszy kursora. Kazde
z okien na Twoim roboczym ekranie ma nazwe - nagłówek - np.
NONAME00.CPP, PIERWSZY.CPP, ale nie tylko. Pierwsze dziesiec
okien ma równiez swoje numery - podane blisko prawego - górnego
rogu okna w nawiasach kwadratowych - np. [1], [2] itd.
Posługujac sie tym rozkazem mozesz przechodzic od okna do okna
nie zamykajac zadnego z okien. Spróbuj!

Jest jeszcze inny sposób przejscia od okna do okna. Jesli chcesz
przejsc do okna o numerze np. [1], [2], [5] itp. powinienes
nacisnac kombinacje klawiszy [Alt]-[1], [Alt]-[5] itp..
Niestety, tylko pierwsze 9 okien ma swoje numerki.

Mozesz korzystac z listy okien (Window | List) lub klawisza
funkcyjnego [F6].

[S] ACTIVE WINDOW - AKTYWNE OKNO.
________________________________________________________________
Na ekranie moze sie znajdowac jednoczesnie wiele okien, ale w
danym momencie tylko jedno z nich moze byc AKTYWNE. Aktywne
okno, to to, w którym miga kursor i w którym aktualnie
pracujesz. Aktywne okno jest dodatkowo wyróznione podwójna
ramka.
________________________________________________________________


[???] Robi "na szaro"?
________________________________________________________________

Zwróc uwage, ze dopóki biezacym aktywnym oknem jest okienko
komunikatów (Message - to w dolnej czesci ekranu), nie mozesz
np. powtórzyc kompilacji programu. Rozkazy Compile | Compile i
Run | Run beda "zrobione na szaro" (ang. grayed out) - czyli
nieaktywne. Najpierw trzeba przejsc do okna edycji tekstu
programu (np. poprzez klikniecie myszka).
________________________________________________________________


Rozwin menu Options (opcje).
Mozesz to zrobic na wiele sposobów. Najszybciej chyba naciskajac:

[Alt]+[O]

Rozwineło sie menu, udostepniajac nastepujaca liste polecen:

FULL MENUs - Pełne Menu ("s" oznacza, ze chodzi o "te" menu w
liczbie mnogiej, a nie o pojedyncze menu).
COMPILER - Kompilator.
MAKE... - dosł. "ZRÓB", dotyczy tworzenia "projektów" (zwróc
uwage na wielokropek [...]).
DIRECTORIES... - KATALOGI (znów wielokropek !).
ENVIRONMENT... - OTOCZENIE lub inaczej SRODOWISKO.
SAVE - ZAPAMIETAJ (UWAGA: To jest zupełnie inne SAVE niz
w menu File. Nie wolno mylic tych polecen.
Pomyłka grozi utrata tekstu programu!).
10

Popatrz na linie statusowa. Jesli bedziesz poruszac sie po menu
Option, podswietlajac kolejne rozkazy, w wierszu statusowym
bedzie wyswietlany krótki opis działania wskazanego rozkazu. I
tak, powinienes zobaczyc kolejno nastepujace napisy:

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

(Stosuj lub nie stosuj pełnego zestawu rozkazów w menu -
domyslnie przyjmowane jest Off/Nie).

Compiler - Set compiler defaults for code generation, error
messages and names.

(Ustaw domyslne parametry pracy kompilatora dotyczace
generowania kodu programu, komunikatów o błedach i nazw).

Make... - Set condition for project-makes.
(Ustawianie warunków do tworzenia projektu).

Directories... - Set path for compile, link and executable
files.
(Wybierz katalogi i ustaw sciezki dostepu dla kompilacji,
konsolidacji i WSKAZ MIEJSCE - GDZIE ZAPISAC PLIK TYPU *.EXE po
kompilacji).

Environment... - Make environment wide settings (eg, mouse
settings).
(Ustawienie parametrów rozszerzonego otoczenia, np. parametrów
pracy myszki).

Save - Save all the settings you've made in the Options menu.

(Powoduje zapamietanie na dysku wszystkich zmian parametrów
roboczych IDE, które ustawiłes, korzystajac z rozkazów
dostepnych za posrednictwem menu Options.).

Ten rozkaz pozwala Ci ustawic konfiguracje IDE "raz na zawsze".

Przygotujmy sie do powtórzenia kompilacji programu PIERWSZY.CPP.

Jesli masz na ekranie rozwiniete menu Options, wybierz z menu
polecenie Directories... .

KOMPILACJA ZE WSKAZANIEM ADERSU.

1. Wskaz w menu polecenie Directories i nacisnij [Enter].
Po poleceniu umieszczony jest wielokropek. Znaczy to, ze rozkaz
nie zostanie wykonany, zanim komputer nie uzyska od Ciebie
pewnych dodatkowych informacji. Wiesz juz, ze praktycznie
oznacza to dla Ciebie koniecznosc "wypełnienia" okienka
dialogowego. Po wybraniu polecenia Directories ukazało sie
okienko dialogowe juz "wstepnie wypełnione". Takie "wstepne
wypełnienie" okienka daje Ci pewne dodatkowe informacje. Wynika
z niego mianowicie JAKIE PARAMETRY SA PRZYJMOWANE
DOMYSLNIE
(default).

W okienku dialogowym masz trzy okienka tekstowe:

* Include Directories (Katalog zawierajacy pliki nagłówkowe, np.

STDIO.H, CONIO.H, GRAPHICS.H itp. dołaczane do programów).

* Library Directories (Katalog zawierajacy gotowe biblioteki,
zawarte w plikach typu *.LIB,).

* Output Directory (Katalog wyjsciowy, w którym po kompilacji
beda umieszczane Twoje programy w wersji *.EXE).

Pierwsze dwa zostawimy w spokoju.

2. Nacisnij dwukrotnie klawisz [Tab]. Kursor wskazuje teraz
okienko tekstowe Output Directory.

3. Wpisz do okienka tekstowego Output Directory:
A:\ lub C:\C-BELFER

znaczy to, ze od teraz po wykonaniu kompilacji i utworzeniu
pliku wykonywalnego typu *.EXE, plik taki zostanie zapisany we
wskazanym katalogu i na wskazanym dysku/dyskietce.

UWAGA:
________________________________________________________________
* Jesli zainstalowałes zawartosc dyskietki na dysku i wolisz
posługiwac sie własnym katalogiem roboczym - wpisz tam
odpowiednia sciezke dostepu - np. C:\C-BELFER. Jesli Twój
katalog zagniezdzony jest głebiej (np. w przypadku uzytkowników
sieci Novell) - podaj pełna sciezke dostepu - np.:
F:\USERS\ADAM\C-BELFER
* Wszedzie, gdzie w tresci ksiazki odwołuje sie do dyskietki A:
mozesz konsekwentnie po zainstalowaniu stosowac odpowiedni
katalog na dysku stałym, badz na dysku sieciowym.
________________________________________________________________

4. Nacisnij [Enter].

Spróbuj teraz, znana z poprzedniej lekcji metoda, wczytac do
okienka edytora Twój pierwszy program. Musisz wykonac
nastepujace czynnosci:

1. Włóz do napedu A: dyskietke z programem PIERWSZY.CPP (jesli
jeszcze jej tam nie ma).
2. Rozwin menu File, naciskajac kombinacje klawiszy [Alt]-[F].
3. Wybierz z menu rozkaz Open, naciskajac klawisz [O].
Pojawi sie znane Ci okienko dialogowe. Zwróc uwage na wiersz
statusowy. Napis:

Enter directory path and file mask
znaczy:
Wpisz sciezke dostepu do katalogu i "wzorzec" nazwy pliku.

Uzyte słowo "wzorzec" oznacza, ze wolno Ci wpisac do okienka
tekstowego takze nazwy wieloznaczne, zawierajace znaki "*" i
"?", np.:

*.C
A:\???.C
D:\BORLANDC\SOURCE\P*.*

itp. (Spróbuj!, zawsze mozesz sie wycofac lub zmienic zdanie,
posługujac sie klawiszami [BackSpace], [Shift], [Tab] i [Esc].).

Klawisz [Tab] umozliwia Ci skok od okienka do okienka "do
przodu", a [Shift]-[Tab] - "do tyłu". Zgodnie z nazwa (ang.
ESCape - uciekac), klawisz [Esc] pozwala Ci wycofac sie z
niewygodnych sytuacji - np. zamknac okienko dialogowe lub zwinac
rozwiniete menu bez zadnej akcji.

Jesli wpiszesz wzorzec nazwy, to w okienku z lista zobaczysz
wszystkie pliki wybrane z podanego dysku i z podanego katalogu
według zadanego wzorca. Aby wybrac plik z listy nalezy klawiszem

[Tab] przejsc do okienka z lista, klawiszami kursora wskazac
potrzebny plik i nacisnac [Enter].

4. Wpisz do okienka tekstowego
A:\PIERWSZY.CPP
5. Nacisnij [Enter].

[!!!]FAST START - SZYBKI START.
________________________________________________________________
Jesli chcesz by C++ automatycznie wczytał Twój program do
okienka edytora, to mozesz zadac nazwe pliku z tekstem programu
jako parametr w wierszu polecenia, uruchamiajac C++ np. tak:

BC A:\PIERWSZY.CPP

Jesli korzystasz z programu Norton Commander, to mozesz dodac do

pliku NC.EXT nastepujacy wiersz:

C: TC !.!
cpp: bc !.!

wówczas wystarczy tylko wskazac odpowiedni plik typu *.C lub
.CPP z tekstem programu i nacisnac [Enter].
________________________________________________________________

Kompilatory Borlanda moga w róznych wersjach nazywac sie róznie:

TC.EXE, BC.EXE, BCW.EXE (dla Windows), itp.. Sprawdz swoja
wersje kompilatora i wpisz własciwe nazwy dodajac ewentualnie
sciezki dostepu - np.:

C: D:\BORLANDC\BIN\BC !.!
11
CPP: WIN C:\BORLANDC\BIN\BCW !.!

[!!!]UWAGA
________________________________________________________________
Rozkazy uruchamiajace kompilator moga byc złozone nawet z 4
parametrów - np.:

WIN /3 C:\BORLANDC\BIN\BCW C:\C-BELFER\PROGRAMY\P027.CPP

spowoduje:
* uruchomienie Windows w trybie rozszerzonym 386
* uruchomienie kompilatora w wersji dla Windows - BCW.EXE
* załadowanie pliku z programem - P27.CPP z wskazanego katalogu
________________________________________________________________


[P002.CPP]

Dokonaj w swoim programie nastepujacych zmian:
________________________________________________________________
#include (stdio.h>
#include <conio>

main()
{
printf("\n");
printf("Autor: np. Antoni Kowalski\n");
printf("program: PIERWSZY.CPP \n - wersja II \n");
getch();
}
________________________________________________________________

******Uwaga: Jesli pracujesz w Windows - Z TEGO MIEJSCA********

przy pomocy rozkazów Edit | Copy
mozesz przeniesc program do okna kompilatora
poprzez schowek Windows (Clipboard).
W oknie kompilatora nalezy:
1. Otworzyc nowe okno edytora tekstowego:
File | New
2. Wstawic plik ze schowka:
Edit | Paste
--- To okno (AM-Edit) i całego BELFRA mozesz w tym czasie zredukowac
--- Do ikonki.------------------------------------------------------
****************************************************************
****

Dzieki dodaniu do tekstu programu funkcji getch(), program nie
powinien juz tak szybko mignac na ekranie i zniknac. Zatrzyma
sie teraz i zaczeka na przycisniecie klawisza. Funkcja getch(),
działa podobnie do:

10 IF INKEY$="" GOTO 10

w Basicu lub Readln w Pascalu.

Nazwa pochodzi od GET CHaracter (POBIERZ ZNak, z klawiatury).

Skompiluj program PIERWSZY.CPP. Aby to zrobic, powinienes:

1. Rozwinac menu Compile - [Alt]-[C].
2. Wybrac z menu rozkaz Compile - [C].

Ostrzezenie WARNING na razie ignorujemy.

Wykonaj kompilacje programu powtórnie przy pomocy rozkazu Run z
menu Run. Nacisnij kolejno klawisze:

[Alt]-[R], [R]
lub
[Alt]-[R], [Enter]

Ten sam efekt uzyskasz naciskajac kombinacje klawiszy
[Ctrl]-[F9].

Uruchom program powtórnie naciskajac kombinacje klawiszy
[Alt]-[R], [R]. Zwróc uwage, ze teraz kompilacja nastapi
znacznie szybciej. Tak naprawde C++ stwierdzi tylko, ze od
ostatniej kompilacji nie dokonano zadnych zmian w programie i
odstapi od zbednej kompilacji. Takie własnie znaczenie ma
komunikat "Checking dependences" (sprawdzam zaleznosci, który
mignie w okienku kompilacji. Po korekcie programu napisy
wygladaja znacznie przyzwoiciej, prawda? Po obejrzeniu napisów
nacisnij [Enter].

Mozemy teraz wyjsc z programu C++. Rozwin menu File naciskajac
klawisze [Alt]-[F] i wybierz z menu rozkaz Quit. Pojawi sie
okienko z ostrzezeniem:

WARNING: A:\PIERWSZY.CPP not saved. Save?
(UWAGA: plik A:\PIERWSZY.CPP nie zapisany na dysku. Zapisac ?).

W ten sposób C++ ZNOWU chce Cie uchronic przed utrata programu,
ale uwazaj! Jesli odpowiesz Tak ([Y] lub [Enter]), to nowa
wersja programu zostanie nadpisana na stara! Jesli odpowiesz Nie

[N]

na dysku pozostanie stara wersja programu a nowa
zniknie.

Po wyjsciu z C++ znajdziesz sie w jego katalogu roboczym, lub w
tym katalogu biezacym, z którego wydałes rozkaz uruchomienia
kompilatora C++. Aby uruchomic swój program musisz zatem wydac
nastepujacy rozkaz:

A:\PIERWSZY.EXE
lub krócej
A:\PIERWSZY

a jesli chcesz sie przekonac, czy Twój program jest tam, gdzie
powinien byc, mozesz go zobaczyc. Napisz rozkaz

DIR A:\
lub
DIR A:\*.EXE

Aby upewnic sie całkowicie, ze to własnie ten program, zwróc
uwage na date i czas utworzenia pliku. Jesli masz prawidłowo
ustawiony zegar w swoim komputerze, data powinna byc dzisiejsza
a czas - kilka minut temu. Jesli cos jest nie tak, powinienes
przy pomocy rozkazów systemu DOS: DATE i TIME zrobic porzadek w
swoim systemie. O takich drobiazgach warto pamietac. Pozwoli Ci
to w przyszłosci odróznic nowsze i starsze wersje programów,
uniknac pomyłek i zaoszczedzic wiele pracy.

[Z] 1. - Propozycja zadania - cwiczenia do samodzielnego wykonania.
-------------------------------------------------------------------

Spróbuj odszukac plik zródłowy .CPP i plik wynikowy .EXE
wychodzac "na chwile" z IDE przy pomocy rozkazu File | DOS
Shell.
-------------------------------------------------------------------

A teraz zajrzyjmy do srodka do pliku PIERWSZY.EXE. Jesli
korzystasz z programu Norton Commander, to masz do dyspozycji
opcje [F3] - View (przegladanie) i [F4] - Edit (edycja). Jesli
nie korzystasz z NC, musisz wydac nastepujacy rozkaz:

TYPE A:\PIERWSZY.EXE | C:\DOS\MORE
lub
C:\DOS\EDIT A:\PIERWSZY.EXE

Jak widzisz na ekranie, napisy zawarte w programie pozostały
czytelne, ale to co widac dookoła nie wyglada najlepiej. Na
podstawie tego co widzisz, mozna (na razie ostroznie) wysnuc
wniosek, ze ani Viewer (przegladarka), ani Edytor, które
doskonale spisuja sie przy obróbce plików tekstowych, nie nadaja
sie do analizy i obróbki programów w wersji *.EXE. Narzedziami,
które bedziemy musieli stosowac, moga byc programy typu
DEBUGGER, PROFILER, LINKER (konsolidator), kompilator i in..

Mam nadzieje, ze czujesz sie w srodowisku IDE juz troche
swobodniej, a wiec bierzemy sie za drugi program.
__________________________________________________________
EOF


LEKCJA 5 - DZIAŁANIA PRZY POMOCY MYSZKI I BŁEDY W
PROGRAMIE.
________________________________________________________________
Z tej lekcji dowiesz sie,
* Jak posługiwac sie myszka w srodowisku IDE (DOS)
* O czy nalezy pamietac, przy tworzeniu i uruchamianiu
programów.
* Jak poprawiac błedy w programie.
12
________________________________________________________________


Zanim bedzie mozna kontynuowac eksperymenty, trzeba cos zrobic,
by robocze okno edytora było puste. Aby otworzyc takie nowe
puste okno edytora nalezy:

* Rozwinac menu File;
* Wybrac z menu rozkaz New (nowy).

Na ekranie monitora otworzy sie nowe puste okno zatytułowane
"NONAME00.CPP", "NONAME01.CPP", itp (lub "bez nazwy" i o
kolejnym numerze). Rózne edytoro-podobne aplikacje maja zwyczaj
otwierania okna dla nowego pliku tekstowego i nadawanie mu na
poczatku jednej z dwóch nazw:

[S] SŁOWNICZEK: UFO w trybie Edycji
________________________________________________________________
Untitled - niezatytułowany
Noname - bez nazwy
(Tak na marginesie UFO to skrót od Unidentified Flying Object -
Niezidentyfikowany Obiekt Latajacy, gdy przejdziemy do
programowania obiektowego, znajomosc tego terminu tez Ci sie
przyda).
________________________________________________________________

Nadanie plikowi dyskowemu z tekstem zródłowym programu jego
własciwej nazwy i zapisanie go na dysku stałym komputera w
okreslonym miejscu nastepuje w tym momencie, kiedy po napisaniu
programu zapisujesz go na dysk rozkazem:

File | Save lub File | Save As...

Zapis File | Save oznacza "Rozkaz Save z menu File". Gdy po
opracowaniu programu rozwiniesz menu File i wybierzesz rozkaz
Save as... (zapisz jako...), pojawi sie okienko dialogowe "Save
File as" (zapis pliku jako...).

Do okienka edycyjnego "Name" (nazwa) mozesz wpisac nazwe, która
chcesz nadac swojemu nowemu programowi. Zwróc uwage, ze mozesz
podac nazwe pliku i jednoczesnie wskazac miejsce - np.:

Name:
F:\USERS\ADAM\PROBY\PROGRAM.CPP

Po wpisaniu nazwy nacisnij klawisz [Enter] lub wybierz klawisz
[OK] w okienku dialogowym myszka. Tytuł okna edytora zmieni sie
na wybrana nazwe.

Mozesz równiez (jesli odpowiedni katalog juz istnieje), wskazac
własciwy katalog w okienku z lista "Files" i dwukrotnie
"kliknac" lewym klawiszem myszki.

Mozesz wskazac myszka okienko edycyjne i nacisnac lewy klawisz
myszki, badz naciskac klawisz [Tab] az do momentu, gdy kursor
zostanie przeniesiony do okienka edycyjnego. Okienko edycyjne to
to okienko, do którego wpisujesz nazwe pliku. W okienku
edycyjnym (Save File As) naciskaj klawisz [BackSpace] az do
chwili skasowania zbednej nazwy pliku i pozostawienia tam tylko
sciezki dostepu - np. A:\PROBY\. Wpisz nazwe programu - np.
PROG1.CPP. Po wpisaniu nazwy mozesz nacisnac [Enter] lub wskazac
myszka klawisz [OK] w okienku i nacisnac lewy klawisz myszki.
Jesli tak zrobisz w przypadku pustego okienka NONAME00.CPP -
kompilator utworzy na dysku we wskazanym katalogu plik o zadanej
nazwie - np. A:\PROBY\PROGR1.CPP (na razie pusty). Zmieni sie
takze nagłówek (nazwa) okienka edycyjnego na ekranie roboczym.

[!!!]UWAGA.
________________________________________________________________
Wszystkie pliki zawierajace teksty programów w jezyku C++
powinny ˙miec charakterystyczne rozszerzenie *.CPP (CPP to skrót
od C Plus Plus), lub .C. Po tym rozszerzeniu rozpoznaje te
programy kompilator. Nadanie rozszerzenia .C lub .CPP moze
dodatkowo wpływac na sposób kompilacji programu. Zanim wyjasnimy
te szczegóły, bedziemy zawsze stosowac rozszerzenie .CPP.
Wszelkie inne rozszerzenia (.BAK, .TXT, .DEF, itp.) nie
przeszkadzaja w edycji i kompilacji programu, ale moga w
niejawny sposób wpłynac na sposób kompilacji.
________________________________________________________________

Jesli masz puste robocze okno edytora - mozesz wpisac tam
swój własny nowy program. Wpisz:

void main(void)

Kazdy program w C++ składa sie z instrukcji. Wiele takich
instrukcji to wywołania funkcji. W C++ rozkaz wywołania i
wykonania funkcji polega na wpisaniu nazwy funkcji (bez zadnego
dodatkowego słowa typu run, execute, load, itp.). Tych funkcji
moze byc w programie jedna, badz wiecej. Tworzenie programu w
C++ z zastosowaniem funkcji (takich jakgdyby mini-programików)
przypomina składanie wiekszej całosci z klocków.

Nalezy podkreslic, ze:

kazdy program w C++ musi zawierac funkcje main() (ang. main -
główna).

Wykonanie kazdego programu rozpoczyna sie własnie od poczatku
funkcji main(). Innymi słowy - miejsce zaznaczone w programie
przy pomocy funkcji main() to takie miejsce, w które komputer
zaglada zawsze na poczatku wykonania programu i od tego własnie
miejsca rozpoczyna poszukiwanie i wykonywanie rozkazów.


[S] Entry Point
________________________________________________________________
___
Punkt wejscia do programu nazywa sie:
Program Entry Point
Taki własnie punkt wejscia wskazuje słowo main().
Punk wejscia moga miec nie tylko programy .EXE ale takze biblioteki
(.DLL - dynamicznie dołaczanie biblioteki).
________________________________________________________________
____


Kazda funkcja powinna miec poczatek i koniec. Poczatek funkcji w
C/C++ zaznacza sie przez otwarcie nawiasów klamrowych { a koniec
funkcji poprzez zamkniecie } . Poczatek głównej funkcji main()
to zarazem poczatek całego programu. Zaczynamy zwykle od
umieszczenia w oknie edytora C++ znaków poczatku i konca
programu.

main()
{
<-- tu rozbudowuje sie tekst programu
}

Najpierw nacisnij [Enter] i przejdz do poczatku nowej linii.
Umiesc w tej nowej linii znak poczatku programu - nawias { (lewy
nawias klamrowy). Nastepnie nacisnij [Enter] powtórnie i umiesc
w nastepnej linii prawy nawias klamrowy - }.

[!!!] NAJPIERW Save !!!
________________________________________________________________
Zanim jeszcze skonczysz redagowanie programu i siegniesz do
klawiszy [Alt]+[R], pamietaj, ze przed próbami kompilacji i
uruchomienia programu zawsze NAJPIERW nalezy zapisac program na
dysk. Jesli przy próbach uruchomienia cos pójdzie nie tak - masz
pewnosc, ze Twoja praca nie pójdzie na marne. Czasami przy
próbach uruchamiania programów zdarza sie, ze błedy moga
spowodowac zawieszenie komputera. Programista jest wtedy
zmuszony do restartu komputera, przy wyłaczeniu komputera to, co
było tylko na ekranie i tylko w pamieci operacyjnej - niestety
znika bezpowrotnie.
________________________________________________________________

Aby zapisac tekst programu na dysk nalezy:

* Wybrac z menu rozkaz File | Save As... albo
* Nacisnac klawisz funkcyjny [F2] (działa jak File | Save)

Po wydaniu rozkazu Save mozesz byc pewien, ze Twój program jest
bezpieczny i komputer moze sie spokojnie "ZAWIESIC" nie czyniac szkody.
Aby skompilowac i uruchomic ten program nalezy:

* Wybrac rozkaz Run | Run
* Nacisnac kombinacje klawiszy [Ctrl]+[F9]

Podobnie jak wczesniej, kompilator wyswietli na ekranie okienko
zawierajace komunikaty o przebiegu kompilacji. Po zakonczeniu
kompilacji nastapi wykonanie programu. Na moment mignie roboczy
ekran uzytkownika. Na nieszczescie program nic nie robi, wiec
nic sie tam nie wydarzy.

13
Aby przeanalizowac, jak kompilator C++ reaguje na błedy w
programach, zmien tekst w pierwszej linii programu na błedny:

vod main(void)
{
}

Spróbuj powtórnie skompilowac i uruchomic program.

Kompilator wyswietli okienko, w którym pojawi sie komunikat o
błedach. W taki własnie sposób kompilator taktownie informuje
programiste, ze nie jest az taki dobry, jak mu sie czasami
wydaje. Komputer jest niestety pedantem. Oczekuje (my, ludzie
tego nie wymagamy) absolutnej dokładnosci i zelaznego
przestrzegania pewnych zasad. "Zjadajac" jedna litere naruszyłes
takie zasady, co zauwazył kompilator.

W górnej czesci ekranu kompilator wyróznił paskiem podswietlenia
ten wiersz programu, który zawiera bład. W dolnej czesci ekranu,
w tzw. okienku komunikatów (ang. Message window) pojawił sie
komunikat, jaki rodzaj błedu został wykryty w Twoim programie. W

danym przypadku komunikat brzmi:

Declaration syntax error - Bład w składni deklaracji

Co to jest deklaracja?

Pierwsza linia (wiersz) funkcji nazywa sie deklaracja funkcji.
Taka pierwsza linia zawiera informacje wazne dla kompilatora:
nazwe funkcji oraz tzw. typy wartosci uzywanych przez funkcje.
Komunikat o błedzie oznacza, ze nieprawidłowo została napisana
nazwa funkcji lub nazwy typów wartosci, którymi posługuje sie
funkcja. W naszym przypadku słowo void zostało przekrecone na
"vod", a słowo to ma w C++ specjalne znaczenie. Słowo "void"
jest czescia jezyka C++, a dokładniej - słowem kluczowym (ang.
keyword).

[S] Function declaration - Deklaracja funkcji.
Keyword - Słowo kluczowe.
________________________________________________________________

Function declaration - Deklaracja funkcji.
Pierwszy wiersz funkcji jest nazywany deklaracja funkcji. Ten
wiersz zawiera informacje dla kompilatora C++ pozwalajace
poprawnie przetłumaczyc funkcje na kod maszynowy.

Keyword - Słowo kluczowe.
to specjalne słowo wchodzace w skład jezyka programowania. Słowa

kluczowe to słowa o zastrzezonym znaczeniu, które mozna stosowac
w programach wyłacznie w przewidzianym dla nich sensie.
________________________________________________________________


Popraw bład w tekscie. Aby robocze okienko edytora stało sie
oknem aktywnym, wskaz kursorem myszki dowolny punkt w oknie
edytora i nacisnij lewy klawisz myszki, albo nacisnij klawisz
[F6]. Zmien słowo "vod" na "void". Przy pomocy klawiszy ze
strzałkami umiesc migajacy kursor po prawej stronie nawiasu {
sygnalizujacego ˙poczatek programu i nacisnij [Enter]. Spowoduje
to wstawienie pomiedzy poczatek a koniec programu nowej pustej
linii i umieszczenie kursora na poczatku nowego wiersza. Wpisz
do nowego wiersza instrukcje oczyszczenia ekranu (odpowiednik
instrukcji CLS w Basicu):

clrscr();

W C++ clrscr() oznacza wywołanie funkcji czyszczacej roboczy
ekran programu (User screen). Nazwa funkcji pochodzi od skrótu:

CLeaR SCReen - czysc ekran.

Ze to funkcja - mozna rozpoznac po dodanej za nazwa parze
nawiasów okragłych - (). W tym jednak przypadku wiersz:

clrscr();

stanowi nie deklaracje funkcji, lecz wywołanie funkcji (ang.
function call). C++ znalazłszy w programie wywołanie funkcji
wykona wszystkie rozkazy, które zawiera wewnatrz funkcja
clrscr(). Nie musisz przejmowac sie tym, z jakich rozkazów
składa sie funkcja clrscr(). Te rozkazy nie stanowia czesci
Twojego programu, lecz sa zawarte w jednym z "fabrycznych"
plików bibliotecznych zainstalowanych wraz z kompilatorem C++.


[S]
Function - Funkcja
Fuction call - Wywołanie funkcji
________________________________________________________________

Funkcja to cos przypominajace mini-program. Funkcja zawiera
liste rozkazów słuzacych do wykonania typowych operacji (np.
czyszczenie ekranu, wyswietlanie menu, wydruk, czy sortowanie
listy imion). W programach posługujemy sie zwykle wieloma
funkcjami. Poznałes juz najwazniejsza funkcje główna - main(). W
C/C++ mozesz posługiwac sie gotowymi funkcjami (tzw.
bibliotecznymi) a takze tworzyc nowe własne funkcje. Na razie
bedziemy posługiwac sie gotowymi funkcjami dostarczanymi przez
producenta wraz z kompilatorem C++.
________________________________________________________________


Włacz kompilacje i próbe uruchomienia programu.
Kompilator stwierdzi, ze program zawiera błedy.
Nacisnij dowolny klawisz, by znikneło okienko kompilacji.
Kompilator napisał:

Error: Function 'clrscr' should have a prototype

(Funkcja 'clrscr' powinna miec prototyp)

[???] O co mu chodzi?
________________________________________________________________
Tzw. PROTOTYP funkcji to cos bardzo podobnego do deklaracji
funkcji. Prototyp słuzy do przekazania kompilatorowi pewnych
informacji o funkcji jeszcze przed uzyciem tej funkcji w
programie. Dla przykładu, gdy pisałes pierwsza linie programu:

void main(void)

podałes nie tylko nazwe funkcji - main, lecz takze umiescilismy
tam dwukrotnie słowo void. Dokładnie o znaczeniu tych słów
napiszemy w dalszej czesci ksiazki. Na razie zwrócmy jedynie
uwage, ze podobnych "dodatkowych" informacji dotyczacych funkcji
clrscr() w naszym programie nie ma.
________________________________________________________________


Zwróc uwage, ze zapisy:

main() int main(void) main(void) {
{ { } }

}

sa całkowiecie równowazne. Fakt, ze słowa kluczowe void (w nawiasie)
i int (przed funkcja i tylko tam!) moga zostac pominiete wskazuje, ze sa
to wartosci domyslne (default settings) przyjmowane automatycznie.

Funkcja clrscr() została napisana przez programistów z firmy
BORLAND i znajduje sie gdzies w osobnym pliku dołaczonym do
kompilatora C++. Aby móc spokojnie posługiwac sie ta funkcja w
swoich programach, powinienes dołaczyc do swojego programu
informacje w jakim pliku dyskowym znajduje sie opis funkcji
clrscr(). Taki (dosc szczegółowy) opis funkcji nazywa sie
własnie prototypem funkcji. Aby dodac do programu te (niezbedna)
informacje

* nacisnij [F6] by przejsc do okna edytora
* ustaw migajacy kursor na poczatku tekstu programu
* nacisnij [Enter] dwukrotnie, by dodac dwie nowe puste linie do

tekstu programu
* na samym poczatku programu wpisz:

#include <conio.h>

Takie specjalne linie (zwróc uwage na podswietlenie)
rozpoczynajace sie od znaku # (ASCII 35) nie sa własciwie
normalna czescia składowa programu. Nie stanowia one jednej z
instrukcji programu, mówiacej komputerowi CO NALEZY ROBIC, lecz
stanowia tzw. dyrektywe (rozkaz) dla kompillatora C++ - W JAKI
SPOSÓB KOMPILOWAC PROGRAM. Dyrektywa kompilatora (ang.
compiler
14
directive) powoduje dokonanie okreslonych działan przez
kompilator na etapie tłumaczenia programu na kod maszynowy. W
danym przypadku dyrektywa

#include ....

(ang. include - włacz, dołacz) powoduje właczenie we wskazane
miejsce zawartosci zewnetrznego tekstowego pliku dyskowego - np.:

CONIO.H,

(plik CONIO.H
nazywany ˙takze "plikiem nagłówkowym" znajduje sie w podkatalogu
\INCLUDE). Kompilator dołacza zawartosc pliku CONIO.H jeszcze
przed rozpoczeciem procesu kompilacji programu.

Nacisnij kombinacje klawiszy [Ctrl]+[F9]. Spowoduje to
kompilacje i uruchomienie programu (Run). Przykładowy program
powinien tym razem przekompilowac sie bez błedów. Po dokonaniu
kompilacji powinien szybko błysnac ekran uzytkownika. Po tym
błysku powinien nastapic powrót do roboczego srodowiska IDE
kompilatora C++. Jesli nie zdazyłes sie przyjrzec i chcesz
spokojnie sprawdzic, co zrobił Twój program - nacisnij
kombinacje klawiszy [Alt]+[F5].
Dzieki działaniu funkcji clrscr() ekran bedzie całkowicie
czysty.

[S] Compiler directive - DYREKTYWA KOMPILATORA
________________________________________________________________

Dyrektywa kompilatora to rozkaz wyjasniajacy kompilatorowi C++ w
jaki sposób dokonywac kompilacji programu. Dyrektywy kompilatora
zawsze rozpoczynaja sie od znaku # (ang. hash).
Kompilatory C++ posiadaja pewien dodatkowy program nazywany
PREPROCESOREM. Preprocesor dokonuje przetwarzania tekstu
programu jescze przed rozpoczeciem własciwej kompilacji.
Dokładniej rzecz biorac #include jest własciwie dyrektywa
preprocesora (szczegóły w dalszej czesci ksiazki).
________________________________________________________________


[Z] - Propozycje zadan do samodzielnego wykonania.
________________________________________________________________
1. Spróbuj poeksperymentowac "zjadajac" kolejno rózne elementy w

poprawnie działajacym na poczatku programie:

- litera w nazwie funkcji
- srednik na koncu wiersza
- cudzysłów obejmujacy tekst do wydrukowania
- nawias ( lub ) w funkcji printf()
- nawias klamrowy { lub }
- znak dyrektywy #
- cała dyrektywe #include <stdio.h>

Porównaj komunikaty o błedach i zgłaszana przez kompilator
liczbe błedów. Czy po przekłamaniu jednego znaku kompilator
zawsze zgłasza dokładnie jeden bład?
________________________________________________________________

________________________________________________________________
______
EOF



LEKCJA 6 - NASTEPNY PROGRAM - KOMPUTEROWA
ARYTMETYKA.
________________________________________________________________
W trakcie tej lekcji napiszesz i uruchomisz nastepny program
wykonujacy proste operacje matematyczne.
________________________________________________________________

Aby przystapic po wyjasnieniach do pracy nad drugim programem,
powinienes wykonac nastepujace czynnosci:

1. Zrób porzadek na ekranie. Zamknij rozkazem Close z menu
Window zbedne okna (mozesz posłuzyc sie kombinacja [Alt]-[F3]).
2. Rozwin menu File.
3. Wybierz z menu rozkaz Open...
4. Wpisz do okienka tekstowego:
A:\DRUGI.CPP
5. Nacisnij [Enter].
6. Wpisz do okienka edytora tekst programu:

[P003.CPP ]
/* Program przykladowy: _DRUGI.CPP */


# include <conio.h> /* zwróc uwage, ze tu NIE MA [;] ! */
# include <stdio.h> /* drugi plik nagłówkowy */

int main() /* tu tez nie ma srednika [;] ! */
{
float x, y;
float wynik;

clrscr();
printf("Zamieniam ulamki zwykle na dziesietne\n");
printf("\nPodaj licznik ulamka: ");
scanf("%f", &x); /* pobiera liczbe z klawiatury */
printf("\nPodaj mianownik ulamka: ");
scanf( "%f", &y);

wynik = x / y; /* tu wykonuje sie dzielenie */

printf("\n %f : %f = %f", x, y, wynik);
printf("\n nacisnij dowolny klawisz...\n");

getch(); /* program czeka na nacisniecie klawisza. */

return 0; //<-- zwrot zera do systemu
}


UWAGA:
________________________________________________________________
_
* Komentarze ujete w [/*.....*/] mozesz pominac. Komentarz jest
przeznaczony dla człowieka. Kompilator ignoruje całkowicie
komentarze i traktuje komentarz jak puste miejsce, a dokładniej
- tak samo jak pojedyncza spacje. Komentarz w C++ moze miec dwie
formy:

/* Tekst komentarza */
// Tekst komentarza

w drugim przypadku ogranicznikiem pola komentarza jest koniec
wiersza.

* Spacjami i TABami mozesz operowac dowolnie. Kompilator
ignoruje takze puste miejsca w tekscie. Nie nalezy natomiast
stosowac spacji w obrebie słów kluczowych i identyfikatorów.
________________________________________________________________


7. Skompiluj program [Alt]-[C], [M] lub [Enter].

8. Popraw ewentualne błedy.

9. Uruchom program rozkazem Run, naciskajac [Alt]-[R], [R].

10. Zapisz wersje zródłowa programu DRUGI.CPP na dyskietke A:\
stosujac tym razem SHORTCUT KEY - klawisz [F2].

[S!] scanf() - SCANing Function - Funkcja SKANujaca.
________________________________________________________________
Funkcja pobiera ze standardowego urzadzenia wejscia- zwykle z
klawiatury podana przez uzytkownika liczbe lub inny ciag znaków.

Działa podobnie do funkcji INPUT w Basicu, czy readln w Pascalu.

* float - do Floating Point - "Pływajacy" - zmienny przecinek.
Słowo kluczowe słuzace do tzw. DEKLARACJI TYPU ZMIENNEJ lub
funkcji. Oznacza liczbe rzeczywista np.: float x = 3.14;

* int - od Integer - całkowity.
Słowo kluczowe słuzace do deklaracji typu zmiennej lub funkcji.
Oznacza liczbe całkowita np.: 768.

* #include - Włacz.
Dyrektywa właczajaca cały zewnetrzny plik tekstowy. W tym
przypadku właczone zostały dwa tzw. pliki nagłówkowe:
CONIO.H i STDIO.H.

* CONIO.H - CONsole Input/Output.
15
Plik nagłówkowy zawierajacy prototypy funkcji potrzebnych do
obsługi standardowego Wejscia/Wyjscia na/z konsoli (CONsole).
Plik zawiera miedzy innymi prototyp funkcji clrscr(), potrzebnej
nam do czyszczenia ekranu.

*STDIO.H - STanDard Input/Output
Plik nagłówkowy zawierajacy prototypy funkcji potrzebnych do
obsługi standardowego Wejscia/Wyjscia na/z konsoli (Input -
Wejscie, Output - Wyjscie). Plik zawiera miedzy innymi prototyp
funkcji printf(), potrzebnej nam do drukowania wyników na
ekranie.

return - słowo kluczowe: Powrót, zwrot.

Po wykonaniu programu liczba 0 (tak kazalismy programowi
rozkazem return 0;) jest zwracana do systemu operacyjnego, w
naszym przypadku do DOSa. Zwróc uwage, ze nie pojawiło sie tym
razem ostrzezenie WARNING podczas kompilacji.
________________________________________________________________


OPERATORY ARYTMETYCZNE C++.

C++ potrafi oczywiscie nie tylko dzielic i mnozyc. Oto tabela
operatorów arytmetycznych c i C++.

OPERATORY ARYTMETYCZNE jezyka C++.
________________________________________________________________

Operator Nazwa Tłumaczenie Działanie
________________________________________________________________

+ ADDition Dodawanie Suma liczb
- SUBstraction Odejmowanie Róznica liczb
* MULtiplication Mnozenie Iloczyn liczb
/ DIVision Dzielenie Iloraz liczb
% MODulus Dziel Modulo Reszta z dzielenia
________________________________________________________________


Przykładowe wyniki niektórych operacji arytmetycznych.
________________________________________________________________

Działanie (zapis w C++) Wynik działania
________________________________________________________________

5 + 7 12
12 - 7 5
3 * 8 24
10 / 3 3.333333
10 % 3 1
________________________________________________________________



[???] Czym rózni sie dzielenie / od % ?
________________________________________________________________
Operator dzielenia modulo % zamiast wyniku dzielenia - daje
rzeszte z dzielenia. Dla przykładu, dzielenie liczby 14 przez
liczbe 4 daje wynik 3, reszta z dzielenia 2. Wynik operacji

14%4

bedzie wiec wynosic 2. Operator ten jest niezwykle przydatny np.
przy sprawdzaniu podzielnosci, skalowaniu, okreslaniu zakresów
liczb przypadkowych, itp..

Przykłady generacji liczb pseudolosowych wybiegaja nieco w przyszłosc,
ale postanowiłem w Samouczku umiescic je razem. Po przestudiowaniu
tworzenia petli programowych mozesz wrócic do tej lekcji i rozwazyc
przykłady po raz wtóry.

Przykład 1:

randomize();
int X=ramdom();
X = X % 10;

Przykład 2:
---------------------
#include <stdlib.h> /* Zwróc uwage na dołaczony plik */
#include <stdio.h>

main()
{
int i;

printf("Dziesiec liczb pseudo-losowych od 0 do 99\n\n");
for(i=0; i<10; i++)
printf("%d\n", rand() % 100);
return 0;
}

Przykad3
--------------------
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

void main()
{
randomize();
printf("Liczby pseudolosowe z zakresu: 0-99 --> %d\n", random (100));
}

Przykład 4
-----------------
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

int main(void)
{
int i;

randomize();
printf("Liczby pseudolosowe: 0 to 99\n\n");
for(i=0; i<10; i++)
printf("%d\n", rand() % 100);
return 0;
}

Zwróc uwage, ze to randomize() uruchamia generator liczb pseudolosowych,
czyli jakgdyby "włacza beben maszyny losujacej".
________________________________________________________________


Wykonaj z programem DRUGI.CPP kilka eksperymentów.

[Z]
________________________________________________________________
1. Zamien operator dzielenia na operator mnozenia [*]:

wynik = x * y; /* tu wykonuje sie mnozenie */

i napis w pierwszej funkcji printf() na np. taki:

printf( "Wykonuje mnozenie liczb" );

Uruchom program. Sprawdz poprawnosc działania programu w
szerokim zakresie liczb. Przy jakiej wielkosci liczb pojawiaja
sie błedy?

2. Zmien nazwy zmiennych x, y, wynik na inne, np.:
to_jest_liczba_pierwsza,
to_jest_liczba_druga,
itp.
Czy C++ poprawnie rozpoznaje i rozróznia takie długie nazwy?
Kiedy zaczynaja sie kłopoty? Sprawdz, czy mozna w nazwie
zmiennej uzyc spacji? Jaki komunikat wyswietli kompilator?
________________________________________________________________

[???] PRZEPADŁ PROGRAM ???
________________________________________________________________
Nie przejmuj sie. Wersja poczatkowa programu DRUGI.CPP jest na
dyskietce dołaczonej do niniejszej ksiazki (tam nazywa sie
DRUGI.CPP).
Zwróc uwage, ze kompilator C++ tworzy automatycznie kopie
zapasowe plików zródłowych z programami i nadaje im standardowe
rozszerzenie *.BAK. Zanim zatem zaczniesz sie denerwowac,
sprawdz, czy kopia np. DRUGI.BAK nie jest własnie ta wersja
programu, która Ci "przepadła".
________________________________________________________________

________________________________________________________________
__
16
EOF
LEKCJA 7. Z czego składa sie program.
_______________________________________________________________
W trakcie tej lekcji:
* Dowiesz sie co robic, jesli tesknisz za Pascalem.
* Zapoznasz sie wstepnie z preprocesorem C++.
* Poznasz dokładniej niektóre elementy jezyka C++.
_______________________________________________________________

Zanim zagłebimy sie w szczegóły działania preprocesora i
kompilatora, dla zilustrowania mechanizmu działania dyrektyw
popełnimy zart programistyczny. Nie ma nic gorszego niz spalic
dobry zart, upewnijmy sie wiec najpierw, czy nasza
"czarodziejska kula" jest gotowa do magicznych sztuczek.
Sprawdz, czy na dyskietce znajduja sie pliki

A:\PASCAL.H
A:\POLTEKST.H

Jesli nie, to przed zabawa w magiczne sztuczki programistyczne
musisz odtworzyc te pliki z zapasowej kopii dyskietki, która
sporzadziłes przed rozpoczeciem LEKCJI 1.

Jesli masz juz oba pliki, to wykonaj nastepujace czynnosci:

1. Włóz do napedu A: dyskietke z plikami PASCAL.H i POLTEKST.H.
2. Uruchom kompilator C++.

PROGRAMY HOKUS.EXE i POKUS.EXE - czyli sztuczki z Preprpcesorem
C++

1. Zrób porzadek na ekranie - pozamykaj zbedne okna.
2. Nacisnij klawisz [F3]. Pojawi sie znajome okienko dialogowe
"Open".
3. Wpisz do okienka tekstowego nazwe nowego programu:
A:\HOKUS.C
i nacisnij [Enter].
4. Wpisz nastepujacy tekst programu:

[P004.CPP]

#include <a:\pascal.h>

Program
Begin
Write("Ten program jest podobny");
Write(" do Turbo Pascala ");
Write(" tak tez mozna pisac w BORLAND C++ !");
Readln;
End


5. Uruchom program [Ctrl]-[F9]. Jesli wystapia błedy, skoryguj
ewentualne niezgodnosci z oryginałem. Ostrzezenie "WARNING"
mozesz zignorowac.

UWAGA: MUSI ZOSTAC ZACHOWANA IDEALNA ZGODNOSC z
tekstem
oryginału!

6. Uruchom program rozkazem Run [Alt]-[R], [Enter]. Zwróc uwage,

ze powtórna kompilacja przebiega szybciej, jesli w miedzyczasie
nie dokonałes zmian w programie.
7. Zamknij okno edytora rozkazem Close (z menu Window). Zapisz
program HOKUS.CPP w wersji zródłowej na dyskietke A:.

A teraz nastepna sztuczka, na która pozwala C++.

Utworzymy nastepny program POKUS.CPP.

1. Wykonaj czynnosci z pp. 1 i 2 z poprzedniego przykładu.
2. Otwórz okienko nowego programu - File | Open (np. klawiszem
[F3]) i wpisz nazwe programu. Mozesz zastosowac równiez File |
New.

A:\POKUS.CPP

3. Nacisnij [Enter].
4. Wpisz tekst programu:

[P005.CPP]

# include <a:\poltekst.h>
program
poczatek
czysty_ekran
drukuj ("Ten program - POKUS.CPP ");
drukuj ("Jest napisany po polsku ");
drukuj ("a mimo to Turbo C++ go rozumie!");
czekaj;
koniec

5. Uruchom program [Alt]-[R], [R]. Jesli wystapia błedy,
skoryguj ewentualne niezgodnosci z oryginałem. Ostrzezenie
"WARNING" mozesz zignorowac.
UWAGA: MUSI ZOSTAC ZACHOWANA IDEALNA ZGODNOSC!

6. Zamknij okno edytora rozkazem Close (z menu Window). Zapisz
program HOKUS.C w wersji zródłowej na dyskietke A:.

WYJASNIENIE SZTUCZEK - PREPROCESOR C++ CPP.EXE.

A teraz wyjasnienie naszych magicznych sztuczek. Jesli jestes
niecierpliwy, na pewno juz sam zajrzałes do plików PASCAL.H i
POLTEKST.H, bo jest chyba oczywiste od poczatku, ze to tam
własnie musi ukrywac sie to wszystko, co pozwala nam robic nasze

hokus-pokus. Skorzystalismy z pewnej nie wystepujacej ani w
Pascalu, ani w Basicu umiejetnosci jezyków C i C++ - a
mianowicie z PREPROCESORA.

Najczesciej stosowanymi dyrektywami preprocesora sa:

# include - włacz
i
# define - zdefiniuj

Do rozpoznania dyrektyw preprocesora słuzy znak (#) - HASH.

Zwróc uwage, ze zapisy

#include
# include

sa całkowicie równowazne. Poza tym dyrektywy preprocesora nie
koncza sie srednikiem.

Działanie preprocesora (czyli wstepne przetwarzanie tekstu
programu jeszcze przed przystapieniem do kompilacji) polega na
zastapieniu w tekscie programu jednych łancuchów znaków przez
inne. Takie pary mozemy "zadac" preprocesorowi własnie dyrektywa

#define. Nasze nagłówki wygladaja nastepujaco:

PASCAL.H:
_______________________________________________________________
# include <stdio.h>
# define Program main()
# define Begin {
# define Writeln printf
# define Readln getch()
# define End }
________________________________________________________________

POLTEKST.H:
________________________________________________________________
# include <stdio.h>
# define program main()
# define poczatek {
# define koniec }
# define czysty_ekran clrscr();
# define drukuj printf
# define czekaj getch()
________________________________________________________________

Zwróc uwage, ze warunkiem poprawnego zadziałania preprocesora
jest zrezygnowanie ze spacji wewnatrz łancuchów znakowych,
spacje bowiem w preprocesorze rozdzielaja dwa łancuchy znaków - np.

"drukuj"

- ten ZA KTÓRY CHCEMY COS PODSTAWIC oraz np.

"printf"

17
- ten, KTÓRY NALEZY PODSTAWIAC. Czesto w programach
zauwazysz łancuchy znaków pisane w dosc specjalny sposób:

napisy_w_których_unika_sie_spacji.

ELEMENTY PROGRAMU W JEZYKU C++.

Uogólniajac, program w jezyku C++ składa sie z nastepujacych
elementów:

1. Dyrektyw preprocesora. Przykład:

#define drukuj printf

Działanie: W tekscie programu PONIZEJ niniejszej dyrektywy
zastap wszystkie łancuchy znaków "drukuj" łancuchami znaków
"printf".

#include <D:\KATALOG\nazwa.roz>

Działanie: W to miejsce pliku wstaw zawartosc pliku tekstowego
NAZWA.ROZ z katalogu KATALOG na dysku D:.

2. Komentarzy. Przykład:

// Tu obliczamy sume lub /*To jest komentarz*/

3. Deklaracji. Przykład:
KAZDY PROGRAM musi zawierac deklaracje funkcji main (ang. main -
główna). Funkcja ta czesto jest bezparametrowa, co mozna
zaakcentowac wpisujac w nawiasy słowo kluczowe void:

main(void)

lub piszac puste nawiasy:

main()

4. Instrukcji.

i++;

Działanie: Dokonaj inkrementacji zmiennej i, tzn. wykonaj
operacje i:=i+1

[???] Dla dociekliwych - kilka słów o funkcji main()
________________________________________________________________
Funkcja main() wystepuje najczesciej w nastepujacych
(równowaznych) postaciach:

main() int main() int main(void)

- program w momencie uruchomienia nie pobiera zadnych argumentów

z wiersza rozkazu --> () lub (void)
- program zwraca po zakonczeniu jedna liczne (int = integer -
liczba całkowita) do systemu operacyjnego informujac go w taki
sposób, czy wykonał sie do konca i bezbłednie i czy mozna go
usunac z pamieci (bywaja takze programy rezydujace w pamieci -
tzw. TSR, o czym system operacyjny powinien "wiedziec").

void main() void main(void)

- program nie pobiera i nie zwraca zadnych paramatrów.
Główna funkcja main() moze w srodowisku okienkowym przeobrazic
sie w główna funkcje okienkowa:

WinMain(.....)

a w srodowisku obiektowym w

OwlMain(....)

OWL - biblioteka obiektów dla Windows - Object Windows Library.

W nawiasach funkcji main(), WinMain() i OwlMain() moga pojawic
sie parametry (argumenty) pobierane przez program w momencie
uruchomienia z wiersza rozkazu lub od srodowiska operacyjnego
(szczegóły w dalszej czesci ksiazki).
Programy w C++ moga składac sie z wielu plików dyskowych. Typowy

program zawiera. Nazywa sie to zwykle projektami wielomodułowymi

- a poszczególne pliki - modułami lub elementami składowymi
projektu:

* plik nagłówkowy - NAZWA.H
* moduł główny - NAZWA.CPP (ten i tylko ten zawiera funkcje
main())
* moduły pomocnicze - NAZWA2.CPP, NAZWA3.CPP, itp
* pliki z zasobami typu menu, okienka dialogowe, itp - NAZWA.RC,

NAZWA.DLG
* wreszcie plik instruktazowy - jak z tego wszystkiego zrobic
koncowa aplikacje. W zaleznosci od wersji kompilatora pliki
instruktazowe moga miec nazwy: NAZWA.PRJ (Project - BORLAND),
NAZWA.IDE, a dla programu MAKE - MAKEFILE, NAZWA.MAK,
NAZWA.NMK,

itp.
W srodowisku Windows wystepuje jeszcze zwykle w składzie
projektów aplikacji tzw. plik definicji sposobu wykorzystania
zasobów - NAZWA.DEF.
________________________________________________________________

[S!] void - czyli nijaki.
________________________________________________________________
Słowa kluczowe:
void - pusty, wolny, nieokreslony, avoid - unikac.
main - główny, główna.
return - powrót, zwrot.
Nazwa funkcji:
exit() - wyjscie.
________________________________________________________________

Po nazwie funkcji main() NIE NALEZY stawiac srednika (;).
Przy pomocy tej funkcji program kontaktuje sie z systemem
operacyjnym. Parametry funkcji main, to te same parametry z
którymi uruchamiamy nasz program w systemie DOS. Np. rozkaz

FORMAT A:

oznacza, ze do programu przekazujemy parametr A:.

Poniewaz w kazdym programie oprócz nagłówka funkcji:

main(void)

podajemy równiez tzw. ciało funkcji, np.:

{
printf("wydrukuj cokolwiek");
return 0;
}

jest to jednoczesnie DEFINICJA FUNKCJI main().
Zwróc uwage, ze funkcja printf() nie jest w powyzszym
przykładzie w zaden sposób ani deklarowana ani definiowana.
Wiersz:

printf("pisz!");

stanowi WYWOŁANIE funkcji printf() z parametrem 'pisz!' -
łancuchem znaków, który nalezy wydrukowac.
W C++ nawet jesli nawiasy przeznaczone w funkcji na przekazanie
jej argumentów sa puste - musza byc obecne. Poprawne wywołanie
funkcji w jezyku C++ moze miec nastepujaca forme:

nazwa_funkcji();

nazwa_funkcji(par1, par2, par3, .....);

zmienna = nazwa_funkcji(par1, par2, ...);

Funkcja w momencie jej wywołania uzyskuje przekazane jej
parametry. Sa to tzw. ARGUMENTY FUNKCJI. Aby to wszystko
bardziej przypominało to, co znasz ze szkoły popatrzmy na
analogie. W zapisie:

y = sin(x) lub y = sin(90)

x - oznacza argument funkcji, który moze byc zmienna (w szkole
nazywałes zmienne "niewiadomymi")
y - oznacza wartosc zwracana "po zadziałaniu" funkcji
sin() - oznacza nazwe funkcji. Zastosowanie funkcji bedziemy w
programach nazywac "wywołaniem funkcji".
18

Jezyk C++ operuje wyłacznie pojeciem FUNKCJI. W C ani w C++ nie
ma podziału na FUNKCJE i PROCEDURY.

Kazda funkcja moze byc w programie wywoływana wielokrotnie.
Kazde wywołanie funkcji moze nastepowac z innymi argumentami.
Funkcja moze w wyniku swojego działania zmieniac wartosc jakiejs

zmiennej wystepujacej w programie. Mówimy wtedy, ze funkcja
ZWRACA wartosc do programu. Funkcja main() jest funkcja
szczególna, która "zwraca" wartosc do systemu operacyjnego, w
którym pracuje program. Zapis:

main() lub int main()
{ {
return 5; exit(5);
} }

oznacza:
1. Funkcja main jest bezparametrowa (nie przyjmuje zadnych
argumentów z zewnatrz).
2. Funkcja main zwraca jako wynik swojego działania liczbe
całkowita typu int (ang. INTeger - całkowita). Zwróc uwage, ze
jest to domyslny sposób działania funkcji main(). Jesli nie
napiszemy przed funkcja main() słowa "int" - kompilator C++ doda

je sobie automatycznie. Jesli swiadomie nie zamierzamy zwracac
do systemu operacyjnego zadnych informacji - musimy wyraznie
napisac tam "void".
3. Funkcja zwróci do systemu DOS wartosc 5. Zwróc uwage na
istotna róznice formalna, Słowo "return" jest słowem kluczowym
jezyka C, natomiast słowo "exit" jest nazwa funkcji exit().
Zastosowanie tej funkcji w programie wymaga dołaczenia pliku
nagłówkowego z jej prototypem.

Poniewaz nasz kurs jezyka C++ rozpoczelismy od programu z
funkcja printf() i zapewne bedzie nam ona towarzyszyc jeszcze
długo, pora poswiecic jej troche uwagi.

FUNKCJA printf().

Jest to funkcja FORMATOWANEGO wyjscia na standardowe urzadzenie
wyjscia (ang. stdout - STandarD OUTput). Definicja - scislej
tzw. PROTOTYP tej funkcji znajduje sie w pliku nagłówkowym
STDIO.H. Wniosek praktyczny: Kazdy program korzystajacy z
funkcji printf() powinien zawierac dyrektywe preprocesora:

#include <stdio.h>

zanim nastapi wywołanie funkcji printf().

[???] A JESLI ZAPOMNIAŁEM O <STDIO.H> ???
________________________________________________________________
Mozesz nadac plikowi z tekstem zródłowym programu rozszerzenie
.C zamiast .CPP. W kompilatorach Borlanda powoduje to przy
domyslnych ustawieniach kompilatora wywołanie kompilatora C
zamiast C++. C jest bardziej tolerancyjny i dokona kompilacji
(wyswietli jedynie komunikat ostrzegawczy - Warning). Kompilator

C++ jest mniej tolerancyjny. Jesli zapomnisz dołaczyc odpowiedni

plik nagłówkowy moze pojawic sie komunikat:

Error: Function printf() should have a prototype in function
main
(Funkcja printf() powinna miec prototyp)

Wiecej o zawartosci i znaczeniu plików nagłówkowych *.h dowiesz
sie z nastepnych lekcji. Na razie postaraj sie pomietac o
dołaczeniu wskazanego w przykładzie pliku.
________________________________________________________________

[???] Skad to wiadomo?
________________________________________________________________
Jesli masz watpliwosci, jaki plik nagłówkowy nalezałoby dołaczyc

- najprosciej zajrzec do systemu pomocy - Help. Na pasku
głównego menu w IDE masz napis Help. Menu Help mozesz rozwinac
myszka lub naciskajac kombinacje klawiszy [Alt]+[H]. Jesli w
menu wybierzesz rozkaz Index (Spis) przeniesiesz sie do okienka
z alfabetycznym spisem haseł. Sa tam słowa kluczowe, nazwy
funkcji i jeszcze wiele innych interesujacych rzeczy. Powinienes

teraz wykonac nastepujace czynnosci:

* posługujac sie klawiszami kursora (ze strzałkami) odszukac w
spisie nazwe funkcji
albo
* rozpoczac pisanie nazwy funkcji na klawiaturze (system Help
sam wyszuka w spisie wypisana w ten sposób nazwe)
* nacisnac [Enter]
Przeniesiesz sie do okienka opisu danej funkcji. Na samym
poczatku w okienku kazdej funkcji podana jest nazwa pliku
nagłówkowego, w którym znajduje sie prototyp funkcji. Nawet
jesli nie jestes biegłym anglista, łatwo rozpoznasz pliki
nagłówkowe - po charakterystycznych rozszerzeniach .H (rzadziej
.HPP. Charakterystyczne rozszerzenie *.H pochodzi od "plik
nagłówkowy" - ang. Header file).
________________________________________________________________

Funkcja printf() zwraca wartosc całkowita typu int:

* liczbe bajtów przesłanych na standardowe urzadzenie wyjscia;
* w przypadku wystapienia błedu - kod znaku EOF.

[S!]
EOF - End Of File - znak konca pliku.
EOL - End Of Line - znak konca linii.
Indicator - znak, wskaznik (nie mylic z pointerem !)

[???] SKAD TO WIADOMO ?
________________________________________________________________
Kody EOF, EOL sa tzw. predefiniowanymi stałymi. Ich szyfrowanie
(przypisywanie tym identyfikatorom okreslonej stałej wartosci
liczbowej) dokonuje sie z zastosowaniem preprocesora C++.
To, ze nie musisz sie zastanawiac ile to własciwie jest EOF
(zero ? czy -1 ?) zawdzieczamy tez dołaczanym plikom typu *.H, w

których np. przy uzyciu dyrektywy #define zostały PREDEFINIOWANE

(zdefiniowane wstepnie) niektóre stałe. Jesli jestes bardzo
dociekliwy, zajrzyj do wnetrza pliku STDIO.H (view, edit, type).

Znajdziesz tam miedzy innymi taki wiersz:

#define EOF (-1) //End of file indicator
________________________________________________________________

Składnia prototypu (ang. syntax):

int printf(const char *format [arg1, arg2,.....]);

lub troche prosciej:

printf(format, arg1, arg2,.....argn);

Liczba argumentów moze byc zmienna.

C++ oferuje wiele funkcji o podobnym działaniu - np.:

cprintf(), fprintf(), sprintf(), vprintf(), vsprintf(), itp.

Poniewaz FORMAT brzmi moze troche obco, nazwijmy go WZORCEM. Jak

wiesz, wszystkie informacje przechowywane sa w pamieci komputera

jako ciagi zer i jedynek. Jest to forma troche niewygodna dla
człowieka, wiec zanim informacja trafi na ekran musi zostac
zamieniona na postac dla nas wygodniejsza - np. na cyfry
dziesietne, litery itp.. Taki proces nazywany jest KONWERSJA, a
podany w funkcji printf() FORMAT - WZORZEC to upraszczajac,
rozkaz dokonania takiej własnie konwersii. Mozesz wiec zarzadac
przedstawienia liczby na ekranie w postaci np. SZESNASTKOWEJ lub

DZIESIETNEJ - tak, jak Ci wygodniej. Wzorce konwersji w
najprostszym przypadku maja postac %s, %d, %f, itp.:
I tak:

%s - wyprowadz łancuch znaków (s - String - łancuch)
Przykład:

printf("%s","jakis napis");
ale takze
printf("Jakis napis");

poniewaz format "%s" jest formatem domyslnym dla funkcji
19
printf().

Przykład:

printf("%39s","jakis napis");

spowoduje uzupełnienie napisu spacjami do zadanej długosci 39
znaków (Sprawdz!). Funkcja printf() operuje tzw. POLEM
WYJSCIOWYM. Długosc pola wyjsciowego mozemy okreslic przy pomocy

liczb wpisanych pomiedzy znaki % oraz typ - np. s. Mozemy takze
okreslic ilosc cyfr przed i po przecinku.

%c - wyprowadz pojedynczy znak (c - Character - znak)
Przykład:

printf("%c",'X');

(spowoduje wydrukowanie litery X)

%d - wyprowadz liczbe całkowita typu int w postaci dziesietnej
ttttt(d - Decimal - dziesietny).
Przykład:

printf("%d", 1994);

%f - wyprowadz liczbe rzeczywista typu float w postaci
dziesietnej (f - Floating point - zmienny przecinek).

Przykład:

printf("%f", 3.1416);
printf("%f3.2", 3.14159);

%o - wyprowadz liczbe całkowita typu int w postaci ósemkowej
(o - Octal - ósemkowa).
Przykład:

printf("%o", 255);

%x - wyprowadz liczbe całkowita typu int w postaci szesnastkowej

ttttt(x - heXadecimal - szesnastkowa).
%x lub %X - cyfry szesnastkowe a,b,c,d,e,f lub A,B,C,D,E,F.

%ld - liczba całkowita "długa" - long int.

%Lf - liczba rzeczywista poczwórnej precyzji typu long double
float.

%e - liczba w formacie wykładniczym typu 1.23e-05 (0.0000123)

%g - automatyczny wybór formatu %f albo %e.

Po przytoczeniu przykładów uogólnijmy sposób zastosowania wzorca

formatu:

%[przełaczniki][szerokosc_pola][.precyzja][rozmiar]Typ

Posługujac sie róznymi sposobami formatowania liczb mozemy
zazadac wydrukowania liczb w najwygodniejszej dla nas formie. W
programie przykładowym dokonujemy zamiany liczb dziesietnych na
szesnastkowe.

[P006.CPP]

// Program przykladowy 10na16.CPP

#include <stdio.h>
#include <conio.h>

int liczba;

int main()
{
clrscr();
printf("Podaj liczbe dziesietna calkowita ? \n");
scanf("%d", &liczba);
printf("\nSzesnastkowo to wynosi: ");
printf("%x",liczba);
getch();
return 0;
}

Ten program pozwala zamienic dziesietne liczby całkowite na
liczby szesnastkowe. Zakres dostepnych liczb wynika z
zadeklarowanego typu int. Wiecej na ten temat dowiesz sie z
nastepnych lekcji. Spróbujmy odwrotnie:

[P007.CPP]

// Program przykladowy 16na10.CPP
//UWAGA: Sam dołacz pliki nagłówkowe

int liczba;

int main()
{
clrscr();
printf("Podaj liczbe SZESNASTKOWA-np. AF - DUZE LITERY: \n");
scanf("%X", &liczba);
printf("%s","\nDziesietnie to wynosi: ");
printf("%d",liczba);
getch();
return 0;
}

Mysle, ze program 16NA10.CPP mozna pozostawic bez dodatkowego
komentarza. Zwróc uwage, ze funkcja scanf() "formatuje" dane
wejsciowe bardzo podobnie do funkcji printf(). Pewnie dziwi Cie
troche "dualny" zapis:

liczba i &liczba.

Zagadka zostanie niebawem wyjasniona. W trakcie nastepnych
Lekcji zajmiemy sie dokładniej zmiennymi, i ich rozmieszczeniem
w pamieci a na razie wracamy do funkcji printf().

Jako sie rzekło wczesniej - funkcja printf() moze miec wiele
argumentów. Pozwala nam to przy pomocy jednego wywołania funkcji

wyprowadzac złozone napisy.

Przykład:

printf("Iloczyn 3 %c 5 %8s %d", '*', "wynosi ",15);

Działanie:
"Iloczyn_3_ - wyprowadz jako łancuch znaków.
%c - tu wyprowadz pojedynczy znak - '*'.
_5_ - wyprowadz jako łancuch znaków.
%8s - wyprowadz łancuch "wynosi_" uzupełniajac go z przodu
spacjami do długosci 8 znaków.
%d - wyprowadz 15 jako liczbe dziesietna.

UWAGA: Znakiem podkreslenia w tekscie ksiazki "_" oznaczyłem
spacje, spacja to tez znak.

Przykład:

printf("Iloczyn 3 %c 5 %9s %f", 'x', "wynosi ", 3*5);

Zwróc uwage, ze tym razem kazalismy komputerowi samodzielnie
policzyc ile wynosi nasz iloczyn, tzn. zastosowalismy jako
argument funkcji printf() nie stała, a WYRAZENIE. Działanie
mozesz przesledzic przy pomocy programu przykładowego:

[P008.CPP]

// Program WYRAZ.CPP - Dołacz pliki nagłówkowe

int main()
{
clrscr();
printf("Skomplikowany napis:\n");
printf("Iloczyn 3 %c 5 %8s %d", '*', "wyniosi ", 15);
getch();
printf("\nWyrazenie jako argument:\n");
printf("Iloczyn 3 %c 5 %9s %d", 'x', "wynosi ", 3*5);
printf("\n\n\n");
printf("Przyjrzyj sie i nacisnij klawisz...");
getch();
return 0;
}

20
Wyjasnijmy jeszcze jedno "dziwactwo" - znaki sterujace
rozmieszczeniem napisów na ekranie. Oto tabelka z najczesciej
uzywanymi znakami specjalnymi:

________________________________________________________________

Znak Nazwa Działanie
________________________________________________________________

\n New Line Przejscie na poczatek nowego wiersza

\b BackSpace Cofniecie kursora o jeden znak
\f Form feed O stronice w dół
\r Carriage return Powrót na poczatek biez. wiersza
\t Horizontal Tab Tabulacja pozioma
\v Vertical Tab Tabulacja pionowa
\a Sound a beep Pisk głosniczka
\\ Displ. backslash Wyswietl znak \
\' Display ' Wyswietl znak ' (apostrof)
\" Display " Wyswietl znak " (cudzysłów)
________________________________________________________________

UWAGA: Trzy ostatnie "backlash-kody" pozwalaja wyprowadzic na
ekran znaki specjalne \ ' i ", co czasami sie przydaje.
Szczególnie \\ jest czesto przydatny.

[Z]
Spróbuj samodzielnie:

1. Napisac i uruchomic program wykonujacy konwersje liczb
ósemkowych na dziesietne i odwrotnie.
2. Przy pomocy pojedynczego wywołania funkcji printf()
wydrukowac kilka złozonych napisów typu:
* suma 2+4 to 6
* działanie 5*7*27+6-873 daje wynik...( własnie, ile?).
3. Sprawdz działanie tabulacji pionowej \v. Ile to wierszy?

[???] DYSKIETKA NIE JEST Z GUMY !!!
________________________________________________________________
Jesli podczas kompilacji programów w okienku bedzie sie
uporczywie, bez widocznego powodu pojawiał napis "Errors" -
błedy, a w okienku komunikatów "Message" pojawi sie napis:

Fatal A:\PROGRAM.C: Error writing output file
(Fatalny bład podczas kompilacji pliku A:\PROGRAM.C: Bład przy
zapisie pliku wyjsciowego),

to znak, ze na dyskietce zabrakło miejsca. Pora zmienic katalog
wyjsciowy kompilatora C++. Aby to zrobic nalezy:
1. Rozwinac menu Option - [Alt]-[O].
2. Wybrac rozkaz Directories... - [D].
3. Przejsc do okienka "Output Directory" - 2 razy [Tab].
4. Wpisac do okienka katalog z dysku stałego, np.: C:\
5. Nacisnac [Enter].
6. Powtórzyc kompilacje programu, przy której nastapiło
przepełnienie dyskietki.
7. Usunac z dyskietki A: zbedne pliki *.EXE (TYLKO *.EXE !!!).

Oczywiscie lepiej posługiwac sie własnym katalogiem na dysku
stałym, ale dysk tez niestety nie jest z gumy. Złosliwi twierdza

nawet, ze kazdy dysk jest za mały a kazdy procesor zbyt wolny
(to ponoc tylko kwestia czasu...).
________________________________________________________________


[!!!] Dla dociekliwych - Przykłady programów.
________________________________________________________________
Jesli zajrzysz juz do systemu Help, przwin cierpliwie tekst
opisu funkcji do konca. W wiekszosci funkcji na koncu
umieszczony jest krótki "firmowy" program przykładowy.
Nie musisz go przepisywac!
W menu Edit IDE masz do dyspozycji rozkaz
Edit | Copy Example (Skopiuj przykład)
Przykład zostanie skopiowany do Schowka (Clipboard).
Po wyjsciu z systemu pomocy warto rozkazem
File | New
otworzyc nowe okno robocze a nastepnie rozkazem
Edit | Paste (Wstaw)
wstawic program przykładowy ze schowka. Mozesz go teraz
uruchamiac, modyfikowac a nawet wstawic jako fragment do swojego

programu.
Podobnie jak wiekszosc edytorów tekstu zintegrowany edytor
srodowiska IDE pozwala manipulowac fragmentami blokami tekstu i
wykonywac typowe operacje edytorskie zarówno w obrebie
pojedynczego okna, jak i pomiedzy róznymi okienkami. Słuza do
tego celu nastepujace operacje:

* Select/Mark text block - zaznaczenie fragmentu tekstu.
Mozesz dokonac tego klawiszami- np.: [Shift]+[-->], badz
naciskajac i przytrzymujac lewy klawisz myszki i "przejezdzajac
nad odpowiednim fragmentem tekstu". Wybrany fragment tekstu
zostanie wyrózniony podswietleniem.
* Edit | Cut - wytnij.
Zaznaczony wczesniej fragment tekstu zostanie skopiowany do
Schowka i jednoczesnie usuniety z ekranu.
* Edit | Copy - skopiuj.
Zaznaczony wczesniej fragment tekstu zostanie skopiowany do
Schowka i bez usuwania z ekranu.
* Edit | Paste - wstaw.
Zaznaczony wczesniej w Schowku fragment tekstu zostanie
skopiowany na ekran poczawszy od miejsca wskazanego w danej
chwili kursorem.


LEKCJA 8. Jakich słów kluczowych uzywa C++.

W trakcie tej lekcji dowiesz sie:
* Jakie znaczenie maja słowa kluczowe jezyka C++.
* Jakie jeszcze dziwne słowa moga pojawiac sie w programach w
pisanych C++.
* Troche wiecej o wczytywaniu i wyprowadzaniu danych.
* Co to jest i do czego słuzy zmienna.
_______________________________________________________________

Kazdy jezyk musi operowac tzw. słownikiem - zestawem słów
zrozumiałych w danym jezyku. Jak wiesz z doswiadczenia, komputer
jest pedantem i wymaga dodatkowo (my, ludzie, tego nie
wymagamy), aby znaczenie słów było absolutnie jednoznaczne i
precyzyjne. Aluzje, kalambury i zabawne niedomówienia sa na
razie w dialogu z komputerem niedopuszczalne. Pamieci
asocjatywne (oparte na skojarzeniach), sieci neuronowe (neural
networks), tworzone bardzo czesto własnie przy pomocy C++
- systemy expertowe,
- systemy z tolerancja błedów - np. OCR - systemy optycznego
rozpoznawania pisma,
- "rozmyta" arytmetyka i logika (fuzzy math)
- logika wiekszosciowa i mniejszosciowa
- algorytmy genetyczne (genetic algorithms)
i inne pomysły matematyków oraz informatyków rozpoczeły juz
proces "humanizowania" komputerowego myslenia. Powstała nawet
specjalna "mutacja" neural C i neural C++, ale to temat na
oddzielna ksiazke. Na razie traktujemy nasz komputer jako
automat cyfrowy pozbawiony całkowicie wyobrazni i poczucia
humoru, a jezyk C++, jako srodek porozumiewania sie z tym
"ponurakiem".

Podobnie do słów jezyka naturalnego (rzeczowników, czasowników)
i słowa jezyka programowania mozna podzielic na kilka grup
rózniacych sie przeznaczeniem. Takie niby - słowa czasem nazywa
sie równiez tokenami lub JEDNOSTKAMI LEKSYKALNYMI (leksykon -
inaczej słownik) a sposoby tworzenia wyrazen (expressions)
nazywane sa syntaktyka jezyka (stad bierze sie typowy komunikat
o błedach "Syntax Error" - bład syntaktyczny, czyli niewłasciwa
składnia). Słownik jezyka C++ składa sie z:

* Słów kluczowych
* Identyfikatorów
* Stałych liczbowych i znakowych
* Stałych tekstowych (łancuchów znaków - napisów)
* Operatorów (umownych znaków operacji)
* Znaków interpunkcyjnych
* Odstepów

UWAGA: Zarówno pojedyncza spacja czy ciag spacji, tabulator
poziomy, znak nowej linii, jak i komentarz dowolnej długosci (!)
sa traktowane przez kompilator jak pojedyncza spacja.
Od zarania dziejów informatyki twórcy uniwersalnych jezyków
programowania starali sie upodobnic słowa tych jezyków do
zrozumiałych dla człowieka słów jezyka naturalnego - niestety -
angielskiego (swoja droga, moze to i lepiej, ze C++ nie
wymyslili Japonczycy...). Najwazniejsza czescia słownika sa tzw.

SŁOWA KLUCZOWE (keywords).

21
SŁOWA KLUCZOWE w C++.

Oto pełna lista słów kluczowych Turbo C++ v 1.0 z krótkim
wyjasnieniem ich znaczenia. Zaczynam od listy podstawowej wersji
kompilatora, poniewaz rozwazania o niuansach dotyczacych kilku
specyficznych słów kluczowych (np. friend, template) pozostawiam
sobie na pózniej. Krótkie wyjasnienie - jak to krótkie
wyjasnienie - pewnie nie wyjasni wszystkiego od razu, ale na
pewno pomoze zrozumiec znaczenie wiekszosci słów kluczowych.

[S] Keywords - słowa kluczowe.

asm
Pozwala wstawic kod w ASEMBLERZE bezposrednio do programu
napisanego w C lub C++.

auto - zmienna lokalna. Przyjmowane domyslnie.

break - przerwij.

case - w przypadku.

cdecl - spec. konwencja nazewnictwa/przekazania parametrów
zgodna ze standardem jez. C.

char - znak, typ zmiennej - pojedynczy bajt.

class - klasa.

const - stała, konstanta.

continue - kontynuuj.

default - przyjmij domyslnie.

delete - skasuj obiekt.

do - wykonaj.

double - podwójna (długosc/precyzja).

else - w przeciwnym wypadku.

enum - wylicz kolejno.

_export - dotyczy tylko OS/2, ignorowany.

extern - zewnetrzna.

far - dalekie. Wskaznik - podwójne słowo (w zakresie do 1 MB).

float - zmiennoprzecinkowy, rzeczywisty.

for - dla (wskazanie zmiennej roboczej w petli).

friend - zaprzyjazniona funkcja z dostepem do prywatnych i
ttttttttttchronionych członków danej klasy.

goto - skocz do (skok bezwarunkowy).

huge - daleki, podobnie do far.

if - jezeli (pod warunkiem, ze...).

inline - funkcja z rozwinietym wstawionym kodem

int - typ zmiennej, liczba całkowita, dwa bajty

interrupt - przerwanie.

_loadds - podobne do huge, ustawia rejestr DS (Data Segment).

long - długi.

near - bliski, wskaznik o dł. 1 słowa. Obszar max. 64 K.

new - nowy, utwórz nowy obiekt.

operator - operator, okresla nowy sposób działania operatora.

pascal - deklar. funkcji zgodnej ze standardem przekazywania
parametrów przyjetym w Pascalu.

private - prywatna, wewnetrzna, niedostepna z zewnatrz.

protected - chroniona, czesc danych i funkcji, do których
dostep. jest ograniczony.

public - publiczna, dostepna z zewnatrz.

register - zmienna przechwaj nie w pamieci a w rejestrze CPU.

return - powrót, zwrot wartosci.

_saveregs - save registers, zachowaj zawartosc rejestrów a
nastepnie odtwórz rejestry przed powrotem.

_seg - segment.

short - krótka (mała ilosc cyfr).

signed - ze znakiem (+/-).

unsigned - bez znaku (+/-).

sizeof - podaj wielkosc.

static - statyczna.

struct - struktura.

switch - przełacz.

this - ten, wstazanie biezacego, własnego obiektu (tylko C++).

typedef - definicja typu.

union - unia, zmienna wariantowa.

virtual - wirtualna, pozorna.

void - nieokreslona.

volatile - ulotna.

while - dopóki.


Panuje mnienanie, ze jezyk C++ posługuje sie stosunkowo skromnym
zestawem słów kluczowych. To prawda, ale nie cała prawda o
jezyku C++. Zauwazyłes zapewne, ze nie ma tu:

define, include, printf

i innych znanych Ci juz słów. To po prostu jeszcze nie cały
słownik jezyka. Zdajac sobie sprawe z nieprecyzyjnosci tego
porównania mozesz przyjac, ze to cos na kształt listy
czasowników. A sa przeciez jeszcze i inne słowa - o innej roli i
przeznaczeniu.

[???]tA GDZIE SIE PODZIAŁY REJESTRY ???

Nazwy rejestrów mikroprocesora Intel 80X86:

_AXttttttt_ALttttttt_AHttttttt_SIttttttt_CS
_BXttttttt_BLttttttt_BHttttttt_SPttttttt_DS
_CXttttttt_CLttttttt_CHttttttt_BPttttttt_ES
_DXttttttt_DLttttttt_DHttttttt_DIttttttt_SS
_FLAGS

Takie oznaczenia wynikaja z architektury konkretnej rodziny
mikroprocesorów, nie moga stanowic uniwersalnego standardu
jezyka C++. Efekt dostosowania C++ do IBM PC to np. odnoszace
sie do modeli pamieci słowa kluczowe near, far i huge.
Wymóg zgodnosci ze standardem ANSI C spowodował, ze w C++ nazwy
rejestrów pozostaja nazwami o zastrzezonym znaczeniu, ale
nazywaja sie PSEUDOZMIENNYMI REJESTROWYMI (ang.: Register
Pseudovariables).

Próba uzycia słowa o zastrzezonym znaczeniu w jakiejkolwiek
innej roli (np. jako nazwa Twojej zmiennej) moze spowodowac
wadliwe działanie programu lub uniemozliwic kompilacje. Unikaj
przypadkowego zastosowania słów o zastrzezonym znaczeniu!

[???] A SKAD MAM WIEDZIEC ?

22
Liste nazw, które maja juz nadane scisle okreslone znaczenie w
C++ znajdziesz w Help. Dostep do spisu uzyskasz przez:

* Rozwiniecie menu Help [Alt]-[H];
* Wybranie z menu Help rozkazu Index (spis).

Wrócic do edytora IDE C++ mozesz przez [Esc].

SŁOWA TYPOWE DLA PROGRAMÓW OBIEKTOWYCH.

W porównaniu z klasycznym jezykiem C (wobec którego C++ jest
nadzbiorem - ang. superset), w nowoczesnych programach
obiektowych i zdarzeniowych pisanych w C++ moga pojawiac sie i
inne słowa. Przyjrzyjmy sie na troche inna technike
programowania - bardziej charakterystyczna dla C++.

Procesy wprowadzania i wyprowadzania danych do- i z- komputera
nazywaja sie Input i Output - w skrócie I/O (lub bardziej
swojsko We/Wy). Obsługa We/Wy komputera to sała obszerna wiedza,
na poczatek bedzie nam jednak potrzebne tylko kilka najbardziej
istotnych informacji.

PROBLEM ˙WEJSCIA/WYJSCIA W PROGRAMACH - troche bardziej
ogólnie.

Operacje wejscia i wyjscia sa zwykle kontrolowane przez
pracujacy własnie program. Jesli uruchomiłes program, który nie
korzysta z klawiatury i nie oczekuje na wprowadzenie przez
uzytkownika zadnych informacji - mozesz naciskac dowolne
klawisze - program i tak ma to w nosie. Podobnie, jesli w
programie nie przewidziano wykorzystania drukarki, chocbys
"wyłaził ze skóry", zadne informacje nie zostana przesłane do
drukarki, dla programu i dla uzytkownika drukarka pozostanie
niedostepna. Aby programy mogły zapanowac nad Wejsciem i
Wyjsciem informacji, wszystkie jezyki programowania musza
zawierac specjalne rozkazy przeznaczone do obsługi
Wejscia/Wyjscia (ang. Input/Output commands, lub I/O
instructions). Bez umiejetnosci obsługi We/Wy, czyli bez
mozliwosci porozumiewania sie ze swiatem zewnetrznym psu na bude
zdałby sie kazdy jezyk programowania. Kazdy program musi w
wiekszym, badz mniejszym stopniu pobierac informacje ze swiata
zewnetrznego do komputera i wysyłac informacje z komputera na
zewnatrz.

Podobnie, jak wszystkie uniwersalne jezyki programowania - jezyk
C++ zawiera pewna ilosc rozkazów przeznaczonych do zarzadzania
obsługa wejscia i wyjscia. Dla przykładu, mozemy w jezyku C++
zastosowac OBIEKT cout obsługujacy strumien danych wyjsciowych.
Obiekt cout (skonstruowany przez producenta i zdefiniowany w
pliku nagłówkowym IOSTREAM.H) pozwala programiscie
przesłac dane tekstowe i/lub numeryczne do strumienia wyjsciwego
i umiescic tekst na ekranie monitora.

Wczytaj plik zródłowy z programem COUT1.CPP lub wpisz
samodzielnie nastepujacy program przykładowy. Program drukuje
tekst na ekranie monitora.

[P009.CPP]

#include <iostream.h> <-- zwróc uwage na inny, nowy plik
#include <conio.h>

void main(void)
{
clrscr();
cout << "Stosujemy obiekt cout:\n";
cout << "Tekst pierwszy\n";
cout << "Tekst drugi...\n";
getch();
}

Jak widzisz, kazdy rozkaz z uzyciem obiektu cout tworzy
pojedyncza linie tekstu (wiersz) na ekranie monitora. Kompilator
jezyka C++ wie, ze chcesz wysłac tekst na ekran monitora dzieki
słowu cout i znakowi << (znak << to tzw. operator przesyłania do
strumienia). Wysłany na ekran zostaje tekst umieszczony po
operatorze << i (obowiazkowo, podobnie jak w funkcji printf())
ujety w cudzysłów ("). Tekst ujety w cudzysłów nazywa sie
łancuchem znakowym (ang. string literal).

[S] String literal - łancuch znaków.

Łancuch znaków to grupa znaków alfanumerycznych (tekstowych).
Łancuch znaków to taki ciag znaków, który komputer moze
rozpatrywac wyłacznie jako całosc i posługiwac sie nim tylko
tak, jak go wpisałes. Aby komputer poprawnie rozpoznawał
łancuchy tekstowe - nalezy ujmowac je w cudzysłów. Łancuch
znaków moze byc nazywany równiez literałem, badz literałem
łancuchowym.

[!!!] Dla dociekliwych - jak C++ zapamietuje tekst?

Pojedyncze znaki mozna zapisywac w C++ tak:

'A' - pojedynczy znak reprezentowany w pamieci komutera jako
jeden bajt zawierajacy liczbe - numer litery A według kodu
ASCII. W tym przypadku byłaby to liczba 65 (dwójkowo i
szesnastkowo- odpowiednio: 0100 0001 i 41).
"A" - jednoelementowy łancuch znaków zajmujacy w pamieci dwa
bajty (kod litery A i znak konca łancucha - \0). Reprezentacja w

pamieci wygladałaby tak:

Bajt Nr X 0100 0001 - kod ASCII litery A
Bajt Nr X+1 0000 0000 - kod ASCII 0 - znak konca


Wiesz juz, ze clrscr(); stanowi wywołanie gotowej funkcji (tzw.
funkcji bibliotecznej). Informacja dotyczaca tej funkcji (tzw.
prototyp funkcji) znajduje sie w pliku CONIO.H, dlatego
dołaczylismy ten plik nagłówkowy na poczatku programu dyrektywa
#include. A cóz to za dziwolag ten "cout" ?
Po cout nie ma pary nawiasów okragłych (gdyby to była
funkcja - powinno byc cout()) - nie jest to zatem wywołanie
funkcji. ˙Strumien danych wyjsciowych cout - JEST OBIEKTEM (ang.
I/O stream object - obiekt: strumien Wejscia/Wyjscia). Ale nie
przestrasz sie. Popularne wyobrazenie, ze programowanie
obiektowe jest czyms bardzo skomplikowanym nie ma z prawda
wiecej wspólnego, niz powszechny dosc poglad, ze baba z pustym
wiadrem jest gorsza od czarnego kota. W gruncie rzeczy jest
to proste. Strumien to nic innego jak zwyczajny przepływ
informacji od jednego urzadzenia do innego. W tym przypadku
strumien (przepływ) danych oznacza przesłanie informacji
(tekstu) z pamieci komputera na ekran monitora. Trójkatne
nawiasy (<< lub >>) wskazuja kierunek przepływu informacji.
Przesyłanie nastepuje w naszym przypadku z pamieci do strumienia

Pojawiło sie tu wazne słowo - OBIEKT. Obiekt podobnie jak
program komputerowy jest to grupa danych i funkcji działajacych
wspólnie i przeznaczonych razem do wykonania jakichs zadan. Dla
przykładu obiekt cout słuzy do obsługi przesyłania danych na
ekran monitora. Słowo "obiekt" jest czesto uzywane w opisach
nowoczesnych technik programowania - tzw. PROGRAMOWANIA
OBIEKTOWEGO. Programowanie obiektowe, ta "wyzsza szkoła jazdy"
dla programistów z lat 80-tych jest juz własciwie w naszych
czasach norma. Zreszta widzisz sam - napisałes program obiektowy
i co - i nic strasznego sie nie stało. Na poczatek musisz
wiedziec tylko tyle, ze aby posługiwac sie obiektami -
strumieniami wejscie i wyjscia - nalezy dołaczyc w C++ plik
nagłówkowy IOSTREAM.H. Dlatego dyrektywa #include <iostream.h>
znajduje sie na poczatku przykładowego programu.

KILKA ARGUMENTÓW FUNKCJI w praktyce.

Jak starałem sie wykazac w przykładzie z sinusem, funkcja moze
otrzymac jako argument stała - np. okreslona liczbe, badz
zmienna (niewiadoma). Niektóre funkcje moga otrzymywac w
momencie ich wywołania (uzycia w programie) wiecej niz jeden
argument. Rozwazmy to dokładniej na przykładzie funkcji
fprintf() zblizonej w działaniu do printf(), lecz bardziej
uniwersalnej. Funkcja fprintf() pozwala wyprowadzac dane nie
tylko na monitor, ale takze na drukarke. Skoro urzadzenia
wyjscia moga byc rózne, trzeba funkcji przekazac jako jeden z
jej argumentów informacje o tym - na które urzadzenie zyczymy
sobie w danej chwili wyprowadzac dane.

Słowo stdout jest pierwsza informacje (tzw. parametrem, badz
argumentem funkcji) przekazanym do funkcji fprintf(). Słowo
stdout jest skrótem od Standard Output - standardowe wyjscie.
Oznacza to w skrócie typowe urzadzenie wyjsciowe podłaczone do
komputera ˙i umozliwiajace wyprowadzenie informacji z komputera.

W komputerach osobistych zgodnych ze standardem IBM PC tym
typowym urzadzeniem wyjsciowym jest prawie zawsze ekran
monitora.

23
Tekst, który ma zostac wydrukowany na ekranie monitora jest
druga informacja przekazywana do funkcji fprintf() - inaczej -
stanowi drugi parametr funkcji. Tekst - łancuch znaków - musi
zostac ujety w znaki cudzysłowu.

A jesli zechcesz wyprowadzic tekst na drukarke?
W C++ zapisuje sie to bardzo łatwo. Wystarczy słowo stdout
(oznaczajace monitor) zamienic na słowo stdprn. Słowo stdprn to
skrót od Standard Printer Device - standardowa drukarka. Oto
przykład praktycznego uzycia funkcji fprintf(). Program przesyła
tekst na drukarke. Przed uruchomieniem programu pamietaj o
właczeniu drukarki.

[P010.CPP]

#include <stdio.h>
#include <conio.h>

int main(void)
{
clrscr();
fprintf(stdout, "Drukuje...\n");
fprintf(stdprn, "Pierwsza proba drukowania\n");
fprintf(stdprn, "Autor: ....................");
fprintf(stdout, "Koniec drukowania.");
fprintf(stdout, "Skonczylem, nacisnij cosik...");
getch();
return 0;
}

Gdyby w programie nie było wiersza:

fprintf(stdout, "Drukuje...\n");

- uzytkownik przez pewien czas nie mógłby sie zorientowac,
czym ˙własciwie zajmuje sie komputer. Wszystko stałoby sie jasne

dopiero wtedy, gdy drukarka rozpoczełaby drukowanie tekstów.
Jest uznawane za dobre maniery praktyczne stosowanie dwóch
prostych zasad:

BZU - Bez Zbednych Udziwnien
DONU - Dbaj O Nerwy Uzytkownika

Jesli efekty działania programu nie sa natychmiast zauwazalne,
nalezy poinformowac uzytkownika CO PROGRAM ROBI. Jesli
uzytkownik odnosi wrazenie, ze komputer nic nie robi - ma zaraz
watpliwosci. Czesto próbuje wtedy wykonac reset komputera i
wypowiada mnóstwo słów, których nie wypada mi tu zacytowac.

Nietrudno zgadnac, ze C++ powinien posiadac takze srodki obsługi

wejscia. W C++ jest specjalny obiekt (ang. input stream object)
o nazwie cin słuzacy do pobierania od uzytkownika tekstów i
liczb. Zanim zajmiemy sie dokładniej obiektem cin i obsługa
strumienia danych wejsciowych - powinienes zapoznac sie ze
ZMIENNYMI (ang. variables).

ZMIENNE.

Gdy wprowadzisz jakies informacje do komputera - komputer
umieszcza je i przechowuje w swojej pamieci (ang. memory -
pamiec). Pamiec komputera moze byc jego pamiecia stała. Taka
pamiec "tylko do odczytu" nazywa sie ROM (read only memory - to
własnie "tylko do odczytu"). Pamiec o swobodnym dostepie, do
której i komputer i Ty mozecie zapisywac wszystko, co Wam sie
spodoba - nazywa sie RAM (od Random Access Memory - pamiec o
swobodnym dostepie). Pamieci ROM i RAM podzielone sa na małe
"komóreczki" nazywane Bajtami, Kazdy bajt w pamieci ma swój
numer. Ten numer nazywany jest adresem w pamieci. Poniewaz nie
wszystko da sie pomiescic w jednym bajcie (to tylko 8 bitów -
miejsca wystarczy na zapamietanie tylko jednej litery), bajty
(zwykle kolejne) moga byc łaczone w wieksze komórki - tzw. pola
pamieci (ang. memory fields). Najczesciej łaczy sie bajty:

2 Bajty = 16 bitów = Słowo (WORD)
4 Bajty = 32 bity = Podwójne słowo (DOUBLE WORD - DWORD)

W uproszczeniu mozesz wyobrazic sobie pamiec komputera jako
miliony pojedynczych komórek, a w kazdej z komórek jakas jedna
wartosc (ang. value) zakodowana w postaci ZER i JEDYNEK. Kazda
taka "szara" komórka ma numer-adres. Numeracja komórek
rozpoczyna sie nie od 1 lecz od zera (pierwsza ma numer 0).
Ilosc tych komórek w Twoim komputerze zalezy od tego ile pamieci

zainstalujesz (np. 4MB RAM to 4x1024x124x8 bitów - chcesz -
policz sam ile to bitów). Przeliczajac zwróc uwage, ze kilobajt
(KB to nie 1000 - lecz 1024 bajty a megabajt - 1024 kB).

Zastanówmy sie, skad program moze wiedziec gdzie, w której
komórce zostały umieszczone dane i jak sie do nich dobrac, gdy
stana sie potrzebne. Własnie do takich celów potrzebne sa
programowi ZMIENNE (ang. variables).

Dawno, dawno temu rozwiazywałes zapewne zadania typu:

3 + [ ] = 5

Otóz to [ ] było pierwszym sposobem przedstawienia Ci zmiennej.
Jak widac - zmienna to miejsce na wpisanie jakiejs (czasem
nieznanej w danej chwili wartosci). Gdy przeszedłes do nastepnej

klasy, zadania skomplikowały sie:

3 + [ ] = 5
147.968 + [ ] = 123876.99875

Na rózne zmienne moze byc potrzeba rózna ilosc miejsca i na
kartce i w pamieci komputera. Gdy "zestarzałes sie" jeszcze
troche - te same zadania zaczeto Ci zapisywac tak:

3 + x = 5
147.968 + y = 123876.99875

Jak widac, zmienne moga posiadac takze swoje nazwy -
identyfikatory (z których juz niestety nie wynika jasno, ile
miejsca potrzeba do zapisania biezacej wartosci zmiennej).


[???] Jak C++ wskazuje adres w pamieci?

Podobnie, jak w bajeczce o zabawie w chowanego kotka i myszki
(myszka mówiła: "Gdybys mnie długo nie mógł znalesc - bede
czekac na czwartej półce od góry..."), niektórzy producenci gier

komputerowych zycza sobie czasem przy uruchamianiu gry podania
hasła umieszczonego:
"W instrukcji na str. 124 w czwartym wierszu do góry"
No cóz. Zamiast nazywac zmienne - niewiadome x, y, czy z, badz
rezerwowac dla nich puste miejsce [ ], mozemy jeszcze
wskazac miejsce, w którym nalezy ich szukac. Takie wskazanie to
trzeci sposób odwoływania sie do danych. W C++ moze sie to
nazywac referencja do zmiennej lub wskazaniem adresu zmiennej w
pamieci przy pomocy wskaznika. Wskaznik w C++ nazywa sie
"pointerem". Pointerem mozna wskazac takze funkcje - podajac ich
adres startowy (poczatek kodu funkcji w pamieci RAM).


Zmienne mozesz sobie wyobrazic jako przegródki w pamieci
komputera zaopatrzone w nazwe - etykietke. Poniewaz nazwy dla
tych przegródek nadaje programista w programie - czyli Ty sam,
mozesz wybrac sobie prawie kazda, dowolna nazwe. Zwykle nazwy
nadaje sie w taki sposób, by program stał sie bardziej czytelny
i łatwiejszy do zrozumienia. Dla przykładu, by nie przepadły z
pamieci komputera wyniki gier komputerowych czesto stosuje sie
zmienna o nazwie WYNIK (ang. Score). Za kazdym razem, gdy
zmienia sie wynik gracza (ang. player's score) w pamieci
komputera (w to samo miejsce) zostaje zapisana nowa liczba. W
taki sposób pewien niewielki (a zawsze ten sam) fragment pamieci
komputera przechowuje dane potrzebne do pracy programu.

PRZYPISYWANIE ZMIENNYM KONKRETNEJ WARTOSCI.

Aby komputer mogł pobrac informacje od uzytkownika, mozesz
zastosowac w programie np. obiekt - strumien wejsciowy - cin
(ang. input stream object). Obiekt cin i zmienne chodza zwykle
parami. Przy obiekcie cin musisz zawsze podac operator
pobierania ze strumienia wejsciowego >> i nazwe zmiennej. Zapis

cin >> nazwa_zmiennej;

oznacza ˙w C++ : pobierz dane ze strumienia wejsciowego i umiesc
w zmiennej o nazwie "nazwa_zmiennej".Te informacje, które
zostana ˙wczytane, C++ przechowuje w przgródce oznaczonej nazwa,
która nadajesz zmiennej. Oto program przykładowy ilustrujacy
zapamietywanie danych wprowadzonych przez uzytkownika z
24
klawiatury, wczytanych do programu przy pomocy obiektu cin i
zapamietanych w zadeklarowanej wczesniej zmiennej x:

[P011.CPP]

#include <iostream.h>
#include <conio.h>

void main(void)
{
int x;
cout << "Podaj liczbe calkowita 0 - 1000 do zapamietania: ";
cin >> x;
cout << "Pamietam! ";
cout << "Wielokrotnosci liczby: \n":
cout << "x, 2x, 3x: " << x << " " << 2*x << " " << 3*x;
cout << "\n ...Nacisnij dowolny klawisz...";
getch();
}


Zapis cin >> x oznacza: "pobierz dane ze strumienia danych
wejsciowych i umiesc je w pamieci przeznaczonej dla zmiennej x".

x - to nazwa (identyfikator) zmiennej. Ta nazwa jest stosowana
przez komputer do identyfikacji przegródki w pamieci, w której
bedzie przechowywana liczba wpisana przez uzytkownika jako
odpowiedz na zadane pytanie. Kompilator C++ zarezerwuje dla
zmiennej x jakas komórke pamieci i umiesci tam wpisana przez
Ciebie liczbe. W trakcie pracy kompilator C++ tworzy dla
własnego uzytku tzw. tablice symboli, która posługuje sie do
rozmieszczania danych w pamieci. Jesli chcesz, mozesz sprawdzic
przy pomocy Debuggera (Debug | Inspect) w których bajtach RAM
C++ umiescił Twoja zmienna.

[???] Ile miejsca trzeba zarezerwowac?

To, ile miejsca trzeba zarezerwowac dla danej zmiennej
kompilator "wie" dzieki Twojej deklaracji, jakiego typu dane
beda przechowywane w miejscu przeznaczonym dla zmiennej. Dla
przykładu:
- jesli napiszesz int x;
Kompilatoer zarezerwuje 2 bajty
- jesli napiszesz float y;
Kompilatoer zarezerwuje 4 bajty
itp...(szczegóły - patrz nizej).

Zwykle nie musisz sie przejmowac tym, w którym miejscu
kompilator rozmiescił Twoje dane. Wszystkie czynnosci C++ wykona
automatycznie. Aby jednak wszystko przebiegało poprawnie - zanim
zastosujesz jakakolwiek zmienna w swoim programie - musisz
ZADEKLAROWAC ZMIENNA. Deklaracja zmiennej to informacja dla
kompilatora, ile i jakich zmiennych bedziemy stosowac w
programie. Deklaracja zawiera nie tylko nazwe zmiennej, ale
równiez typ wartosci, jakie ta zmienna moze przybierac.
Przykładem deklaracji jest wiersz:

int x;

Słowo kluczowe int okresla typ danych. Tu oznacza to, ze zmienna
x moze przechowywac jako wartosci liczby całkowite (ang. INTeger
- całkowity) o wielkosci zawartej w przedziale - 32768...+32767.
Po okresleniu typu danych nastepuje w deklaracji nazwa zmiennej
i srednik.

[S] Variable Declaration - Dekaracja Zmiennej.

Deklaracja zmiennej w C++ to okreslenie typu wartosci zmiennej i
podanie nazwy zmiennej.


Zwróc uwage w przykładowym programie, ze kierujac kolejno dane
do strumienia wyjsciwego cout mozemy je poustawiac w tzw.
łancuch (ang. chain). Przesyłanie danych do obiektu cout
operatorem << jest bardzo elastyczne. Wysyłamy na ekran zarówno
tekst jak i liczbe - biezaca wartosc zmiennej x oraz wyniki
obliczenia wartosci wyrazen ( 2*x i 3*x). Posługujac sie
łaczonym w "łancuch" operatorem << mozna wyprowadzac na ekran
wiersz zbudowany z róznych elementów. Operator przesyłania
danych do strumienia wyjsciowego << (ang. insertor - dosł. -
operator wstawiania) powoduje przesłanie do obiektu cout kolejno
wszystkich (róznego typu) elementów. Zwróc uwage na uzycie znaku
\n na poczatku nowego wiersza, na koncu wiersza tekstu (mozna go
zastosowac nawet w srodku wiersza tekstu - sprawdz).
Zwróc uwage w jaki sposób C++ rozpoznaje róznice pomiedzy:

- łancuchem znaków - napisem (napis powinien byc podany tak):
cout << "x, 2x, 3x";
- wartoscia zmiennej:
cout << x;

Widac tu wyraznie, dlaczego znak cudzysłowu jest dla kompilatora
istotny. Jesli pominiemy cudzysłów, C++ bedzie próbował
zinterpretowac litere (tekst) jako nazwe zmiennej a nie jako
napis.

RODZAJE ZMIENNYCH: ZMIENNE NUMERYCZNE I ZMIENNE
TEKSTOWE.

Zmienne moga w C++ byc bardzo elastyczne. Dokładnie rzecz
biorac, zmienne moga byc:

RÓZNYCH TYPÓW - moga byc liczbami, moga takze byc tekstami.

Uruchom program jeszcze raz i zamiast liczby nacisnij w
odpowiedzi na pytanie klawisz z litera. Program wydrukuje jakies
bzdury. Dzieje sie tak dlatago, ze program oczekuje podania
liczby i zakłada, ze wprowadzone przez uzytkownika dane sa
liczba.

[???] A jesli uzytkownik nie czyta uwaznie???

C++ zakłada, ze uzytkownik wie co robi gdy podaje wartosc
zmiennej. Jesli wprowadzone zostana dane niewłasciwego typu -
C++ nie przerywa działania programu i nie ostrzega przed
niebezpieczenstwem błedu. Sam dokonuje tzw. konwersji typów -
tzn. przekształca dane na wartosc typu zgodnego z zadeklarowanym
w programie typem zmiennej. To programista musi dopilnowac, by
pobrane od uzytkownika dane okazały sie wartoscia odpowiedniego,
oczekiwanego przez program typu, lub przewidziec w programie
sposób obsługi sytuacji błednych.


Mozna utworzyc zmienna przeznaczona do przechowywania w pamieci
tekstu - napisu. Aby to zrobic musimy zadeklarowac cos
jakosciowo nowego tzw. TABLICE ZNAKOWA (ang. character array).
Jest to nazwa, przy pomocy której komputer lokalizuje w pamieci
zbiór znaków. Aby zadeklarowac zmienna (tablice) znakowa w C++
musimy zaczac od słowa kluczowego char (ang. CHARacter - znak).
Nastepnie podajemy nazwe zmiennej a po nazwie w nawiasach
kwadratowych ilosc znaków, z których moze składac sie zmienny
tekst, który zamierzamy przechowywac w pamieci pod ta nazwa.

W programie ponizej zmienna x nie jest juz miejscem w pamieci
słuzacym do przechowywania pojedynczej liczby. Tym razem nazwa
(identyfikator zmiennej) x oznacza tablice znakowa, w której
mozna przechowywac tekst o długosci do 20 znaków. W C++ ostatnim
znakiem w łancuchu znakowym (tekscie) badz w tablicy znakowej
zwykle jest tzw. NULL CHARACTER - niewidoczny znak o kodzie
ASCII 0 (zero). W C++ znak ten podaje sie przy pomocy szyfru
'\0'. Przy pomocy tego znaku C++ odnajduje koniec tekstu,
łancucha znaków, badz koniec tablicy znakowej. Tak wiec w
tablicy x[20] w rzeczywistosci mozna przechowac najwyzej 19
dowolnych znaków plus na koncu obowiazkowy NULL (wartownik).

[P012.CPP]

#include <conio.h>
#include <iostream.h>

void main(void)
{
char x[20]; //<---- deklaracja tablicy znakowej.

clrscr();
cout << "Podaj mi swoje imie: : ";
cin >> x;
cout << "\nNazywasz sie " << x << ", ladne imie!\n";
cout << "...Nacisnij dowolny klawisz...";
getch();
}

[Z]

1. Spróbuj w przykładowych programach z poprzednich lekcji
zastapic funkcje obiektami - strumieniami We/Wy:
25

printf() - cout <<
scanf() - cin >>

2. Spróbuj napisac program zawierajacy i funkcje i obiekty. Czy
program pracuje bezkonfliktowo? Pamietaj o dołaczeniu
odpowiednich plików nagłówkowych.



LEKCJA 9: O SPOSOBACH ODWOŁYWANIA SIE DO DANYCH.
________________________________________________________________
W trakcie tej lekcji poznasz:
* sposoby wyprowadzania napisów w róznych kolorach
* sposoby zapamietywania tekstów
* sposoby odwoływania sie do danyc i zmiennych przy pomocy ich
nazw - identyfikatorów.
________________________________________________________________

Mozemy teraz poswiecic chwile na zagadnienie kolorów, które
pojawiaja sie na monitorze. Po uruchomieniu program przykładowy
ponizej wygeneruje krótki dzwiek i zapyta o imie. Po wpisaniu
imienia program zapiszczy jeszcze raz i zapyta o nazwisko. Po
wpisaniu nazwiska program zmieni kolor na ekranie monitora i
wypisze komunikat kolorowymi literami. Rózne kolory zobaczysz
oczywiscie tylko wtedy, gdy masz kolorowy monitor. Dla
popularnego zestawu VGA mono beda to rózne odcienie szarosci.
Tekst powinien zmieniac kolor i "migac" (ang. - blinking text).

[P012.CPP]

#include <conio.h>
#include <iostream.h>

main()
{
char imie[20];
char nazwisko[20];

clrscr();
cout << "\aPodaj imie: ";
cin >> imie;
cout << "\aPodaj nazwisko: ";
cin >> nazwisko;
cout << '\n' << imie << ' ' << nazwisko << '\n';
textcolor(4+128);
cprintf("\nPan(i), %s %s? Bardzo mi milo!", imie, nazwisko);
getch();
cout << '\a';
return 0;
}

Wyjasnijmy kilka szczegółów technicznych:

cout << "\aPodaj nazwisko? ";
/* \a to kod pisku głosniczka (beep) */

cin >> nazwisko;
textcolor(4+128); <---- funkcja zmienia kolor tekstu

cprintf("\nPan(i), %s %s? Bardzo mi milo!", imie, nazwisko);
___ tu funkcja wstawi "string" nazwisko
| |________ a tu wstawi "string" imie
|_________ funkcja wyprowadza tekst na ekran w kolorach
(cprintf = Color PRINTing Function)


Operator >> pobiera ze strumienia danych wejsciowych cin wpisane

przez Ciebie imie i zapisuje ten tekst do tablicy znakowej
imie[20]. Po wypisaniu na ekranie nastepnego pytania nastepuje
pobranie drugiego łancucha znaków (ang. string) wpisanego przez
Ciebie jako odpowiedz na pytanie o nazwisko i umieszczenie tego
łancucha w tablicy znakowej nazwisko[]. Wywołana nastepnie
funkcja textcolor() powoduje zmiane roboczego koloru
wyprowadzanego tekstu. Tekst nie tylko zmieni kolor, lecz takze
bedzie "migac" (blink). Funkcja cprintf() wyprowadza na ekran
koncowy napis. Funkcja cprintf() to Color PRINTing Function -
funkcja drukowania w kolorze.

Funkcja textcolor() pozwala na zmiane koloru tekstu
wyprowadzanego na monitor. Mozna przy pomocy tej funkcji takze
"zmusic" tekst do migotania. Aby funkcja zadziałała - musimy
przekazac jej ARGUMENT. Argument funkcji to numer koloru. Zwróc
jednak uwage, ze zamiast prostego, zrozumiałego zapisu:

textcolor(4); /* 4 oznacza kolor czerwony */

mamy w programie podany argument w postaci wyrazenia (sumy dwu
liczb):

textcolor(4+128); // to samo, co: textcolor(132);

Wbrew pierwszemu mylnemu wrazeniu te dwie liczby stanowia jeden
argument funkcji. C++ najpierw dokona dodawania 4+128 a dopiero
uzyskany wynik 132 przekaze funkcji textcolor jako jej argument
(parametr). Liczba 4 to kod koloru czerwonego, a zwiekszenie
kodu koloru o 128 powoduje, ze tekst bedzie migał.

Numery (kody) kolorów, które mozesz przekazac jako argumenty
funkcji textcolor() podano w tabeli ponizej. Jesli tekst ma
migac - nalezy dodac 128 do numeru odpowiedniego koloru.

Kod koloru przekazywany do funkcji textcolor().
________________________________________________________________

Kod Kolor (ang) Kolor (pol) Stała
n (przykład)
________________________________________________________________

0 Black Czarny BLACK
1 Blue Niebieski BLUE
2 Green Zielony GREEN
3 Cyan Morski CYAN
4 Red Czerwony
5 Magenta Fioletowy
6 Brown Brazowy
7 White Biały
8 Gray Szary
9 Light blue Jasno niebieski
10 Light green Jasno zielony
11 Light cyan Morski - jasny
12 Light red Jasno czerwony
13 Light magenta Jasno fio;etowy (fiol-rózowy)
14 Yellow Zółty
15 Bright white Biały rozjasniony
128 + n Blinking Migajacy BLINK
________________________________________________________________


[!!!]UWAGA:
________________________________________________________________
* W pliku CONIO.H sa predefiniowane stałe (skrajna prawa kolumna

- przykłady), które mozesz stosowac jako argumenty funkcji.
Kolor tła mozesz ustawic np. przy pomocy funkcji
textbackground() - np. textbacground(RED);

* Manipulujac kolorem tekstu musisz pamietac, ze jesli kolor
napisu:
- foreground color, text color
i kolor tła:
- background color
okaza sie identyczne - tekst zrobi sie NIEWIDOCZNY. Jesli kazesz

komputerowi pisac czerwonymi literami na czerwonym tle -
komputer wykona rozkaz. Jednakze wiekszosc ludzi ma kłopoty z
odczytywaniem czarnego tekstu na czarnym tle. Jest to jednak
metoda stosowana czasem w praktyce programowania do kasowania
tekstów i elementów graficznych na ekranie.
________________________________________________________________


Powołujac sie na nasze wczesniejsze porównanie (NIE TRAKTUJ GO
ZBYT DOSŁOWNIE!),zajmiemy sie teraz czyms, co troche przypomina
rzeczowniki w normalnym jezyku.

O IDENTYFIKATORACH - DOKŁADNIEJ.

Identyfikatorami (nazwami) moga byc słowa, a dokładniej ciagi
liter, cyfr i znaków podkreslenia rozpoczynajace sie od litery
lub znaku podkreslenia (_). Za wyjatkiem słów kluczowych, (które

to słowa kluczowe - MUSZA ZAWSZE BYC PISANE MAŁYMI LITERAMI)
mozna stosowac i małe i duze litery. Litery duze i małe sa
rozrózniane. Przykład:
26

[P013.CPP]

#include <stdio.h>
#include <conio.h>

float PI = 3.14159; <-- stała PI
float r; <-- zmienna r

int main(void)
{
clrscr();
printf("Podaj promien ?\n");
scanf("%f", &r);
printf("\nPole wynosi P = %f", PI*r*r );
getch();
return 0;
}

* Uzyte w programie słowa kluczowe:
int, float, void, return.

* Identyfikatory
- nazwy funkcji (zastrzezone):
main, printf, scanf, getch, clrscr.
- nazwy zmiennych (dowolne):
PI, r.

* Dyrektywy preprocesora:
# include

Zwróc uwage, ze w wierszu:
float PI = 3.14159;
nie tylko DEKLARUJEMY, zmienna PI jako zmiennoprzecinkowa, ale
takze od razu nadajemy liczbie PI jej wartosc. Jest to tzw.
ZAINICJOWANIE zmiennej.

[Z]
________________________________________________________________
1. Uruchom program przykładowy. Spróbuj zamienic identyfikator
zmiennej PI na pisane małymi literami pi. Powinien wystapic
bład.
________________________________________________________________

Dla porównania ten sam program w wersji obiektowo-strumieniowej:


[P013-1.CPP]

#include <stdio.h>
#include <conio.h>

const float PI = 3.14159; <-- stała PI
float r; <-- zmienna r

int main(void)
{
clrscr();
cout << "Podaj promien ?\n";
cin >> r;
cout << "\nPole wynosi P = " << PI*r*r;
getch();
return 0;
}

LITERAŁY.

Literałem nazywamy reprezentujacy dana NAPIS, na podstawie
którego mozna jednoznacznie zidentyfikowac dana, jej typ,
wartosc i inne atrybuty. W jezyku C++ literałami moga byc:
* łancuchy znaków - np. "Napis";
* pojedyncze znaki - np. 'X', '?';
* liczby - np. 255, 3.14

[!!!] Uwaga: BARDZO WAZNE !!!
________________________________________________________________
* Role przecinka dziesietnego spełnia kropka. Zapis Pi=3,14 jest

nieprawidłowy.
* Próba zastosowania przecinka w tej roli SPOWODUJE BŁEDY !
________________________________________________________________

Liczby całkowite moga byc:
* Dziesietne (przyjmowane domyslnie - default);
* Ósemkowe - zapisywane z zerem na poczatku:
017 = 1*8 + 7 = 15 (dziesietnie);
* Szesnastkowe - zapisywane z 0x na poczatku:
0x17 = 1*16 + 7 = 23 (dziesietnie);
0x100 = 16^2 + 0 + 0 = 256 .

Liczby rzeczywiste moga zawierac czesc ułamkowa lub byc zapisane

w postaci wykładniczej (ang. scientific format) z litera "e"
poprzedzajaca wykładnik potegi.

Przykład:

Zapis liczbyttttttttWartosc dziesietna

.0123ttttttttttttttt0.0123
123e4ttttttttttttttt123 * 10^4 = 1 230 000
1.23e3ttttttttttttttt1.23 * 10^3 = 1230
123e-4tttttttttttttt0.0123

Literały składajace sie z pojedynczych znaków maja jedna z
trzech postaci:

* 'z' - gdzie z oznacza znak "we własnej osobie";
* '\n' - symboliczne oznaczenie znaku specjalnego - np.
sterujacego - tu: znak nowej linii;
* '\13' - nr znaku w kodzie ASCII.

UWAGA:
'\24' - kod Ósemkowy ! (dziesietnie 20)
'\x24' - kod SZESNASTKOWY ! (dziesietnie 36)


[S]ttSLASH, BACKSLASH.
tttttKreska "/" nazywa sie SLASH (czyt. "slasz") - łamane,
ukosnik zwykły. Kreska "\" nazywa sie BACKSLASH (czyt.
"bekslasz") - ukosnik odwrotny.

Uzupełnimy teraz liste symboli znaków z poprzedniej lekcji.

Znak ttttÓSEMKOWOttASCII (10)tttttZNACZENIE
\attttttt'\7'tttttt7tttttttttttttt- sygn. dzwiekowy BEL
\nttttttt'\12'ttttt10ttttttttttttt- nowy wiersz LF
\tttttttt'\11'ttttt9tttttttttttttt- tabulacja pozioma HT
\vtttttt '\13'ttttt11ttttttttttttt- tabulacja pionowa VT
\bttttttt'\10'ttttt8tttttttttttttt- cofniecie kursora o 1 znak
\rttttttt'\15'ttttt13ttttttttttttt- powrót do poczatku linii CR
\fttttttt'\14'ttttt12ttttttttttttt- nowa strona (form feed) FF
\\ttttttt'\134'tttt92ttttttttttttt- poprostu znak backslash "\"
\'ttttttt'\47'ttttt39ttttttttttttt- apostrof "'"
\"ttttttt'\42'ttttt34ttttttttttttt- cudzysłów (")
\0ttttttt'\0'tttttt0tttttttttttttt- NULL (znak pusty)

Komputer przechowuje znak w swojej pamieci jako "krótka", bo
zajmujaca tylko jeden bajt liczbe całkowita (kod ASCII znaku).
Na tych liczbach wolno Ci wykonywac operacje arytmetyczne !
(Od czego mamy komputer?) Przekonaj sie o tym uruchamiajac
nastepujacy program.

[P014.CPP]

# include <stdio.h> //prototypy printf() i scanf()
# include <conio.h> //prototypy clrscr() i getch()
int liczba; //deklaracja zmiennej "liczba"

int main(void)
{
clrscr();
printf("Wydrukuje A jako \nLiteral znakowy:\tKod ASCII:\n");
printf("%c", 'A');
printf("\t\t\t\t%d", 'A');
printf("\nPodaj mi liczbe ? ");
scanf("%d", &liczba);
printf("\n%c\t\t\t\t%d\n", 'A'+liczba, 'A'+liczba);
scanf("%d", &liczba);
printf("\n%c\t\t\t\t%d", 'A'+liczba, 'A'+liczba);
getch();
return 0;
}

Uruchom program kilkakrotnie podajac rózne liczby całkowite z
zakresu od 1 do 100.
27

Przyjrzyj sie sposobowi formatowania wyjscia:
%c, %d, \t, \n

Jesli pamietasz, ze kody ASCII kolejnych liter A,B,C... i
kolejnych cyfr 1, 2, 3 sa kolejnymi liczbami, to zauwaz, ze
wyrazenia:

'5' + 1 = '6' oraz 'A' + 2 = 'C'
(czytaj: kod ASCII "5" + 1 = kod ASCII "6")
sa poprawne.

[!!!]Jak sprawdzic kod ASCII znaku?
________________________________________________________________
Mozna oczywscie nauczyc sie tabeli kodów ASCII na pamiec (dla
poczatkowych i najwazniejszych stronic kodowych - przede
wszystkom od 0 do 852). Dla hobbystów - stronica kodowa 1250 i
1252 tez czasem sie przydaje.
(to oczywiscie zart - autor nie zna ani jednego faceta o tak
genialnej pamieci)
Mozna skorzystac z edytora programu Norton Commander. W trybie
Edit [F4] po wskazaniu kursorem znaku w górnym wierszu po prawej

stronie zostanie wyswietlony jego kod ASCII.
________________________________________________________________

CZY PROGRAM NIE MÓGŁBY CHODZIC W KÓŁKO?

Twoja intuicja programisty z pewnoscia podpowiada Ci, ze gdyby
zmusic komputer do pracy w petli, to nie musiałbys przykładowych

programów uruchamiac wielokrotnie. Spróbujmy nakazac programowi
przykładowemu chodzic "w kółko". To proste - dodamy do programu:


* na koncu rozkaz skoku bezwarunkowego goto (idz do...),
* a zeby wiedział dokad ma sobie isc - na poczatku programu
zaznaczymy miejsce przy pomocy umownego znaku - ETYKIETY.

Zwróc uwage, ze piszac pliki wsadowe typu *.BAT w jezyku BPL
(Batch Programming Language - jezyk programowania wsadowego)
stawiasz dwukropek zawsze na poczatku etykiety:

:ETYKIETA (BPL)

a w jezyku C++ zawsze na koncu etykiety:

ETYKIETA: (C/C++)

Przystepujemy do opracowania programu.

[P015.CPP]

# include <stdio.h>

short int liczba;

int main(void)
{
clrscr();
printf("Wydrukuje A jako \nLiteral znakowy:\tKod ASCII:\n");
printf("%c", 'A');
printf("\t\t\t\t%d", 'A');
etykieta:
printf("\npodaj mi liczbe ? ");
scanf("%d", &liczba);
printf("\n%c\t\t\t\t%d\n", 'A'+liczba, 'A'+liczba);
goto etykieta;
return 0;
}

Skompiluj program do wersji *.EXE:
Compile | Make
(rozkazem Make EXE file z menu Compile). Musisz nacisnac
nastepujace klawisze:
[Alt]-[C], [M]. (lub [F9])

* Jesli wystapiły błedy, popraw i powtórz próbe kompilacji.
* Uruchom program [Alt]-[R], [R] (lub [Ctrl]-[F9]).
* Podaj kilka liczb: np. 1,2,5,7,8 itp.
* Przerwij działanie programu naciskajac kombinacje klawiszy
[Ctrl]+[Break] lub [Ctrl]+[C].
* Sprawdz, jaki jest katalog wyjsciowy kompilatora.
- Rozwin menu Options [Alt]-[O],
- Otwórz okienko Directories... [D],
- Sprawdz zawartosc okienka tekstowego Output Directory.
Teraz wiesz juz gdzie szukac swojego programu w wersji *.EXE.

- Uruchom program poza srodowiskiem IDE.
- Sprawdz reakcje programu na klawisze:
[Esc], [Ctrl]-[C], [Ctrl]-[Break].

Uruchom powtórnie kompilator C++ i załaduj program rozkazem:
BC A:\GOTOTEST.CPP

Wykonaj od nowa kompilacje programu [F9].

[???] ... is up to date...
________________________________________________________________
Jesli C++ nie zechce powtórzyc kompilacji i odpowie Ci:

ttttttttttttttttttttMaking
A:\GOTOTEST.CPP

tttttttttttttttis up to date

(Program w takiej wersji juz skompilowałem, wiecej nie bede!)

nie przejmuj sie. Dokonaj jakiejkolwiek pozornej zmiany w
programie (np. dodaj spacje lub pusty wiersz w dowolnym
miejscu). Takich pozornych zmian wystarczy by oszukac C++. C++
nie jest na tyle inteligentny, by rozrózniac zmiany rzeczywiste
w pliku zródłowym od pozornych.
________________________________________________________________


Powtórz kompilacje programu. Nie musisz uruchamiac programu.
Zwróc uwage tym razem na pojawiajace sie w okienku komunikatów
ostrzezenie:

Warning: A:\GOTOTEST.CPP 14: Unreachable code in function main.
(Uwaga: Kod programu zawiera takie rozkazy, które nigdy nie
zostana wykonane inaczej - "sa nieosiagalne").

O co chodzi? Przyjrzyj sie tekstowi programu. Nawet jesli po
rozkazie skoku bezwarunkowego:

goto etykieta;

dopiszesz jakikolwiek inny rozkaz, to program nigdy tego rozkazu

nie wykona. Własnie o to chodzi. Program nie moze nawet nigdy
wykonac rozkazu "return 0", który dodalismy "z przyzwyczajenia".

Petla programowa powinna byc wykonywana w nieskonczonosc. Taka
petla nazywa sie petla nieskonczona (ang. infinite loop).

Mimo to i w srodowisku IDE (typowy komunikat: User break) i w
srodowisku DOS te petle uda Ci sie przerwac.
Kto wobec tego przerwał działanie Twojego programu? Nieskonczona

petle programowa przerwał DOS. Program zwrócił sie do systemu
DOS, a konkretnie do którejs z DOS'owskich funkcji obsługi
WEJSCIA/WYJSCIA i to DOS wykrył, ze przycisnałes klawisze
[Ctrl]-[C] i przerwał obsługe Twojego programu. Nastepnie DOS
"wyrzucił" twój program z pamieci operacyjnej komputera i
zgłosił gotowosc do wykonania dalszych Twoich polecen - swoim
znakiem zachety C:\>_ lub A:\>_.

Spróbujmy wykonac taki sam "face lifting" i innych programów
przykładowych, dodajac do nich najprostsza petle. Zanim jednak
omówimy szczegóły techniczne petli programowych w C++ rozwazmy
prosty przykład. Wyobrazmy sobie, ze chcemy wydrukowac na
ekranie kolejne liczby całkowite od 2 do np. 10. Program
powinien zatem liczyc ilosc wykonanych petli, badz sprawdzac,
czy liczba przeznaczona do drukowania nie stała sie zbyt duza.

W C++ do takich konstrukcji uzywa sie kilku bardzo waznych słów
kluczowych:

[S] some important keywords - kilka waznych słów kluczowych
________________________________________________________________
for - dla (znaczenie jak w Pascalu i BASICu)
while - dopóki
do - wykonuj
if - jezeli
28
break - przerwij wykonywanie petli
continue - kontynuuj petelkowanie
goto - skocz do wskazanej etykiety
________________________________________________________________

Nasz program mógłby przy zastosowaniu tych słów zostac napisany
np. tak:

[LOOP-1]

#include <iostream.h>
void main()
{
int x = 2;
petla:
cout << x << '\n';
x = x + 1;
if (x < 11) goto petla;
}

Mozemy zastosowac rozkaz goto w postaci skoku bezwarunkowego, a
petelkowanie przerwac rozkazem break:

[LOOP-2]

#include <iostream.h>
void main()
{
int x = 2;
petla:
cout << x << '\n';
x = x + 1;
if(x > 10) break;
goto petla;
}

Mozemy zastosowac petle typu for:

[LOOP-3]

#include <iostream.h>
int main(void)
{
for(int x = 2; x < 11; x = x + 1)
{
cout << x << '\n';
}
return 0;
}

Mozemy zastosowac petle typu while:

[LOOP-4]

#include <iostream.h>
int main(void)
{
int x = 2;
while (x < 11)
{
cout << x << '\n';
x = x + 1;
}
return 0;
}

Mozemy takze zastosowac petle typu do-while:

[LOOP-5]

#include <iostream.h>
int main(void)
{
int x = 2;
do
{
cout << x << '\n';
x = x + 1;
}while (x < 11);
return 0;
}

Mozemy wreszcie nie precyzowac warunków petelkowania w nagłówku
petki for, lecz przerwac petle w jej wnetrzu (po osiagnieciu
okreslonego stanu) przy pomocy rozkazu break:

[LOOP-6]

#include <iostream.h>
int main(void)
{
for(;;)
{
cout << x << '\n';
x++;
if( x > 10) break;
}
return 0;
}

Wszytkie te petle (sprawdz!) beda działac tak samo. Spróbuj przy

ich pomocy, zanim przejdziesz dalej, wydrukowac np. liczby od 10

do 100 i wykonaj jeszcze kilka innych eksperymentów.
Dokładniejszy opis znajdziesz w dalszej czesci ksiazki, ale
przykład - to przykład.

Wrócmy teraz do "face-liftingu" naszych poprzednich programów.
Poniewaz nie mozemy sprecyzowac zadnych warunków, kazemy
programowi przykładowemu wykonywac petle bezwarunkowo.

Wpisz tekst programu:

[P016.CPP]

// Przyklad FACELIFT.CPP
// Program przykladowy 10na16.CPP / 16na10.CPP FACE LIFTING.

# include <stdio.h>

int liczba;

int main()
{
clrscr();
printf("Kropka = KONIEC \n");
for(;;)
{
printf("Podaj liczbe dziesietna calkowita ? \n");
scanf("%d", &liczba);
printf("Szesnastkowo to wynosi:\n");
printf("%X",liczba);
getch();
printf("Podaj liczbe SZESNASTKOWA-np.DF- DUZE LITERY: \n");
scanf("%X", &liczba);
printf("%s","Dziesietnie to wynosi: ");
printf("%d",liczba);
if(getch() == '.') break;
}
return 0;
}

- Uruchom program Run, Run.
- Dla przetestowania działania programu:
* podaj kolejno liczby o róznej długosci 1, 2, 3, 4, 5, 6
cyfrowe;
* zwróc uwage, czy program przetwarza poprawnie liczby dowolnej
długosci?
- Przerwij program naciskajac klawisz z kropka [.]
- Zapisz program na dysk [F2].
- Wyjdz z IDE naciskajac klawisze [Alt]-[X].

Zwróc uwage na dziwny wiersz:

if(getch() == '.') break;

C++ wykona go w nastepujacej kolejnosci:
1) - wywoła funkcje getch(), poczeka na nacisniecie klawisza i
wczyta znak z klawiatury:
getch()
2) - sprawdzi, czy znak był kropka:
(getch() == '.') ?
3) - jesli TAK - wykona rozkaz break i przerwie petle,
if(getch() == '.') break;
- jesli NIE - nie zrobi nic i petla "potoczy sie" dalej.
29
if(getch() != '.') ...--> printf("Podaj liczbe dziesietna...

[Z]

________________________________________________________________
2. Opracuj program pobierajacy znak z klawiatury i podajacy w
odpowiedzi kod ASCII pobranego znaku dziesietnie.
3. Opracuj program pobierajacy liczbe dziesietna i podajacy w
odpowiedzi:
* kod ósemkowy,
* kod szesnastkowy,
* znak o zadanym
** dziesietnie
** szesnastkowo
kodzie ASCII.
_______________________________________________________________


LEKCJA 10 Jakie operatory stosuje C++.
_______________________________________________________________
Podczas tej lekcji:
* Poznasz operatory jezyka C++.
* Przetestujesz działanie niektórych operatorów.
* Dowiesz sie wiecej o deklarowaniu i inicjowaniu zmiennych.
_______________________________________________________________

Słów kluczowych jest w jezyku C++ stosunkowo niewiele, za to
operatorów wyraznie wiecej niz np. w Basicu. Z kilku operatorów
juz korzystałes w swoich programach. pełna liste operatorów
wraz z krótkim wyjasnieniem przedstawiam ponizej. Operatory C++
sa podzielone na 16 grup i mozna je scharakteryzowac:

* priorytetem
** najwyzszy priorytet ma grupa 1 a najnizszy grupa 16 -
przecinek, np. mnozenie ma wyzszy priorytet niz dodawanie;
** wewnatrz kazdej z 16 grup priorytet operatorów jest równy;
* łacznoscia (wiazaniem).

[S!] Precedence - kolejnosc, priorytet.
________________________________________________________________
Dwie cechy opertorów C++ priorytet i łacznosc decyduja o
sposobie obliczania wartosci wyrazen.
Precedence - kolejnosc, priorytet.
Associativity - asocjatywnosc, łacznosc, wiazanie. Operator jest
łaczny lewo/prawo-stronnie, jesli w wyrazeniu zawierajacym na
tym samym poziomie hierarchii nawiasów min. dwa identyczne
operatory najpierw jest wykonywany operator lewy/prawy. Operator
jest łaczny, jesli kolejnosc wykonania nie wpływa na wynik.
________________________________________________________________

Przykład:
a+b+c+d = (a+d)+(c+b)

[S]
________________________________________________________________
ASSIGN(ment) - Przypisanie.
EQAL(ity) - Równy, odpowiadajacy.
BITWISE - bit po bicie (bitowo).
REFERENCE - odwołanie do..., powołanie sie na..., wskazanie
na... .

Funkcje logiczne:
OR - LUB - suma logiczna (alternatywa).
AND - I - iloczyn logiczny.
XOR (eXclusive OR) - ALBO - alternatywa wyłaczajaca.
NOT - NIE - negacja logiczna.
________________________________________________________________


Oznaczenia łacznosci przyjete w Tabeli:

{L->R} ttt(Left to Right) z lewa na prawo.
{L<<-R} tt(Right to Left) z prawa na lewo.


Lista operatorów jezyka C++.
________________________________________________________________
Kategoriat| Operatorttttt| tttCo robi / jak działa
----------|--------------|--------------------------------------

1. Highest| ()ttttttttttt| * ogranicza wyrazenia,
(Najwyzszy|Parentheses | * izoluje wyrazenia warunkowe,
priorytet)|tttttttttttttt| * wskazuje na wywołanie funkcji,
{L->R}ttt|tttttttttttttt| grupuje argumenty funkcji.
tttttttttt|--------------|--------------------------------------
tttttttttt| []ttttttttttt| zawartosc jedno- lub wielowymiarowych
tttttttttt|Brackets | tablic
tttttttttt|--------------|--------------------------------------
tttttttttt| . |(direct component selector)
tttttttttt| -> |(indirect, or pointer, selection)
tttttttttt|tttttttttttttt| Bezposrednie lub posrednie wskazanie
| | elementu unii badz struktury.
|--------------|--------------------------------------
| :: | Operator specyficzny dla C++.
| | Pozwala na dostep do nazw GLOBALNYCH,
| | nawet jesli zostały "przysłoniete"
| | przez LOKALNE.
----------|--------------|--------------------------------------
2. ttttttt| ! ttttttttttt| Negacja logiczna (NOT)
Jednoar-t|--------------|------------------------------------
gumentowe | ~ | Zamiana na kod KOMPLEMENTARNY bit po
(Unary) | | bicie. Dotyczy liczb typu int.
{L<<-R} |--------------|--------------------------------------
| + | Bez zmiany znaku (Unary plus)
|--------------|--------------------------------------
| - | Zmienia znak liczby / wyrazenia
| | (Unary minus)
|--------------|--------------------------------------
| ++ | PREinkrementacja/POSTinkrementacja
|--------------|--------------------------------------
| -- | PRE/POSTdekrementacja
|--------------|--------------------------------------
| & | Operator adresu(Referencing operator)
|--------------|--------------------------------------
| * | Operator wskazania
| | (Dereferencing operator)
|--------------|--------------------------------------
| sizeof | Zwraca wielkosc argumentu w bajtach
|--------------|--------------------------------------
| new | Dynamiczne zarzadzanie pamiecia:
| delete | new - przydziela pamiec,
| | delete - likwiduje przydział pamieci
----------|--------------|--------------------------------------
3. Multi- | * | Mnozenie (UWAGA: Druga rola "*")
plikatywne|--------------|--------------------------------------
{L->R} | / | Dzielenie
|--------------|--------------------------------------
| % | Reszta z dzielenia (modulo)
----------|--------------|--------------------------------------
4. Dostepu| .* | Operatory specyficzne dla C++.
(Member |(dereference) | Skasowanie bezposredniego wskazania
access) | | na członka klasy (Class Member).
{L->R} |--------------|--------------------------------------
| ->* | Skasowanie posredniego wskazania typu
objektowe | | "wskaznik do wskaznika"
----------|--------------|--------------------------------------
5. Addy - | + | Dodawanie dwuargumentowe.
tywne |--------------|--------------------------------------
{L->R} | - | Odejmowanie dwuargumentowe.
----------|--------------|--------------------------------------
6. Przesu-| << | Binarne przesuniecie w lewo.
niecia |--------------|--------------------------------------
(Shift) | >> | Binarne przesuniecie w prawo.
{L->R} | | (bit po bicie)
----------|--------------|--------------------------------------
7. Relacji| < | Mniejsze niz...
{L->R} |--------------|--------------------------------------
| > | Wieksze niz....
|--------------|--------------------------------------
| <= | Mniejsze lub równe.
|--------------|--------------------------------------
| >= | Wieksze lub równe.
----------|--------------|--------------------------------------
8.Równosci| == | Równe (równa sie).
{L->R} | != | Nie równe.
----------|--------------|--------------------------------------
9. | & | AND binarnie (Bitwise AND)
{L->R} | | UWAGA: Druga rola "&".
----------|--------------|--------------------------------------
10. | ^ | XOR binarnie (Alternatywa wyłaczna).
{L->R} | | UWAGA: To nie potega !
----------|--------------|-------------------------------------
11.{L->R} | | | OR binarnie (bit po bicie)
----------|--------------|-------------------------------------
12.{L->R} | && | Iloczyn logiczny (Logical AND).
----------|--------------|-------------------------------------
30
13.{L->R} | || | Suma logiczna (Logical OR).
----------|--------------|--------------------------------------
14. Oper. | ?: | Zapis a ? x : y oznacza:
Warunkowy | | "if a==TRUE then x else y"
Conditional | gdzie TRUE to logiczna PRAWDA "1".
{L<<-R} | |
----------|--------------|--------------------------------------
15. Przy- | = | Przypisz wartosc (jak := w Pascalu)
pisania |--------------|--------------------------------------
{L<<-R} | *= | Przypisz iloczyn. Zapis X*=7
| | oznacza: X=X*7 (o 1 bajt krócej!).
|--------------|--------------------------------------
| /= | Przypisz iloraz.
|--------------|--------------------------------------
| %= | Przypisz reszte z dzielenia.
|--------------|--------------------------------------
| += | Przypisz sume X+=2 oznacza "X:=X+2"
|--------------|--------------------------------------
| -= | Przypisz róznice X-=5 ozn. "X:=X-5"
|--------------|--------------------------------------
| &= | Przypisz iloczyn binarny ( Bitwise
| | AND)
| | bit po bicie.
|--------------|--------------------------------------
| ^= | Przypisz XOR bit po bicie.
|--------------|--------------------------------------
| |= | Przypisz sume log. bit po bicie.
|--------------|--------------------------------------
| <<= | Przypisz wynik przesuniecia o jeden
| | bit w lewo.
|--------------|--------------------------------------
| >>= | j. w. o jeden bit w prawo.
----------|--------------|--------------------------------------
16. Prze- | , | Oddziela elementy na liscie argu -
cinek | | mentów funkcji,
(Comma) | | Stosowany w specjalnych wyrazeniach
{L->R} | | tzw. "Comma Expression".
----------|--------------|-------------------------------------
UWAGI:
* Operatory # i ## stosuje sie tylko w PREPROCESORZE.
* Operatory << i >> moga w C++ przesyłac tekst do obiektów cin i

cout dzieki tzw. Overloadingowi (rozbudowie, przeciazeniu)
operatorów. Takiego rozszerzenia ich działania dokonali juz
programisci producenta w pliku nagłówkowym IOSTREAM.H>


Gdyby okazało sie, ze oferowane przez powyzszy zestaw operatory
nie wystarczaja Ci lub niezbyt odpowiadaja, C++ pozwala na tzw.
OVERLOADING, czyli przypisanie operatorom innego, wybranego
przez uzytkownika działania. Mozna wiec z operatorami robic
takie same sztuczki jak z identyfikatorami. Sadze jednak, ze ten
zestaw nam wystarczy, w kazdym razie na kilka najblizszych
lekcji.

Podobnie, jak pieniadze na bezludnej wyspie, niewiele warta jest
wiedza, której nie mozna zastosowac praktycznie. Przejdzmy wiec
do czynu i przetestujmy działanie niektórych operatorów w
praktyce.

TEST OPERATORÓW JEDNOARGUMENTOWYCH.

Otwórz plik nowego programu:
* Open [F3],
* Wpisz:

A:\UNARY.CPP

* Wybierz klawisz [Open] w okienku lub nacisnij [Enter].

Wpisz tekst programu:

[P017.CPP ]

// UNARY.CPP - operatory jednoargumentowe

# include <stdio.h>
# include <conio.h>
float x;

void main(void)
{
clrscr();
for(;;)
{
printf("\n Podaj liczbe...\n");
scanf("%f", &x);
printf("\n%f\t%f\t%f\n", x, +x, -x );
printf("\n%f", --x );
printf("\t%f", x );
printf("\t%f", ++x);
if(getch() = '.') break;
};
}


Zwróc uwage, ze po nawiasie zamykajacym petle nie ma tym razem
zadnego rozkazu. Nie wystapi takze ostrzezenie (Warning:) przy
kompilacji.

Uruchom program Run | Run. Popraw ewentualne błedy.

Podajac rózne wartosci liczby x:
- dodatnie i ujemne,
- całkowite i rzeczywiste,
przeanalizuj działanie operatorów.
Przerwij program naciskajac klawisz [.]

Zmodyfikuj w programie deklaracje typu zmiennej X wpisujac
kolejno:
- float x; (rzeczywista)
- int x; tttttt(całkowita)
- short int x; ttttt(krótka całkowita)
- long int x;ttttt(długa całkowita)

Zwróc uwage, ze zmiana deklaracji zmiennej bez JEDNOCZESNEJ
zmiany formatu w funkcjach scanf() i printf() spowoduje
komunikaty o błedach.

Spróbuj samodzielnie dobrac odpowiednie formaty w funkcjach
scanf() i printf(). Spróbuj zastosowac zamiast funkcji printf()
i scanf() strumienie cin i cout. Pamietaj o dołaczeniu
własciwych plików nagłówkowych.

Jesli miałes kłopot z dobraniem stosownych formatów, nie
przejmuj sie. Przyjrzyj sie nastepnym przykładowym programom.
Zajmijmy sie teraz dokładniej INKREMENTACJA, DEKREMENTACJA i
OPERATORAMI PRZYPISANIA.

1. Zamknij zbedne okna na ekranie. Pamuietaj o zapisaniu
programów na dyskietke/dysk w tej wersji, która poprawnie działa

lub w ostatniej wersji roboczej.
2. Otwórz plik:
ASSIGN.CPP
3. Wpisz tekst programu:

[P018.CPP]

# include <stdio.h>
# include <conio.h>

long int x;
short int krok;
char klawisz;

int main()
{
clrscr();
printf("Test operatora przypisania += \n");
x=0;
printf("Podaj KROK ? \n");
scanf("%d",&krok);
for(;;)
{
printf("\n%d\n", x+=krok);
printf("[Enter] - dalej [K] - Koniec\n");
klawisz = getch();
if (klawisz=='k'|| klawisz=='K') goto koniec;
}
koniec:
printf("\n Nacisnij dowolny klawisz...");
getch();
return 0;
}

31
W tym programie juz sami "recznie" sprawdzamy, czy nie pora
przerwac petle. Zamiast uzyc typowej instrukcji break (przerwij)

stosujemy nielubiane goto, gdyz jest bardziej uniwersalne i w
przeciwienstwie do break pozwala wyraznie pokazac dokad
nastepuje skok po przerwaniu petli. Zwróc uwage na nowe elementy

w programie:

* DEKLARACJE ZMIENNYCH:
long int x; (długa, całkowita)
short int krok; tttt(krótka, całkowita)
char klawisz;ttttt(zmienna znakowa)

* INSTRUKCJE WARUNKOWA:
if (KLAWISZ=='k'|| KLAWISZ=='K') goto koniec;
(JEZELI zmienna KLAWISZ równa sie "k" LUB równa sie "K"
idz do etykiety "koniec:")
* Warunek sprawdzany po słowie if jest ujety w nawiasy.
* Nadanie wartosci zmiennej znakowej char klawisz przez funkcje:

klawisz = getch();

4. Skompiluj program. Popraw ewentualne błedy.
5. Uruchom program. Podajac rózne liczby (tylko całkowite!)
przesledz działanie operatora.
6. Zapisz poprawna wersje programu na dysk/dyskietke [F2].
7. Jesli masz juz dosc, wyjdz z TC - [Alt]-[X], jesli nie,
pozamykaj tylko zbedne okna i mozesz przejsc do zadan do
samodzielnego rozwiazania -> [Z]!

[Z]
________________________________________________________________
1. Do programu przykładowego wstaw kolejno rózne operatory
przypisania:

*=, -=, /= itp.

Przesledz działanie operatorów.

2. W programie przykładowym zmien typ zmiennych:
long int x; na float x;
short int KROK; float KROK;
Przetestuj działanie operatorów w przypadku liczb
zmiennoprzecinkowych.

3. Zapisz w jezyku C++
* negacje iloczynu logicznego,
* sume logiczna negacji dwu warunków.
________________________________________________________________

TEST OPERATORÓW PRE/POST-INKREMENTACJI.

W nastepnym programie zilustrujemy działanie wszystkich pieciu
operatorów inkrementacji (dekrementacja to tez inkrementacja
tylko w przeciwna strone).

[P019.CPP]

# include <stdio.h>
# include <conio.h>

int b,c,d,e;
int i;
int STO = 100;

void main(void)
{
clrscr();
printf("Demonstruje dzialanie \n");
printf(" PREinkrementacji POSTinkrementacji");
printf("\nNrtttt--Xtttttt++XttttttttttttX--tttttttX++ \n");

b = c = d = e = STO;
for(i=1; i<6; i++)
{
printf("%d\t%d\t%d\t\t%d\t%d\t\n", i,--b,++c,d--,e++);
}
getch();
}

[S!] PRE / POSTINKREMENTACJA.
________________________________________________________________
INKREMENTACJA oznacza zwiekszenie liczby o jeden,
DEKREMENTACJA oznacza zmniejszenie liczby o jeden.
PRE oznacza wykonanie in/de-krementacji przed uzyciem zmiennej,
POST - in/de-krementacje po uzyciu zmiennej.
________________________________________________________________

Działanie mozesz przesledzic na wydruku, który powinien Ci dac
program przykładowy INDEKREM.CPP:

Demonstruje dzialanie
PREinkrementacji POSTinkrementacji
Nrtttt--Xtttttt++XttttttttttttX--tttttttX++
1 99 101 100 100
2 98 102 99 101
3 97 103 98 102
4 96 104 97 103
5 95 105 96 104

JAK KORZYSTAC Z DEBUGGERA?

Uruchom program powtórnie naciskajac klawisz [F7]. Odpowiada to
poleceniu Trace into (włacz sledzenie) z menu Run. Przesledzimy
działanie programu przy pomocy Debuggera.

Po wykonaniu kompilacji (lub odstapieniu od kompilacji, jesli
nie dokonałes zmian w programie) pojawił sie na ekranie pasek
wyróznienia wokół funkcji main(), bo to od niej rozpoczyna sie
zawsze wykonanie programu. Nacisnij powtórnie [F7].

Pasek przesunał sie na funkcje clrscr();. Mignał na chwile ekran

uzytkownika, ale na razie nie ma po co tam zagladac, wiec
wykonamy kolejne kroki. Podam klejno: [Klawisz]-[wiersz].

[F7] - printf("Demonstruje...");

Zagladamy na ekran uzytkownika [Alt]-[F5].....[Enter] - wracamy
do edytora.

[F7],[F7]... doszlismy do wiersza
b=c=d=e=STO;

Zapraszamy teraz debugger do pracy wydajac mu polecenie "Wykonaj
Inspekcje" [Alt]-[D] | Inspect. Pojawia sie okienko dialogowe
"Inspect".
* Wpisz do okienka tekstowego nazwe zmiennej b i nacisnij
[Enter].

Pojawiło sie okienko dialogowe "Inspecting b" zawierajace
fizyczny adres pamieci RAM, pod którym umieszczono zmienna b i
wartosc zmiennej b (zero; instrukcja przypisania nada jej
wartosc 100). Nacisnij [Esc]. Okienko znikneło.

[F7] - for(i=1; i<6; i++);

* Naprowadz kursor na zmienna d w tekscie programu i wykonaj
inspekcje powtórnie [Alt]-[D], [I]. Jak widzisz w okienku
zmiennej d została nadana wartosc 100. Nacisnij [Esc].

Dokonamy teraz modyfikacji wartosci zmiennej przy pomocy
polecenia Evaluate and Modify (sprawdz i zmodyfikuj) z menu
Debug.
* Nacisnij klawisze [Alt]-[D], [E]. Pojawiło sie okienko
dialogowe "Evaluate and Modify". W okienku tekstowym
"Expression" (wyrazenie) widzisz swoja zmienna d.
* Przejdz przy pomocy [Tab] do okienka tekstowego "New Value"
(nowa wartosc) i wpisz tam liczbe 1000. Nacisnij [Enter] a
nastepnie [Esc]. Okienko zamkneło sie. Zmiana wartosci zmiennej
została dokonana.

[F7] - printf("...") - wnetrze petli for.

[F7] - wykonała sie petla.

Obejrzyjmy wyniki [Alt]-[F5].

W czwartej kolumnie widzisz efekt wprowadzonej zmiany:

Demonstruje dzialanie
PREinkrementacji POSTinkrementacji
Nrtttt--Xtttttt++XttttttttttttX--tttttttX++
1 99 101 1000 100
2 98 102 999 101
32
3 97 103 998 102
4 96 104 997 103
5 95 105 996 104

Zwróc uwage w programie przykładowym na:

* Zliczanie ilosci wykonanych przez program petli.

int i; (deklaracja, ze i bedzie zmienna całkowita)
...
i=1; (zainicjowanie zmiennej, nadanie poczatkowej wartosci)
...
i++; (powiekszanie i o 1 w kazdej petli)
...
i<6 (warunek kontynuacji)


* Mozliwosc grupowej deklaracji zmiennych tego samego typu:
int b,c,d,e;

[Z]
________________________________________________________________
4. Zmien w programie przykładowym wartosc poczatkowa STO na
dowolna inna - np. zero. Przetestuj działanie programu.
5. Sprawdz, czy mozna wszystkie zmienne uzywane w programie
przykładowym zadeklarowac wspólnie (jeden wiersz zamiast
trzech).
________________________________________________________________



LEKCJA 11. Jak deklarowac zmienne. Co to jest wskaznik.
________________________________________________________________

W trakcie tej lekcji:
1. Dowiesz sie wiecej o deklaracjach.
2. Poprawisz troche system MS DOS.
3. Dowiesz sie co to jest wskaznik i do czego słuzy.
________________________________________________________________


Wiecej o deklaracjach.

Deklarowac mozna w jezyku C++:
* zmienne;
* funkcje;
* typy (chodzi oczywiscie o typy "nietypowe").

Zmienne w jezyku C++ moga miec charakter:
* skalarów - którym przypisuje sie nierozdzielne dane np.
całkowite, rzeczywiste, wskazujace (typu wskaznik) itp.
* agregatów - którym przypisuje sie dane typu strukturalnego np.

obiektowe, tablicowe czy strukturowe.

Powyzszy podział nie jest tak całkiem precyzyjny, poniewaz
pomiedzy wskaznikami a tablicami istnieje w jezyku C++ dosc
specyficzna zaleznosc, ale wiecej na ten temat dowiesz sie z
pózniejszych lekcji.

Zmienne moga byc:

* deklarowane,
* definiowane i
* inicjowane.

Stała to to taka zmienna, której wartosc mozna przypisac tylko
raz. Z punktu widzenia komputera niewiele sie to rózni, bo
miejsce w pamieci i tak, stosownie do zadeklarowanego typu
zarezerwowac trzeba, umiescic w tablicy i zapamietac sobie
identyfikator i adres tez. Jedyna praktyczna róznica polega na
tym, ze zmiennej zadeklarowanej jako stała, np.:

const float PI = 3.142;

nie mozna przypisac w programie zadnej innej wartosci, innymi
słowy zapis:

const float PI = 3.14;

jest jednoczesnie DEKLARACJA, DEFINICJA i ZAINICJOWANIEM stałej
PI.

Przykład :

float x,y,z;ttttttttttttttttttttttt(DEKLARACJA)
const float TEMP = 36.6;ttttttttttt(DEFINICJA)
x = 42;ttttttttttttttttttttttttttttt(ZAINICJOWANIE zmiennej)

[S!] constant/variable - STAŁA czy ZMIENNA.
________________________________________________________________
const - (CONSTant) - stała. Deklaracja stałej, słowo kluczowe w
jezyku C.
var - (VARiable) - zmienna. W jezyku C przyjmowane domyslnie.
Słowo var (stosowane w Pascalu) NIE JEST słowem kluczowym jezyka

C ani C++ (!).
________________________________________________________________

Skutek praktyczny:
* Ma sens i jest poprawna deklaracja:
const float PI = 3.1416;
* Niepoprawna natomiast jest deklaracja:
var float x;
Jesli nie zadeklarowano stałej słowem const, to "zmienna" (var)
przyjmowana jest domyslnie.

Definicja powoduje nie tylko okreslenie, jakiego typu
wartosciami moze operowac dana zmienna badz funkcja, która
zostaje od tego momentu skojarzona z podanym identyfikatorem,
ale dodatkowo powoduje:
* w przypadku zmiennej - przypisanie jej wartosci,
* W przypadku funkcji - przyporzadkowanie ciała funkcji.
Zdefiniujmy dla przykładu kilka własnych funkcji.

Przykład:

void UstawDosErrorlevel(int n) /* nazwa funkcji*/
{
exit(n); /* skromne ciało funkcji */
}

Przykład

int DrukujAutora(void)
{
printf("\nAdam MAJCZAK AD 1993/95 - C++ w 48 godzin!\n");
printf("\n Wydanie II Poprawione i uzupełnione.")
return 0;
}

Przykład

void Drukuj_Pytanie(void)
{
printf("Podaj liczbe z zakresu od 0 do 255");
printf("\nUstawie Ci ERRORLEVEL\t");
}

W powyzszych przykładach zwróc uwage na:
* sposób deklarowania zmiennej, przekazywanej jako parametr do
funkcji - n i err;
* definicje funkcji i ich wywołanie w programie (podobnie jak w
Pascalu).

Zilustrujemy zastosowanie tego mechanizmu w programie
przykładowym. Funkcje powyzsze sa PREDEFINIOWANE w pliku
FUNKCJE1.H na dyskietce dołaczonej do ksiazki. Wpisz i uruchom
program:
[P020.CPP]

# include "stdio.h"
# include "A:\funkcje1.h"

int err;

void main(void)
{
DrukujAutora();
Drukuj_Pytanie();
scanf("%d", &err);
UstawDosErrorlevel(err);
}

Wykorzystajmy te funkcje praktycznie, by zilustrowac sposób
przekazywania informacji przez pracujacy program do systemu DOS.
33

Zmienna otoczenia systemowego DOS ERRORLEVEL moze byc z wnetrza
programu ustawiona na zadana - zwracana do systemu wartosc.

[Z]
________________________________________________________________
1. Sprawdz, w jakim pliku nagłówkowym znajduje sie prototyp
funkcji exit(). Opracuj najprostszy program PYTAJ.EXE
ustawiajacy zmienna systemowa ERRORLEVEL według schematu:

main()
{
printf("....Pytanie do uzytkownika \n...");
scanf("%d", &n);
exit(n);
}

2. Zastosuj program PYTAJ.EXE we własnych plikach wsadowych typu

*.BAT według wzoru:

@echo off
:LOOP
cls
echo 1. Wariant 1
echo 2. Wariant 2
echo 3. Wariant 3
echo Wybierz wariant działania programu...1,2,3 ?
PYTAJ
IF ERRORLEVEL 3 GOTO START3
IF ERRORLEVEL 2 GOTO START2
IF ERRORLEVEL 1 GOTO START1
echo Chyba zartujesz...?
goto LOOP
:START1
'AKCJA WARIANT 1
GOTO KONIEC
:START2
'AKCJA WARIANT 2
GOTO KONIEC
:START3
'AKCJA WARIANT 3
:KONIEC

'AKCJA WARIANT n - oznacza dowolny ciag komend systemu DOS, np.
COPY, MD, DEL, lub uruchomienie dowolnego programu. Do
utworzenia pliku wsadowego mozesz zastosowac edytor systemowy
EDIT.

3. Skompiluj program posługujac sie oddzielnym kompilatorem
TCC.EXE. Ten wariant kompilatora jest pozbawiony zintegrowanego
edytora. Musisz uruchomic go piszac odpowiedni rozkaz po
DOS-owskim znaku zachety C:\>. Zastosowanie przy kompilacji
małego modelu pamieci pozwol Ci uzyskac swój program w wersji
*.COM, a nie *.EXE. Wydaj rozkaz:

c:\borlandc\bin\bcc -mt -lt c:\pytaj.cpp

Jesli pliki znajduja sie w róznych katalogach, podaj własciwe
sciezki dostepu (path).
________________________________________________________________


[???] CO TO ZA PARAMETRY ???
________________________________________________________________
Przez swa "ułomnosc" - 16 bitowa szyne i segmentacje pamieci
komputery IBM PC wymusiły wprowadzenie modeli pamieci:
TINY, SMALL, COMPACT, MEDIUM, LARGE, HUGE. Wiecej informacji
na
ten temat znajdziesz w dalszej czesci ksiazki.
Parametry dotycza sposobu kompilacji i zastosowanego modelu
pamieci:
-mt - kompiluj (->*.OBJ) wykorzystujac model TINY
-lt - konsoliduj (->*.COM) wykorzystujac model TINY i zatem
odpowiednie biblioteki (do kazdego modelu jest odpowiednia
biblioteka *.LIB).
Mozesz stosowac takze:
ms, mm, ml, mh, ls, lm, ll, lh.
________________________________________________________________


Po instalacji BORLAND C++/Turbo C++ standardowo jest przyjmowany

model SMALL. Zatem kompilacja, która wykonujesz z IDE daje taki
sam efekt, jak zastosowanie kompilatora bcc/tcc w nastepujacy
sposób:

tcc -ms -ls program.c

Moga wystapic kłopoty z przerobieniem z EXE na COM tych
programów, w których wystepuja funkcje realizujace arytmetyke
zmiennoprzecinkowa (float). System DOS oferuje Ci do takich
celów program EXE2BIN, ale lepiej jest "panowac" nad tym
problemem na etapie tworzenia programu.

PODSTAWOWE TYPY DANYCH W JEZYKU C++.

Jezyk C/C++ operuje piecioma podstawowymi typami danych:

* char (znak, numer znaku w kodzie ASCII) - 1 bajt;
* int (liczba całkowita) - 2 bajty;
* float (liczba z pływajacym przecinkiem) - 4 bajty;
* double (podwójna ilosc cyfr znaczacych) - 8 bajtów;
* void (nieokreslona) 0 bajtów.

Zakres wartosci przedstawiono w Tabeli ponizej.

Podstawowe typy danych w C++.
________________________________________________________________

Typ Znak Bajtów Zakres wartosci
________________________________________________________________

char signed 1 -128...+127
int signed 2 -32768...+32767
float signed 4 +-3.4E+-38 (dokładnosc: 7 cyfr)
double signed 8 1.7E+-308 (dokładnosc: 15 cyfr)
void nie dotyczy 0 bez okreslonej wartosci.
________________________________________________________________

signed - ze znakiem, unsigned - bez znaku.

Podstawowe typy danych moga byc stosowane z jednym z czterech
modyfikatorów:

* signed / unsigned - ze znakiem albo bez znaku
* long / short - długi albo krótki

Dla IBM PC typy int i short int sa reprezentowane przez taki sam

wewnetrzny format danych. Dla innych komputerów moze byc
inaczej.

Typy zmiennych w jezyku C++ z zastosowaniem modyfikatorów
(dopuszczalne kombinacje).
________________________________________________________________

Deklaracja Znak Bajtów Wartosci Dyr. assembl.
________________________________________________________________

char signed 1 -128...+127 DB
int signed 2 -32768...+32767 DB
short signed 2 -32768...+32767 DB
short int signed 2 -32768...+32767 DB
long signed 4 -2 147 483 648... DD
+2 147 483 647
long int signed 4 -2 147 483 648... DW
+2 147 483 647
unsigned char unsigned 1 0...+255 DB
unsigned unsigned 2 0...+65 535 DW
unsigned int unsigned 2 0...+65 535 DW
unsigned short unsigned 2 0...+65 535 DW
signed int signed 2 -32 768...+32 767 DW
signed signed 2 -32 768...+32 767 DW
signed long signed 4 -2 147 483 648... DD
+2 147 483 647
enum unsigned 2 0...+65 535 DW
float signed 4 3.4E+-38 (7 cyfr) DD
double signed 8 1.7E+-308 (15 cyfr) DQ
long double signed 10 3.4E-4932...1.1E+4932 DT
far * (far pointer, 386) 6 unsigned 2^48 - 1 DF, DP
________________________________________________________________

UWAGI:
* DB - define byte - zdefiniuj bajt;
DW - define word - zdefiniuj słowo (16 bitów);
34
DD - double word - podwójne słowo (32 bity);
DF, DP - define far pointer - daleki wskaznik w 386;
DQ - quad word - poczwórne słowo (4 * 16 = 64 bity);
DT - ten bytes - dziesiec bajtów.
* zwróc uwage, ze typ wyliczeniowy enum wystepuje jako odrebny
typ danych (szczegóły w dalszej czesci ksiazki).
________________________________________________________________



Poniewaz nie ma liczb ani short float, ani unsigned short float,

słowo int moze zostac opuszczone w deklaracji. Poprawne sa zatem

deklaracje:

short a;
unsigned short b;

Zapis +-3.4E-38...3.4E+38 oznacza:
-3.4*10^+38...0...+3.4*10^-38...+3.4*10^+38
Dopuszczalne sa deklaracje i definicje grupowe z zastosowaniem
listy zmiennych. Zmienne na liscie nalezy oddzielic przecinkami:

int a=0, b=1, c, d;
float PI=3.14, max=36.6;

Poswiecimy teraz chwile drugiej funkcji, która juz wielokrotnie
stosowalismy - funkcji wejscia - scanf().

FUNKCJA scanf().

Funkcja formatowanego wejscia ze standardowego strumienia
wejsciowego (stdin). Funkcja jest predefiniowana w pliku STDIO.H

i korzystajac z funkcji systemu operacyjnego wczytuje dane w
postaci tekstu z klawiatury konsoli. Interpretacja pobranych
przez funkcje scanf znaków nastapi zgodnie z zyczeniem
programisty okreslonym przez zadany funkcji format (%f, %d, %c
itp.). Wywołanie funkcji scanf ma postac:

scanf(Format, Adres_zmiennej1, Adres_zmiennej2...);

dla przykładu

scanf("%f%f%f", &X1, &X2, &X3);

wczytuje trzy liczby zmiennoprzecinkowe X1, X2 i X3.

Format decyduje, czy pobrane znaki zostana zinterpretowane np.
jako liczba całkowita, znak, łancuch znaków (napis), czy tez w
inny sposób. Od sposobu interpretacji zalezy i rozmieszczenie
ich w pamieci i pózniejsze "siegniecie do nich", czyli odwołanie

do danych umieszczonych w pamieci operacyjnej komputera.

Zwróc uwage, ze podajac nazwy (identyfikatory) zmiennych nalezy
poprzedzic je w funkcji scanf() operatorem adresowym [&].
Zapis:

int X;
...
scanf("%d", &X);

oznacza, ze zostana wykonane nastepujace działania:
* Kompilator zarezerwuje 2 bajty pomieci w obszarze pamieci
danych programu na zmienna X typu int;
* W momencie wywołania funkcji scanf funkcji tej zostanie
przekazany adres pamieci pod którym ma zostac umieszczona
zmienna X, czyli tzw. WSKAZANIE DO ZMIENNEJ;
* Znaki pobrane z klawiatury przez funkcje scanf maja zostac
przekształcone do postaci wynikajacej z wybranego formatu %d -
tzn. do postaci zajmujacej dwa bajty liczby całkowitej ze
znakiem.

[???] A JESLI PODAM INNY FORMAT ?
________________________________________________________________
C++ wykona Twoje rozkazy najlepiej jak umie, niestety nie
sprawdzajac po drodze formatów, a z zer i jedynek zapisanych w
pamieci RAM zaden format nie wynika. Otrzymasz błedne dane.
________________________________________________________________

Ponizej przykład skutków błednego formatowania. Dołacz pliki
STDIO.H i CONIO.H.

[P021.CPP]
//UWAGA: Dołacz własciwe pliki nagłówkowe !

void main()
{
float A, B;
clrscr();
scanf("%f %f", &A, &B);
printf("\n%f\t%d", A,B);
getch();
}

[Z]
________________________________________________________________
3 Zmien w programie przykładowym, w funkcji printf() wzorce
formatu na %s, %c, itp. Porównaj wyniki.
________________________________________________________________

Adres w pamieci to taka sama liczba, jak wszystkie inne i wobec
tego mozna nia manipulowac. Adresami rzadza jednak dosc
specyficzne prawa, dlatego tez w jezyku C++ wystepuje jeszcze
jeden specjalny typ zmiennych - tzw. ZMIENNE WSKAZUJACE (ang.
pointer - wskaznik). Twoja intuicja podpowiada Ci zapewne, ze sa

to zmienne całkowite (nie ma przeciez komórki pamieci o adresie
0.245 ani 61/17). Pojecia "komórka pamieci" a nie np. "bajt"
uzywam swiadomie, poniewaz obszar zajmowany w pamieci przez
zmienna moze miec rózna długosc. Aby komputer wiedział ile
kolejnych bajtów pamieci zajmuje wskazany obiekt (liczba długa,
krótka, znak itp.), deklarujac wskaznik trzeba podac na co
bedzie wskazywał. W sposób "nieoficjalny" juz w funkcji scanf
korzystalismy z tego mechanizmu. Jest to zjawisko specyficzne
dla jezyka C++, wiec zajmijmy sie nim troche dokładniej.

POJECIE ZMIENNEJ WSKAZUJACEJ I ZMIENNEJ WSKAZYWANEJ.

Wskaznik to zmienna, która zawiera adres innej zmiennej w
pamieci komputera. Istnienie wskazników umozliwia posrednie
odwoływanie sie do wskazywanego obiektu (liczby, znaku, łancucha

znaków itp.) a takze stosunkowo proste odwołanie sie do obiektów

sasiadujacych z nim w pamieci. Załózmy, ze:

x - jest umieszczona gdzies w pamieci komputera zmienna
całkowita typu int zajmujaca dwa kolejne bajty pamieci, a

px - jest wskaznikiem do zmiennej x.

Jednoargumentowy operator & podaje adres obiektu, a zatem
instrukcja:

px = &x;

przypisuje wskaznikowi px adres zmiennej x. Mówimy, ze:

px wskazuje na zmienna x lub
px jest WSKAZNIKIEM (pointerem) do zmiennej x.

Jednoargumentowy operator * (naz. OPERATOREM WYŁUSKANIA)
powoduje, ze zmienna "potraktowana" tym operatorem jest
traktowana jako adres pewnego obiektu. Zatem, jesli przyjmiemy,
ze y jest zmienna typu int, to działania:

y = x;

oraz

px = &x;
y = *px;

beda miec identyczny skutek. Zapis y = x oznacza:
"Nadaj zmiennej y dotychczasowa wartosc zmiennej x";
a zapis y=*px oznacza:
"Nadaj zmiennej y dotychczasowa wartosc zmiennej, której adres w

pamieci wskazuje wskaznik px" (czyli własnie x !).

Wskazniki takze wymagaja deklaracji. Poprawna deklaracja w
opisanym powyzej przypadku powinna wygladac tak:

35
int x,y;
int *px;
main()
......

Zapis int *px; oznacza:
"px jest wskaznikiem i bedzie wskazywac na liczby typu int".

Wskazniki do zmiennych moga zamiast zmiennych pojawiac sie w
wyrazeniach po PRAWEJ STRONIE, np. zapisy:

int X,Y;
int *pX;
...
pX = &X;
.......
Y = *pX + 1; tttttt/* to samo, co Y = X + 1 */
printf("%d", *pX);ttttttt/* to samo, co printf("%d", X); */
Y = sqrt(*pX);ttttttttttt/* pierwiastek kwadrat. z X */
......

sa w jezyku C++ poprawne.

Zwróc uwage, ze operatory & i * maja wyzszy priorytet niz
operatory arytmetyczne, dzieki czemu
* najpierw nastepuje pobranie spod wskazanego przez
wskaznik adresu zmiennej;
* potem nastepuje wykonanie operacji arytmetycznej;
(operacja nie jest wiec wykonywana na wskazniku, a na
wskazywanej zmiennej!).

W jezyku C++ mozliwa jest takze sytuacja odwrotna:

Y = *(pX + 1);

Poniewaz operator () ma wyzszy priorytet niz * , wiec:

najpierw wskaznik zostaje zwiekszony o 1;
potem zostaje pobrana z pamieci wartosc znajdujaca sie pod
wskazanym adresem (w tym momencie nie jest to juz adres zmiennej

X, a obiektu "nastepnego" w pamieci) i przypisana zmiennej Y.

Taki sposób poruszania sie po pamieci jest szczególnie wygodny,
jesli pod kolejnymi adresami pamieci rozmiescimy np. kolejne
wyrazy z tablicy, czy kolejne znaki tekstu.

Przyjrzyjmy sie wyrazeniom, w których wskaznik wystepuje po
LEWEJ STRONIE. Zapisy:

*pX = 0;ttttttttttttitttttttttX = 0;
*pX += 1;tttttttttttitttttttttX += 1;
(*pX)++;ttttttttttttitttttttttX++; /*3*/

maja identyczne działanie. Zwróc uwage w przykładzie /*3*/, ze
ze wzgledu na priorytet operatorów

() - najwyzszy - najpierw pobieramy wskazana zmienna;
++ - nizszy, potem zwiekszmy wskazana zmienna o 1;

Gdyby zapis miał postac:

*pX++;

najpierw nastapiłoby
- zwiekszenie wskaznika o 1 i wskazanie "sasiedniej" zmiennej,
potem
- wyłuskanie, czyli pobranie z pamieci zmiennej wskazanej przez
nowy, zwiekszony wskaznik, zawartosc pamieci natomiast, tj.
wszystkie zmienne rozmieszczone w pamieci pozostałyby bez zmian.


[???] JAK TO WŁASCIWIE JEST Z TYM PRIORYTETEM ?
________________________________________________________________
Wszystkie operatory jednoargumentowe (kategoria 2, patrz Tabela)

maja taki sam priorytet, ale sa PRAWOSTRONNIE ŁACZNE {L<<-R}.
Oznacza to, ze operacje beda wykonywane Z PRAWA NA LEWO. W
wyrazeniu *pX++; oznacza to:

najpierw ++
potem *

Zwróc uwage, ze kolejnosc {L<<-R} dotyczy WSZYSTKICH operatorów
jednoargumentowych.
________________________________________________________________


Jesli dwa wskazniki wskazuja zmienne takiego samego typu, np. po

zadeklarowaniu:

int *pX, *pY;
int X, Y;

i zainicjowaniu:

pX = &X; pY = &Y;

mozna zastosowac operator przypisania:

pY = pX;

Spowoduje to skopiowanie wartosci (adresu) wskaznika pX do pY,
dzieki czemu od tego momentu wskaznik pY zacznie wskazywac
zmienna X. Zwróc uwage, ze nie oznacza to bynajmniej zmiany
wartosci zmiennych - ani wielkosc X, ani wielkosc Y, ani ich
adresy w pamieci NIE ULEGAJA ZMIANIE. Zatem działanie
instrukcji:

pY = pX; i *pY = *pX;

jest RÓZNE a wynika to znowu z priorytetu operatorów:

najpierw * wyłuskanie zmiennych spod podanych adresów,
potem = przypisanie wartosci (ale juz zmiennym a nie
wskaznikom!)

C++ chetnie korzysta ze wskazania adresu przy przekazywaniu
danych - parametrów do/od funkcji.

Asekurujac sie na całej linii i podkreslajac, ze nie zawsze
wyglada to tak prosto i ładnie, posłuze sie do zademonstrowania
działania wskazników przykładowym programem. Wpisz i uruchom
nastepujacy program:

[P022-1.CPP wersja 1]

# include "stdio.h"
# include "conio.h"

int a=1,b=2,c=3,d=4,e=5,f=6,g=7,h=8,x=9,y=10,i;
int *ptr1;
long int *ptr2;

void main()
{
clrscr();
ptr1=&a;
ptr2=&a;
printf("Skok o 2Bajty Skok o 4Bajty");

for(i=0; i<=9; i++)
{
printf("\n%d", *(ptr1+i));
printf("\t\t%d", *(ptr2+i));
}
getch();
}

[P022-2.CPP wersja 2]

int a=11,b=22,c=33,d=44,e=55,f=66,g=77,h=88,x=99,y=10,i;
int *ptr1;
long int *ptr2;

void main()
{
clrscr();
ptr1=&a;
ptr2=&a;
for (i=0; i<=9; i++)
{
printf("\n%d", *(ptr1+i));
printf("\t%d", *(ptr2+i));
getch();
36
}
}

W programie wykonywane sa nastepujace czynnosci:

1. Deklarujemy zmienne całkowite int (kazda powinna zajac 2
bajty pamieci) i nadajemy im wartosci w taki sposób aby łatwo
mozna je było rozpoznac.
2. Deklarujemy dwa wskaznki:
ptr1 - poprawny - do dwubajtowych zmiennych typu int;
ptr2 - niepoprawny - do czterobajtowych zmiennych typu long int.

3. Ustawiamy oba wskazniki tak by wskazywały adres w pamieci
pierwszej liczby a=11.
4. Zwiekszamy oba wskazniki i sprawdzamy, co wskazuja.

Jesli kompilator rozmiesci nasze zmienne w kolejnych komórkach
pamieci, to powinnismy uzyskac nastepujacy wydruk:

Skok o 2B Skok o 4B
11ttttttttttttt11
22ttttttttttttt33
33ttttttttttttt55
44ttttttttttttt77
55ttttttttttttt99
66ttttttttttttt27475
77ttttttttttttt28448
88ttttttttttttt8258
99ttttttttttttt27475
10ttttttttttttt2844

Zwróc uwage, ze to deklaracja wskaznika decyduje, co praktycznie

oznacza operacja *(ptr + 1). W pierwszym przypadku wskaznik
powieksza sie o 2 a w drugim o 4 bajty. Te odpowiednio 2 i 4
bajty stanowia długosc komórki pamieci lub precyzyjniej, pola
pamieci przeznaczonego dla zmiennych okreslonego typu.
Wartosci pojawiajace sie w drugiej kolumnie po 99 sa
przypadkowe i u Ciebie moga okazac sie inne.

C++ pozwala wskaznikom nie tylko wskazywac adres zmiennej w
pamieci. Wskaznik moze równiez wskazywac na inny wskaznik. Takie

wskazania:

int X; tttint pX; ttint ppX;
pX = &X;ttppX = &pX;
oznaczamy:
*pXtt- pX wskazuje BEZPOSREDNIO zmienna X;
**ppXt- ppX skazuje POSREDNIO zmienna X (jest wskaznikiem do
wskaznika).
***pppX - pppX wskazuje posrednio wskaznik do zmiennej X itd.

[Z]
________________________________________________________________
4 Wybierz dowolne dwa przykładowe programy omawiane wczesniej i
przeredaguj je posługujac sie zamiast zmiennych - wskaznikami do

tych zmiennych. Pamietaj, ze przed uzyciem wskaznika nalezy:
* zadeklarowac na jaki typ zmiennych wskazuje wskaznik;
* przyporzadkowac wskaznik okreslonej zmiennej.

5 Zastanów sie, co oznacza ostrzezenie wypisywane podczas
uruchomienia programu przykładowego:
Warning 8: Suspicious pointer conversion in function main.
________________________________________________________________



LEKCJA 12. Wskazniki i tablice w C i C++.
________________________________________________________________

W czasie tej lekcji:
1. Dowiesz sie wiecej o zastosowaniu wskazników.
2. Zrozumiesz, co maja wspólnego wskazniki i tablice w jezyku
C/C++.
________________________________________________________________


WSKAZNIKI I TABLICE W C i C++.

W jezyku C/C++ pomiedzy wskaznikami a tablicami istnieje bardzo
scisły zwiazek. Do ponumerowania elementów w tablicy słuza tzw.
INDEKSY. W jezyku C/C++

* KAZDA OPERACJA korzystajaca z indeksów moze zostac wykonana
przy pomocy wskazników;
* posługiwanie sie wskaznikiem zamiast indeksu na ogół
przyspiesza operacje.

Tablice, podobnie jak zmienne i funkcje wymagaja przed uzyciem
DEKLARACJI. Upraszczajac problem - komputer musi wiedziec ile
miejsca zarezerwowac w pamieci i w jaki sposób rozmiescic
kolejne OBIEKTY, czyli kolejne elementy tablicy.

[???] CO Z TYMI OBIEKTAMI ?
________________________________________________________________
OBIEKTEM w szerokim znaczeniu tego słowa jest kazda liczba,
znak, łancuch znaków itp.. Takimi klasycznymi obiektami jezyki
programowania operowały juz od dawien dawna. Prawdziwe
programowanie obiektowe w dzisiejszym, wezszym znaczeniu
rozpoczyna sie jednak tam, gdzie obiektem moze stac sie takze
cos "nietypowego" - np. rysunek. Jest to jednak własciwy chyba
moment, by zwrócic Ci uwage, ze z punktu widzenia komputera
obiekt to cos, co zajmuje pewien obszar pamieci i z czym wiadomo
jak postepowac.
________________________________________________________________

Deklaracja:

int A[12];
oznacza:
nalezy zarezerwowac 12 KOLEJNYCH komórek pamieci dla 12 liczb
całkowitych typu int (po 2 bajty kazda). Jednowymiarowa tablica
(wektor) bedzie sie nazywac "A", a jej kolejne elementy zostana
ponumerowane przy pomocy indeksu:
- zwróc uwage, ze w C zaczynamy liczyc OD ZERA A NIE OD JEDYNKI;


A[0], A[1], A[2], A[3], .... A[11].

Jesli chcemy zadeklarowac:
- indeks i;
- wskaznik, wskazujacy nam poczatek (pierwszy, czyli zerowy
element) tablicy;
- sama tablice;
to takie deklaracje powinny wygladac nastepujaco:

int i;
int *pA;
int A[12];

Aby wskaznik wskazywał na poczatek tablicy A[12], musimy go
jeszcze zainicjowac:

pA = &A[0];

Jesli poszczególne elementy tablicy sa zawsze rozmieszczane
KOLEJNO, to:

*pA[0]

oznacza:
"wyłuskaj zawartosc komórki pamieci wskazanej przez wskaznik",
czyli inaczej - pobierz z pamieci pierwszy (zerowy!) element
tablicy A[]. Jesli deklaracja typów elementów tablicy i
deklaracja typu wskaznika sa zgodne i poprawne, nie musimy sie
dalej martwic ile bajtów zajmuje dany obiekt - element tablicy.
Zapisy:

*pA[0];tttttttt*pA;tttttttttttA[0]
*(pA[0]+1)ttttt*(pA+1)ttttttttA[1]
*(pA[0]+2)ttttt*(pA+2)ttttttttA[2]ttttttitd.

sa równowazne i oznaczaja kolejne wyrazy tablicy A[].

Jesli tablica jest dwu- lub trójwymiarowa, poczatek tablicy
oznacza zapis:

A[0][0];
A[0][0][0];
itd.

Zwróc uwage, ze wskaznik do tablicy *pA oznacza praktycznie
wskaznik do POCZATKOWEGO ELEMENTU TABLICY:

37
*pA == *pA[0]

To samo mozna zapisac w jezyku C++ w jeszcze inny sposób. Jesli
A jest nazwa tablicy, to zapis:

*A

oznacza wskazanie do poczatku tablicy A, a zapisy:

*(A+1)tttttttttttt*(pA+1)ttttttttA[1]
*(A+8)tttttttttttt*(pA+8)ttttttttA[8] itd.

sa równowazne. Podobnie identyczne znaczenie maja zapisy:

x = &A[i]ttttttttx=A+i
*pA[i]ttttttttt*(A+i)

Nalezy jednak podkreslic, ze pomiedzy nazwami tablic (w naszym
przykładzie A) a wskaznikami istnieje zasadnicza róznica.
Wskaznik jest ZMIENNA, zatem operacje:

pA = A;
pA++;

sa dopuszczalne i sensowne. Nazwa tablicy natomiast jest STAŁA,
zatem operacje:

A = pA;tttttttttttttZLE !
A++;ttttttttttttttttZLE !

sa niedopuszczalne i próba ich wykonania spowoduje błedy !

DEKLAROWANIE I INICJOWANIE TABLIC.

Elementom tablicy, podobnie jak zmiennym mozemy nadawac
watrosci. Wartosci takie nalezy podawac w nawiasach klamrowych,
a wielkosc tablicy - w nawiasach kwadratowych.

Przykład

int WEKTOR[5];
Tablica WEKTOR jest jednowymiarowa i składa sie z 5 elementów
typu int: WEKTOR[0]....WEKTOR[4].

Przykład

float Array[10][5];
Tablica Array jest dwuwymiarowa i składa sie z 50 elementów typu

float: Array[0][0], Array[0][1]......Array[0][4]
tttttttArray[1][0], Array[1][1]......Array[1][4]
ttttt...........................................
ttttt Array[9][0], Array[9][1]......Array[9][4]

Przykład

const int b[4]={1,2,33,444};
Elementom jednowymiarowej tablicy (wektora) b przypisano
wartosci: b[0]=1; b[1]=2; b[2]=33; b[3]=444;

Przykład

int TAB[2][3]={{1, 2, 3},{2, 4, 6}};
tttttTAB[0][0]=1ttttTAB[0][1]=2ttttTAB[0][2]=3
tttttTAB[1][0]=2ttttTAB[1][1]=4ttttTAB[1][2]=6

Przykład : Tablica znakowa. Obie formy zapisu daja ten sam
efekt.

char hej[5]="Ahoj";
char hej[5]={'A', 'h', 'o', 'j'};
ttttthej[0]='A'ttttthej[1]='h'ttttthej[2]='o' itp.

Przykład : Tablica uzupełniona zerami przez domniemanie.

float T[2][3]={{1, 2.22}, {.5}};
kompilator uzupełni zerami do postaci:

tttttT[0][0]=1ttttttT[0][1]=2.22ttttttttT[0][2]=0
tttttT[1][0]=0.5ttttT[1][1]=0tttttttttttT[1][2]=0

Jesli nawias kwadratowy zawierajacy wymiar pozostawimy pusty, to

kompilator obliczy jego domniemana zawartosc w oparciu o podana
zawartosc tablicy. Nie spowoduje wiec błedu zapis:

char D[]="Jakis napis"
int A[][2]={{1,2}, {3,4}, {5,6}}

Jesli nie podamy ani wymiaru, ani zawartosci:

int A[];

kompilator "zbuntuje sie" i wykaze bład.

Dla przykładu, skompiluj program przykładowy. Zwróc uwage na
sposób zainicjowania wskaznika.

[P023.CPP]

# include "stdio.h"
# include <conio.h>
int a[][2]={ {1,2},{3,4},{5,6},{7,8},{9,10},{11,12} };
char b[]={ "Poniedzialek" };
int i;
int *pa;
char *pb;

void main()
{
pa = &a[0][0];
pb = b; // lub pb = b[0];
clrscr();
for (i=0; i<12; i++)
printf("%d\t%c\n", *(pa+i), *(pb+i));
getch();
}

Zwróc uwage, ze w C++ kazdy wymiar tablicy musi miec swoja pare
nawiasów kwadratowych. Dla przykładu, tablice trójwymiarowa
nalezy deklarowac nie tak TAB3D[i, j, k] lecz tak:

int i, j, k;
...
TAB3D[i][j][k];

Jest w dobrym stylu panowac nad swoimi danymi i umieszczac je w
tzw. BUFORACH, czyli w wydzielonych obszarach pamieci o znanym
adresie, wielkosci i przeznaczeniu. W nastepnym programie
przykładowym utworzymy taki bufor w postaci tablicy bufor[20] i
zastosujemy zamiast funkcji scanf() czytajacej bezposrednio z
klawiatury pare funkcji:

gets() - GET String - pobierz łancuch znaków z klawiatury do
bufora;
sscanf(bufor) - odczytaj z bufora (z pamieci).

Aby uniknac nielubianego goto stosujemy konstrukcje for - break.

Dokładniej petle for omówimy w trakcie nastepnej lekcji.
Poniewaz mam nadzieje, ze "podstawowa" postac petli for
pamietasz z przykładów LOOP-n:

for(i=1; i<100; i++)
{
...
}

pozwalam sobie troche wyprzedzajaco zastosowac ja w programie.
Niepodobny do Pascala ani do Basica zapis wynika własnie z tego,

ze skok nastepuje bezwarunkowo. Nagłówek petli for

* nie inicjuje licznika petli (zbedne typowe i=1);
* nie sprawdza zadnego warunku (zbedne i<100),
* nie liczy peti (i=i+1 lub i++ tez zbedne !).

[P024.CPP]

# include <stdio.h>
# include <conio.h>

int liczba, ile = 0, suma = 0;

void main()
{
38
char bufor[20];
clrscr();
printf("podaj liczby - ja oblicze SREDNIA i SUMA\n");
printf("ZERO = KONIEC\n");

for(;;) // Wykonuj petle BEZWARUNKOWO
{
gets(bufor);
sscanf(bufor, "%d", &liczba);
suma += liczba;
ile++;
if (liczba == 0) break; // JESLI ==0 PRZERWIJ PETLE
}
printf("Suma wynosi: %d\n", suma);
printf("Srednia wynosi: %d\n", (suma / ile));
getch();
}

Ponizej troche bardziej "elegancka wersja" z zastosowaniem petli

typu while. Wiecej o petlach dowiesz sie z nastepnej Lekcji.

[P025.CPP]

# include <stdio.h>
# include <conio.h>

int liczba, ile=1, suma=0;

void main()
{
char bufor[20];
clrscr();
printf("podaj liczby - ja oblicze SREDNIA i SUMA\n");
printf("ZERO = KONIEC\n");

gets(bufor);
sscanf(bufor, "%d", &liczba);

while (liczba != 0)
{
suma += liczba;
gets(bufor);
sscanf(bufor, "%d", &liczba);
if(liczba == 0)
printf("I to by bylo na tyle...\n");
else
ile++;
}
printf("Suma wynosi: %d\n", suma);
printf("Srednia wynosi: %d\n", suma / ile);
getch();
}

Program powyzszy, choc operuje tablica, robi to troche jakby za
kulisami. Utwórzmy zatem inna - bardziej "dydaktyczna" tablice,
której elementy byłyby łatwo rozpoznawalne.

PRZYKŁADY TABLIC WIELOWYMIAROWYCH.

Dzieki matematyce bardziej jestesmy przyzwyczajeni do zapisu
tablic w takiej postaci:

tttttttttta11tta12tta13tta14tta15tta16
tttttttttta21tta22tta23tta24tta25tta26
tttttttttta31tta32tta33tta34tta35tta36
tttttttttta41tta42tta43tta44tta45tta46

gdzie a i,j /** indeks**/ oznacza element tablicy zlokalizowany
w:
- wierszu i
- kolumnie j
Przypiszmy kolejnym elementom tablicy nastepujace wartosci:

tttttttttt11ttt12ttt13ttt14ttt15ttt16
tttttttttt21ttt22ttt23ttt24ttt25ttt26
tttttttttt31ttt32ttt33ttt34ttt35ttt36
tttttttttt41ttt42ttt43ttt44ttt45ttt46

Jest to tablica dwuwymiarowa o wymiarach 4WIERSZE X 6KOLUMN,
czyli krócej 4X6. Liczby bedace elementami tablicy sa typu
całkowitego. Jesli zatem nazwiemy ja TABLICA, to zgodnie z
zasadami przyjetymi w jezyku C/C++ mozemy ja zadeklarowac:

int TABLICA[4][6];

Pamietajmy, ze C++ liczy nie od jedynki a od zera, zatem

TABLICA[0][0] = a11 = 11,
TABLICA[2][3] = a34 = 34 itd.

Znajac zawartosc tablicy mozemy ja zdefiniowac/zainicjowac:

int TABLICA[4][6]={{11,12,13,14,15,16},{21,22,23,24,25,26}
{31,32,33,34,35,36},{41,42,43,44,45,46}};

Taki sposób inicjowania tablicy, aczkolwiek pomaga wyjasnic
metode, z punktu widzenia programistów jest troche
"nieelegancki". Liczbe przypisywana danemu elementowi tablicy
mozna łatwo obliczyc.

TABLICA[i][j] = (i+1)*10 + (j+1);

Przykładowo:

TABLICA[2][5] = (2+1)*10 +(5+1) = 36

Najbardziej oczywistym rozwiazaniem byłoby napisanie petli

int i, j;
for (i=0; i<=3; i++)
ttttt{ for (j=0; j<=5; j++)
tttttttttt{ TABLICA[i][j] = (i+1)*10 + (j+1);}
ttttt}

Spróbujmy przesledzic rozmieszczenie elementów tablicy w pamieci

i odwołac sie do tablicy na kilka sposobów.

[P026.CPP]

int TABLICA[4][6]={{11,12,13,14,15,16},{21,22,23,24,25,26},
{31,32,33,34,35,36},{41,42,43,44,45,46}};
# include <stdio.h>
# include <conio.h>
int *pT;
int i, j;
void main()
{
clrscr();
printf("OTO NASZA TABLICA \n");
for (i=0; i<=3; i++)
{
for (j=0; j<=5; j++)
printf("%d\t", TABLICA[i][j]);
printf("\n");
}
printf("\n\Inicjujemy wskaznik na poczatek tablicy\n");
printf("i INKREMENTUJEMY wskaznik *pT++ \n");

pT=&TABLICA[0][0];
for (i=0; i<4*6; i++)
printf("%d ", *(pT+i));

getch();
}

Zwróc uwage, ze jesli tablica ma wymiary A * B (np. 3 * 4) i
składa sie z k=A*B elementów, to w C++ zakres indeksów wynosi
zawsze 0, 1, 2, .....A*B-2, A*B-1. Tak wiec tablica 10 x 10
(stuelementowa) bedzie składac sie z elementów o numerach
0...99, a nie 1...100.

[P027.CPP]

# include <stdio.h>
# include <conio.h>
int TABLICA[4][6];
int *pT;
int i, j;

void main()
{
clrscr();
printf("Inicjujemy tablice\n");
for (i=0; i<4; i++)
39
tttttfor (j=0; j<6; j++)
ttttt{ TABLICA[i][j] = (i+1)*10 + (j+1); } // INDEKS!
printf("OTO NASZA TABLICA \n");
for (i=0; i<=3; i++)
{
for (j=0; j<=5; j++)
printf("%d\t", TABLICA[i][j]);
printf("\n");
}
printf("\n\Inicjujemy wskaznik na poczatek tablicy\n");
printf("i INKREMENTUJEMY wskaznik *pT++ \n");
pT=&TABLICA[0][0];
for (i=0; i<4*6; i++)
printf("%d ", *(pT+i));

getch();
}

RECZNE I AUTOMATYCZNE GENEROWANIE TABLIC
WIELOWYMIAROWYCH.

Aby nabrac wprawy, spróbujmy pomanipulowac inna tablica, znana
Ci prawie "od urodzenia" - tabliczka mnozenia. Jest to
kwadratowa tablica 10 x 10, której kazdy wyraz opisuje sie
prosta zaleznoscia T(i,j)=i*j. Jesli przypomnimy sobie, ze
indeksy w C++ zaczna sie nie od jedynki a od zera, zapis ten
przybierze nastepujaca forme:

int T[10][10];
T[i][j] = (i+1)*(j+1);

Do pełni szczescia brak jeszcze wskaznika do tablicy:

int *pT;

i jego zainicjowania

pT = &T[0][0];

I juz mozemy zaczynac. Moglibysmy oczywiscie zainicjowac tablice

"na piechote", ale to i nieeleganckie, i pracochłonne, i o
pomyłke łatwiej. Pamietaj, ze komputer myli sie rzadziej niz
programista, wiec zawsze lepiej jemu zostawic mozliwie jak
najwiecej roboty.

[P028.CPP]

# include <stdio.h>
# include <conio.h>
int T[10][10];
int *pT;
int i, j, k;
char spacja = ' ';
void main()
{
clrscr();
printf("\t TABLICZKA MNOZENIA (ineksy)\n");
for (i=0; i<10; i++)
{
for (j=0; j<10; j++)
{ T[i][j] = (i+1)*(j+1);
if (T[i][j]<10)
printf("%d%c ", T[i][j], spacja);
else
printf("%d ", T[i][j]);
}
printf("\n");
}
printf("\n Inicjujemy i INKREMENTUJEMY wskaznik *pT++ \n\n");
pT=&T[0][0];
for (k=0; k<10*10; k++)
{
if (*(pT+k) < 10)
printf("%d%c ", *(pT+k) , spacja);
else
printf("%d ", *(pT+k));
if ((k+1)%10 == 0) printf("\n");
}
getch();
}

Po wynikach jednocyfrowych dodajemy trzy spacje a po
dwucyfrowych dwie spacje. Po dziesieciu kolejnych wynikach
trzeba wstawic znak nowego wiersza. Sprawdzamy te warunki:

if (*(pT+k) < 10) - jesli wynik jest mniejszy niz 10...
lub if (T[i][j] < 10);
if ((k+1) % 10 == 0) - jesli k jest całkowita wielokrotnoscia
10, czyli - jesli reszta z dzielenia równa sie zero...

Zastosowane w powyzszych programach nawiasy klamrowe {}
spełniaja role INSTRUKCJI GRUPUJACEJ i pozwalaja podobnie jak
para BEGIN...END w Pascalu zamknac w petli wiecej niz jedna
instrukcje. Instrukcje ujete w nawiasy klamrowe sa traktowane
jak pojedyncza instrukcja prosta.

Tablice moga zawierac liczby, ale moga zawierac takze znaki.
Przykład prostej tablicy znakowej zawiera nastepny program
przykładowy.

[P029.CPP]

# include <stdio.h>
# include <conio.h>

char T[7][12]={"Poniedzialek",
"Wtorek",
"Sroda",
"Czwartek",
"Piatek",
"Sobota",
"Niedziela"};

char *pT;
int i, j, k;
char spacja=' ';
void main()
{
clrscr();
pT =&T[0][0];
printf("\t TABLICA znakowa (ineksy)\n\n");

for (i=0; i<7; i++)
{
for (j=0; j<12; j++)
printf("%c ", T[i][j] );
printf("\n");
}
printf("\n\t Przy pomocy wskaznika \n\n");

for (k=0; k<7*12; k++)
{
printf("%d ", *(pT+k) ); //TU! - opis w tekscie
if ((k+1)%12 == 0) printf("\n");
}
getch();
}

Nazwy dni maja rózna długosc, czym wiec wypełniane sa puste
miejsca w tablicy? Jesli w miejscu zaznaczonym komentarzem //TU!

zmienisz format z

printf("%c ", *(pT+k) ); na printf("%d ", *(pT+k) );

uzyskasz zamiast znaków kody ASCII.

TABLICA znakowa (ineksy)

P o n i e d z i a l e k
W t o r e k
S r o d a
C z w a r t e k
P i a t e k
S o b o t a
N i e d z i e l a

Przy pomocy wskaznika:

80 111 110 105 101 100 122 105 97 108 101 107
87 116 111 114 101 107 0 0 0 0 0 0
83 114 111 100 97 0 0 0 0 0 0 0
67 122 119 97 114 116 101 107 0 0 0 0
80 105 97 116 101 107 0 0 0 0 0 0
83 111 98 111 116 97 0 0 0 0 0 0
40
78 105 101 100 122 105 101 108 97 0 0 0


Okaze sie, ze puste miejsca zostały wypełnione zerami. Zero w
kodzie ASCII - NUL - '\0' jest znakiem niewidocznym, nie było
wiec widoczne na wydruku w formie znakowej printf("%c"...).

[Z]
________________________________________________________________
1. Posługujac sie wskaznikiem i inkrementujac wskaznik z róznym
krokiem - np. pT += 2; pT += 3 itp., zmodyfikuj programy
przykładowe tak, by uzyskac wydruk tylko czesci tablicy.
2. Spróbuj zastapic inkrementacje wskaznika pT++ dekrementacja,
odwracajac tablice "do góry nogami". Jak nalezałoby poprawnie
zainicjowac wskaznik?
3. Napisz program drukujacy tabliczke mnozenia w układzie
szesnastkowym - od 1 * 1 do F * F.
4. Wydrukuj nazwy dni tygodnia pionowo i wspak.
5. Zinterpretuj nastepujace zapisy:

int *pt_int;
float *pt_float;
int p = 7, d = 27;
float x = 1.2345, Y = 32.14;
void *general;

pt_int = &p;
*pt_int += d;

general = pt_int;

pt_float = &x;
Y += 5 * (*pt_float);

general = pt_float;

const char *name1 = "Jasio"; // wskaznik do STALEJ
char *const name2 = "Grzesio"; // wskaznik do STALEGO ADRESU
________________________________________________________________



LEKCJA 13. Jak tworzyc w programie petle i rozgałezienia.
_______________________________________________________________
W trakcie tej lekcji:
1. Dowiesz sie znacznie wiecej o petlach.
2. Przeanalizujemy instrukcje warunkowe i formułowanie warunków.

_______________________________________________________________

Zaczniemy te lekcje nietypowo - od słownika, poniewaz dobrze
jest rozumiec dokładnie co sie pisze. Tym razem słownik jest
troche obszerniejszy. Pozwalam sobie przytoczyc niektóre słowa
powtórnie - dla przypomnienia i Twojej wygody. Do organizacji
petli beda nam potrzebne nastepujace słowa:

[S!] conditional expressions - wyrazenia warunkowe
structural loops - petle strukturalne
________________________________________________________________
if - jezeli (poprzedza warunek do sprawdzenia);
else - a jesli nie, to (w przeciwnym wypadku...);
for - dla;
while - dopóki (dopóki nie spełnimy warunku);
do - wykonaj, wykonuj;
break - przerwij (wykonanie petli);
switch - przełacz;
case - przypadek, wariant (jedna z mozliwosci);
goto - idz do...
default - domyslny, (automatyczny, pozostały);
continue - kontynuuj (petle);
________________________________________________________________
UWAGA: W C/C++ nie stosuje sie słowa THEN.

PETLA TYPU for.

Ogólna postac petli for jest nastepujaca:

for (W_inicjujace; W_logiczne; W_kroku) Instrukcja;

gdzie skrót W_ oznacza wyrazenie. Kazde z tych wyrazen moze
zostac pominiete (patrz --> for(;;)).

Wykonanie petli for przebiega nastepujaco:

1. Wykonanie JEDEN raz WYRAZENIA INICJUJACEGO.
2. Obliczenie wartosci LOGICZNEJ wyrazenia logicznego.
3. Jesli W_logiczne ma wartosc PRAWDA (TRUE) nastapi wykonanie
Instrukcji.
4. Obliczenie wyrazenia kroku.
5. Powtórne sprawdzenie warunku - czy wyrazenie logiczne ma
wartosc rózna od zera. Jesli wyrazenie logiczne ma wartosc zero,
nastapi zakonczenie petli.

Warunek jest testowany PRZED wykonaniem instrukcji. Jesli zatem
nie zostanie spełniony warunek, instrukcja moze nie wykonac sie
ANI RAZ.

Instrukcja moze byc INSTRUKCJA GRUPUJACA, składajaca sie z
instrukcji prostych, deklaracji i definicji zmiennych lokalnych:


{ ciag deklaracji lub definicji;
ciag instrukcji; }

Ogromnie wazny jest fakt, ze C++ ocenia wartosc logiczna
wyrazenia według zasady:

0 - FALSE, FAŁSZ, inaczej ZERO LOGICZNE jesli WYRAZENIE == 0 lub

jest fałszywe w znaczeniu logicznym;
1 - TRUE, PRAWDA, JEDYNKA LOGICZNA, jesli wyrazenie ma
DOWOLNA
WARTOSC NUMERYCZNA RÓZNA OD ZERA (!) lub jest prawdziwe w
sensie
logicznym.

Przykład:

"Klasycznie" zastosowana petla for oblicza pierwiastki
kwadratowe kolejnych liczb całkowitych.

#include <math.h>
#include <stdio.h>

void main()
{
int n;
for (n=0; n<=100; n++) printf("%f\t", sqrt(n));
getch();
}

Wyrazenie inicjujace moze zostac pominiete. Innymi słowy zmienna

moze zostac zainicjowana na zewnatrz petli, a petla przejmie ja
taka jaka jest w danym momencie. Przykładowo:
.....
{
float n;
n=(2*3)/(3*n*n - 1.234);
......
for (; n<=100; n++) printf("%f4.4\t", sqrt(n));

Przykład:

Warunek przerwania petli moze miec takze inny charakter. W
przykładzie petla zostanie przerwana, jesli róznica pomiedzy
kolejnymi pierwiastkami przekroczy 3.0.

void main()
{
float y=0, n=0;
for (; (sqrt(n)-y)<=3.0; n++)
ttttt{ y=sqrt(n);
ttttt printf("%f2.3\t", y);
ttttt}
getch();
}

UWAGA:
Sprawdz, czy nawias (sqrt(n)-y)<=3 mozna pominac? Jaki jest
priorytet operatorów w wyrazeniach:
(sqrt(n)-y)<=3.0 i sqrt(n)-y<=3.0
Jaki bedzie wynik? Dlaczego?

Przykład:

41
Instrukcja stanowiaca ciało petli moze byc instrukcja pusta a
wszystkie istotne czynnosci moga zostac wykonane w ramach samego

"szkieletu" for. Program przykładowy sprawdza ile kolejnych
liczb całkowitych trzeba zsumowac by uzyskac sume nie mniejsza
niz tysiac.

void main()
{
float SUMA=0, n=0;
for (; SUMA < 1000; SUMA+=(++n));
printf("%f", n);
getch();
}

[???] CZY NIE MOZNA JASNIEJ ???
________________________________________________________________
Mozna, ale po nabraniu wprawy takie skróty pozwola Ci
przyspieszyc tworzenie programów. Zmniejszenie wielkosci pliku
tekstowego jest w dzisiejszych czasach mniej istotne.
Rozszyfrujmy zapis SUMA+=(++n). Preinkrementacja nastepuje PRZED

uzyciem zmiennej n, wiec:
1. Najpierw ++n, czyli n=n+1.
2. Potem SUMA=SUMA+ (n+1).
Dla wyjasnienia przedstawiam dwie wersje (obie z petla for):

void main() { void main()
float SUMA=0; { float SUMA=0, n=0;
int n; for (; SUMA < 1000; SUMA+=(++n)); }
clrscr();
for (n=0; SUMA<=1000; n++)
{
SUMA=SUMA+n;
}
}
________________________________________________________________

To jeszcze nie koniec pokazu elastycznosci C/C++. W petli for
wolno nam umiescic wiecej niz jedno wyrazenie inicjujace i
wiecej niz jedno wyrazenie kroku oddzielajac je przecinkami.

flat a, b, c;
const float d=1.2345;
void main()
{
for (a=5,b=3.14,c=10; c; ++a,b*=d,c--)
printf("\n%f\t%f\t%f", a,b,c);
getch();
}

Zwróc uwage, ze zapisy warunku:

if (c)...; i if (c != 0)...;

sa w C++ równowazne.

Przykład:

Program bedzie pisał kropki az do nacisniecia dowolnego
klawisza, co wykryje funkcja kbhit(), bedaca odpowiednikem
KeyPressed w Pascalu. Zapis !kbhit() oznacza "NIE NACISNIETO
KLAWISZA", czyli w buforze klawiatury nie oczekuje znak. Zwróc
uwage, ze funkcja getch() moze oczekiwac na klawisz w
nieskonczonosc. Aby uniknac kłopotliwych sytuacji, czasem
znacznie wygodniej jest zastosowac kbhit(), szczególnie, jesli
czekamy na DOWOLNY klawisz.

void main()
{
for (; !kbhit(); printf("."));
}

Przykład:

Wskaznik w charakterze zmiennej roboczej w petli typu for. Petla

powoduje wypisanie napisu.

char *Ptr = "Jakis napis";
void main()
{
for (; (*Ptr) ;)
printf("%c",*Pt++);
getch();
}


AUTOMATYCZNE GENEROWANIE TABLIC W PETLI for

Na dyskietce znajdziesz jeszcze kilka przykładów FORxx.CPP
uzycia petli. A teraz, zanim bedziemy kontynuowac nauke -
przykładowy program do zabawy. Petla for słuzy do wykrywania
zgodnosci klawisza z elementami tablicy TABL[]. W tablicy D[]
umieszczone zostały czestotliwosci kolejnych dzwieków, które
program oblicza sam, wykorzystujac przyblizony współczynnik.

[P030.CPP]

# include "conio.h"
# include "dos.h"
# include "math.h"
# include "stdio.h"

char TABL[27]={"zsxdcvgbhnjm,ZSXDCVGBHNJM<"};
char k;
float D[26];
int i;

void main()
{
clrscr();
printf("[A]- KONIEC, dostepne klawisze: \n");
printf(" ZSXDCVGBHNJM,i [Shift]");
D[0]=200.0;
for(i=1; i<26; i++) D[i]=D[i-1]*1.0577;
for (;;) //patrz przyklad {*}
{
k = getch();
for(i=0; i<27; i++)
{ if (k==TABL[i])
{ sound(D[i]); delay(100); nosound(); }
};
if (k=='a'|| k=='A') break; //Wyjscie z petli.
k = '0';
};
}

Po uruchomieniu programu klawisze działaja w sposób
przypominajacy prosty klawiszowy instrument muzyczny.

Automatyczne zainicjowanie tablicy wielowymiarowej mozemy
pozostawic C++. Wielkosc tablicy moze byc znana na etapie
kompilacji programu, lub okreslona w ruchu programu.

C++ traktuje stała (const) jako szczególny przypadek wyrazenia
stałowartosciowego (ang. true constant expression). Jesli
zadeklarowalismy zmienna wymiar jako stała, mozemy zastosowac ja

np. do zwymiarowania tablicy TAB[]. Przykład ponizej przedstawia

takie własnie zastosowanie stałych w C++.

[P031.CPP]
/* Inicjowanie tablicy przy pomocy stałej */

# include <iostream.h>

main()
{
const int wymiar = 7; //Deklaracja stałej
char TAB[wymiar]; //Deklaracja tablicy

cout << "\n Wielkosc tablicy TAB[] wynosi: " << sizeof TAB;
cout << " bajtow.";
return 0;
}

Umozliwia to dynamiczne inicjowanie tablic pod warunkiem
rygorystycznego przestrzegania zasady, ze do zainicjowana stałej

mozemy zastosowac wyłacznie wyrazenie stałowartosciowe. .

[S] sizeof - wielkosc w bajtach.

DANE PREDEFINIOWANE.
42

Dla ułatwienia zycia programiscie producenci kompilatorów C++
stosuja stałe predefiniowane w plikach nagłówkowych, np.:

_stklen - wielkosc stosu,
O_RDONLY - tryb otwarcia pliku "tylko do odczytu",
GREEN - numer koloru w palecie, itp., itp.

Predefiniowanych stałych mozemy uzywac do deklarowania
indeksów/rozmiarów tablic.



PETLA TYPU while.

Petle typu while stosuje sie na ogół "do skutku", tj. do momentu

spełnienia warunku, zwykle wtedy, gdy nie jestesmy w stanie
przewidziec potrzebnej ilosci cykli. Konstrukcja petli while
wyglada nastepujaco:

while (Wyrazenie_logiczne) Instrukcja;

Jesli Wyrazenie_logiczne ma wartosc rózna od zera, to zostanie
wykonana Instrukcja. Sprawdzenie nastepuje PRZED wykonaniem
Instrukcji, totez Instrukcja moze nie zostac wykonana ANI RAZU.
Instrukcja moze byc INSTRUKCJA GRUPUJACA.

Przykład

Stosujemy petle while do programu piszacego kropki (patrz
wyzej).

void main()
{
while (!kbhit()) printf(".");
}

Przykład

Stosujemy petle while w programie obliczajacym sume.

void main(){
float SUMA=0, n=0;
clrscr();
while (SUMA<1000) SUMA+=(++n);
printf("SUMA: %4.0f ostatnia liczba: %3.0f",
SUMA, n);
getch();
}

[P032.CPP]

char *Pointer1="Koniec napisu to \0, *Pointer==0 ";
char *Pointer2="Koniec napisu to \0, *Pointer==0 ";

void main(){
clrscr();
while (*Pointer1)
printf("%c", *Pointer1++);
printf("\nZobacz ten NUL na koncu lancucha znakow\n");
while (*Pointer2)
printf("%c", *Pointer2++);
printf("%d", *Pointer2);
getch();
}

PETLA do...while.

Konstrukcja dwuczłonowa do...while tworzy petle, która:

* jest wykonywana zawsze CO NAJMNIEJ JEDEN RAZ, poniewaz warunek

jest sprawdzany nie na wejsciu do petli, a na wyjsciu z petli;
* przerwanie petli powodowane jest przez NIESPEŁNIENIE WARUNKU.

Schemat petli do...while jest nastepujacy:

do Instrukcja while (Wyrazenie_logiczne);

Instrukcja moze byc instrukcja grupujaca.

Przykład:

void main()
{
do
{printf(".");}
while (!kbhit());
printf("Koniec petli....");
}

INSTRUKCJA WARUNKOWA if, if...else i if...else...if..

Instrukcja warunkowa ma postac:

if (Wyrazenie) Instrukcja;
if (Wyrazenie) Instrukcja1 else Instrukcja2;

Jesli Wyrazenie ma wartosc rózna od zera (LOGICZNA badz
NUMERYCZNA !) to zostanie wykonana Instrukcja1, w przeciwnym
razie wykonana zostanie Instrukcja2. Instrukcje moga byc
instrukcjami grupujacymi. Słowa kluczowe if i else moga byc
stosowane wielokrotnie. Pozwala to tworzyc np. tzw. drzewa
binarne.

Przykład:

void main()
{
float a;
scanf("%f", &a);
if (a<0) printf("Ujemna!");
tttttelse if (a==0) printf("Zero!");
ttttttttttelse printf("Dodatnia!");
}

Przykład:

if (a>0) if (a<100) printf("Dwucyfrowa"); else printf("100+");

inaczej:

if(a>0) {if(a<100) printf("Dwucyfrowa"); else printf("100+");}

Wyrazenie moze zawierac operatory logiczne:

if (a>0 && a<100) printf("Dwucyfrowa"); else printf("100+");

Zapis 100+ oznacza "sto i wiecej".

Przykład:

C++ pozwala na krótszy zapis instrukcji warunkowej:

(a>b)? MAX=a : MAX=b;

inaczej:

if (a>b) MAX=a; else MAX=b;

INSTRUKCJE break i continue.

Instrukcja break powoduje natychmiastowe bezwarunkowe
opuszczenie petli dowolnego typu i przejscie do najblizszej
instrukcji po zakonczeniu petli. Jesli w petli for opuscimy
wyrazenie logiczne, to zostanie automatycznie przyjete 1. Petla
bedzie zatem wykonywana bezwarunkowo w nieskonczonosc. W
przykładzie ponizej nieskonczona petle przerywa po podaniu z
kalwiatury zera instrukcja break.

Przykład:

float a, sigma=0;
void main(){
for (;;)
{
printf("\n Podaj liczbe do sumowania\n");
scanf("%f", &a);
if (a==0) break;
sigma+=a;
printf("\n SUMA: %f",sigma);
}
printf("Nastapil BREAK");
getch();
}
43


Instrukcja continue.

Instrukcja continue powoduje przedwczesne, bezwarunkowe
zakonczenie wykonania wewnetrznej instrukcji petli i podjecie
próby realizacji nastepnego cyklu petli. Próby, poniewaz
najpierw zostanie sprawdzony warunek kontynuacji petli. Program
z przykładu poprzedniego zmodyfikujemy w taki sposób, by

* jesli liczba jest dodatnia - dodawał ja do sumy sigma;
* jesli liczba jest ujemna - nie robił nic, pomijał biezaca
petle przy pomocy rozkazu continue;
(Poniewaz warunek wejsciowy petli jest zawsze spełniony, to
petle zawsze uda sie kontynuowac.)
* jesli liczba równa sie zero - przerywał petle instrukcja break


Przykład:

float a, sigma=0;
void main()
{
for (;;)
{
printf("\n Sumuje tylko liczby dodatnie\n");
scanf("%f", &a);
if (a<0) continue;
if (a==0) break;
sigma+=a;
printf("\n SUMA: %f",sigma);
}
printf("Nastapil BREAK");
getch();
}

INSTRUKCJE switch i case.

Instrukcja switch dokonuje WYBORU w zaleznosci od stanu
wyrazenia przełaczajacego (selector) jednego z mozliwych
przypadków - wariantów (case). Kazdy wariant jest oznaczony przy

pomocy stałej - tzw. ETYKIETY WYBORU. Wyrazenie przełaczajace
moze przyjmowac wartosci typu int. Ogólna postac istrukcji jest
nastepujaca:

switch (selector)
{
case STAŁA1: Ciag_instrukcji-wariant 1;
case STAŁA2: Ciag_instrukcji-wariant 2;
...............................
case STAŁAn: Ciag_instrukcji-wariant n;
default : Ostatni_ciag_instrukcji;
}

Nalezy podkreslic, ze po dokonaniu wyboru i skoku do etykiety
wykonane zostana równiez WSZYSTKIE INSTRUKCJE PONIZEJ DANEJ
ETYKIETY. Jesli chcemy tego uniknac, musimy dodac rozkaz break.

[P033.CPP]

# define pisz printf //dla przypomnienia
# include <stdio.h>
void main()
{
int Numer_Dnia;
pisz("\nPodaj numer dnia tygodnia\n");
scanf("%d", &Numer_Dnia);
switch(Numer_Dnia)
{
case 1: pisz("PONIEDZIALEK.");
case 2: pisz("WTOREK");
case 3: pisz("SRODA.");
case 4: pisz("CZWARTEK.");
case 5: pisz("PIATEK.");
case 6: pisz("SOBOTA.");
case 7: pisz("NIEDZIELA.");
default: pisz("\n *********************");
}
}

Zwróc uwage, ze w przykładzie wariant default zostanie wykonany
ZAWSZE, nawet jesli podasz liczbe wieksza niz 7.

[P034.CPP]

# define pisz printf
# include <stdio.h>

void main()
{
int Numer_Dnia;
pisz("\nPodaj numer dnia tygodnia\n");
scanf("%d", &Numer_Dnia);
switch(Numer_Dnia)
{
case 1: pisz("PON."); break;
case 2: pisz("WTOR"); break;
case 3: pisz("SRO."); break;
case 4: pisz("CZW."); break;
case 5: pisz("PIO."); break;
case 6: pisz("SOB."); break;
case 7: pisz("NIEDZ."); break;
default: pisz("\n ?????");
}
}

Instrukcja break przerywa wykonanie. Wariant default zostanie
wykonany TYLKO w przypadku podania liczby wiekszej niz 7.

INSTRUKCJA POWROTU return.

Słuzy do zakonczenia wykonania zawierajacej ja funkcji i moze
miec postac:

return;
return stała;
return Wyrazenie;
return (wyrazenie);

Przykład:

Definiujemy funkcje _dodaj() zwracajaca, poprzez instrukcje
return wartosc przekazanego jej w momencie wywołania argumentu
powiekszona o 5.

float _dodaj(float x)
{
x+=5;
return x;
}

Funkcja _dodaj() zwraca wartosc i nadaje te wartosc zmiennej
wynik zadeklarowanej nazewnatrz funkcji i znanej w programie
głównym. A oto program w całosci.

[P035.CPP]

float funkcja_dodaj(float x)
{
x += 5;
return x;
}
float dana = 1, wynik = 0;

void main()
{
clrscr();
wynik = funkcja_dodaj(dana);
printf("%f", wynik);
}

INSTRUKCJA SKOKU BEZWARUNKOWEGO goto I ETYKIETY.

Składnia instrukcji skoku goto jest nastepujaca:

goto Identyfikator_etykiety;

UWAGA: Po kazdej etykiecie musi wystapic CO NAJMNIEJ JEDNA
INSTRUKCJA. Jesli etykieta oznacza koniec programu, to musi po
niej wystapic instrukcja pusta. Instrukcja goto nie cieszy sie
powodzeniem ani dobra sława (niesłusznie!). Ostrozne i umiejetne

jej stosowanie jeszcze nikomu nie zaszkodziło. Nalezy tu
zaznaczyc, ze etykieta nie wymaga deklaracji.

44
Przykład:

Program ponizej generuje dzwieki i "odlicza".

[P036.CPP]

#include <dos.h>
#include <stdio.h>

void main()
{
int czestotliwosc=5000, n=10, milisekundy=990;
printf("\n");
start:
{
sound(czestotliwosc);
delay(milisekundy);
nosound();
czestotliwosc/=1.2;
printf("%d\b", --n);
if (n) goto start; //petle strukturalne zrob sam(a)
}

koniec: ;
} // Tu jest instrukcja pusta.

[S!] DOS API function names - nazwy funkcji z interfejsu DOS
________________________________________________________________
sound - dzwiek;
delay - opóznienie, zwłoka;
nosound - bez dzwieku (wyłacz dzwiek);
________________________________________________________________

[Z]
________________________________________________________________
1. Biorac pod uwage, ze iloraz czestotliwosci kolejnych dzwieków

jest stały tzn. Fcis/Fc=Ffis/Ff=....=const oraz, ze oktawa to
podwojenie czestotliwosci, opracuj program i oblicz
czestotliwosci poszczególnych dzwieków.
2. Spróbuj zastosowac w programie przykładowym kolejno petle
for, while, do...while.
3. Zastosuj we własnym programie doswiadczalnym instrukcje
switch.



LEKCJA 14. Jak tworzyc i stosowac struktury.

________________________________________________________________

W trakcie tej lekcji poznasz pojecia:
* Klasy zmiennej.
* Struktury.
* Pola bitowego.
* Unii.
Dowiesz sie takze wiecej o operacjach logicznych.
________________________________________________________________


CO TO JEST KLASA ZMIENNEJ?

W jezyku C i C++ programista ma wiekszy wpływ na rozmieszczenie
zmiennych w pamieci operacyjnej komputera i w rejestrach
mikroprocesora. Moze to miec decydujacy wpływ na dostepnosc
danych z róznych miejsc programu i szybkosc działania programu.
Nalezy podkreslic, ze TYP ZMIENNEJ (char, int, float itp.)
decyduje o sposobie interpretacji przechowywanych w pamieci zer
i jedynek, natomiast KLASA ZMIENNEJ decyduje o sposobie
przechowywania zmiennej w pamieci. W C++ wystepuja cztery klasy
zmiennych.

ZMIENNE STATYCZNE - static.

Otrzymuja stała lokalizacje w pamieci w momencie uruchamiania
programu. Zachowuja swoja wartosc przez cały czas realizacji
programu, chyba, ze swiadomie zazadamy zmiany tego stanu - np.
instrukcja przypisania.
Przykład deklaracji: static float liczba;

W wiekszosci kompilatorów C++ zmienne statyczne, które nie
zostały jawnie zainicjowane w programie, otrzymuja po
zadeklarowaniu wartosc ZERO.

ZMIENNE AUTOMATYCZNE - auto.

Otrzymuja przydział miejsca w pamieci dynamicznie - na stosie
procesora, w momencie rozpoczecia wykonania tego bloku programu,

w którym zmienne te zostały zadeklarowane. Przydzielenie pamieci

nie zwalnia nas z obowiazku zainicjowania zmiennej (wczesniej
wartosc zmiennej jest przypadkowa). Zmienne automatyczne
"znikaja" po zakonczeniu wykonywania bloku. Pamiec im
przydzielona zostaje zwolniona. Przykład: auto long suma;

ZMIENNE REJESTROWE - register.

Zmienne rejestrowe sa takze zmiennymi lokalnymi, widocznymi
tylko wewnatrz tego bloku programu, w którym zostały
zadeklarowane. C++ moze wykorzystac dwa rejestry mikroprocesora
- DI i SI do przechowywania zmiennych. Jesli zadeklarujemy w
programie wiecej zmiennych jako zmienne rejestrowe - zostana one

umieszczone na stosie. Znaczne przyspieszenie działania programu

powoduje wykorzystanie rejestru do przechowywania np. licznika
petli.

Przykład:

register int i;
.....
for (i=1; i<1000; i++) {.....}

ZMIENNE ZEWNETRZNE - extern.

Jesli zmienna została - raz i TYLKO RAZ - zadeklarowana w
pojedynczym segmencie duzego programu, zostanie w tymze
segmencie umieszczona w pamieci i potraktowana podobnie do
zmiennych typu static. Po zastosowaniu w innych segmentach
deklaracji extern zmienna ta moze byc dostepna w innym segmencie

programu.

Przykład: extern int NUMER;


STRUKTURY.

Poznane wczesniej tablice moga zawierac wiele danych, ale
wszystkie te dane musza byc tego samego typu. Dla zgrupowania
powiazanych ze soba logicznie danych róznego typu C/C++ stosuje
STRUKTURY, deklarowane przy pomocy słowa struct. Kolejne pola
struktury sa umieszczane w pamieci zgodnie z kolejnoscia ich
deklarowania. Strukture, podobnie jak zmienna, MUSIMY
ZADEKLAROWAC. Struktura jest objektem bardziej złozonym niz
pojedyncza zmienna, wiec i deklaracja struktury jest bardziej
skomplikowana. Deklaracja struktury składa sie z nastepujacych
elementów:

1. Słowo kluczowe struct (obowiazkowe).
2. Nazwa (opcjonalna). Jesli podamy nazwe, to nazwa ta bedzie
oznaczac dany typ struktury.
3. Nawias klamrowy {
4. Deklaracje kolejnych składników struktury.
5. Nawias klamrowy }
6. Lista nazw struktur okreslonego powyzej typu (moze zostac
zadeklarowana oddzielnie).

Przykład. Deklaracja ogólnego typu struktury i okreslenie
wewnetrznej postaci struktury.

struct Ludzie
{
char Imiona[30];
char Nazwisko[20];
int wiek;
char pokrewienstwo[10]
};

Jesli okreslimy juz typ struktury - czyli rodzaj, wielkosc i
przeznaczenie poszczególnych pól struktury, mozemy dalej tworzyc

- deklarowac i inicjowac konkretne struktury danego typu.

45
Przykład. Deklaracja zmiennych - struktur tego samego typu.

struct Ludzie Moi, Twoi, Jego, Jej, Szwagra;

Deklaracje struktur mozna połaczyc.

Przykład. Połaczona deklaracja struktur.

struct Ludzie
{ char pokrewienstwo[10];
char Imiona[30];
int wiek;
} Moi, Twoi, Szwagra;

Struktury statyczne

* maja stałe miejsce w pamieci w trakcie całego programu;
* sa "widoczne" i dostepne w całym programie.

Zadeklarujemy teraz typ struktury i zainicjujemy dwie struktury.


Przykład. Zainicjowanie dwu struktur statycznych.

struct Ludzie
{ char pokrewienstwo[10];
char Imiona[30];
int wiek;
};

struct Ludzie Moi, Szwagra;
static struct Ludzie Moi = { "Stryjek", "Walenty", 87 };
static struct Ludzie Szwagra = { "ciotka", "Ala", 21 };

Zapis

static struct Ludzie Szwagra;

oznacza:
statyczna struktura typu "Ludzie" pod nazwa "Szwagra".

Do struktury w całosci mozemy odwoływac sie za pomoca jej nazwy
a do poszczególnych elementów struktury poprzez nazwe struktury
i nazwe pola struktury - ROZDZIELONE KROPKA ".". Zademonstrujmy
to na przykładzie. Zwróc uwage na rózne sposoby przekazywania
danych pomiedzy strukturami:

C4.Wiek=Czlowiek2.Wiek; - przekazanie zawartosci pojedynczego
pola numerycznego;
C4=Czlowiek3; - przekazanie zawartosci całej struktury Czlowiek3

do C4.

Przykład. Program manipulujacy prosta struktura.

[P037.CPP]

int main()
{

struct Ludzie
{
char Imie[20];
int Wiek;
char Status[30];
char Tel_Nr[10];
};

static struct Ludzie
Czlowiek1={"Ala", 7, "Ta, co ma Asa","?"},
Czlowiek2={"Patrycja", 13, "Corka", "8978987"},
Czlowiek3={"Krzysztof", 27, "Kolega z przedszkola", "23478"};

struct Ludzie C4, C5;

C4=Czlowiek3;
C4.Wiek=Czlowiek2.Wiek;
C5=Czlowiek1;

clrscr();

printf("%s %d %s\n", C4.Imie, C4.Wiek, C4.Status);
printf("%s %s",C5.Imie, C5.Status);

return 0;
}

Tablice moga byc elementami struktur, ale i odwrotnie - ze
struktur, jak z cegiełek mozna tworzyc konstrukcje o wyzszym
stopniu złozonosci - struktury struktur i tablice struktur.
Jesli tablica składa sie z liczb typu int, to deklarujemy ja:

int TABLICA[10];

jesli tablica składa sie ze struktur, to deklarujemy ja:

struct TABLICA[50];

W przykładzie ponizej przedstawiono

* deklaracje jednowymiarowej tablicy LISTA[50],
* elementami tablicy sa struktury typu SCzlowiek,
* jednym z elementów kazdej struktury SCzlowiek jest struktura
"nizszego rzedu" typu Adres;

[P038.CPP]

int main()
{

struct Adres
{
char Ulica[30];
int Nr_Domu;
int Nr_Mieszk;
};

struct SCzlowiek
{
char Imie[20];
int Wiek;
struct Adres Mieszkanie;
};

struct SCzlowiek LISTA[50];

LISTA[1].Wiek=34;
LISTA[1].Mieszkanie.Nr_Domu=29;
printf("%d", LISTA[1].Mieszkanie.Nr_Domu);

return 0;
}

Zapis

printf("%d", LISTA[1].Mieszkanie.Nr_Domu

oznacza:
* wybierz element nr 1 z tablicy LISTA;
(jak wynika z deklaracji tablicy, kazdy jej element bedzie miał
wewnetrzna strukture zorganizowana tak, jak opisano w deklaracji

struktury SCzlowiek);
* wybierz ze struktury typu SCzlowiek pole Mieszkanie;
(jak wynika z deklaracji, pole Mieszkanie bedzie miało
wewnetrzna organizacje zgodna ze struktura Adres);
* ze struktury typu Adres wybierz pole Nr_Domu;
* Wydrukuj zawartosc pola pamieci interpretujac ja jako liczbe
typu int - w formacie %d.

Słowo struktura tak doskonale pasuje, ze chciałoby sie
powiedziec:
jesli struktura struktur jest wielopoziomowa, to podobnie, jak
przy wielowymiarowych tablicach, kazdy poziom przy nadawaniu
wartosci musi zostac ujety w dodatkowa pare nawiasów klamrowych.


[???] A CO Z ŁANCUCHAMI ZNAKOWYMI ?
________________________________________________________________
Jezyk C++ oferuje do kopiowania łancuchów znakowych specjalna
funkcje strcpy(). Nazwa funkcji to skrót STRing CoPY (kopiuj
łancuch). Sposób wykorzystania tej funkcji:

strcpy(Dokad, Skad); lub
strcpy(Dokad, "łancuch znaków we własnej osobie");

46
Szczegóły - patrz Lekcja o łancuchach znakowych.
________________________________________________________________


STRUKTURY I WSKAZNIKI.

Wskazniki moga wskazywac strukture w całosci lub element
struktury. Jezyk C/C++ oferuje specjalny operator -> który
pozwala na odwoływanie sie do elementów struktury. W przykładzie

ponizej przedstawiono rózne sposoby odwołania sie do elementów
trzech identycznych struktur STA, STB, STC.

[P039.CPP]

int main()
{

struct
{
char Tekst[20];
int Liczba1;
float Liczba2;
} STA, STB, STC, *Pointer;

STA.Liczba1 = 1;
STA.Liczba2 = 2.2;
strcpy(STA.Tekst, "To jest tekst");

STB=STA;

Pointer = &STC;
Pointer->Liczba1 = 1;
Pointer->Liczba2 = 2.2;
strcpy(Pointer->Tekst, STA.Tekst);

printf("\nLiczba1-STA Liczba2-STB Tekst-STC\n\n");
printf("%d\t", STA.Liczba1);
printf("%f\t", STB.Liczba2);
printf("%s", Pointer->Tekst);

return 0;
}

Rozszyfrujmy zapis:

strcpy(Pointer->Tekst, STA.Tekst);

Skopiuj łancuch znaków z pola Tekst struktury STA do pola Tekst
struktury wskazywanej przez pointer. Prawda, ze to całkiem
proste?

[???] CZY MUSIMY TO ROZDZIELAC ?
________________________________________________________________
Jak zauwazyłes, liczby moglibysmy zapisywac takze jako łancuchy
znaków, ale wtedy nie moglibysmy wykonywac na tych liczbach
działan. Konwersje liczba - łancuch znaków lub odwrotnie łancuch

znaków - liczba wykonuja w C specjalne funkcje np.:
atoi() - Ascii TO Int.;
itoa() - Int TO Ascii itp.
Wiecej informacji na ten temat i przykłady znajdziesz w dalszej
czesci ksiazki.
________________________________________________________________


Elementami struktury moga byc zmienne dowolnego typu, ładznie z
innymi strukturami.

Ciekawostka:
________________________________________________________________
Wskaznik do deklarowanej struktury moze byc w jezyku C/C++ jak
jeden z jej WŁASNYCH elementów. Jesli wskaznik wchodzacy w skład

struktury wskazuje na WŁASNA strukture, to nazywa sie to
AUTOREFERENCJA STRUKTURY.
________________________________________________________________

POLA BITOWE.

Czesto zdarza sie, ze jakas zmienna ma zawezony zakres wartosci.

Dla przykładu zmienne logiczne (tzw. flagi) to zawsze tylko 0
lub 1. Wiek rzadko przekracza 255 lat a liczba dzieci zwykle nie

jest wieksza niz 15. Nawet najbardziej niestali panowie nie
zdaza ozenic sie i rozwiesc wiecej niz 7 razy. Gdybysmy zatem
chcieli zapisac informacje

* płec 0 - mezczyzna, 1 - kobieta ( 1 bit );
* wiek 0 - 255 lat (8 bitów);
* ilosc dzieci 0 - 15 (4 bity);
* kolejny numer małzenstwa 0 - 7 (3 bity);

to przeciez wszystkie te informacje moga nam sie zmiescic w
jednym szesnastobitowym rejestrze lub w dwu bajtach pamieci.
Takie kilka bitów wydzielone i majace okreslone znaczenie to
własnie pole bitowe. C++ pozwala takze na uwzglednianie znaku w
polach bitowych. Pola bitowe moga byc typu int i unsigned int
(czyli takie jak w przykładzie ponizej). Jesli jakies dane
chcemy przechowywac w postaci pola bitowego, w deklaracji
struktury sygnalizujemy to dwukropkiem. Stwarza to dwie istotne
mozliwosci:
* bardziej ekonomicznego wykorzystania pamieci;
* łatwego dodatkowego zaszyfrowania danych.

[P040.CPP]

//Pamietaj o dolaczeniu plikow naglowkowych !

int main()
{

struct USC {
int Sex : 1;
unsigned Wiek : 8;
unsigned Dzieci : 4;
unsigned Ktora : 3; } Facet;

int bufor;
clrscr();
Facet.Sex = 0;
printf("\n Ile ma lat ? : ");
scanf("%d", &bufor); Facet.Wiek = bufor;
printf("\n Ktore malzenstwo ? : ");
scanf("%d", &bufor); Facet.Ktora = bufor;
printf("\n Ile dzieci ? : ");
scanf("%d", &bufor); Facet.Dzieci = bufor;
printf("\n\n");
if (Facet.Ktora) printf("Facet ma %d zone", Facet.Ktora);
printf("\nPlec: Dzieci: Wiek (lat): \n\n");
printf("%d\t%d\t%d", Facet.Sex, Facet.Dzieci, Facet.Wiek);
getch();

return 0;
}

Uruchom program i sprawdz co sie stanie, jesli Facet bedzie miał

np. 257 lat lub 123 zone. Przekroczenie zadeklarowanego zakresu
powoduje obciecie czesci bitów.

Aby uzyskac "wyrównanie" pola bitowego do poczatku słowa nalezy
przed interesujacym naspolem bitowym zdefiniowac tzw. pole
puste:

* pole bitowe bez nazwy;
* długosc pola pustego powinna wynosic 0.

Ponizej przedstawiam przykład pola bitowego zajmujacego trzy
kolejne słowa 16 bitowe. Dodanie pola pustego wymusza
rozpoczecie pola pole_IV od poczatku trzeciego słowa maszynowego

(zakładamy, ze pracujemy z komputerem 16 bitowym).

struct
{
unsigned pole_I:4;
unsigned pole_II:10;
unsigned pole_III:4;
unsigned :0; /* to jest pole puste */
unsigned pole_IV:5;
} pole_przykladowe;

Zwróc uwage, ze czesc bitów w drugim i trzecim słowie maszynowym

47
nie zostanie wykorzystana.

UNIE czyli ZMIENNE WARIANTOWE.

Unie to specyficzne struktury, w których pola pamieci
przeznaczone na objekty róznego typu nakładaja sie. Jesli jakas
zmienna moze byc reprezentowana na kilka sposobów (wariantów) to

sensowne jest przydzielenie jej nie struktury a unii. W danej
chwili pole pamieci nalezace do unii moze zawierac TYLKO JEDEN
WARIANT. W przykładzie - albo cyfre (która znakowo jest widziana

jako znak ASCII o kodzie 2,3,4 itd.) albo napis. Do
zadeklarowania unii słuzy słowo kluczowe union.

[P041.CPP]

#include "string.h"
#include "stdio.h"

int BUFOR, i;

int main()
{

union
{
int Cyfra;
char Napis[20];
} Unia;

for (i=1; i<11; i++)
{
printf("\n Podaj liczbe jednocyfrowa: ");
scanf("%d", &BUFOR);
if (BUFOR<0 || BUFOR>9)
strcpy(Unia.Napis, "TO NIE CYFRA !");
else
Unia.Cyfra = BUFOR;
printf("\n Pole jako Cyfra Pole jako Napis \n");

/* Tu wyswietlimy warianty: Pole jako cyfra i jako napis*/
/* Petla pozwoli Ci przeanalizowac wszystkie cyfry 0...9 */

printf(" %d\t\t\t%s", Unia.Cyfra, Unia.Napis);
}
return 0;
}


Petla w przykładzie nie ma znaczenia. Słuzy tylko dla Twojej
wygody - dzieki niej nie musisz uruchamiac programu
przykładowego wielokrotnie. Podobnie zmienne BUFOR oraz i maja
znaczenie pomocnicze. Zwróc uwage, ze nieprawidłowa
interpretacja zawartosci pola unii moze spowodowac wadliwe
działanie programu.

[Z]
________________________________________________________________
1. W programie przykładowym zamien unie na strukture. Porównaj
działanie.
2 Przydziel na Wiek w strukturze Facet o jeden bit mniej. Ile
lat moze teraz miec Facet ?
3. Zmodyfikuj program przykładowy tak, by napis o liczbie
mezów/zon zalezał od płci - pola Sex.
4. Zamieniwszy unie na strukture w programie, sprawdz, czy
wpływa to na wielkosc pliku *.EXE.
________________________________________________________________

OPERACJE LOGICZNE.

Zaczniemy od operacji logicznych na pojedynczych bitach liczb
całkowitych. W C++ mamy do dyspozycji nastepujace operatory:

~ttttZaprzeczenie (NOT) ~0=1; ~1=0;
|ttttSuma (OR) 0|0=0; 0|1=1; 1|0=1; 1|1=1;
&ttttIloczyn (AND) 0&0=0; 0&1=0; 1&0=0; 1&1=1;
^ttttAlternatywa wyłaczna ALBO...ALBO (XOR)
ttttt0^0=0; 0^1=1; 1^0=1; 1^1=0;
<<tttPrzesuniecie bitów w lewo (Shift Left)
ttttt<< 00001000 = 00010000 dzies. 8<<1=16
>>tttPrzesuniecie bitów w prawo (Shift Right)
ttttt>> 00001000 = 00000100 dzies. 8>>2=2

Miło byłoby poogladac to troche dokładniej w przykładowych
programach, ale potrzebne nam do tego beda funkcje. Zajmijmy sie
wiec uwazniej funkcjami.


LEKCJA 15. Jak posługiwac sie funkcjami.
________________________________________________________________

W trakcie tej lekcji dowiesz sie wiecej o:
* funkcjach i prototypach funkcji;
* przekazywaniu argumentów funkcji;
* współpracy funkcji ze wskaznikami.
_______________________________________________________________

Aby przedstawic działanie operatorów logicznych opracujemy
własna funkcje Demo() i zastosujemy ja w programie przykładowym
[najwazniejszy fragment].

int Demo(int Liczba)
{
int MaxNr=15;
for (; MaxNr>=0; MaxNr--)
{
if ((Liczba>>MaxNr)&1)
printf("1");
else
printf("0");
}
return 0; //Funkcja nie musi nic zwracac
}

Funkcja przesuwa liczbe o kolejno 15, 14, 13 itd. bitów w prawo
i sprawdza, czy 16, 15, 14 bit jest jedynka, czy zerem. Iloczyn
logiczny z jedynka ( 0000000000000001 ) gwarantuje nam, ze wpływ
na wynik operacji bedzie miał tylko ten jeden bit (patrz wyzej -
jak działaja operatory logiczne).

[P042.CPP]

# include <stdio.h>
int Demo(int Liczba)
{
int MaxNr=15;
for (; MaxNr>=0; MaxNr--)
if ((Liczba>>MaxNr)&1) printf("1");
else printf("0");
return 0;
}

char odp;

int main()
{
int X, Y;
clrscr();
printf("\nPodaj dwie liczby calkowite od -32768 do +32767\n");
printf("\nLiczby X i Y rozdziel spacja");
printf("\nPo podaniu drugiej liczby nacisnij [Enter]");
printf("\nLiczby ujemne sa w kodzie dopelniajacym");
printf("\nskrajny lewy bit oznacza znak 0-Plus, 1-Minus");
for(;;)
{
printf("\n");
scanf("%d %d", &X, &Y);
printf("\nX:\t"); Demo(X);
printf("\nY:\t"); Demo(Y);
printf("\n~Y:\t"); Demo(~Y);
printf("\nX&Y:\t"); Demo(X&Y);
printf("\nX|Y:\t"); Demo(X|Y);
printf("\nX^Y:\t"); Demo(X^Y);
printf("\nY:\t"); Demo(Y);
printf("\nY>>1:\t"); Demo(Y>>1);
printf("\nY<<2:\t"); Demo(Y<<2);
printf("\n\n Jeszcze raz? T/N");
odp=getch();
if (odp!='T'&& odp!='t') break;
}
return 0;
}

Jesli operacje maja byc wykonywane nie na bitach a na logicznej
wartosci wyrazen:
48

|| oznacza sume (LUB);
&& oznacza iloczyn (I);
! oznacza negacje (NIE).

Przykłady:

(x==0 || x>5) - x równa sie 0 LUB x wiekszy niz 5;
(a>5 && a!=11) - a wieksze niz 5 I a nie równe 11;
(num>=5 && num!=6 || a>0)

num nie mniejsze niz 5 I num nie równe 6 LUB a dodatnie;
Wyrazenia logiczne sprawdzane instrukcja if MUSZA byc ujete w
nawiasy okragłe.

Do wytworzenia wartosci logicznej wyrazenia moze zostac uzyty
operator relacji: < <= == >= > != . Jesli tak sie nie
stanie, za wartosc logiczna wyrazenia przyjmowane jest:

1, PRAWDA, TRUE, jesli wartosc numeryczna wyrazenia jest rózna
od zera.
0, FAŁSZ, FALSE, jesli wartosc numeryczna wyrazenia jest równa
zero.

Porównaj:

if (a<=0) ...
if (a) ...
if (a+b) ...

Konwersja - przykłady.

C++ dysponuje wieloma funkcjami wykonujacymi takie działania,
np:

itoa() - Integer TO Ascii - zamiana liczby typu int na łancuch
znaków ASCII;
ltoa() - Long int TO Ascii - zamiana long int -> ASCII;
atoi() - zamiana Ascii -> int;
atol() - zamiana Asdii -> long int .

Wszystkie wymienione funkcje przekształcajac liczby na łancuchy
znaków potrzebuja trzech parametrów:

p1 - liczby do przekształcenia;
p2 - bufora, w którym beda przechowywac wynik - łancuch ASCII;
p3 - podstawy (szesnastkowa, dziesietna itp.).

Jesli chcemy korzystac z tych funkcji, powinnismy dołaczyc plik
nagłówkowy z ich prototypami - stdlib.h (STandarD LIBrary -
standardowa biblioteka). A oto przykład.

[P043.CPP]

# include "stdio.h"
# include "stdlib.h"

main()
{
int i;
char B10[10], B2[20], B16[10]; //BUFORY
for (i=1; i<17; i++)
printf("%s %s %s\n",
itoa(i, B10[i], 10),
itoa(i, B2[i], 2),
itoa(i, B16[i], 16));
return 0;
}

[Z]
________________________________________________________________
1. Opracuj program testujacy działanie funkcji atoi().
________________________________________________________________

KILKA SŁÓW O TYPACH DANYCH i KONWERSJI W C/C++ .

Przed przystapieniem do obszernego zagadnienia "funkcje w C"
krótko zasygnalizujemy jeszcze jedno zjawisko. Wiesz z
pewnoscia, ze wykonywane na liczbach dwójkowych mnozenie moze
dac wynik o długosci znacznie wiekszej niz mnozna i mnoznik. W
programach moze sie poza tym pojawic koniecznosc np. mnozenia
liczb zmiennoprzecinkowych przez całkowite. Jak w takich
przypadkach postepuje C++ ?

Po pierwsze:

C/C++ moze sam dokonywac konwersji, czyli zmiany typów danych
naogół zgodnie z zasada nadawania zmiennej "mniej pojemnego"
rodzaju typu zmiennej "bardziej pojemnego" rodzaju przed
wykonaniem operacji;

Po drugie:

my sami mozemy zmusic C++ do zmiany typu FORSUJAC typ swiadomie
w programie.
W przykładzie ponizej podajac w nawiasach zadany typ zmiennej
forsujemy zmiane typu int na typ float.

[P044.CPP]

# include "stdio.h"
void main()
{
int a=7;
printf("%f", (float) a);
}

Konwersja typów nazywana bywa takze "rzutowaniem" typów (ang.
type casting). A oto kilka przykładów "forsowania typów":

int a = 2;
float x = 17.1, y = 8.95, z;
char c;

c = (char)a + (char)x;
c = (char)(a + (int)x);
c = (char)(a + x);
c = a + x;

z = (float)((int)x * (int)y);
z = (float)((int)x * (int)y);
z = (float)((int)(x * y));
z = x * y;

c = char(a) + char(x);
c = char(a + int(x));
c = char(a + x);
c = a + x;

z = float(int(x) * int(y));
z = float(int(x) * int(y));
z = float(int(x * y));
z = x * y;

FUNKCJE BIBLIOTECZNE I WŁASNE W JEZYKU C/C++ .

Pojecie funkcji obejmuje w C/C++ zarówno pascalowe procedury,
jak i basicowe podprogramy. Funkcji zdefiniowanych w C++ przez
prducenta jest bardzo duzo. Dla przykładu, funkcje arytmetyczne,

które mozesz wykorzystac do obliczen numerycznych to np.:

abs() - wartosc bezwzgledna,
cos() - cosinus, sin() - sinus, tan() - tangens,
asin(), atan(), acos(), - funkcje odwrotne ARCUS SINUS...
funkcje hiperboliczne: sinh(), cosh(), tanh(),
wykładnicze i logarytmiczne:
exp() - e^x
log() - logarytm naturalny,
log10() - logarytm dziesietny.

Jesli skorzystasz z systemu Help i zajrzysz do pliku math.h
(Help | Index | math.h), znajdziesz tam jeszcze wiele
przydatnych funkcji.

Funkcja moze, ale nie musi zwracac wartosc do programu -
dokładniej do funkcji wyzszego poziomu, z której została
wywołana. W ciele funkcji słuzy do tego instrukcja return.
Uzytkownik moze w C++ definiowac własne funkcje. Funkcja moze
byc bezparametrowa. Oto przykład bezparametrowej funkcji,
zwracajacej zawsze liczbe całkowita trzynascie:

int F_Trzynascie()
{
return 13;
}
49

Poprawne wywołanie naszej funkcji w programie głównym miałoby
postac:

int main()
{
......
int X;
........ // Funkcja typu int nie musi byc deklarowana.
X = F_Trzynascie();
......
}

Jesli funkcja musi pobrac jakies parametry od programu (funkcji
wyzszego poziomu, wywołujacej)? Zwróc uwage, ze program główny w

C/C++ to tez funkcja - main(). Przykład nastepny pokazuje
definicje funkcji obliczajacej piata potege pobranego argumentu
i wywołanie tej funkcji w programie głównym.

Przykład:

int F_XdoPiatej(int argument)
{
int robocza; //automatyczna wewnetrzna zmienna funkcji
robocza = argument * argument;
robocza = robocza * robocza * argument;
return (robocza);
}

int main()
{
int Podstawa, Wynik, a, b;
... /* Funkcja nie jest deklarowana przed uzyciem */
Wynik = F_XdoPiatej(Podstawa);
.....
a = F_XdoPiatej(b);
.....
return 0;
}

Zwróc uwage, ze definiujac funkcje podajemy nazwe i typ
ARGUMENTU FORMALNEGO funkcji - tu: argument. W momencie
wywołania na jego miejsce podstawiany jest rzeczywisty biezacy
argument funkcji.

Aby zapewnic wysoka dokładnosc obliczen wymienione wyzej funkcje

biblioteczne sqrt(), sin() itp. "uprawiaja" arytmetyke na
długich liczbach typu double. Funkcje taka przed uzyciem w swoim

programie MUSISZ ZADEKLAROWAC. Przykład:

[P045.CPP]

main()
{
double a, b;
double sqrt(); // tu skasuj deklaracje funkcji sqrt()
// a otrzymasz bledny wynik !
clrscr();
printf("Podaj liczbe\n");
scanf("%lf", &a);
b = sqrt(a);
printf("\n %Lf", (long double) b);
getch();
return 0;
}

PROTOTYPY FUNKCJI, czyli jeszcze o deklaracjach funkcji.

Prototyp funkcji to taka deklaracja, która:

* została umieszczona na poczatku programu poza funkcja main(),
* zawiera deklaracje zarówno typu funkcji, jak i typów
argumentów.

Przykład prototypu (funkcja2.cpp):

double FUNKCJA( double X, double Y);

main()
{
double A=0, B=3.14;
printf("Wynik działania funkcji: \n");
printf("%lf", FUNKCJA(A,B));
return 0; }

double FUNKCJA(double X, double Y)
{
return ((1+X)*Y);
}

Prototyp mógłby równie dobrze wygladac tak:

double FUNKCJA(double, double);

nazwy parametrów formalnych nie sa istotne i mozna je pominac.
Jesli prototyp funkcji wyglada tak:

int Funkcja(int, char*, &float)

oznacza to, ze parametrami funkcji sa wskazniki do zmiennych,
badz referencje do zmiennych. Przy rozszyfrowywaniu takiej
"abrakadabry" warto wiedziec, ze

char* oraz char *
int& oraz int &

ma w tym przypadku identyczne znaczenie.

W C++ wolno nie zwracac wartosci funkcjom typu void. To dlatego
własnie czesto rozpoczynalismy programy od

void main()

Skutek praktyczny: Jesli w ciele funkcji typu void wystepuje
instrukcja return (nie musi wystapic) to instrukcja ta nie moze
miec argumentów.

Oto przykład prototypu, definicji i wywołania funkcji typu void:


[P046.CPP]

#include <stdio.h>
#include <conio.h>

void RYSUJPROSTOKAT( int Wys, int Szer, char Wzorek);

void main()
{
clrscr();
RYSUJPROSTOKAT(5, 20, 't'); // klocek ASCII 176 - [Alt]-[176]
getch();
RYSUJPROSTOKAT(15, 15, 't'); //[Alt]-[177]
getch();
}

void RYSUJPROSTOKAT( int Wys, int Szer, char Wzorek)
{
int i, j; // automatyczne zmienne wewnetrzne funkcji
for(i=1; i<=Wys; i++)
{
for(j=1; j<=Szer; j++) printf("%c",Wzorek);
printf("\n");
}
}

Prototypy wszystkich funkcji standardowych znajduja sie w
plikach nagłówkowych *.H (ang. Header file).

Skutek praktyczny:
JESLI DOŁACZYSZ DO PROGRAMU STOSOWNE PLIKI
NAGŁÓWKOWE *.h,mozesz

ZREZYGNOWAC Z DEKLARACJI FUNKCJI. Dodajac do programu wiersz:

#include <math.h>

dołaczajacy plik zawierajacy prototyp funkcji sqrt(), mozesz
napisac program tak:

#include <stdio.h>
#include <math.h>
main()
50
{

double a, b;
clrscr();
printf("Podaj liczbe\n");
scanf("%lf", &a);
b = sqrt(a);
printf("\n %Lf", (long double) b);
getch();

return 0;
}


PRZEKAZYWANIE PARAMETRÓW DO FUNKCJI.

W C++ czesto przekazuje sie parametry do funkcji przy pomocy
wskazników. Aby przesledzic co dzieje sie wewnatrz funkcji wpisz

i uruchom podany nizej program przykładowy. Najpierw
skonstruujemy sam program a nastepnie zmodyfikujemy go w taki
sposób, abys mógł sobie popodgladac cały proces. Przy pomocy
funkcji printf() kazemy wydrukowac kolejne stany zmiennych, stan

programu i funkcji, a funkcja getch() pozwoli Ci obejrzec to
"krok po kroku". Mogłoby sie wydawac, ze program ponizej
skonstruowany jest poprawnie...

void FUNKCJA( int ); //Prototyp, deklaracja funkcji

void main()
{
int Zmienna; //Zmienna funkcji main, rzeczywisty argument
clrscr();
Zmienna = 7;

FUNKCJA( Zmienna); //Wywolanie funkcji

printf("%d", Zmienna); //Wydruk wyniku
}

void FUNKCJA( int Argument) //Definicja funkcji
{
Argument = 10 * Argument + Argument;
}

FUNKCJA() jest jak widac trywialna. bedzie zamieniac np. 2 na
22, 3 na 33 itp. tylko w tym celu, by łatwo było stwierdzic, czy

funkcja zadziałała czy nie.

Rozbudujmy program tak by przesledzic kolejne stadia.

[P047.CPP]

void FUNKCJA( int ); //Prototyp

int Zmienna;
void main()
{
clrscr();
printf("Stadium: \tZmienna Argument");
printf("\nStadium 1\t%d\tnie istnieje\n", Zmienna);
Zmienna = 7;
printf("Stadium 2\t%d\tnie istnieje\n", Zmienna );
FUNKCJA( Zmienna);
printf("Stadium 3\t%d", Zmienna);
// printf("%d", Argument);
// taka proba sie NIE UDA !
getch();
}

void FUNKCJA( int Argument) //Definicja funkcji
{
printf("jestesmy wewnatrz funkcji\n");
printf("Nastapilo kopiowanie Zmienna -> Argument\n" );
printf("\t\t%d\t%d\n", Zmienna, Argument);
getch();
Argument = 10*Argument + Argument;
printf("\t\t%d\t%d\n", Zmienna, Argument);
getch();
}


Próba wydrukowania zmiennej Argument gdziekolwiek poza wnetrzem
FUNKCJI() nie uda sie i spowoduje komunikat o błedzie. Oznacza
to, ze POZA FUNKCJA zmienna Argument NIE ISTNIEJE. Jest tworzona

na stosie jako zmienna automatyczna na wyłaczny uzytek funkcji,
w której została zadeklarowana i znika po wyjsciu z funkcji.
Przy takiej organizacji funkcji i programu funkcja otrzymuje
kopie zmiennej, na niej wykonuje swoje działania, natomiast
zmienna (zmienne) wewnetrzna funkcji znika po wyjsciu z funkcji.

Problem przekazania parametrów pomiedzy funkcjami wywołujacymi
("wyzszego rzedu" - tu: main) i wywoływanymi (tu: FUNKCJA) mozna

rozwiazac przy pomocy

* instrukcji return (zwrot do programu jednej wartosci) lub
* wskazników.

Mozemy przeciez funkcji przekazac nie sama zmienna, a wskaznik
do zmiennej (robilismy to juz w przypadku funkcji scanf() -
dlatego, ze samej zmiennej jeszcze nie było - miała zostac
dopiero pobrana, ale istniało juz przeznaczone na ta nowa
zmienna - zarezerwowane dla niej miejsce. Mogł zatem istniec
wskaznik wskazujacy to miejsce). wskaznik nalezy oczywiscie
zadeklarowac. Nasz program przybrałby zatem nowa postac.
Wskaznik do zmiennej nazwiemy *Argument.

[P048.CPP]

//Pamietaj o plikach naglowkowych !
void FUNKCJA( int *Argument); //Prototyp
int Zmienna;
void main()
{
clrscr();
printf("Stadium: \tZmienna Argument");
printf("\nStadium 1\t%d\tnie istnieje\n", Zmienna);
Zmienna = 7;
printf("Stadium 2\t%d\tnie istnieje\n", Zmienna );
FUNKCJA( &Zmienna); //Pobierz do funkcji ADRES Zmiennej
printf("Stadium 3\t%d", Zmienna);
// printf("%d", Argument);
// taka proba sie NIE UDA !
getch();
}

void FUNKCJA( int *Argument) // Definicja funkcji
{
printf("jestesmy wewnatrz funkcji\n");
printf("Nastapilo kopiowanie ADRESOW a nie zmiennej\n" );
printf("ADRESY:\t\t %X\t%X\n", &Zmienna, Argument);
getch();
*Argument = 10* *Argument + *Argument; /* DZIALANIE */
printf("\t\t%d\t%d\n", Zmienna, *Argument);
getch();
}


W linii /* DZIALANIE */ mnozymy i dodajemy to, co wskazuje
wskaznik, czyli Zmienna. Funkcja działa zatem nie na własnej
kopii zmiennej a bezposrednio na zmiennej zewnetrznej. Zwróc
uwage na analogie w sposobie wywołania funkcji:

FUNKCJA( &Zmienna );
scanf( "%d", &Zmienna );

A jesli argumentem funkcji ma byc tablica? Rozwaz przykładowy
program. Program zawiera pewna nadmiarowosc (ku wiekszej
jasnosci mechanizmów).

[P049.CPP]

# include <conio.h>
# include <stdio.h>

SUMA( int k, int Tablica[] )
{
int i, SumTab=0;
for (i=0; i<k; i++)
{
SumTab = SumTab + Tablica[i];
printf("%d + ", Tablica[i]);
51
}
printf("\b\b= %d", SumTab);
return SumTab;
}

int suma=0, N; char Odp;
int TAB[10] = {1,2,3,4,5,6,7,8,9,10};

main()
{
clrscr();
do
{
printf("\n Ile wyrazow tablicy dodac ??? \n");
scanf("%d", &N);
if (N>10)
{ printf("TO ZA DUZO ! - max. 10");
continue;
}
suma = SUMA( N,TAB );
printf("\nTO JEST suma z progr. glownego %d", suma);
printf("\n Jeszcze raz ? T/N");
Odp = getch();
}
while (Odp!='N' && Odp!='n');
return 0;
}


Kompilacja w C++ jest wieloprzebiegowa (PASS 1, PASS 2), wiec
definicja funkcji moze byc zarówno na poczatku jak i na koncu.

A oto nastepny przykład. Operujac adresem - wskaznikiem do
obiektu (tu wskaznikami do dwu tablic) funkcja Wypelniacz()
zapisuje pod wskazany adres ciag identycznych znaków. Na koncu
kazdego łancucha znaków zostaje dodany NUL - (\0) jako znak
konca. Taki format zapisu łancuchów znakowych nazywa sie ASCIIZ.


[P050.CPP]

void Wypelniacz(char *BUFOR, char Znak, int Dlugosc);

char TAB2D[5][10]; // Tablica 5 X 10 = 50 elementow
char TAB_1D[50]; // Tablica 1 X 50 = 50 elementow
int k;

main()
{
clrscr();
Wypelniacz( TAB_1D, 'X', 41); //Wypelnia X-ami
printf("%s\n\n", TAB_1D);
for (k=0; k<5; k++) Wypelniacz( TAB2D[k], 65+k, 9);
//ASCII 65 to 'A'; 66 to 'B' itd.

for (k=0; k<5; k++) printf("%s\n", TAB2D[k]);
getch();
return 0;
}

void Wypelniacz( char *BUFOR, char Znak, int Dlugosc )
{
int i;
for ( i=0; i<=(Dlugosc-1); i++) *(BUFOR+i) = Znak;
*(BUFOR+Dlugosc) = '\0';
}

Zwróc uwage, ze:
* NAZWA TABLICY (tu: TAB_1D i TAB2D) funkcjonuje jako wskaznik
PIERWSZEGO ELEMENTU TABLICY.

FUNKCJE TYPU WSKAZNIKOWEGO.

Funkcje moga zwracac do programu zarówno wartosci typu int, czy
float, jak i wartosci typu ADRESU. Podobnie jak wskaznik wymaga
deklaracji i podania w deklaracji na jakiego typu obiekty bedzie

wskazywał, podobnie funkcja takiego typu wymaga w deklaracji
okreslenia typu wskazywanych obiektów. Wiesz juz, ze zalezy od
tego tzw. krok wskaznika. W przykładzie ponizej funkcja
Minimum() poszukuje najmniejszego elementu tablicy i zwraca
wskaznik do tegoz elementu. Znajac lokalizacje najmniejszego
elementu mozemy utworzyc nowa tablice, ale juz uporzadkowana
według wielkosci.

[P051.CPP]

int BALAGAN[10];
int PORZADEK[10]; // Tablica koncowa - uporzadkowana
int k, *pointer , MAX=10000 ;

int *Minimum(int Ilosc, int *TABL);

main()
{
clrscr();
printf("Podaj 10 liczb calkowitych od -10000 do 10000\n");
for (k=0; k<=9; k++) scanf("%d", &BALAGAN[k]);
printf("Po kolei: \n\n");
for ( k=0; k<=9; k++ )
{
tttttpointer=Minimum(10, BALAGAN);
tttttPORZADEK[k]=*pointer;
ttttt*pointer=MAX;
}
for(k=0; k<=9; k++) printf("%d ", PORZADEK[k]);

getch();
return 0;
}

int *Minimum( int Ilosc, int *TABL )
{
int *pMin; int i;
pMin=TABL;
for (i=1; i<Ilosc; i++)
{
if (*(TABL+i) < *pMin) pMin=(TABL+i);
}
return (pMin);
}


WSKAZNIKI DO FUNKCJI.

W C++ mozemy nie tylko podstawic dana w miejsce zmiennej (co
jest trywialna i oczywista operacja we wszystkich jezykach
programowania), ale mozemy takze podstawiac na miejsce funkcji
stosowanej w programie te funkcje, która w danym momencie jest
nam potrzebna. Aby wskazac funkcje zastosujemy, jak sama nazwa
wskazuje - WSKAZNIK DO FUNKCJI. Aby uniknac deklarowania funkcji

standardowych i byc w zgodzie z dobrymi manierami nie zapomnimy
o dołaczeniu pliku z prototypami. Deklaracje

double ( *FUNKCJA ) (double);

nalezy rozumiec:
"Przy pomocy wskaznika do funkcji *FUNKCJA wolno nam wskazac
takie funkcje, które
* pobieraja jeden argument typu double float;
* zwracaja do programu wartosc typu double float. "
Dostepne sa dla nas zatem wszystkie standardowe funkcje
arytmetyczne z pliku MATH.H (MATH pochodzi od MATHematics -
matematyka.)

[P052.CPP]

# include <conio.h>
# include <math.h>
double NASZA( double ); //Deklaracja zwyklej funkcji

double (*Funkcja)(double ARG); //pointer do funkcji

double Liczba, Wynik; //Deklaracje zmiennych
int WYBOR;

main()
{
clrscr();
printf("Podaj Liczbe \n");
scanf("%lf", &Liczba);
printf("CO MAM ZROBIC ?\n");
printf("1 - Sinus \n");
printf("2 - Pierwiastek\n");
printf("3 - Odwrotnosc 1/x\n");
52
scanf("%d", &WYBOR);
switch(WYBOR)
{
case 1: Funkcja=sin; break;
case 2: Funkcja=sqrt; break;
case 3: Funkcja=NASZA; break;
}
Wynik=Funkcja(Liczba); // Wywolanie wybranej funkcji
printf("\n\nWYNIK = %lf", Wynik);

getch();
return 0;
}

double NASZA(double a)
{
printf("\n A TO NASZA PRYWATNA FUNKCJA\n");
if (a!=0) a=1/a; else printf("???\n");
return a;
}

main() - FUNKCJA SPECJALNA.

Ta ksiazka siła rzeczy, ze wzgledu na swoja skromna objetosc i
skale zagadnienia o którym traktuje (autor jest zdania, ze jezyk

C to cała filozofia nowoczesnej informatyki "w pigułce") pełna
jest skrótów. Nie mozemy jednak pozostawic bez, krótkiego
chocby, opisu pomijanego dyskretnie do tej pory problemu
PRZEKAZANIA PARAMETRÓW DO PROGRAMU.

Konwencja funkcji w jezyku C/C++ wyraznie rozgranicza dwa rózne
punkty widzenia. Funkcja pozwala na swego rodzaju separacje
swiata wewnetrznego (lokalnego, własnego) funkcji od swiata
zewnetrznego. Nie zdziwi Cie wiec zapewne, ze i sposób widzenia
parametrów przekazywanych programowi przez DOS i sposób widzenia

"od wewnatrz" argumentów pobierabych przez funkcje main() jest
diametralnie rózny.

To, co DOS widzi tak:

PROGRAM PAR1 PAR2 PAR3 PAR4 PAR5 [...][Enter]

funkcja main() widzi tak:

main(int argc, char **argv, char **env)

lub tak:

main(int argc, char *argv[], char *env[])

[???]CO TO JEST ???
________________________________________________________________
Zapisane zgodnie z obyczajami stosowanymi w prototypach funkcji:

int argc - liczba całkowita (>=1, bo parametr Nr 1 to nazwa
samego programu, za posrednictwem której DOS wywołuje funkcje
main). Liczba argumentów - parametrów moze byc zmienna.

UWAGA: Jezyk programowania wsadowego BPL przyjmuje nazwe
programu za parametr %0 a C++ uznaje ja za parametr o numerze
argv[0], tym niemniej, nawet jesli nie ma zadnych parametrów
argc = 1.

argv - to tablica zawierajaca wskazniky do łancuchów tekstowych
reprezentowanych w kodzie ASCIIZ - nazw kolejnych paramentrów, z

którymi został wywołany program.
Pierszy element tej tablicy to nazwa programu. Ostatni element
tej tablicy, o numerze argv - 1 to ostatni niezerowy parametr
wywołania programu.

env - to takze tablica zawierajaca wskazniki do łancuchów
znakowych w kodzie ASCIIZ reprezentujacych parametry srodowiska
(environment variables). Wskaznik o wartosci NUL sygnalizuje
koniec tablicy. W Turbo C++ istnieje takze predefiniowana
zmienna globalna (::), przy pomocy której mozna uzyskac dostep
do srodowiska operacyjnego - environ .
________________________________________________________________

Przykłady ponizej przedstawiaja sposób wykorzystania parametrów
wejsciowych programu.

[P053.CPP]

# include "stdio.h"
# include "stdlib.h"

main(int argc, char *argv[], char *env[])
{
printf("Parametry srodowiska DOS: \n");
int i = 0;
do
{
printf("%s \n", env[i]);
i++;
};
while (env[i] != NULL);
printf("Lista parametrow programu: \n");
for(i=1; i<= argc - 1; i++)
printf("%s \n", argv[i]);
printf("Nazwa programu: \n");
printf("%s", argv[0]);
return 0;
}

Poniewaz C++ traktuje nazwe tablicy i wskaznik do tablicy w
specjalny sposób, nastepujace zapisy sa równowazne:

*argv[] oraz **argv
*env[] oraz **env

Nazwy argumentów argc, argv i env sa zastrzezone i musza
wystepowac zawsze w tej samej kolejnosci. Argumenty nie musza
wystepowac zawsze w komplecie. Dopuszczalne sa zapisy:

main(int argc, char **argv, char **env)
main(int argc, char *argv[])
main(int argc)
main()

ale niedopuszczalny jest zapis:

main(char *env[])

Nawet jesli nie zamierzamy wykorzystac "wczesniejszych"
parametrów - MUSIMY JE PODAC.


[Z]
________________________________________________________________
1. Spróbuj tak zmodyfikowac funkcje Demo(), by liczba w formie
dwójkowej była pisana "od tyłu". Do cofania kursora w funkcji
printf uzyj sekwencji \b\b.

2. Zinterpretuj zapis:
if (MIANOWNIK) printf("%f", 1/MIANOWNIK); else exit(1);

3 Spróbuj przeprowadzic rzutowanie typu we własnym programie.

4 Przekaz wartosc w programie przykładowym posługujac sie
instrukcja:
return (10*Argument + Argument);
5 Rozszerz zestaw funkcji do wyboru w programie przykładowym.


LEKCJA 16 - ASEMBLER TASM i BASM.
________________________________________________________________
W trakcie tej lekcji:
* dowiesz sie , jak łaczyc C++ z assemblerem
* poznasz wewnetrzne formaty danych
________________________________________________________________

WEWNETRZNY FORMAT DANYCH I WSPÓŁPRACA Z
ASSEMBLEREM.

W zaleznosci od wybranej wersji kompilatora C++ zasady
współpracy z asemblerem moga sie troche róznic. Generalnie,
kompilatory współpracuja z tzw. asemblerami in-line (np. BASM),
lub asemblerami zewnetrznymi (stand alone assembler np. MASM,
TASM). Wstawki w programie napisane w assemblerze powinny zostac

poprzedzone słowem asm (BORLAND/Turbo C++), badz _asm (Microsoft

53
C++). Przy kompilacji nalezy zatem stosownie do wybranego
kompilatora przestrzegac specyficznych zasad współpracy. Np. dla

BORLAND/Turbo C++ mozna stosowac do kompilacji BCC.EXE/TCC.EXE
przy zachowaniu warunku, ze TASM.EXE jest dostepny na dysku w
biezacym katalogu.

Typowymi sposobami wykorzystania assemblera z poziomu C++ sa:

* umieszczenie ciagu instrukcji assemblera bezposrednio w
zródłowym tekscie programu napisanym w jezyku C/C++,
* dołaczeniu do programu zewnetrznych modułów (np. funkcji)
napisanych w assemblerze.

W C++ w tekscie zródłowym programu blok napisany w asemblerze
powinien zostac poprzedzony słowem kluczowym asm (lub _asm):

# pragma inline

void main()
{
asm mov dl, 81
asm mov ah, 2
asm int 33
}

Program bedzie drukował na ekranie litere "Q" (ASCII 81).

JAK POSŁUGIWAC SIE DANYMI W ASEMBLERZE.

Napiszemy w asemblerze program drukujacy na ekranie napis "tekst

- test". Rozpczynamy od zadeklarowania łancucha znaków:

void main()
{
char *NAPIS = "tekst - test$"; /* $ - ozn. koniec */

Umiescilismy w pamieci łancuch, bedacy w istocie tablica
składajaca sie z elementów typu char. Wskaznik do łancucha moze
zostac zastapiony nazwa-identyfikatorem tablicy. Zwróc uwage, ze

po łancuchu znakowym dodalismy znak '$'. Dzieki temu mozemy
skorzystac z DOS'owskiej funkcji nr 9 (string-printing DOS
service 9). Mozemy utworzyc kod w asemblerze:

asm mov dx, NAPIS
asm mov ah, 9
asm int 33

Cały program bedzie wygladał tak:

[P054.CPP]

# pragma inline
void main()
{
char *NAPIS = "\n tekst - test $";

asm {
MOV DX, NAPIS
MOV AH, 9
INT 33
}
}

Zmienna NAPIS jest pointerem i wskazuje adres w pamieci, od
którego rozpoczyna sie łancuch znaków. Mozemy przesłac zmienna
NAPIS bezposrednio do rejestru i przekazac wprost przerywaniu
Int 33. Program assemblerowski (tu: TASM) mógłby wygladac np.
tak:

[P055.ASM]

.MODEL SMALL ;To zwylke robi TCC
.STACK 100H ;TCC dodaje standardowo 4K
.DATA
NAPIS DB 'tekst - test','$'
.CODE
START:
MOV AX, @DATA
MOV DS, AX ;Ustawienie segmentu danych
ASM:
MOV DX, OFFSET NAPIS
MOV AH, 9
INT 21H ;Drukowanie
KONIEC:
MOV AH, 4CH
INT 21H ;Zakonczenie programu
END START

Inne typy danych mozemy stosowac podobnie. Wygodna taktyka jest
deklarowanie danych w tej czesci programu, która została
napisana w C++, aby inne fragmenty programu mogły sie do tych
danych odwoływac. Mozemy we wstawce asemblerowskiej odwoływac
sie do tych danych w taki sposób, jakgdyby zostały zadeklarowane

przy uzyciu dyrektyw DB, badz DW.

WEWNETRZNE FORMATY DANYCH W C++.

LICZBY CAŁKOWITE typów char, short int i long int.

Liczba całkowita typu short int stanowi 16-bitowe słowo i moze
zostac zastosowana np. w taki sposób:

[P056.CPP]

#pragma inline
void main()
{
char *napis = "\nRazem warzyw: $";
int marchewki = 2, pietruszki = 5;
asm {
MOV DX, napis
MOV AH, 9
INT 33
MOV DX, marchewki
ADD DX, pietruszki
ADD DX, '0'
MOV AH, 2
INT 33
}
}

Zdefiniowalismy dwie liczby całkowite i łancuch znaków - napis.
Poniewaz obie zmienne (łancuch znków jest stała) maja długosc
jednego słowa maszynowego, to efekt jest taki sam, jakgdyby
zmienne zostały zadeklarowane przy pomocy dyrektywy asemblera DW

(define word). Mozemy pobrac wartosc zmiennej marchewki do
rejestru instrukcja

MOV DX, marchewki ;marchewki -> DX

W rejestrze DX dokonujemy dodawania obu zmiennych i wyprowadzamy

na ekran sume, posługujac sie funkcja 2 przerywania DOS 33
(21H).

W wyniku działania tego programu otrzymamy na ekranie napis:

Razem warzyw: 7

Jeczsze jeden szczegół techniczny. Poniewaz stosowana funkcja
DOS pracuje w trybie znakowym i wydrukuje nam znak o kodzie
ASCII przechowywanym w rejestrze, potrzebna jest manipulacja:

ADD DX, '0' ;Dodaj kod ASCII "zera" do rejestru

Mozesz sam sprawdzic, ze po przekroczeniu wartosci 9 przez sume
wszystko sie troche skomplikuje (kod ASCII zera - 48). Z równym
skutkiem moznaby zastosowac rozkaz

ADD DX, 48

Jesli prawidłowo dobierzemy format danych, fragment programu
napisany w asemblerze moze korzystac z danych dokładnie tak
samo, jak kazdy inny fragment programu napisany w C/C++. Mozemy
zastosowac dane o jednobajtowej długosci (jesli drugi, pusty
bajt nie jest nam potrzebny). Zwróc uwage, ze posługujemy sie w
tym przypadku tylko "połówka" rejestru DL (L - Low - młodszy).

[P057.CPP]

#pragma inline
54
void main()
{
const char *napis = "\nRazem warzyw: $";
char marchewki = 2, pietruszki = 5;
asm {
MOV DX, napis
MOV AH, 9
INT 33
MOV DL, marchewki
ADD DL, pietruszki
ADD DL, '0'
MOV AH, 2
INT 33
}
}

W tej wersji zadeklarowalismy zmienne marchewki i pietruszki
jako zmienne typu char, co jest równoznaczne zadeklarowaniu ich
przy pomocy dyrektywy DB.

Zajmijmy sie teraz maszynowa reprezentacja liczb typu unsigned
long int (długie całkowite bez znaku). Ze wzgledu na specyfike
zapisu danych do pamieci przez mikroprocesory rodziny Intel
80x86 długie liczby całkowite (podwójne słowo - double word) np.

12345678(hex) sa przechowywane w pamieci w odwróconym szyku.
Zamieniony miejscami zostaje starszy bajt z młodszym jak równiez

starsze słowo z młodszym słowem. Liczba 12345678(hex) zostanie
zapisana w pamieci komputera IBM PC jako 78 56 34 12.

Gdy inicjujemy w programie zmienna

long int x = 2;

zostaje ona umieszczona w pamieci tak: 02 00 00 00 (hex).
Młodsze słowo (02 00) jest umieszczone jako pierwsze. To własnie

słowo zawiera interesujaca nas informacje, mozemy wczytac to
słowo do rejestru rozkazem

MOV DX, X

Jesli bedzie nam potrzebna druga połówka zmiennej - starsze
słowo (umieszczone w pamieci jako nastepne), mozemy zastosowac
pointer (czyli podac adres nastepnego słowa pamieci).

[P058.CPP]

# pragma inline
void main()
{
unsigned long marchewki = 2, pietruszki = 5;
const char *napis = "\nRazem warzyw: $";
asm
{
MOV DX, napis
MOV AH, 9
INT 33
MOV DX, marchewki
ADD DX, pietruszki
ADD DX, '0'
MOV AH, 2
INT 33
}
}

W przypadku liczb całkowitych ujemnych C++ stosuje zapis w
kodzie komplementarnym. Aby móc manipulowac takimi danymi kazdy
szanujacy sie komputer powinien miec mozliwosc stosowania liczb
ujemnych.

Najstarszy bit w słowie, badz bajcie (pierwszy z lewej) moze
spełniac role bitu znakowego. O tym, czy liczba jest ze znakiem,

czy tez bez decyduje wyłacznie to, czy zwracamy uwage na ten
bit. W liczbach bez znaku, obojetnie, czy o długosci słowa, czy
bajtu, ten bit równiez jest (i był tam zawsze!), ale
traktowalismy go, jako najstarszy bit nie przydajac mu poza tym
zadnego szczególnego znaczenia. Aby liczba stała sie liczba ze
znakiem - to my musimy zaczac ja traktowac jako liczbe ze
znakiem, czyli zaczac zwracac uwage na ten pierwszy bit.
Pierwszy, najstarszy bit liczby ustawiony do stanu 1 bedzie
oznaczac, ze liczba jest ujemna - jesli zechcemy ja potraktowac
jako liczbe ze znakiem.

Filozofia postepowania z liczbami ujemnymi opiera sie na
banalnym fakcie:

(-1) + 1 = 0

Twój PC "rozumuje" tak: -1 to taka liczba, która po dodaniu 1
stanie sie 0. Czy mozna jednakze wyobrazic sobie np.
jednobajtowa liczbe dwójkowa, która po dodaniu 1 da nam w
rezultacie 0 ? Wydawałoby sie, ze w dowolnym przypadku wynik
powinien byc conajmniej równy 1.

A jednak. Jesli ograniczymy swoje rozwazania do osmiu bitów
jednego bajtu, moze wystapic taka, absurdalna tylko z pozoru
sytuacja. Jesli np. dodamy 255 + 1 (dwójkowo 255 = 11111111):

1111 1111 hex FF dec 255
+ 1 + 1 + 1
___________ _____ _____
1 0000 0000 100 256


otrzymamy 1 0000 0000 (hex 100). Dla Twojego PC oznacza to, ze w

osmiobitowym rejestrze pozostanie 0000 0000 , czyli po prostu 0.

Nastapi natomiast przeniesienie (carry) do dziewiatego (nie
zawsze istniejacego sprzetowo bitu).

Wystapienie przeniesienia powoduje ustawienie flagi CARRY w
rejestrze FLAGS. Jesli zignorujemy flage i bedziemy brac pod
uwage tylko te osiem bitów w rejestrze, okaze sie, ze
otrzymalismy wynik 0000 0000. Krótko mówiac FF = (-1), poniewaz
FF + 1 = 0.

Aby odwrócic wszystkie bity bajtu, badz słowa mozemy w
asemblerze zastosowac instrukcje NOT. Jesli zawartosc rejestru
AX wynosiła np. 0000 1111 0101 0101 (hex 0F55), to instrukcja
NOT AX zmieni ja na 1111 0000 1010 1010 (hex F0AA). Dokładnie
tak samo działa operator bitowy ~_AX w C/C++. W zestawie
rozkazów mikroprocesorów rodziny Intel 80x86 jest takze
instrukcja NEG, powodujaca zamiane znaku liczby (dokonujac
konwersji liczby na kod komplementarny). Instrukcja NEG robi to
samo, co NOT, ale po odwróceniu bitów dodaje jeszcze jedynke.
Jesli rejestr BX zawierał 0000 0000 0000 0001 (hex 0001), to po
operacji NEG AX zawartosc rejestru wyniesie 1111 1111 1111 1111
(hex FFFF).

Zastosujmy praktycznie uzupełnienia dwójkowe przy współdziałaniu

asemblera z C++:

[P059.CPP]

#pragma inline
void main()
{
const char *napis = "\nRazem warzyw: $";
int marchewki = -2, pietruszki = 5;
asm {
MOV DX, napis
MOV AH, 9
INT 33
MOV DX, marchewki
NEG DX
ADD DX, pietruszki
ADD DX, '0'
MOV AH, 2
INT 33
}
}

Dzieki zamianie (-2) na 2 przy pomocy instrukcji NEG DX
otrzymamy wynik, jak poprzednio równy 7.

Przypomnijmy prezentacje działania operatorów bitowych C++.
Wykorzystaj program przykładowy do przegladu bitowej
reprezentacji liczb typu int (ze znakiem i bez).

[P060.CPP]

55
/* Program prezentuje format liczb i operatory bitowe */

# include "iostream.h"
# pragma inline

void demo(int liczba) //Definicja funkcji
{
int n = 15;
for (; n >= 0; n--)
if ((liczba >> n) & 1)
cout << "1";
else
cout << "0";
}

char odp;
char *p = "\nLiczby rozdziel spacja $";

int main()
{
int x, y;

cout ˙<< "\nPodaj dwie liczby calkowite od -32768 do +32767\n";

asm {
mov dx, p
mov ah, 9
int 33
}
cout << "\nPo podaniu drugiej liczby nacisnij [Enter]";
cout << "\nLiczby ujemne sa w kodzie dopelniajacym";
cout << "\nSkrajny lewy bit oznacza znak 0-Plus, 1-Minus";

for(;;)
{
cout << "\n";
cin >> x >> y;
cout << "\nX: "; demo(x);
cout << "\t\tY: "; demo(y);
cout << "\n~X: "; demo(~x);
cout << "\t\t~Y: "; demo(~y);
cout << "\nX & Y: "; demo(x & y);
cout << "\nX | Y: "; demo(x | y);
cout << "\nX ^ Y: "; demo(x ^ y);
cout << "\n Y: "; demo(y);
cout << "\nY >> 1: "; demo(y >> 1);
cout << "\nY << 2: "; demo(y << 2);

cout << "\n\n Jeszcze raz? T/N: ";
cin >> odp;
if (odp!='T'&& odp!='t') break;
}
}

Wstawka asemblerowa nie jest w programie niezbedna, ale w tym
miejscu wydaje sie byc "a propos". Przy pomocy programu
przykładowego mozesz zobaczyc "na własne oczy" jak wyglada
reprezentacja bitowa liczb całkowitych i ich kody
komplementarne.

Praca bezposrednio ze zmiennymi jest jednym ze sposobów
komunikowania sie z programem napisanym w C++. Moga jednak
wystapic sytuacje bardziej skomplikowane, kiedy to nie bedziemy
znac nazwy zmiennej, przekazywanej do funkcji. Jesli napiszemy w

asemblerze funkcje w celu zastapienia jakiejs funkcji
bibliotecznej C++ , program wywołujac funkcje przekaze jej
parametry i bedzie oczekiwał, iz funkcja pobierze sobie te
parametry ze stosu. Rozwazmy sie to zagadnienie dokładniej.
Typowa sytuacja jest pisanie w asemblerze tylko kilku funkcji
(zwykle takich, które powinny działac szczególnie szybko). Aby
to zrobic, musimy nauczyc sie odczytywac parametry, które
program przekazuje do funkcji w momencie jej wywołania.
Zaczynamy od trywialnej funkcji, która nie pobiera w momencie
wywołania zadnych parametrów. W programie moze to wygladac np.
tak:

[P061.CPP]

//*TEKST to znany funkcji zewnetrzny wskaznik

#pragma inline

char *TEKST = "\ntekst - test$";

void drukuj(void); //Prototyp funkcji

void main()
{
drukuj(); //Wywołanie funkcji drukuj()
}

void drukuj(void) //Definicja funkcji
{
asm MOV DX, TEKST
asm MOV AH, 9
asm INT 33
}

Funkcja moze oczywiscie nie tylko zgłosic sie napisem, ale takze

zrobic dla nas cos pozytecznego. W kolejnym programie
przykładowym czyscimy bufor klawiatury (flush), co czasami sie
przydaje, szczególnie na starcie programów.

[P062.CPP]

# pragma inline

char *TEKST = "\nBufor klawiatury PUSTY. $";

void czysc_bufor(); //Tez prototyp funkcji

void main()
{
czysc_bufor(); //Czyszczenie bufora klawiatury
}

void czysc_bufor(void) //Definicja funkcji
{
START:
asm MOV AH, 11
asm INT 33
asm OR AL, AL
asm JZ KOMUNIKAT
asm MOV AH, 7
asm INT 33
asm JMP START
KOMUNIKAT:
asm MOV DX, TEKST
asm MOV AH, 9
asm INT 33
}

Póki nie wystapi problem przekazania parametrów, napisanie dla
C++ funkcji w asemblerze jest banalnie proste. Zwróc uwage, ze
zmienne wskazywane w programach przez pointer *TEKST zostały
zadeklarowane poza funkcja main() - jako zmienne globalne.
Dzieki temu nasze funkcje drukuj() i czysc_bufor() maja dostep
do tych zmiennych.

Spróbujemy przekazac funkcji parametr. Nazwiemy nasza funkcje
wyswietl() i bedziemy ja wywoływac przekazujac jej jako argument

znak ASCII przeznaczony do wydrukowania na ekranie:
wyswietl('A'); . Pojawia sie zatem problem - gdzie program
"pozostawia" argumenty przeznaczone dla funkcji przed jej
wywołaniem? W Tabeli ponizej przedstawiono w skrócie "konwencje
wywoływania funkcji" (ang. Function Calling Convention) jezyka
C++.

Konwencje wywołania funkcji.
________________________________________________________________

Jezyk Argumenty na stos Postac Typ wart. zwrac.
________________________________________________________________

BASIC Kolejno offset adresu Return n
C++ Odwrotnie wartosci Return
Pascal Kolejno wartosci Return n
________________________________________________________________

Return n oznacza liczbe bajtów zajmowanych łacznie przez
wszystkie odłozone na stos parametry.

W C++ parametry sa odkładane na stos w odwróconej kolejnosci.
56
Jesli chcemy, by parametry zostały odłozone na stos kolejno,
powinnismy zadeklarowac funkcje jako "funkcje z Pascalowskimi
manierami" - np.:

pascal void nazwa_funkcji(void);

Dodatkowo, w C++ argumenty sa przekazywane poprzez swoja
wartosc, a nie przez wskazanie adresu parametru, jak ma to
miejsce np. w BASICU. Istnieje tu kilka wyjatków przy
przekazywaniu do funkcji struktur i tablic - bardziej
szczegółowo zajmiemy sie tym w dalszej czesci ksiazki.

Rozbudujemy nasz przykładowy program w taki sposób, by do
funkcji były przekazywane dwa parametry - litery 'A' i 'B'
przeznaczone do wydrukowania na ekranie przez funkcje:

# pragma inline
void wyswietl(char, char); //Prototyp funkcji

void main()
{
wyswietl('A', 'B'); //Wywolanie funkcji
}

void wyswietl(char x, char y) //Definicja (implementacja)
{
....

Parametry zostana odłozone na stos:

PUSH 'B'
PUSH 'A'

Kazdy parametr (mimo typu char) zajmie na stosie pełne słowo.
C++ nie potrafi niestety układac na stosie bajt po bajcie.
Funkcja wyswietl() musi uzyskac dostep do przekazanych jej
argumentówów. Odwołamy sie do zmiennych C++ w taki sposób, jak
robiłaby to kazda inna funkcja w C++:

[P063.CPP]

# pragma inline
void wyswietl(char, char); //Prototyp funkcji
void main()
{
_AH = 2; //BEEEEE !
wyswietl('A', 'B'); //Wywolanie funkcji
}

void wyswietl(char x, char y) //Definicja (implementacja)
{
_DH = 0; // To C++ nie TASM, to samo, co asm MOV DH, 0
_DL = x; // asm MOV DL, x
asm INT 33
_DH = 0; // asm MOV DH, 0
_DL = y; // asm MOV DL, y
asm INT 33
}

Aby pokazac jak dalece BORLAND C++ jest elastyczny wymieszalismy

tu w jednaj funkcji instrukcje C++ (wykorzystujac pseudozmienne)

i instrukcje assemblera. Moze tylko przesadzilismy troche
ustawiajac rejestr AH - numer funkcji DOS dla przerywania int 33

przed wywołaniem funkcji wyswietl() w programie głównym. To
brzydka praktyka (ozn. //BEEEE), której autor nie zaleca.
Jak widzisz, przekazanie parametrów jest proste.


LEKCJA 17: TROCHE SZCZEGÓLÓW TECHNICZNYCH.
________________________________________________________________
W trakcie tej lekcji dowiesz sie wiecej o szczegółach działania
komputera widzianych z poziomu assemblera.
________________________________________________________________

LICZBY ZMIENNOPRZECINKOWE TYPU float.

To, ze C++ przy wywołaniu funkcji jest "przyzwyczajony" do
odkładania argumentów na stos zawsze po dwa bajty moze nam
sprawic troche kłopotów, gdy zechcemy zastosowac argument typu
float, double, badz long - znacznie przekraczajacy długoscia
dwubajtowe słowo maszynowe.

# include <....
....
# pragma inline
void main()
{
float liczba = 3.5;
....

Jezeli zajrzymy do pamieci naszego PC, okaze sie, ze liczba 3.5
została tam "zaszyfrowana" jako 00 00 60 40. Dlaczego? Format
liczb zmiennoprzecinkowych jest znacznie bardziej skomplikowany.

Liczba dziesietna w rodzaju 123.4 to

1*102 + 2*101 + 3*100 + 4*10-1

{* !UWAGA SKLAD tu cyfry potegi wyzej *}

Ale PC moze posługiwac sie wyłacznie zerami i jedynkami, i
liczyc wyłacznie w systemie dwójkowym. Liczbe dziesietna 3.5
moznaby przedstawic dwójkowo np. tak:

1*21 + 1*20 + 1*2-1 = 2 + 1 + 1/2 {* !UWAGA SKLAD: potegi *}

czyli 0000 0000 0000 0011.1000 0000 0000 0000

Kropka oznacza przecinek oddzielajacy czesc całkowita od czesci
ułamkowaj - "przecinek dwójkowy" (a nie dziesietny!). Kazda
liczbe dziesietna mozna zamienic na liczbe dwójkowa.
Przykładowodzieietne 7.75 mozna zamienic na

4 + 2 + 1 + 1/2 + 1/4 = 0000 0000 0000 0111.1100 (dwójkowo)

Pozostaje jednak pewien problem. Komputer nie ma mozliwosci
zaznaczenia przecinka, dlatego tez przecinek musi byc ustawiany
zawsze w tej samej pozycji - blisko poczatku liczby.

Liczby zmiennoprzecinkowe sa poddawane "normalizacji" (ang.
noralized). Nasza liczba 0000 0000 0000 0011.1000 po
normalizacji bedzie wygladac tak: 1.110 0000 0000... * 2^1.
Odbywa sie to zupełnie tak samo, jak normalizacja liczb
dziesietnych. Przesuniecie przecinka powoduje, ze 12345.67 =
1.234567 * 10^4. Aby wróciła do swojej starej "zwykłej" postaci
(jest to tzw. "rozwiniecie" liczby - ang. expand) nalezy
przesunac przecinek o jedno miejsce w prawo - otrzymamy znowu
11.1 . W liczbach dziesietnych pierwsza cyfra moze byc rózna
(tylko nie zero), a w dowolnej poddanej normalizacji
zmiennoprzecinkowej liczbie dwójkowej pierwsza cyfra jest zawsze

1. Skoro w formacie liczb zmiennoprzecinkowych pierwsza jedynka
jest przyjmowana "z definicji" (ang. implicit), wiec mozna ja
pominac. Zostanie nam zatem zamiast 1.11 tylko 11 i ta
przechowywana czesc liczby jest nazywana jej czescia znaczaca
(ang. significant). To jeszcze nie wszystko - powinien tam byc
wykładnik potegi. Wystarczy zapamietac wykładnik, bo podstawa
jest zawsze ta sama - 2. Niestety wykładniki sa przechowywane
nie w sposób naturalny, a po dodaniu do nich tzw. przesuniecia
(ang. offset lub bias). Pozwala to uniknac kłopotów z
okreslaniem znaku wykładnika potegi.

Dla liczb typu float offset wykładnika wynosi +127 a dla liczb
double float +1023. Wrócmy do naszej przykładowej liczby. Jesli
nasza liczba 3.5 = 11.1(B) ma byc zapisana w postaci
zmiennoprzecinkowej - float, zapisany w pamieci wykładnik potegi

wyniesie:

1 + 127 = 128 = 80 (hex)

A teraz znak liczby. Pierwszy bit kazdej liczby
zmiennoprzecinkowej okresla znak liczby (ang. sign bit). Liczby
zmiennoprzecinkowe nie sa przechowywane w postaci dwójkowych
uzupełnien. Jesli pierwszy bit - bit znaku równy jest 1 - liczba

jest ujemna. natomiast jezeli 0, liczba jest dodatnia. Jest to
jedyna róznica pomiedzy dodatnimi a ujemnymi liczbami
zmiennoprzecinkowymi. Nasza liczba 3.5 = 11.1 zostanie
zakodowana jako:

znak liczby - 0
wykładnik potegi - 1000 0000
57
cyfry znaczace liczby - 110000000....

Poniewaz wiemy, ze mamy do dyspozycji dla liczb float 4 bajty
(mozesz to sprawdzic sizeof(float x=3.5)), uzupełnijmy brakujace

do 32 bity zerami:

3.5 = 0100 0000 0110 0000 0000 0000 0000 0000 = 40 60 00 00

zapis 40600000 to oczywiscie szesnastkowa postac naszej liczby.
Jesli teraz wezmiemy pod uwage, ze nasz PC zamieni miejscami
starsze słowo z młodszym 00 00 40 60 a nastepnie w obrebie
kazdego słowa dodatkowo starszy bit z młodszym, to zrozumiemy,
dlaczego nasza liczba "siedziała" w pamieci w zaszyfrowanej
postaci 00 00 60 40.

Rozpatrzmy szkielet programu wykorzystujacego funkcje z "długim"

argumentem. Aby zapanowac nad zapisem liczby zmiennoprzecinkowej

do pamieci naszego PC mozemy na poziomie assemblera postapic np.

tak:


# include <.....
# pragma inline
void funkcja(long int) //Prototyp funkcji
main()
{
long liczba = 0xABCDCDEF; //Deklaracja argumentu
.....
funkcja(liczba); //Wywołanie w programie
....
}

void funkcja(long int x) //Implementacja funkcji
{ ..... } // x - argument formalny

Argument przekazywany funkcji() jest zmienna 4 - bajtowa typu
long int. Mozemy ja zamienic na dwa słowa, zanim przekazemy ja
do wykorzystania w asemblerowskiej czesci programu.

funkcja(long int x)
{
int x1starsze, x2mlodsze; //Wewnetrzne zmienne pomocnicze
x2mlodsze = (int) x;
x >> 16;
x1starsze = (int) x;
_DX = x1starsze;
_BX = x2mlodsze;
asm {
...... //Tu funkcja juz moze działac

Forsujac konwersje typu na (int), spowodujemy, ze młodsze słowo
zostanie przypisane zwyczajnej krótkiej zmiennej x2mlodsze.
Nastepnie zawartosc długiej zmiennej zostanie przesunieta o 16
bitów w prawo (starsze słowo zostanie przesuniete na miejsce
młodszego). Powtórzenie operacji przypisania spowoduje
przypisanie zmiennej x1starsze starszej połówki słowa. Od tej
chwili mozemy odwołac sie do tych zmiennych w naszym fragmencie
napisanym w asemblerze. Postepujemy tak, by to C++ martwił sie o

szczegóły techniczne i sam manipulował stosem i jednoczesnie
pilnował poprawnosci konwersji danych.

ZWROT WARTOSCI PRZEZ FUNKCJE.

A teraz kilka słów o tym, co sie dzieje, gdy funkcja zapragnie
zwrócic jakas wartosc do programu.

Wykorzystanie przez funkcje rejestrów do zwrotu wartosci.
________________________________________________________________

Typ wartosci Funkcja uzywa rejestru (lub pary)
________________________________________________________________
signed char / unsigned char AL
short AX
int AX
enum AX
long para DX:AX (starsze słowo DX, młodsze
AX)
float AX = Adres (jesli far to DX:AX)
double AX = Adres (jesli far to DX:AX)
struct AX = Adres (jesli far to DX:AX)
near pointer AX
far pointer DX:AX
________________________________________________________________


Zaleznie od typu wartosci zwracanej przez funkcje (okreslonej w
prototypie funkcji), C++ odczytuje zawartosc odpowiedniego
rejestru: AL, AX lub DX:AX. Jesli funkcja ma np. zwrócic wartosc

o długosci jednego bajtu, to przed wyjsciem z funkcji nalezy ja
"zostwic" w rejestrze AL. Jesli wywołujac funkcje C++ oczekuje
zwrotu wartosci jednobajtowej, to po powrocie z funkcji
automatycznie pobierze bajt z rejestru AL. Krótkie wartosci
(typu short int) sa "pozostawiane" przez funkcje w AX, a długie
w parze rejestrów: DX - starsze, AX - młodsze słowo.

Zastosujmy to w programie. Funkcja bedzie odejmowac dwie liczby
całkowite. Pobierze dwa argumenty typu int, wykona odejmowanie i

zwróci wynik typu int (return (_AX)). Dla modelu pamieci small
bedzie to wygladac tak:

[P064.CPP]

# include <iostream.h>
# pragma inline
int funkcja(int, int); //Prototyp funkcji

void main()
{
cout << "\nWynik 7 - 8 = " << funkcja(7, 8);
}

int funkcja(int x, int y) //Implementacja funkcji
{
asm {
MOV AX, x
SUB AX, y
}
return (_AX); //Zwróc zawartosc rejestru AX
}

Zwróc uwage, ze po return(_AX); stawiamy srednik, natomiast po
instrukcjach assemblera nie:

asm MOV AX, DX

chyba, ze chcemy umiescic kilka instrukcji assemblera w jednej
linii (patrz nizej).

C++ i assembler sa równoprawnymi partnerami. C++ moze odwoływac
sie do zmiennych i funkcji assemblera, jesli zostały
zadeklarowane, jako publiczne (public) oraz zewnetrzne
(EXTeRNal) i vice versa. C++ oczekuje, ze zewnetrzne
identyfikatory beda sie rozpoczynac od znaku podkreslenia "_".
Jesli w programie pisanym w BORLAND C++ zastosujemy zewnetrzne
zmienne i funkcje, C++ sam automatycznie doda do identyfikatorów

znak podkreslenia. Turbo Assembler nie robi tego automatycznie i

musimy zadbac o to "recznie". Przykładowo, współpraca pomiedzy
programem P .CPP i modułem MODUL.ASM bedzie przebiegac
poprawnie:

[P065.CPP]

extern int UstawFlage(void); //Prototyp funkcji
int Flaga;
void main()
{
UstawFlage();
}

[MODUL.ASM]

.MODEL SMALL
.DATA
EXTRN _Flaga:WORD
.CODE
PUBLIC _UstawFlage
_UstawFlage PROC
58
CMP [_Flaga], 0
JNZ SKASUJ_Flage
MOV [_Flaga], 1
JMP SHORT KONIEC
SKASUJ_Flage: MOV [_Flaga], 0
KONIEC:
RET
_UstawFlage ENDP
END

Kompilacja moze przebiegac oddzielnie wg schematu:

PROGRAM.CPP --> PROGRAM.OBJ
MODUL.ASM --> MODUL.OBJ
TLINK PROGRAM.OBJ MODUL.OBJ --> PROGRAM.EXE

Lub mozemy powierzyc te prace kompilatorowi, który sam wywoła
TASM i TLINK:

TCC PROGRAM.CPP MODUL.ASM

W BORLAND C++ 3.1 mamy do dyspozycji zintegrowany assembler
(ang. in-line) - BASM. Ma on jednak w stosunku do
"wolnostojacego" Turbo Assemblera pewne ograniczenia:

* ma zawezony w stosunku do TASM zestaw dyrektyw (tylko DB, DD,
DW, EXTRN);
* nie pozwala na stosowanie składni typowej dla trybu "Ideal
mode";
* nie pozwala na zastosowanie makra;
* nie pozwala stosowac instrukcji charakterystycznych dla 386
ani 486.

Mozesz stosowac kilka rozkazów assemblera w jednej linii, ale
powinienes rozdzielac je wewnatrz linii srednikami:

asm {
POP AX; POP DX; POP DS
IRET
}

Komentarz we wstawce assemblerowskiej musi zostac poprzedzony
typowym dla C - /* (sam srednik, jak w TASM jest
niedopuszczalny):

asm {
MOV DX, 1 ;TAK NIE MOZNA W BASM !
...
asm {
ADD AX, BX; /* Taki komentarz moze byc */

[???] KŁOPOTY Z REJESTRAMI ?
________________________________________________________________
Jesli zastosujesz rejestry DI i SI we wstawce assemblerowaj,
kompilator C++ nie bedzie miał gdzie umiescic zmiennych klasy
register z programu głónego. Zastanów sie - co sie bardziej
opłaca.
________________________________________________________________

O WEKTORACH PRZERYWAN DOS

Mikroprocesory Intel 80X86 rezerwuja w pamieci naszych PC
poczatkowe 1024 Bajty (adresy fizyczne 00000...00400 hex) na 256

wektorów przerywan (kazdy wektor składa sie z dwu słów i moze
byc traktowany jako DW, badz far pointer). Nastepne 256 bajtów
(00400...00500 hex) zajmuje BIOS, a kolejne 256 (00500...00600
hex) wykorzystuje DOS i Basic.

Wektor to w samej rzeczy pełny adres poczatku procedury
obsługujacej przerywanie o danym numerze

UWAGA:
Wektor zapisywany jest w pamieci w odwrotnej kolejnosci:

Adres pamieci: 0000:0000 [OFFSET Wekt. int 0]
0000:0002 [SEGMENT int 0]
0000:0004 [OFFSET Wekt. int 1]
0000:0006 [SEGMENT int 1]
0000:0008 [OFFSET int 2]
.... ....
Procesory 80X86 zamieniaja jeszcze dodatkowo starszy bajt z
młodszym.

Posługujac sie systemowym debuggerem DEBUG mozesz łatwo
przejrzec tablice wektorów przerywan własnego komputera. Jesli
wydasz rozkaz:

C:\DOS\DEBUG
-D 0:0

zobaczysz zawartosc pierwszych 32 wektorów int #0...int#31,
czyli pierwsze 128 bajtów pamieci:

-d 0:0

0000:0000 FB 91 32 00 F4 06 70 00-78 F8 00 F0 F4 06 70 00
0000:0010 F4 06 70 00 54 FF 00 F0-53 FF 00 F0 53 FF 00 F0
0000:0020 A5 FE 00 F0 87 E9 00 F0-23 FF 00 F0 23 FF 00 F0
0000:0030 23 FF 00 F0 CE 02 00 C8-57 EF 00 F0 F4 06 70 00
0000:0040 D1 0C BD 1B 4D F8 00 F0-41 F8 00 F0 74 07 70 00
0000:0050 39 E7 00 F0 4A 08 70 00-2E E8 00 F0 D2 EF 00 F0
0000:0060 00 00 FF FF FB 07 70 00-5D 0C 00 CA 9F 01 BD 1B
0000:0070 53 FF 00 F0 A0 7C 00 C0-22 05 00 00 2F 58 00 C0

Po zdeszyfrowaniu okaze sie, ze pierwszy wektor (przerywanie 0)
wskazuje na adres startowy: 0032:91FB (adres absolutny 0951B).
Generalnie mozliwe sa cztery sytuacje. Wektor moze wskazywac:

* adres startowy procedur ROM-BIOS: blok F - Fxxx:xxxx,
* adres funkcji DOS,
* adres funkcji działajacego własnie debuggera (DEBUG przejmuje
obsługe niektórych przerywan), lub innego programu rezydujacego
w pamieci - np. NC.EXE,
* wektor moze byc pusty - 00 00:00 00 jesli dane przerywanie nie

jest obsługiwane.

Jesli zechcesz sprawdzic, jak obsługiwane jest dane przerywanie
mozesz znów zastosowac debugger, wydajac mu rozkaz
zdezasamblowania zawartosci pamieci poczawszy od wskazanego
adresu:

-u 32:91FB

0032:91FB BE6B47 MOV SI,476B
0032:91FE 2E CS:
0032:91FF 8B1E7E47 MOV BX,[477E]
0032:9203 2E CS:
0032:9204 8E16D73D MOV SS,[3DD7]
0032:9208 BCA007 MOV SP,07A0
0032:920B ˙˙˙˙˙˙˙˙˙˙˙E80200 ˙˙˙˙˙˙˙˙˙˙CALL ˙˙˙˙˙˙˙˙˙˙9210

0032:920E EBDA JMP 91EA
0032:9210 ˙˙˙˙˙˙˙˙˙˙˙16 ˙˙˙˙˙˙˙˙˙˙˙PUSH ˙˙˙˙˙˙˙˙˙˙˙SS

0032:9211 07 POP ES
0032:9212 ˙˙˙˙˙˙˙˙˙˙˙16 ˙˙˙˙˙˙˙˙˙˙˙PUSH ˙˙˙˙˙˙˙˙˙˙˙SS

0032:9213 1F POP DS
0032:9214 C606940308 MOV BYTE PTR [0394],08
0032:9219 C606920316 MOV BYTE PTR [0392],16

Z poziomu assemblera do wektora i odpowiednio do funkcji
obsługujacej przerywanie mozesz odwołac sie instrukcja INT
numer.

Zmienna numer moze tu przyjmowac wartosci od 00 do FF. Jesli
wydasz taki rozkaz, komputer zapisze na stos (zeby sobie nie
zapomniec) zawartosc rejestrów CS - biez. segment rozkazu, IP -
biezacy offset rozkazu i FLAGS. Nastepnie wykona daleki (far
jump) skok do adresu wskazanego przez wektor.

Jesli jednak czesc przerywan jest "niewykorzystana", lub w Twoim

programie trzeba je obsługiwac inaczej - niestandardowo ? W
BORLAND C++ masz do dyspozycji specjalny typ funkcji: interrupt.

Aby Twoja funkcja mogła stac sie "handlerem" przerywania, mozesz

zadeklarowac ja tak:

void interrupt MojaFunkcja(bp, di, si, ds .....)

Do funkcji klasy interrupt przekazywane sa jako argumenty
rejestry, nie musisz zatem stosowac pseudozmiennych _AX, _FLAGS
59
itp.. Jesli zadeklarujesz funkcje jako handler przy pomocy słowa

"interrupt", funkcja automatycznie zapamietuje stan rejestrów:
AX, BX, CX, DX, SI, DI, BP, ES i DS.
Po powrocie z funkcji rejestry zostana automatycznie odtworzone.

Przykładem funkcji obsługujacej przerywanie moze byc piszczek()
posługujacy sie wbudowanym głosniczkiem i portem:

# define us unsigned
# include <iostram.h>
# include <dos.h>

void InstalujWektor(void interrupt (*adres)(), int numer_wekt);

void interrupt Piszczek(us bp, us di, us si, us ds, us es,
us ax, us bx, us cx, us dx);

void main()
{
.....
}

....

Po zadeklarowaniu prototypów dwu funkcji:
Piszczek() - nasz handler przerywania;
InstalujWektor() - funkcja instalujaca nasz handler;
mozemy przystapic do zdefiniowania oby funkcji. Posłuzymy sie
zmiennymi
nowe_bity, stare_bity. Wydawanie dzwieku polega na właczaniu i
wyłaczaniu głosniczka. Pusta petla posłuzy nam do zwłoki w
czasie.

void interrupt Piszczek(us bp, us di, us si, us ds, us es,
us ax, us bx, us cx, us dx)
{
char nowe_bity, stare_bity, i;
int n;
unsigned char licznik = ax >> 8;

stare_bity = inportb(0x61);

for(nowe_bity = stare_bity, n = 0; n <= licznik; n++)
{
outportb(0x61, 0xFC & nowe_bity); //Wylacz
for(i = 1; i < 255; i++) ; //Czekaj
outportb(0x61, nowe_bity / 2); //WLACZ
for(i = 1; i < 255; i++) ; //Czekaj
}
outportb(0x61, stare_bity); //Stan poczatkowy

}

Funkcja instalujaca handler korzysta z bibliotecznej funkcji C++

setvect() (ustaw wektor przerywania) i potrzebuje dwu
argumentów:

* numeru wektora przerywania (numer * 4 = adres),
* adresu funkcji - handlera - *faddr.

void InstalujWektor(void interrupt (*adres)(), int
numer_wektora)
{
cout << "\nInstaluje wektor" << numer_wektora << "\n";
setvect(numer_wektora, adres);
}

Pozostało nam wygenerowac przerywanie. Załatwimy to funkcja
Start():

void Start(unsigned char licznik, int numer_wektora)
{
_AH = licznik;
geninterrupt(numer_wektora); //generuj przerywanie
}

Nasz główny program bedzie zatem wygladac tak:

# include <...
...
void main()
{
Instaluj(Piszczek, 10);
Start(5, 10);
}

Nalezy do dobrych manier odtworzyc po wykorzystaniu oryginalna
zawartosc wektora przerywania, który "unowoczesnilismy". W
bibliotece BORLAND C++ masz do dyspozycji m. in. funkcje

getvect() - pobierz wektor (ten stary) i
setvect() - ustaw wektor (ten nasz - nowoczesniejszy).

Jesli zechcemy korzystac z rejestrów 386/486?

Jesli mamy komputer z 32 bitowymi rejestrami, to wypadałoby z
tego korzystac. Na poziomie assemblera masz do dyspozycji
dyrektywy:

.386, .386P i .386C
(P oznacza pełny zestaw instrukcji wraz z trybem
uprzywilejowanym - 386 privileged instruction set).

Mikroprocesor Intel 80386 moze obsługiwac pamiec zgodnie z
tradycyjnym podziałem na 64 kilobajtowe segmenty (tryb USE16),
lub podzielona na ciagłe segmenty po 4 GB (tryb USE32).

Rejestry ogólnego przeznaczenia rozrosły sie z 16 do 32 bitów i
zyskały w nazwie dodatkowa litere E (Extended - rozszerzony).
"Stare" rejestry stały sie młodsza połówka nowych. I tak:

EAX = 0...15 to stary AX, 16...31 to rozbudowa do EAX
(dokładniej: 0..7 = AL, 8..15 = AH, 0...15 = AX, 0...31 = EAX)
BX -> 0...31 EBX: 0...7 BL, 8...15 BH, 0...15 BX
CX -> 0...31 ECX
DX -> 0...31 EDX
wszystkie z dodatkowym podziałem na połówki H i L (np.
DX = DH:DL).
SI -> 0...31 ESI w tym (SI = 0..15)
DI -> 0...31 EDI w tym (DI = 0..15)
BP -> 0...31 EBP w tym (BP = 0..15)
SP -> 0...31 ESP w tym (SP = 0..15)
IP -> 0...31 EIP w tym (IP = 0..15)
FLAGS -> 0...31 EFLAGS w tym (FLAGS = 0..15)

Wszystkie "stare" połówki dostepne pod stara nazwa.
Rejestry segmentowe pozostały 16 bitowe, ale jest ich o dwa
wiecej: CS, DS, ES, SS oraz nowe FS i GS.

Nowe 32 bitowe rejestry działaja według tych samych zasad:

.386
...
MOV EAX, 1 ;zapisz 1 do rejestru EAX
SUB EBX, EBX ;wyzeruj rejestr EBX
ADD EBX, EAX ;dodaj (EAX)+(EBX) --> EBX

Dostep do starszej połowy rejestru mozna uzyskac np. poprzez
przesuwanie (rotation):

.386
...
MOV AX, Liczba_16_bitowa
ROR EDX, 16
MOV AX, DX
ROR EDX, 16
... itp.

W assemblerze mozesz stosowac wobec procesora 386 nowe
instrukcje (testowania nie istniejacych wczesniej bitów,
przenoszenia krótkich liczb do 32 bitowych rejestrów z
uwzglednieniem zaku i uzupełnieniem zerami itp.):

BSF, BSR, BTR, BTS, LFS, LGS, MOVZX, SETxx,
BT, BTC, CDQ, CWDE, LSS, MOVSX, SHLD i SHRD.

Przy pomocji instrukcji MOV w trybie uprzywilejowanym (tzw.
most-privileged level 0 - tylko w trybie .386P) mozesz dodatkowo

uzyskac dostep do specjalnych rejestrów mikroprocesora 80386.

CR0, CR2, CR3,
DR0, DR1, DR2, DR3, DR6, DR7
TR6, TR7
60

Wystepuje tu typ danych - FWORD - 48 bitów (6 bajtów). Obok
znanych dyrektyw DB i DW pojawia sie zatem nowa DF, a oprócz
znajomych wskazników BYTE PTR, WORD PTR pojawia sie nowy FWORD
PTR. Przy pomocy dyrektywy .387 mozesz skorzystac z koprocesora.

Jak wynika z zestawu dodatkowych insrukcji:

FCOS, FSINCOS, FUCOMP, FPREM1, FUCOM, FUCOMPP, FSIN

warto dysponowac koprocesorem, jesli czesto korzystasz z
grafiki, animacji i funkcji trygonometrycznych (kompilacji nie
przyspieszy to niestety ani o 10% - tam odbywaja sie operacje
stałoprzecinkowe).

Zwróc uwage, ze procesory 386 i wczesniejsze wymagały instalacji

dodatkowego układu 387 zawierajacego koprocesor
zmiennoprzecinkowy. Procesory 486 jesli maja rozszerzenie DX -
zawieraja juz koprocesor wewnatrz układu scalonego.







LEKCJA 18 - O ŁANCUCHACH TEKSTOWYCH
________________________________________________________________
W trakcie tej lekcji dowiesz sie,
* jak manipulowac łancuchami tekstowymi i poznasz kilka
specjalnych funkcji, które słuza w C++ własnie do takich celów;
* jak wykonuja sie operacje plikowo-dyskowe.
________________________________________________________________

OPERACJE NA ŁANCUCHACH TEKSTOWYCH.

String, czyli łancuch - to gupa znaków "pisarskich" (liter, cyfr

i znaków specjalnych typu ?, !, _ itp.). Poniewaz C++ nie ma
odzielnego typu danych "string" - łancuchy znaków to tablice
złozone z pojedynczych znaków (typowe elementy typu char).
Technika obiektowa mozna utworzyc klase - nowy typ danych
"string". W bibliotekach Microsoft C++ istnieje predefiniowana
klasa CString, ale zanim przejdziemy do programowania
obiektowego i zdarzeniowego - rozwazmy manipulowanie tekstami w
sposób najprostszy.

Maksymalna mozliwa długosc napisu nalezy podac wtedy, gdy w
programie deklaruje sie zmienna tekstowa:

char tekst1[40];

Jest to poprawna deklaracja zmiennej tekstowej o nazwie
(identyfikator) tekst1. Maksymalna długosc tekstu, który mozna
umiescic w tej zmiennej tekstowej to - 40 znaków (liter, cyfr,
itp.). A jesli chce zastosowac tylko pojedynczy znak zamiast
całego napisu? To proste:

char napis[1];

Skoro długosc łancucha wynosi 1, to przeciez nie jest zaden
łancuch! Informacja o długosci (size - wielkosc) wpisywana w
nawiasy jest zbedna. Uproszczona wersja utworzenia zmiennej
jednoznakowej i nadania zmiennej nazwy wyglada w tak:

char znak;

Nie jest to juz jednak deklaracja zmiennej łancuchowej - lecz
deklaracja zmiennej znakowej. Łancuch znaków (string) to grupa
znaków (dokł. tablica znakowa) zakonczona zwykle przez tzw.
"wartownika" - znak NULL (zero). A pojedynczy znak to tylko
pojedynczy znak. Nie ma tu miejsca (i sensu) dodawanie po
pojedynczym znaku "wartownika" konca tekstu - zera.

Gdy w deklaracjach zmiennych tekstowych rezerwujesz miejsce w
pamieci dla napisów - zawsze mozesz zazadac od kompilatora C++
zarezerwowania wiekszej ilosci miejsca - na zapas. Zawsze lepiej

miec zbyt duzo miejsca, niz zbyt mało.

[???] LEPIEJ MIEC NIZ NIE MIEC.
________________________________________________________________

Upewnij sie, czy kompilator zarezerwował (a Ty zadeklarowałes)
wystarczajaco duzo miejsca dla Twoich tekstów. C++ niestety nie
sprawdza tego w trakcie działania programu. Jesli bedziesz
próbował umiescic w pamieci tekst o zbyt duzej długosci (dłuzszy

niz zadeklarowałes w programie), C++ posłusznie zapisze go do
pamieci, ale moze to spowodowac nieprawidłowe działanie, badz
nawet "zawieszenie" programu.
________________________________________________________________

Inna przydatna w praktyce programowania cecha jezyka C++ jest
mozliwosc zadeklarowania zawartosci zmiennej tekstowej w
momencie zadeklarowania samej zmiennej. Takie nadanie
poczatkowej wartosci nazywa sie zdefiniowaniem, badz
zainicjowaniem zmiennej. W programie zapisuje sie to tak:

char napis[] = "To jest jakis napis";

Powoduje to przypisanie zmiennej tekstowej "napis" konkretnego
łancucha tekstowego "To jest jakis napis". Zwróc uwage, ze w
nawiasach nie podajemy ilosci znaków, z których składa sie
tekst. Kompilator sam policzy sobie ilosc znaków (tu 19) i
zarezerwuje miejsce w pamieci dla napisu. Jesli wolisz sam
zadecydowac, mozesz zapisac deklaracje tak:

char napis[35] = "To jest jakis napis";

Jesli to zrobisz, kompilator C++ zarezerwuje w pamieci miejsce
dla 35 znaków, a nie dla 19.

W programach czesto inicjuje sie teksty posługujac sie nie
tablica znakowa - lesz wskaznikiem do tekstu. Deklaracja i
zainicjowanie wskaznika (wskaznik wskazuje pierwszy element
łancucha znakowego) wyglada wtedy tak:

char *p = "Jakis tam napis";

Rzucmy okiem na kilka gotowych funkcji, które do manipulowania
tekstami oferuje C++.

ŁACZENIE TEKSTÓW.

[S] String Concatenation - łaczenie łancuchów tekstowych.
Zlepek/skrót. Słowo strcat w jezyku C++ znaczy sklej.

W praktycznych programach zapewne czesto pojawi sie dwa lub
wiecej tekstów, które trzeba bedzie połaczyc w jeden napis.
Wyobrazmy sobie, ze imie i nazwisko uzytkownika mamy zapisane
jako dwa oddzielne łancuchy tekstowe. Aby połaczyc te dwa teksty

w jeden trzeba przeprowadzic tzw. sklejanie (ang. concatenation)

tekstów. W jezyku C++ mamy w tym celu do dyspozycji specjalna
funkcje:

strcat() - STRing conCATenation - sklejanie łancuchów.

Aby połaczyc dwa łancuchy tekstowe napis1 i napis2 w jeden
nalezy zastosowac te funkcje w taki sposób:

strcat(napis1, napis2);

Funkcja strcat() zadziała w taki sposób, ze łancuch znaków
napis2 zostanie dołaczony do konca łancucha napis1. Po
zakonczeniu działania funkcji zmienna napis1 zawiera "swój
własny" napis i dołaczony na koncu napis zawarty uprzednio w
zmiennej napis2.

Program ponizej przedstawia praktyczny przykład zastosowania
funkcji strcat().

[P066.CPP]

#include <conio.h>
#include <iostream.h>
#include <string.h> //W tym pliku jest prototyp strcat()

int main(void)
{
char imie[50], nazwisko[30];

61
clrscr();
cout << "Podaj imie: ";
cin >> imie;

cout << "Podaj nazwisko: ";
cin >> nazwisko;

strcat(imie, " ");
strcat(imie, nazwisko);

cout << "\nNazywasz sie: " << imie << '\n';
cout << "Nacisnij dowolny klawisz";
getch();
return 0;
}


Program zapyta najpierw o imie a nastepnie o nazwisko. Po
wpisaniu przez Ciebie odpowiedzi program doda do siebie oba
teksty i wypisze na ekranie Twoje imie i nazwisko w całosci.
Interesujaxe w programie jest połaczenie przy pomocy funkcji C++

strcat() dwu łancuchów tekstowych w jeden łancuch z dodaniem
spacji rozdzielajacej łancuchy znaków. Najistotniejszy fragment
programu wraz z komentarzem - ponizej.

strcat(imie, " "); <-- dodaj do konca tekstu spacje
strcat(imie, nazwisko); <-- po dołaczonej spacji dodaj
drugi tekst - nazwisko

Poniewaz prototyp funkcji strcat() znajduje sie w pliku STRING.H

- nalezy dołaczyc ten plik nagłówkowy dyrektywa #include.

DŁUGOSC ŁANCUCHA TEKSTOWEGO.

Kazdy tekst ma swoja długosc: liczbe znaków, z których sie
składa. Dla przykładu łancuch znaków:

"Przychodzi katecheta do lekarza i płacze, a lekarz na to: Bóg
dał - Bóg wział..."

ma dla długosc 71, poniewaz składa sie z 71 znaków (odstep -
spacja to tez znak). Łancuch znaków

"Ile diabłów miesci sie w łebku od szpilki?"

ma długosc 42. Teoretycznie długosc łancuchów znakowych moze
wynosic od 0 do nieskonczonosci, ale w Borland/Turbo C++
wystepuje ograniczenie: łancuch znaków moze miec długosc zawarta

w przedziale od 0 do 65536 znaków. Taki np. łancuch znaków jest
całkiem do przyjecia:

"Nie wazne, czy Polska bedzie bogata, czy biedna - wazne, zeby
była katolicka (czyli nasza), bo nasze beda wtedy pieniadze,
urzedy i nasza władza. Lepiej byc pół-Bogiem wsród nedzarzy
(oczywiscie za ich pieniadze, z ich podatków), niz zarabiac na
chleb własna praca."

[S] Null string - Łancuch zerowy.
________________________________________________________________

Łancuch zerowy (dokładniej: łancuch tekstowy o zerowej długosci)

to taki łancuch, który zawiera 0 (zero) znaków. Jak to mozliwe,
by łancuch tekstowy zawierał zero znaków? W C++ łancuchy znaków
zawieraja na koncu znak '\0' (zero) jako "wartownika" konca
tekstu. Jesli pierwszy element tablicy znakowej bedzie zerem -
powstanie własnie łancuch znakowy o zerowej długosci. Mozna to
zrobic np. tak:

char napis[0] = 0;
char *p = "";
char napis[50] = "";
________________________________________________________________


Kiedy C++ wyznacza długosc łancucha znaków - zlicza kolejne
znaki, az dojdzie do zera. W przykładzie juz pierwszy znak jest
zerem, wiec C++ uzna, ze długosc takiego łancucha wynosi zero.
Czasem w praktyce programowania zainicjowanie takiego pustego
łancucha pozwala miec pewnosc, ze tablica nie zawiera jakichs
starych, zbednych danych.

Mozliwosc sprawdzenia, jaka długosc ma łancuch tekstowy moze sie

to przydac np. do rozmieszczenia napisów na ekranie. Dla
przykładu, pozycja na ekranie, od której rozpocznie sie
wyswietlanie napisu zalezy od długosci tekstu, który został
wyswietlony wczesniej. Do okreslania długosci tekstu masz w C++
do dyspozycji gotowa funkcje:

strlen() - STRing LENgth - długosc łancucha znakowego.

Funkcje strlen() stosuje sie w nastepujacy sposób:

unsigned int dlugosc;
char tekst[...];
...
dlugosc = strlen(tekst);

Funkcja ma jeden argument - napis, którego długosc nalezy
okreslic (tu: zmienna nazywa sie tekst). Funkcja strlen() w
wyniku swojego działania ZWRACA długosc łancucha tekstowego jako

liczbe całkowita bez znaku (nieujemna). Liczba zwrócona jako
wynik przez funkcje strlen() moze zostac uzyta w dowolny sposób
- jak kazda inna wartosc numeryczna.

Funkcja strlen() nie podaje w odpowiedzi na wywołanie (madrze
nazywa sie to "zwraca do programu wartosc") długosci łancucha
tekstowego, która została zadeklarowana (maksymalnej
teoretycznej), lecz FAKTYCZNA DŁUGOSC tekstu. Jesli, dla
przykładu, zadeklarujemy zmienna tekstowa tak:

char string1[30] = "Lubie C++ ";

zadeklarowana maksymalna długosc łancucha znakowego wynosi 30,
natomiast faktyczna długosc łancucha znakowego wynosi 10 znaków.

Jesli wywołamy strlen() i kazemy jej okreslic długosc łancucha
znakowego string1:

unsigned int dlugosc = strlen(string1);

funkcja przypisze zmiennej dlugosc wartosc 10 a nie 30.

Jesli wpisałes poprzedni program program przykładowy do okienka
edycyjnego - wystarczy dodac dwa nowe wiersze.

[P067.CPP]

#include <conio.h>
#include <iostream.h>
#include <string.h>

main()
{
char imie[50], nazwisko[20];
int dlugosc;

clrscr();
cout << "Podaj imie: ";
cin >> imie;
cout << "Podaj nazwisko: ";
cin >> nazwisko;
strcat(imie, " ");
strcat(imie, nazwisko);
cout << "\nNazywasz sie: " << imie << '\n';
dlugosc = strlen(imie);
cout<<"Imie i nazwisko sklada sie z: "<<dlugosc<<"znakow\n";
cout << "Nacisnij dowolny klawisz";
getch();
return 0;
}

W programie z Listingu 5.2 nie musisz stosowac dodatkowej
zmiennej dlugosc. Taki sam efekt uzyskasz piszac zamiast dwu
wierszy jeden:

cout << "Wszystkich znakow bylo: " << strlen(imie) << '\n';

POBIERANIE I WYSZUKIWANIE WYCINKA TEKSTU - substring.

Podobnie łatwo do łaczenia łancuchów mozesz dokonac podziału
62
wiekszych tekstów na mniejsze fragmenty. "Duze" pierwone
łancuchy nazywaja sie "string", a te mniejsze fragmenty -
"substring". Do podziału łancuchów na "podłancuchy" jezyk C++
dysponuje specjalnymi funkcjami:

strncpy() i strcpy() - STRiNg CoPY - kopiuj string.

[S] Substring - Czesc składowa wiekszego łancucha znaków.
________________________________________________________________
Substring to mniejszy łancuch znaków stanowiacy czesc wiekszego
łancucha znaków. Np. substring BAB jest czescia wiekszego
łancucha BABCIA.
source - zródło (miejsce pochodzenia);
destination - miejsce przeznaczenia.
________________________________________________________________


Funkcja strncpy() kopiuje we wskazane miejsce tylko pewna -
zadana liczbe poczatkowych znaków łancucha. Funkcje strncpy()
mozesz zastosowac w swoich programach w nastepujacy sposób:

char tab_A[80] = "BABCIA";
char tab_B[80] = "";

strncpy(tab_B, tab_A, 3); /* kopiuj 3 pierwsze znaki */

W tym przykładzie wywołujemy funkcje strncpy() przekazujac jej
przy wywołaniu trzy argumenty:

tab_B - destination string - wynikowy łancuch tekstowy (ten
nowy, który powstanie);
tabn_A - source string - łancuch zródłowy (ten, z którego
bedziemy "obcinac" kawałek);
3 - maksymalna liczba znaków, która nalezy obciac . Obciete
znaki utworza "substring" - "BAB".

Pobieranie i "wycinanie" znaków rozpocznie sie od pierwszego
znaku łancucha zródłowego tab_A[80], wiec funkcja wywołana w
taki sposób:

strncpy(string1, string2, 3);

spowoduje pobranie pierwszych 3 znaków z łancucha string2 i
skopiowanie ich do łancucha string1.

Funkcja strcpy() (Uwaga! bez "n") powoduje skopiowanie całego
łancucha znaków. Sposób zastosowania funkcji jest podobny do
przykładu z strncpy(), z tym, ze nie trzeba podawac liczby
całkowitej okreslajacej ilosc znaków do kopiowania. Jak
wszystkie, to wszystkie (jak mawiała babcia), zatem wywołanie
funkcji:

strcpy(string1, string2);

spowoduje skopiowanie całego łancucha znaków zawartego w
zmiennej string2 do zmiennej string1. Jesli, dla przykładu,
zmiennej string2 przypiszemy łancuch tekstowy

string2 = "BABCIA";

to po zadziałaniu funkcji strcpy(string1, string2) zmiennej
string1 zostanie przypisany dokładnie taki sam łancuch.

Rozwazmy program przykładowy. Po uruchomieniu program poprosi o
wpisanie łancucha tekstowego. Wpisz dowolny tekst. Tekst
powinien zawierac wiecej niz 3 znaki. Po pobraniu
wyjsciowego/zródłowego tekstu od uzytkownika, program pobierze z

tego tekstu kilka mniejszych łancuchów tekstowych typu
"substring" i wyswietli je na ekranie.

[P068.CPP]

#include <conio.h>
#include <iostream.h>
#include <string.h>
#include <stdio.h>

main()
{
char napis1[80] = "";
char napis2[80] = "";
char napis3[80] = "";

clrscr();
cout << "Wpisz jakis tekst: ";
gets(napis1);

strcpy(napis2, napis1);
strncpy(napis3, napis1, 3);

cout << "\nKopia tekstu: ";
cout << '*' << napis2 << "*\n";
cout << "Pierwsze 3 znaki tekstu: ";
cout << '\'' << napis3 << '\'' << '\n';

cout << "\n\n...dowolny klawisz...";
getch();
return 0;
}


[???] A jesli zabraknie znaków?
________________________________________________________________

Spróbuj uruchomic program podajac mu łancuch tekstowy krótszy
niz 5 znaków. Jest to próba oszukania funkcji, która oczekuje,
ze kopiowane 3 znaki powinny istniec, mało tego, powinny byc
zaledwie czescia wiekszego łancucha.
Jak widzisz, program nie "zawiesza sie". W jezyku C++ funkcje
opracowane sa zwykle w taki sposób, ze nawet otrzymujac
bezsensowne parametry potrafia jakos tam wybrnac z sytuacji. Tym

niemniej, nawet jesli program sie nie zawiesza, nie oznacza to,
ze wyniki działania przy bezsensownych danych wejsciowych beda
miec jakikolwiek sens. Jako programista powinienes wystrzegac
sie takich błedów (dane z poza zakresu, dane bez sensu
merytorycznego) nie liczac na to, ze C++ jakos z tego wybrnie.
________________________________________________________________

Najwazniejszy fragment tekstu programu wraz z komentarzem:

char napis1[80] = ""; <-- deklaracje zmiennych tekstowych
char napis2[80] = ""; <-- i nadanie im zerowej zawartosci
char napis3[80] = ""; <-- długosc pustego napisu - zero.
...
gets(napis1); <-- GET String - pobierz string
strcpy(napis2, napis1); <-- kopiowanie całego tekstu
strncpy(napis3, napis1, 3); <-- kopiowanie czesci tekstu
...

Zwróc uwage, ze program do pobrania danych (tekstu) od
uzytkownika posługuje sie funkcja gets() (ang. GET String -
pobierz łancuch znaków). Obiekt cin jest bardzo wygodnym
srodkiem słuzacyn do wczytywania danych, ale nie pozwala
wprowadzac napisów zawierajacych spacje. Jesli zastosowalibysmy
w programie

cin >> string1;

i wpisali tekst zawierajacy spacje, np.:

To nie wazne, czy Polska...

wczytane zostałyby tylko znaki To (do pierwszej spacji). Z kolei

funkcja gets() pozwala wczytac wiersz tekstu zawierajacy dowolne

znaki uznajac za koniec znak CRLF (powrót karetki, zmiana
wiersza) generowany po nacisnieciu [Entera]. Przeciwna,
symetryczna funkcja do gets() jest funkcja puts() (ang. PUT
String - wyprowadz wiersz tekstu). Prototypy funkcji gets() i
puts() znajduja sie w pliku nagłówkowym STDIO.H. Dlatego ten
plik nagłówkowy został dołaczony na poczatku dyrektywa #include.


WYSZUKIWANIE TEKSTÓW.

Wyobrazmy sobie, ze mamy liste imion i chcemy na tej liscie
odszukac znajome imie np. Alfons. Specjalnie do takich celów C++

dysponuje funkcja:

strstr() - STRing's subSTRing - czesc łancucha tekstowego

Aby wyszukac w wiekszym tekscie mniejszy fragment, powinnismy
63
wywołujac funkcje przekazac jej dwie informacje:

GDZIE SZUKAC - wskazac łancuch tekstowy do przeszukiwania;
i
CZEGO SZUKAC - podac ten tekst, który nas interesuje i który
funkcja powinna dla nas odnalesc.

Funkcja strstr(), powinna zatem miec dwa argumenty:

char Lista[] = "Adam, Buba, Adolf, Magda";
...
gdzie = strstr(Lista, "Adolf");

Funkcja strstr() wyszukuje pierwsze wystapienie danego tekstu.
Po wyszukaniu, funkcja powinna nam w jakis sposób wskazac, gdzie

znajduje sie interesujacy nas tekst. Jak wiesz, do wskazywania
róznych interesujacych rzeczy słuza w C++ WSKAZNIKI (pointer). W

przykładzie powyzej funkcja strstr() w wyniku swojego działania
zwraca wskaznik do szukanego tekstu "Alfons". Aby wskaznik nam
nie przepadł, trzeba go zapamietac. Funkcja zatem przypisuje
wskaznik zmiennej "gdzie". W miejscu przeznaczonym dla tej
zmiennej w pamieci bedzie odtad przechowywany wskaznik,
wskazujacy nam - gdzie w pamieci kmputera znajduje sie
interesujacy nas tekst "Alfons\0".

Aby komputer zarezerwował miejsce w pamieci dla wskaznika,
trzeba go o to "poprosic" na poczatku programu, deklarujac, ze w

programie zamierzamy posługiwac sie wskaznikiem. Deklaracja
wskaznika do zmiennej tekstowej wyglada tak:

char *wskaznik;

Przykładowy program pnizej demonstruje sposób zadeklarowania
wskaznika i wyszukiwanie tekstu. Program nie oczekuje zadnej
informacji wejsciowej od uzytkownika. Uruchom program i
przeanalizuj wydruk na ekranie porównujac go z tekstem programu.


[P069.CPP]

#include <conio.h>
#include <iostream.h>
#include <string.h>

main()
{
char string1[] = "Ala, Magda, Adam, Alfons, Jasiek, Alfons, As";

char *pointer;

clrscr();
cout << "Lista:\n" << string1;

pointer = strstr(string1, "Alfons");

cout << "Tekst 'Alfons' wystapil po raz pierwszy:\n";
cout << " " << pointer << '\n';

pointer = strstr(ptr, "Jasiek");
cout << "Tekst 'Jasiek' wystapil po raz pierwszy:\n";
cout << " " << pointer << '\n';

pointer = strstr(pointer, "As");
cout << "Tekst 'As' wystapil:\n";
cout << " " << ptr << '\n' << "\n\nNacisnij cokolwiek";

getch();
return 0;
}

Inna metoda zastosowania funkcji manipulujacych łancuchami
tekstowymi moze byc "obróbka" tekstu wprowadzonego przez
uzytkownika. Nastepny program przykładowy pozwala uzytkownikowi
wprowadzic tekst do przeszukiwania (odpowiednik listy) i tekst
do wyszukania (odpowiednik imienia). W wyniku wyszukania
wskazanego łancucha program wyswietla liste poczawszy od
wyszukanego pierwszego wystapienia zadanego łancucha znaków.

[P070.CPP]

#include <conio.h>
#include <iostream.h>
#include <string.h>
#include <stdio.h>

main()
{
char str1[80], str2[80];
char *ptr;

clrscr();
cout << "Wpisz tekst do przeszukania:\n ";
gets(str1);

cout << "Co mam wyszukac?\n--> ";
gets(str2);

ptr = strstr(str1, str2); <-- wyszukiwanie tekstu
cout << "Znalazlem: " << '\'' << str1 << '\'' << " w ";
cout << '\'' << str2 << '\'' << '\n';
cout << ptr;
cout << "\n\n ...Nacisnij klawisz...";
getch();
return 0;
}


DUZE I MAŁE LITERY.

Litery moga byc małe i duze. Duze litery nazywaja sie
"capitals". Od słowa CAPitalS pochodzi skrót na klawiszu [Caps
Lock]. Innym, uzywanym do okreslenia tego samego słowem jest
"upper case" (duze litery) lub "lower case" (małe litery).
Czasami pojawia sie potrzeba zaminy duzych liter na małe, badz
odwrotnie. W C++ słuza tego celu funkcje:

strupr() - STRing to UPpeR case - zamien litery włancuchu
tekstowym na duze.
strlwr() - STRing to LoWeR case - zamien litery w łancuchu na
małe.

Program przykładowy ponizej demonstruje działanie tych funkcji.

[P071.CPP]

#include <conio.h>
#include <iostream.h>
#include <string.h>
#include <stdio.h>

main()
{
char string1[80];

clrscr();
cout << "Wpisz tekst do zamiany:\n";
gets(string1);

cout << "\nNormalnie: " << string1 << '\n';
cout << "TYLKO DUZE: " << strupr(string1) << '\n';
cout << "tylko male: " << strlwr(string1) << '\n';
cout << "\n\n...Nacisnij klawisz...";
getch();
return 0;
}


[???] DLA DOCIEKLIWYCH.
________________________________________________________________

* Argumenty funkcji - zawsze w tej samej kolejnosci!
Kiedy wywołujesz gotowa funkcje - np. strstr(), argumenty
funkcji musza byc podane zawsze w tej samej kolejnosci (tak, jak

funkcja "sie spodziewa"). Wywołanie funkcji:

pointer = strstr(string, substring, 3);

powiedzie sie i funkcja zadziała zgodnie z oczekiwaniami.
Natomiast wywołanie funkcji tak:

pointer = strstr(3, substring, string);

64
spowoduje bład przy kompilacji programu.

* Przy manipulacji stringami kłopoty moga sprawiac spacje, badz
ich brak. Dla przykładu przy sklejaniu dwóch łancuchów
tekstowych warto dla czytelnosci dodac spacje, by nie uzyskiwac
napisów typu: WaldekKowalski. Łatwo mozna przegapic i inne
ograniczniki (ang. delimiter).

* Ocena długosci tekstu.
Szczególnie przewidujacy i ostrozny musi byc programista wtedy,
gdy łancuch bedzie wprowadzany przez uzytkownika programu.


LEKCJA 19: KILKA INNYCH PRZYDATNYCH FUNKCJI.
________________________________________________________________

W trakcie tej lekcji dowiesz sie, jak zapisac teksty na dysku i
jak jeszcze mozna nimi manipulowac przy pomocy gotowych funkcji
Borland C++.
________________________________________________________________


Program ponizej demonstruje zastosowanie trzech przydatnych
funkcji:

[P072.CPP]

#include <conio.h>

int main(void)
{
int i, x = 0, y = 0;
clrscr();
for (i = 1; i < 10; i++)
{
y = i;
x = 5*i;
textbackground(16-i);
textcolor(i);
gotoxy(x, y);
cprintf("Wspolrzedne: x=%d y=%d", x, y);
getch();
}
return 0;
}

textbackground() - ustaw kolor tła pod tekstem
texcolor() - ustaw kolor tekstu
gotoxy() - rozpocznij drukowanie tekstu od punktu o
współrzednych ekranowych
x - numer kolumny (w normalnym trybie: 1-80)
y - numer wiersza (w normalnym trybie: 1-25)

[Z]
________________________________________________________________
1. Rozmiesc na ekranie napisy i znaki semigraficzne tworzace
rysunek tabelki.
2. Opracuj program, w którym pojedyncze znaki, badz napisy beda
poruszac sie po ekranie.
3. Spróbuj przyspieszyc działanie swojego programu z
poprzedniego zadania poprzez wstawke w assemblerze.
________________________________________________________________

OPERACJE PLIKOWE - NIEOBIEKTOWO.

W systemia DOS dane i programy sa zgrupowane w pliki. Pliki
(ang. file) moga byc TEKSTOWE i BINARNE. Najczestszymi
operacjami na plikach sa:

* Utworzenie nowego pliku (ang. CREATE);
* Odczyt z pliku (ang. READ);
* Zapis do pliku (WRITE);
* Otwarcie pliku (OPEN);
* Zamkniecie pliku (CLOSE);
* Wyszukanie danej w pliku (SEEK);

W kontaktach z urzadzeniami - np. z dyskiem posrednicza DOS i
BIOS. To system DOS wie, gdzie na dysku szukac pliku (katalogu)
o podanej nazwie i w których sektorach dysku znajduja sie
fizycznie dane nalezace do danego pliku. Operacje z plikami
opieraja sie o odwoływanie do systemu operacyjnego za
posrednictwem tzw. Deskryptora pliku (File Descriptor - numer
identyfikacyjny pliku).

Zestaw "narzedzi" potrzebnych nam do pracy to:

IO.H - prototypy funkcji obsługi WEjscia/WYjscia (ang.
Input/Output=IO);

FCNTL.H - plik zawierajacy definicje wymienionych ponizej
stałych:
O_BINARY - otwarcie pliku w trybie binarnym;
O_TEXT - otwarcie pliku w trybie tekstowym;
O_RDONLY (Open for Read Only) - otwórz tylko do odczytu;
O_WRONLY (...Write Only) - tylko dla zapisu;
O_RDWR (Reading and Writing) dozwolony zapis i odczyt;

STAT.H - zawiera definicje stałych
S_IREAD - plik tylko do odczytu (przydatne dla funkcji creat);
S_IWRITE - tylko zapis (przydatne dla funkcji creat);

FUNKCJE:
int open(p1, p2, p3) - trójparametrowa funkcja otwierajaca plik;

(parametry patrz przykład) zwraca do programu Wynik = -1
(operacja zakonczona niepowodzeniem - np. nie ma pliku)
lub Wynik = File Descriptor - numer pliku przekazany przez DOS.
int creat(p1, p2) - funkcja tworzaca nowy plik;
int read(...) - funkcja czytajaca z pliku;
int write(...) - funkcja zapisu do pliku;
imt close(...) - zamkniecie pliku.

Po uruchomieniu program otwiera automatycznie trzy standardowe
pliki, zwiazane z urzadzeniami:
0 - stdin - standardowy plik wejsciowy (norm. klawiatura
konsoli);
1 - stdout - standardowy plik wyjsciowy (norm. monitor);
2 - stderr - standardowy plik wyjsciowy - diagnostyczny
(komunikaty o błedach).

[S] STD...
STandarD INput - standardowe wejscie.
STD OUTput - standardowe wyjscie.
STD ERRors - plik diagnostyczny.

//[P072-2.CPP]

# include <stdio.h>
# include <conio.h>
# include <SYS\STAT.H> //Duze litery tylko dla podkreslenia
# include <FCNTL.H>
# include <IO.H>

char *POINTER;
int IL_znakow, DLUG_pliku, TRYB_dostepu, Wynik, i;
int Plik_1, Plik_2;
char BUFOR[20] = {"TEKST DO PLIKU"};
char STOS[3], ZNAK='X';

main()
{
POINTER = &BUFOR[0];

printf("Wloz dyskietke do A: i nacisnij cos...\n");

Plik_1 = creat( "a:\\plik1.dat", S_IWRITE);

if (Plik_1 == -1)
printf("\n Nie udalo sie zalozyc plik1.dat...");

Plik_2 = creat( "a:\\plik_2.dat", S_IWRITE);
if (Plik_2 == -1)
printf("\n Klops przy Plik2.dat");

_fmode = O_BINARY; //Bedziemy otwierac w trybie binarnym

Wynik = open( "a:\\plik1.dat", O_WRONLY );
if (Wynik == -1)
printf("\n Nie udalo sie otworzyc pliku...");

IL_znakow = 15; //Ilosc znakow do zapisu

Wynik =write( Plik_1, POINTER, IL_znakow );

printf("Zapisalem %d znakow do pliku.", Wynik);
65

close( Plik_1 );

Plik_1 = open("a:\\Plik1.dat", O_RDONLY );
Plik_2 = open("a:\\Plik_2.dat", O_WRONLY );

POINTER = &STOS[0];

for (i=1; ZNAK; i++) //Kopiuje plik + spacje
{
STOS[1] = ZNAK;
write( Plik_2, POINTER, 2);
read( Plik_1, &ZNAK, 1);
}

close(Plik_1); close(Plik_2);

getch();
return 0;
}


Przykładowy program wykonuje nastepujace czynnosci:

1. Tworzy plik a:\plik1.dat (potrzebny dostep do dyskietki a:).
2. Tworzy plik a:\plik_2.dat.
3. Otwiera plik a:\plik1.dat w trybie binarnym tylko do zapisu.
(ZWRÓC UWAGE, ze tryb binarny nie przeszkadza zapisac tekstu.)
4. Dokonuje zapisu do pliku.
5. Zamyka plik a:\plik1.dat.
6. Otwiera plik1.dat w trybie binarnym tylko do odczytu.
7. Otwiera plik_2.dat tylko do zapisu.
8. Kopiuje plik1.dat do plik_2.dat dodajac spacje.

Zwróc uwage na konstrukcje:

for(i=1; ZNAK; i++)

Wyjasnienie. Póki jest znak wykonuj kopiowanie. Przypominam, ze
koniec to NUL - '\0'.


Jesli czytamy i piszemy po kolei - wszystko jest proste. Jezeli
natomiast chcemy wyszukac w pliku okreslone miejsce, to bedzie
nam jeszcze dodatkowo potrzebny mechanizm do okreslenia pozycji
w pliku - tzw. WSKAZNIK PLIKOWY. Pozycje mozna okreslac wzgledem

poczatku pliku:

SEEK_SET - stała okreslajaca pozycjonowanie wzgledem poczatku
pliku;
SEEK_CUR - wzgledem połozenia biezacego (ang. Current -
biezacy);
SEEK_END - okreslenie pozycji wzgledem konca pliku;
EOF - End Of File - znak konca pliku.

Funkcja lseek():
WSK_PLK = long int lseek( plik, o_ile, kierunek);
słuzy do pozycjonowania w pliku.
Liczba typu long int okreslajaca pozycje w pliku nazywana jest
WSKAZNIKIEM PLIKOWYM ( w programie przykładowym została
oznaczona long int WSK_PLK).

W programie przykładowym wykonywane jest kolejno:
* utworzenie na dysku pliku PROBA.DAT;
* zapis do pliku wprowadzonych z klawiatury liczb całkowitych
typu int;
* zamkniecie pliku;
* otwarcie pliku do odczytu;
* ustawienie wskaznika na koncu pliku;
* odczyt z pliku od konca;
* wyprowadzenie odczytanych z pliku danych na ekran.

[P073.CPP]

# include "sys\stat.h"
# include "conio.h"
# include "stdio.h"
# include "io.h"
# include "fcntl.h"
# define Cofnij_o_Zero 0
# define dwa_bajty 2

int Numer = 0;
int Plik, L, M, i;
long int Dlug_Pliku;

main()
{
clrscr();
creat("A:\PROBA.DAT", S_IWRITE);
printf("\nPodaj liczbe rozna od zera, zero - KONIEC");
_fmode=O_BINARY;
Plik=open("A:\PROBA.DAT", O_WRONLY);
do
{
printf("\n Nr liczby \t%d\t\t", Numer++);
scanf("%d", &L);
if (L) write(Plik, &L, 2);
}
while (L != 0);

close(Plik);
getch();

printf("\n Teraz odczytam te liczby z pliku \n");
Plik=open("A:\PROBA.DAT", O_RDONLY);
Dlug_Pliku=lseek(Plik, 0, SEEK_END);
for (i=Dlug_Pliku-dwa_bajty; i>=0; i-=2)
{
lseek(Plik, i, SEEK_SET);
read(Plik, &M, dwa_bajty);
printf("%d, ", M);
}
close(Plik);
getch();

return 0;
}

[Z]
________________________________________________________________
Opracuj program wykonujacy operacje na tekstach opisane
wczesniej na łancuchach tekstowych pobieranych z zewnetrznych
plików dyskowych i umieszczanych w wynikowych plikach
tekstowych.




LEKCJA 20 - JESLI PROGRAM POWINIEN URUCHOMIC INNY
PROGRAM...
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak w C++ mozna programowac
* procesy potomne
* pisac programy rezydujace w pamieci (TSR)
________________________________________________________________

O programach rezydentnych (TSR) i procesach potomnych.

Warunek zewnetrznej zgodnosci z poprzednimi wersjami DOS
wyraznie hamuje ewolucje systemu MS DOS w kierunku "powaznych"
systemów operacyjnych umozliwjajacych prace wieloprogramowa w
trybie "multiuser", "multitasking" i "time sharing". Pewna
namiastke pracy wieloprocesowej daja nam juz DOS 5/6 i Windows
3.1. Mozna juz otwierac wiele okien programów jednoczesnie,
mozna np. drukowac "w tle", mozna wreszcie pisac rezydujace
stale w pamieci programy klasy TSR (ang. Terminated and Stay
Resident) uaktywniajace sie "od czasu do czasu".

O bloku PSP.

System DOS przydziela programom blok - "nagłówek" wstepny
nazywany PSP (ang. Program Segment Prefix). Blok ten zawiera
informacje o stanie systemu DOS w momencie uruchamiania programu

(nazywanego tu inaczej procesem). Znajduja sie tam informacje o
biezacym stanie zmiennych otoczenia systemowego (ang.
environment variables) i parametrach uruchomieniowych. Blok PSP
zajmuje 256 bajtów na poczatku kodu programu w zakresie adresów:


CS:0000 ... CS:0100 (hex)

Własciwy kod programu zaczyna sie zatem od adresu CS:0100.
66
Interpreter rozkazów systemu DOS ładuje programy do pamieci
posługujac sie funkcja systemowa nr 75 (4B hex). Wszystko jest
proste dopóki mamy do czynienia z programem "krótkim" typu
*.COM. Jesli jednakze program uruchamiany jest w wersji
"długiej" - *.EXE, dowolna moze byc nie tylko długosc pliku, ale

takze poczatkowa zawartosc rejestrów CS, SS, SP i IP. W plikach
typu *.EXE poczatek bloku PSP wskazuja rejestry DS (DS:0000) i
ES. W Borland C++ masz do dyspozycji specjalna funkcje getpsp()
przy pomocy której mozesz uzyskac dostep do bloku PSP programu.
Krótki przykład zastosowania tej funkcji ponizej:

/* Przykład zastosowania funkcji getpsp(): */

# include <stdio.h>
# include <dos.h>

main()
{
static char TAB[128];
char far *ptr;
int dlugosc, i;

printf("Blok PSP: %u \n", getpsp());

ptr = MK_FP(_psp, 0x80);
dlugosc = *ptr;

for (i = 0; i < dlugosc; i++)
TAB[i] = ptr[i+1];

printf("Parametry uruchomieniowe: %s\n", TAB);

}

W normalnych warunkach po wykonaniu "swojej roboty" program
zostaje usuniety z pamieci operacyjnej (czym zajmuje sie funkcja

systemowa nr 76 - 4C (hex)). Aby tak sie nie stało, program
moze:

* uruchomic swój proces (program) potomny;
* wyjsc "na chwile" do systemu DOS - tj. uruchomic jako swój
proces potomny interpreter COMMAND.COM;
* przekazac sterowanie programowi COMMAND.COM pozostajac w
pamieci w postaci "uspionej" oczekujac na uaktywninie.

Ponizej kilka prostych przykładów uruchamiania jednych procesów
przez inne w Borland C++:

/* Funkcja execv(): uruchomienie programu "potomnego"*/

# include <process.h>
# include <stdio.h>
# include <errno.h>

void main(int argc, char *argv[])
{
int i;

printf("Parametry uruchomieniowe:");
for (i=0; i<argc; i++)
printf("\n%d) %s", i, argv[i]);

printf("Przekazuje parametry do procesu 2 par_1, par_2...\n");
execv("CHILD.EXE", argv);
....
exit (2);
}

[P074.CPP]

/* Funkcja system() - na chwile do DOS */

# include <stdlib.h>
# include <stdio.h>

void main()
{
printf("Wyjscie do DOS i wykonanie jednego rozkazu:\n");
system("dir > c:\plik.dir");
}


/* Funkcje grupy spawn...() : spawnl() */

# include <process.h>
# include <stdio.h>
# include <conio.h>

void main()
{
int rezultat;
rezultat = spawnl(P_WAIT, "program.exe", NULL);
if (rezultat == -1)
{
perror(" Fiasko !");
exit(1);
}
}


/* Funkcja spawnle() */

# include <process.h>
# include <stdio.h>
# include <conio.h>

void main()
{
int rezultat;

rezultat = spawnle(P_WAIT, "program.exe", NULL, NULL);
if (rezultat == -1)
{
perror("Fiasko !");
exit(1);
}
}

Zagadnienie uruchamiania programów potomnych (ang. child
process) przez programy maciezyste (ang. parent process) jest
rozpracowane w C++ dosc dokładnie i zarazem obszernie. Istnieje
wiele gotowych funkcji bibliotecznych, z usług których mozesz tu

skorzystac. Wszystko to nie jest jednak "prawdziwym" programem
TSR. Przyjrzyjmy sie zatem dokładniej dopuszcalnym przez system
DOS sposobom zakonczenia programu nie powodujacym usuniecia
programu z pamieci.

Jesli program rezydentny jest niewielki (kod < 64 K), mozemy
zakonczyc program posługujac sie przerywaniem INT 39 (27 hex).
Jesli natomiast zamierzamy posługiwac sie dłuzszymi programami,
mamy do dyspozycji funkcje systemowa nr 49 (31 hex). Nalezy tu
zwrócic uwage, ze zakonczenie programu w taki sposób (z
pozostawieniem w pamieci) nie spowoduje automatycznego
zamkniecia plików, a jedynie opróznienie buforów. Programy
rezydentne dzieli sie umownie na trzy kategorie:

[BP] - background process - procesy działajace "w tle";
[SV] - services - programy usługowe - np. PRINT;
[PP] - pop up programs - uaktywniane przez okreslona kombinacje
klawiszy;

System DOS dysponuje tzw. przerywaniem multipleksowym
(naprzemiennym) wykorzystywanym czesto przez programy
rezydentne. Jest to przerywanie nr INT 47 (2F hex). MS DOS
załatwia takie problemy funkcjami nr 37 (25 hex) - zapisanie
wektora przerywania i 53 (35 hex) - odczytanie wektora
przerywania.

Z jakich funkcji C++ mozna skorzystac?

W C++ masz do dyspozycji pare funkcji getvect() i setvect()
(ang. GET/SET VECTor - pobierz/ustaw wektor przerywania).
Ponizej krótkie przykłady zastosowan tych funkcji.

/* Opcja: Options | Compiler | Code generation | Test Stack
Overflow powinna zostac wyłaczona [ ] (off) */

# include "stdio.h"
# include "dos.h"
# include "conio.h"

/* INT 28 (1C hex) - Przerywanie zegarowe */

67
void interrupt ( *oldhandler)(void);
int licznik = 0;

void interrupt handler(void)
{
/* Inkrementacja globalnej zmiennej licznik */
licznik++;

/* Wywolujemy stary "handler" zegara */
oldhandler();
}

void main()
{
/* Zapamietaj poprzedni wektor przerywania 28 */
oldhandler = getvect(28);

/* Zainstaluj nowa funkcje obslugi przerywania */
setvect(28, handler);

/* Inkrementuj licznik */
for (; licznik < 10; ) printf("licznik: %d\n",licznik);

//odtworz stara funkcje obslugi przerywania: interrupt handler

setvect(28, oldhandler);
}


# include <stdio.h>
# include <dos.h>

void interrupt nowa_funkcja(); // prototyp funkcji - handlera

void interrupt (*oldfunc)(); /* interrupt function pointer */

int warunek = 1;

main()
{
printf("\n [Shift]+[Print Screen] = Quit \n");
printf("Zapamietaj, i nacisnij cosik....");
while(!kbhit());

/* zapamietaj stary wektor */
oldfunc = getvect(5);
/* INT 5 to przerywanie Sys Rq, albo Print Screen */

/* zainstaluj nowa funkcje obslugi: interrupt handler */
setvect(5, nowa_funkcja);

while (warunek) printf(".");

/* Odtworz stary wektor przerywania */
setvect(5, oldfunc);

printf("\n Udalo sie... nacisnij cosik...");
while(!kbhit());
}

/* Definicja nowego handlera */
void interrupt nowa_funkcja()
{
warunek = 0;
/* jesli warunek == 0, petla zostanie przerwana*/
}

Jesli nasz program zamierza korzystac z przerywania
multipleksowego INT 47 (2F hex), nalezy pamietac, ze przerywanie

to wykorzystuja takze inne programy systemowe. Rozrózniac te
programy mozna przy pomocy identyfikatorów (podaje dziesietnie):

01 - PRINT.EXE
06 - ASSIGN.COM
16 - SHARE.EXE (10 hex)
26 - ANSI.SYS
67 - HIMEM.SYS
72 - DOSKEY.COM
75 - TASK SWITCHER
173 - KEYB.COM
174 - APPEND.EXE
176 - GRAFTABL.COM
183 - APPEND.EXE

Identyfikator programu TSR jest przekazywany za posrednictwem
rejestru AH.

System DOS jest na razie systemem w zasadzie jednozadaniowym i
jednouzytkownikowym, w którym zasoby sa przydzielane procesom
kolejno (ang. serially reusable resources). Aby uchronic sie
przed potencjalnym konfliktem, powinnismy upewnic sie, czy DOS
"nic nie robi". Czesto stosowana "sztuczka techniczna" jest
zastosowanie flag ErrorMode i InDos systemu oraz wykorzystanie
mechanizmów przerywan nr 36 i 40 (24 i 28 hex). Przydatna
informacja jest takze identyfikator programu - PID. Na taka
ewntualnosc Borland C++ dysponuje makrem getpid zdefiniowanym w
pliku nagłówkowym <PROCESS.H>:

# define getpid() (_psp)

Inna przydatna funkcja moze okazac sie keep() (ang. keep
resident - pozostan rezydentny). Oto krótki przykład
zastosowania tej funkcji - znów z wykorzystaniem przerywan
zegarowych.

# include <dos.h>

# define INTR 0x1C /* przerywanie INT 28 */
# define ATTR 0x7900

/* ograniczenie wielkosci sterty (heap length) i stosu (stack
length): */
extern unsigned _heaplen = 1024;
extern unsigned _stklen = 512;

void interrupt ( *oldhandler)(void);

void interrupt handler(void)
{
unsigned int (far *ekran)[80];
static int licznik;

// Adres pamieci dla monitora barwnego: B800:0000.
// Dla monitora monochromatycznego: B000:0000.

ekran = MK_FP(0xB800,0);

// piloksztaltna zmiana licznika w przedziale 0 ... 9

licznik++;
licznik %= 10;

ekran[0][79] = licznik + '0' + ATTR;

// wywołaj stara funkcje obslugi - old interrupt handler:
oldhandler();
}

void main()
{

oldhandler = getvect(INTR);

// zainstaluj nowa funkcje interrupt handler
setvect(INTR, handler);

/* _psp - to adres poczatku programu, SS:SP to adres stosu,
czyli koniec programu. Biorac pod uwage przesuniecie
SEGMENT/OFFSET o jedna tetrade: SS:SP = SS + SP/16; */

keep(0, (_SS + (_SP/16) - _psp));
}

Kilka istotnych drobiazgów technicznych.

W Borland C++ masz do dyspozycji predefiniowane struktury
BYTEREGS (rejestry jednobajtowe - "połówki") i WORDREGS
(rejestry dwubajtowe). Mozesz po tych strukturach dziedziczyc i
np. taka metoda wbudowac je do swoich własnych klas. Nic nie
stoi na przeszkodzie, by utworzyc np. klase

class REJESTRY : public WORDREGS
{
...
};
68

czy tez własna strukture:

struct REJESTRY : WORDREGS { ... };

Definicje tych struktur w Borland C++ wygladaja nastepujaco:

struct BYTEREGS
{
unsigned int al, ah, bl, bh, cl, ch, dl, dh;
};

struct WORDREGS
{
unsigned int ax, bx, cx, dx, si, di, cflag, flags;
};

Rejestry segmentowe maja własna strukture:

struct SREGS
{
unsigned int es, cs, ss, ds;
};

Pole WORDREGS::cflag odpowiada stanowi flagi przeniesienia (ang.

Carry Flag) rejestru flags mikroprocesora, a pole
WORDREGS::flags odpowiada stanowi całosci rejestru (w wersji 16
- bitowej). Poniewaz rejestry moga byc widziane alternatywnie
jako podzielone na miezalezne połówki - lub jako całosc, to
własnie "albo - albo" wyraza w C++ unia. W Borland C++ taka
predefiniowana unia nazywa sie REGS:

union REGS
{
struct WORDREGS x;
struct BYTEREGS h;
};

Z tych predefiniowanych struktur danych korzystaja m. in.
funkcje int86() intdosx() i int86x() ("x" pochodzi od eXtended -

rozszerzony). Oto krótkie przykłady zastosowania tych funkcji.

# include <stdio.h>
# include <conio.h>
# include <dos.h>

# define INT_NR 0x10 // 10 hex == 16 (Nr przerywania) VIDEO

void UstawKursor(int x, int y)
{
union REGS regs;

regs.h.ah = 2; // ustaw kursor
regs.h.dh = y; // Wspolrzedne kursora na ekranie
regs.h.dl = x;
regs.h.bh = 0; // Aktywna stronica ekranu --> video page 0
int86(INT_NR, &regs, &regs);
}

void main()
{
clrscr();
UstawKursor(30, 12);
printf("Tekst - Test");
while(!kbhit());
}

# include <dos.h>
# include <process.h>
# include <stdio.h>

void main()
{
char nazwapliku[40];
union REGS inregs, outregs;
struct SREGS segregs;

printf("\nPodaj nazwe pliku: ");
gets(nazwapliku); // gets() == GET String
inregs.h.ah = 0x43;
inregs.h.al = 0x21;
inregs.x.dx = FP_OFF(nazwapliku);
segregs.ds = FP_SEG(nazwapliku);
int86x(0x21, &inregs, &outregs, &segregs);
printf("\n Atrybuty pliku: %X\n", outregs.x.cx);
}

# include <stdio.h>
# include <dos.h>
int SkasujPlik(char far*) // Prototyp

void main()
{
int error;
err = SkasujPlik("PLIK.DAT");
if (!error) printf("\nSkasowalem plik PLIK.DAT");
else
printf("\nNie moge skasowac pliku PLIK.DAT");
}

int SkasujPlik(char far *nazwapliku)
{
union REGS regs; struct SREGS sregs;
int wynik;
regs.h.ah = 0x41; // Funkcja kasowania pliku
regs.x.dx = FP_OFF(nazwapliku);
sregs.ds = FP_SEG(nazwapliku);
wynik = intdosx(&regs, &regs, &sregs);
return(regs.x.cflag ? wynik : 0);
// Jesli CF == 1, nastapilo fiasko operacji
}

I wreszcie na zakonczenie szczegóły techniczne działania funkcji

systemowej nr 49 (31 hex) odpowiedzialnej za obsługe programów
rezydujacych w pamieci (załadowanie procesu z pozostawieniem w
pamieci).

1. Wywołanie funkcji:
AL = kod powrotu (ang. return code);
AH = 0031 (hex) - nr funkcji;
DX = długosc programu TSR w paragrafach - Size/16 [Bajtów];
2. Działanie:
* funkcja nie zamyka plików, lecz opróznia bufory;
* funkcja odtwarza wektory przerywan nr 34, 35, 36 (hex 21, 22,
23);
* proces maciezysty moze uzyskac kod powrotu przy pomocy funkcji

nr 77 (4D hex).

Wykorzystanie struktury SDA (ang. Swappable Data Area - obszar
wymiennych danych) nie jest praktyka zalecana.

Tworzac programy rezydentne badz bardzo ostrozny i pamietaj o
jednej z podstawowych zasad - NIE JESTES (tzn Twój program nie
jest) SAM.


LEKCJA 21: KILKA PROCESÓW JEDNOCZESNIE.
________________________________________________________________

W trakcie tej lekcji dowiesz sie, jak to zrobic, by Twój PC mógł
wykonywac kilka rzeczy jednoczesnie.
________________________________________________________________


Procesy współbiezne.

Sprzet, czyli PC ma mozliwosci zdecydowanie pozwalajace na
techniczna realizacje pracy wielozadaniowej. Nie ma tez zadnych
przeciwskazan, by zamiast koprocesora umozliwic w PC instalacje
drugiego (trzeciego) równoległego procesora i uprawiac na PC
powazne programowanie współbiezne. Po co? To proste. Wyobraz
sobie Czytelniku, ze masz procesor pracujacy z czestotliwoscia
25 MHz (to 25 MILIONÓW elementarnych operacji na sekunde!).
Nawet, jesli wziac pod uwage, ze niektóre operacje (dodawanie,
mnozenie, itp.) wymagaja wielu cykli - i tak mozna w
uproszczeniu przyjac, ze Twój procesor mógłby wykonac od
kilkuset tysiecy do kilku milionów operacji w ciagu sekundy.
Jesli pracujesz np. z edytorem tekstu i piszesz jakis tekst -
znacznie ponad 99% czasu Twój procesor czeka KOMPLETNIE
BEZCZYNNIE (!) na nacisniecie klawisza. Przeciez Twój komputer
mogłby w tym samym czasie np. i formatowac dyskietke (dyskietka
69
tez jest powolna), i przeprowadzac kompilacje programu, i
drukowac dokumenty, i przeprowadzic defragmentacje drugiego
dysku logicznego, itp. itd..

Nawet taka pseudowspółbieznosc realizowana przez DOS, Windows,
czy siec jest oferta dostatecznie atrakcyjna, by warto było
przyjrzec sie mechanizmom PSEUDO-współbieznosci w C i C++.
Współbieznosc procesów, moze byc realizowana na poziomie

* sprzetowym (architektura wieloprocesorowa),
* systemowym (np. Unix, OS/2),
* nakładki (np. sieciowej - time sharing, token passing)
* aplikacji (podział czasu procesora pomiedzy rózne
funkcje/moduły tego samego pojedynczego programu).

My zajmiemy sie tu współbieznoscia widziana z poziomu aplikacji.

Funkcje setjmp() (ang. SET JuMP buffer - ustaw bufor
umozliwiajacy skok do innego procesu) i longjmp() (ang. LONG
JuMP - długi skok - poza moduł) wchodza w skład standardu C i w
zwiazku z tym zostały "przeniesine" do wszystkich kompilatorów
C++ (nie tylko Borlanada).

Porozmawiajmy o narzedziach.

Zaczniemy od klasycznego zestawu narzedzi oferowanego przez
Borlanda. Aby zapamietac stan przerwanego procesu stosowana jest

w C/C++ struktura PSS (ang. Program Status Structure) o nazwie
jmp_buf (JuMP BUFfer - bufor skoku). W przypadku współbieznosci
wielu procesów (wiecej niz dwa) stosuje sie tablice złozona ze
struktur typu

struct jmp_buf TablicaBuforow[n];

Struktura słuzy do przechowywania informacji o stanie procesu
(rejestrach procesora w danym momencie) i jest predefiniowana w
pliku SETJMP.H:

typedef struct
{
unsigned j_sp, j_ss, j_flag, j_cs;
unsigned j_ip, j_bp, j_di, j_es;
unsigned j_si, j_ds;
} jmb_buf[1];

Prototypy funkcji:

int setjmp(jmp_buf bufor);
void longjmp(jmp_buf bufor, int liczba);

W obu przypadkach jmp_buf bufor oznacza ten sam typ bufora
(niekoniecznie ten sam bufor - moze ich byc wiele), natomiast
int liczba oznacza tzw. return value - wartosc zwracana po
powrocie z danego procesu. Liczba ta moze zawierac informacje, z

którego procesu nastapił powrót (lub inna przydatna w
programie), ale nie moze byc ZEREM. Jesli funkcja longjmp()
otrzyma argument int liczba == 0 - zwróci do programu wartosc 1.


Wartosc całkowita zwracana przez funkcje setjmp() przy pierwszym

wywołaniu jest zawsze ZERO a przy nastepnych wywołaniach (po
powrocie z procesu) jest równa parametrowi "int liczba"
przekazanemu do ostatnio wywołanej funkcji longjmp().

Przyjrzyjmy sie temu mechanizmowi w praktyce. Wyobrazmy sobie,
ze chcemy realizowac współbieznie dwa procesy - proces1 i
proces2. Proces pierwszy bedzie nasladował w uproszczeniu
wymieniony wyzej edytor tekstu - pozwoli na wprowadzanie tekstu,

który bedzie powtarzany na ekranie. Proces drugi bedzie
przesuwał w dolnej czesci ekranu swój numerek - cyferke 2 (tylko

po to, by było widac, ze działa). Program główny wywołujacy oba
procesy powinien wygladac tak:

...
void proces1(void);
void proces2(void);

int main(void)
{
clrscr();
proces1();
proces2();
return 0;
}

Alez tu nie ma zadnej współbieznosci! Oczywiscie. Aby
zrealizowac współbieznosc musimy zadeklarowac bufor na biezacy
stan rejestrów i zastosowac funkcje setjmp():

#include <setjmp.h>

void proces1(void);
void proces2(void);

jmp_buf bufor1;

int main(void)
{
clrscr();
if(setjmp(bufor1) != 0) proces1(); //Powrót z procesu2 był?
proces2();
return 0;
}

Po wywołaniu funkcji setjmp() zostanie utworzony bufor1, w
którym zostanie zapamietany stan programu. Funkcja, jak zawsze
przy pierwszym wywołaniu zwróci wartosc ZERO, wiec warunek

if(setjmp(bufor1) != 0) ...

nie bedzie spełniony i proces1() nie zostanie wywołany. Program
pójdzie sobie dalej i uruchomi proces2():

void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
printf(".2\b");
delay(5); //UWAGA: delay() tylko dla DOS!
}
longjmp(bufor1, 1); <--- wróc
} ____________ te jedynke zwróci setjmp()
}

Proces 2 bedzie drukował "biegajaca dwójke" (zwolniona przez
opóznienie delay(5); o piec milisekund), poczym funkcja
longjmp() kaze wrócic z procesu do programu głównego w to
miejsce:

int main(void)
{
clrscr();
if(setjmp(bufor1)) proces1(); <--- tu powrót
proces2();
return 0;
}

Zmieni sie tylko tyle, ze powtórnie wywołana funkcja setjmp()
zwróci tym razem wartosc 1, zatem warunek bedzie spełniony i
rozpocznie sie proces1():

void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);
}

Proces 1 sprawdzi przy pomocy funkcji kbhit() czy w buforze
klawiatury oczekuje znak (czy cos napisałes). Jesli tak -
70
wydrukuje znak, jesli nie - zakonczy sie i program przejdzie do
procesu drugiego. A oto program w całosci:

[P075.CPP]

#include <stdio.h>
#include <process.h>
#include <setjmp.h>
#include <conio.h>
#include <dos.h>

void proces1(void);
void proces2(void);

jmp_buf bufor1, bufor2;

char znak;
int pozycja = 1;

int main(void)
{
clrscr();
if(setjmp(bufor1)) proces1();
proces2();
return 0;
}

void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);
}

void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
printf(".1\b");
delay(5);
}
longjmp(bufor1,1);
}
}

[!!!] UWAGA
________________________________________________________________
Funkcja delay() uzyta dla opóznienia i zwolnienia procesów
bedzie funkcjonowac tylko w srodowisku DOS. Przy uruchamianiu
prykładowego programu pod Windows przy pomocy BCW nalezy te
funkcje poprzedzic znakiem komentzrza // .
________________________________________________________________



Wyobrazmy sobie, ze mamy trzy procesy. Przykład współbieznosci
trzech procesów oparty na tej samej zasadzie zawiera program
ponizej

[P076.CPP]

#include <stdio.h>
#include <process.h>
#include <setjmp.h>
#include <conio.h>
#include <dos.h>

void proces1(void);
void proces2(void);
void proces3(void);

jmp_buf bufor1, bufor2;

char znak;
int pozycja = 1;

int main(void)
{
clrscr();
if(setjmp(bufor1)) proces1();
if(setjmp(bufor2)) proces2();
proces3();
return 0;
}

void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);
}

void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
printf(".2\b");
delay(5);
}
longjmp(bufor1, 1);

}
}


void proces3(void)
{
for(;;)
{
gotoxy(10,23);
printf("PROCES 3: ");
for(int i = 1; i<40; i++)
{
printf(".3\b");
delay(2);
}

longjmp(bufor2,2);
}
}

Procesy odbywaja sie z rózna predkoscia. Kolejnosc uruchamiania
procesów bedzie:

- proces3()
- proces2()
- proces1()

Po uruchomieniu programu zauwazysz, ze proces pierwszy (pisania)

został spowolniony. Mozna jednak temu zaradzic przez ustawienie
flag i priorytetów. Jesli dla przykładu uwazamy, ze pisanie jest

wazniejsze, mozemy wykrywac zdarzenie - nacisniecie klawisza w
kazdym z mniej waznych procesów i przerywac wtedy procesy mniej
wazne. Wprowadzanie tekstu w przykładzie ponizej nie bedzie
spowolnione przez pozostałe procesy.

[P077.CPP]

#include <stdio.h>
#include <process.h>
#include <setjmp.h>
#include <conio.h>
71
#include <dos.h>

void proces1(void);
void proces2(void);
void proces3(void);

jmp_buf BuforStanu_1, BuforStanu_2;

char znak;
int pozycja = 1;

int main(void)
{
clrscr();
if(setjmp(BuforStanu_1)) proces1();
if(setjmp(BuforStanu_2)) proces2();
proces3();
return 0;
}

void proces1(void)
{
while(kbhit())
{
gotoxy(1,1);
printf("PROCES1, Pisz tekst: [Kropka - Koniec]");
gotoxy(pozycja,2);
znak = getch();
printf("%c", znak);
pozycja++;
}
if(znak == '.') exit (0);

}

void proces2(void)
{
for(;;)
{
gotoxy(10,20);
printf("PROCES 2: ");
for(int i = 1; i<40; i++)
{
if(kbhit()) break;
printf(".2\b");
delay(5);
}
longjmp(BuforStanu_1, 1);

}
}


void proces3(void)
{
for(;;)
{
gotoxy(10,23);
printf("PROCES 3: ");
for(int i = 1; i<40; i++)
{
if(kbhit()) break;
printf(".3\b");
delay(2);
}

longjmp(BuforStanu_2,2);
}
}

[!!!]UWAGA
________________________________________________________________
W pierwszych dwu przykładach trzymanie stale wcisnietego
klawisza spowoduje tylko automatyczna repetycje wprowadzanego
znaku. W przykładzie trzecim spowoduje to przerwanie procesów 2
i 3, co bedzie wyraznie widoczne na monitorze (DOS).
Zwróc uwage, ze kbhit() nie zmienia stanu bufora klawiatury.
________________________________________________________________


W bardziej rozbudowanych programach mozna w oparciu o drugi
parametr funkcji longjmp() zwracany przez funkcje setjmp(buf) po

powrocie z procesu identyfikowac - z którego procesu nastapił
powrót i podejmowac stosowna decyzje np. przy pomocy instrukcji
switch:

switch(setjmp(bufor))
{
case 1 : proces2();
case 2 : proces3();
.....
default : proces0();
}

[!!!]UWAGA
________________________________________________________________
* Zmienne sterujace przełaczaniem procesów powinny byc zmiennymi

globalnymi, badz statycznymi. Takze dane, które nie moga ulec
nadpisaniu bezpieczniej potraktowac jako globalne.
________________________________________________________________

W przypadku wielu procesów celowe jest utworzenie listy, badz
kolejki procesów. Przydatny do tego celu bywa mechanizm tzw.
"łancuchowej referencji". W obiektach klasy PozycjaListy nalezy
umiescic pole danych - strukture i pointer do nastepnego
procesu, któremu (zgodnie z ustalonym priorytetem) nalezy
przekazac sterowanie:

static jmp_buf Bufor[m]; <-- m - ilosc procesów
...

class PozycjaListy
{
public:
jmp_buf Bufor[n]; <-- n - Nr procesu
PozycjaListy *nastepna;
}

Wyobrazmy sobie sytuacje odrobine rózna od powyzszych przykładów

(w której zreszta para setjmp() - longjmp() równie czesto
wystepuje.

#include <setjmp.h>

jmp_buf BuforStanu;
int Nr_Bledu;

int main(void)
{
Nr_Bledu = setjmp(BuforStanu) <-- tu nastapi powrót
if(Nr_Bledu == 0) <-- za pierwszym razem ZERO
{
/* PRZED powrotem z procesu (ów) */
....
Proces(); <-- Wywołanie procesu
}
else
{
/* PO powrocie z procesu (ów) */
ErrorHandler(); <-- obsługa błedów
}
....
return 0;
}

Taka struktura zapewnia działanie nastepujace:

- Był powrót z procesu?
NIE: Wywołujemy proces!
TAK: Obsługa błedów, które wystapiły w trakcie procesu.

Jesli teraz proces zaprojektujemy tak:

void Proces()
{
int Flaga_Error = 0;
...
/* Jesli nastapiły błedy, flaga w trakcie pracy procesu jest
ustawiana na wartosc rózna do zera */

if(Error) Flaga_Error++;
...
if(Fllaga_Error != 0) longjmp(BuforStanu, Flaga_Error);
72
...
}

proces przekaze sterowanie do programu w przypadku wystapienia
błedów (jednoczesnie z informacja o ilosci/rodzaju błedów).

[Z]
________________________________________________________________
1. Napisz samodzielnie program realizujacy 2, 3, 4 procesy
współbiezne. Jesli chcesz, by jednym z procesów stał sie
całkowivie odrebny program - skorzystaj z funkcji grupy
spawn...() umozliwiajacych w C++ uruchamianie procesów
potomnych.







LEKCJA 22. NA ZDROWY CHŁOPSKI ROZUM PROGRAMISTY.
________________________________________________________________
W trakcie tej lekcji dowiesz sie:
* jak przyspieszac działanie programów w C++
* jakie dodatkowe narzedzia zyskujesz "przesiadajac sie" na
nowoczesny kompilator C++
________________________________________________________________


UNIKAJMY PETLI, które nie sa NIEZBEDNE !

Unikanie zbednych petli nazywa sie fachowo "rozwinieciem petli"
(ang. loop unrolling). Zwróc uwage, ze zastepujac petle jej
rozwinieciem (ang. in-line code):

* zmniejszamy ilosc obliczen,
* zmniejszamy ilosc zmiennych.

Wyobrazmy sobie petle:

for (i = 0; i < max; i++)
T[i] = i;

Jesli "unowoczesnimy" ja tak:

for (i = 0; i < max; )
{
T[i++] = i - 1;
T[i++] = i - 1;
}

ilosc powtórzen petli zmniejszy sie dwukrotnie. Czai sie tu
jednak pewne niebezpieczenstwo: tablica moze miec NIEPARZYSTA
liczbe elementów. Np. dla 3-elementowej tablicy (max = 3)
nastapiłyby w pierwszym cyklu operacje:

i = 0;
0 < 3 ? == TRUE --> T[0] = 0 // Tu nastepuje i++; //
T[1] = 1 itd...

To, co nastepuje w tak "spreparowanej" tablicy mozesz
przesledzic uruchamiajac program:

[P078.CPP]

# include <iostream.h>
# include <stdio.h>
# include <conio.h>

# define p(x) printf("%d\t", x)

int T[99+1], i, max;

main()
{
cout << "\nPodaj ilosc elem. tablicy T[] - 2...99 \n";
cin >> max;

cout << "T[i]\t\ti\n\n";

for (i = 0; i < max; )
{
T[i++] = i - 1; p(T[i-1]); cout << "\t" << i << "\n";
T[i++] = i - 1; p(T[i-1]); cout << "\t" << i << "\n";
while (!kbhit());
}

return 0;
}

Aby nie spowodowac próby odwołania do nieistniejacego elementu
tablicy, mozemy zadeklarowac tablice T[max + 1]. W przypadku,
gdy max jest liczba nieparzysta, tablica wynikowa posiada
parzysta liczbe elementów. Jesli natomiast max jest parzyste,
tworzymy jeden zbedny element tablicy, który pózniej zostanie
uzyty, ale kompilator ani program nie bedzie nam sie "buntował".


Mozna spróbowac zastapic w programie bardziej czasochłonne
operacje - szybszymi. Dla przykładu, w petli

for(i = 1; i <= 100; i++)
{
n = i * 10;
...

mozna wyeliminowac czasochłonne mnozenie np. tak:

for(i = 1, n = 10; i <= 100; i++, n += 10)
{
...

lub wrecz wprost, jesli dwie zmienne robocze nie sa niezbedne:

for(n = 10; n <= 1000; n += 10)
{
...

Jesli wiadomo, ze jakas petla powinna wykonac sie z definicji
chocby raz, warto wykorzystywac konstrukcje do...while, zamiast
analizowac niepotrzebnie warunek.

Jesli stosujemy w programie petle zagniezdzone (ang. nested
loops), to peta zorganizowana tak:

for(i = 1; i < 5; i++) (1)
for(j = 1; j < 1000; j++)
{ A[i][j] = i + j; }

zadziała szybciej niz

for(j = 1; j < 1000; j++) (2)
for(i = 1; i < 5; i++)
{ A[i][j] = i + j; }

W przypadku (1) zmienna robocza petli wewnetrznej bedzie
inicjowana piec razy, a w przypadku (2) - tysiac (!) razy.

Czasami zdarza sie, ze w programie mozna połaczyc kilka petli w
jedna.

for(i = 1; i < 5; i++)
TAB_1[i] = i;
...
for(k = 0; k < 5; k++)
TAB_2[k] = k;

Zmniejsza to i ilosc zmiennych, i tekst programu i czas pracy
komputera:

TAB_2[0] = 0;
for(i = 1; i < 5; i++)
TAB_1[i] = i;
TAB_2[i] = i;

Czasami wykonywanie petli do konca pozbawione jest sensu.
Przerwac petle w trakcie wykonywania mozna przy pomocy
instrukcji break (jesli petle sa zagniezczone, czesto lepiej
uzyc niepopularnego goto przerywajacego nie jedna - a wszystkie
petle). Stosujac umiejetnie break, continue i goto mozesz
zaoszczedzic swojemu komputerowi wiele pracy i czasu. Rutynowym
"szkolno-strukturalnym" zapetlaniem programu

main() {
char gotowe = 0;
73
...
while (!gotowe)
{
znak = wybrano_z_menu();
if (znak == 'q' || znak == 'Q') gotowe = 1;
else
.......
gotowe = 1;
}

powodujesz czesto zupełnie niepotrzebne dziesiatki operacji,
które juz niczemu nie słuza.

char gotowe;
main() {
...
while (!gotowe)
{
znak = wybrano_z_menu();
if (znak == 'q' || znak == 'Q') break; //Quit !
else
.......
gotowe = 1;
}

Tym razem to, co nastepuje po else zostanie pominiete.

Wskazniki działaja w C++ szybciej, niz indeksy, stosujmy je w
miare mozliwosci w petlach, przy manipulowaniu tablicami i w
funkcjach.

INSTRUKCJE STERUJACE I WYRAZENIA ARYTMETYCZNE.

Na "chłopski rozum" programisty wiadomo, ze na softwarowych
rozstajach, czyli na rozgałezieniach programów
prawdopodobienstwo wyboru kazdwgo z wariantów działania programu

z reguły bywa rózne. Kolejnosc sprawdzania wyrazen warunkowych
nie jest zatem obojetna. Wyobrazmy sobie lekarza, który
zwiezionego na toboganie narciarza pyta, czy ktos w rodzinie
chorował na zółtaczke, koklusz, reumatyzm, podagre, itp. zamiast

zajac sie najpierw wariantem najbardziej prawdopodobnym - czyli
zagipsowaniem nogi nieszczesnika. Absurdalne, prawda? Ale
przeciez (uderzmy sie w piersi) nasze programy czasami postepuja

w taki własnie sposób...

NAJPIERW TO, CO NAJBARDZIE PRAWDOPODOBNE I NAJPROSTSZE.

Jesli zmienna x w naszym programie moze przyjmowac (równie
prawdopodobne) wartosci 1, 2, 3, 4, 5, to "przesiew"

if (x >= 2) { ... }
else if (x == 1) { ... }
else { ... }

okaze sie w praktyce skuteczniejszy, niz

if (x == 0) { ... }
else if (x == 1) { ... }
else { ... }

Nalezy pamietac, ze w drabince if-else-if po spełnieniu
pierwszego warunku - nastepne nie beda juz analizowane.

Zasada ta stosuje sie takze do wyrazen logicznych, w których
stosuje sie operatory logiczne || (lub) i && (i). W wyrazeniach
tych, których ocene C++ prowadzi tylko do uzyskania pewnosci,
jaka bedzie wartosc logiczna (a nie koniecznie do konca
wyrazenia) nalezy zastosowac kolejnosc:

MAX || W1 || W2 || W3 ...
MIN && W1 && W2 && W3 ...

gdzie MAX - oznacza opcje najbardziej prawdopodobna, a MIN -
najmniej prawdopodobna.

Podobnie rzecz ma sie z pracochłonnoscia (zatem i
czso-chłonnoscia) poszczególnych wariantów. Jesli wariant
najprostszy okaze sie prawdziwy, pozostałe mozliwosci mozemy
pominac.

NIE MNÓZ I NIE DZIEL BEZ POTRZEBY.

Prawa MATEMATYKI pozostaja w mocy dla IBM PC i pozostana zawsze,

nawet dla zupełnie nieznanych nam komputerów, które skonstruuja
nasze dzieci i wnuki. Znajomosc praw de Morgana i zasad
arytmetyki jest dla programisty wiedza niezwykle przydatna. Jako

próbke zapiszmy kilka trywialnych tozsamosci przetłumaczonych na

C++:

2 * a == a + a == a << 1
16 * a == a << 4
a * b + a * c == a * (b + c)
~a + ~b == ~(a + b)

Moznaby jeszcze dodac, ze a / 2 == a >> 1, ale to nie zawsze
prawda. Przesuniecie w prawo liczb nieparzystych spowoduje
obciecie czesci ułamkowej. W przypadku wyrazen logicznych:

(x && y) || (x && z) == x && (y || z)
(x || y) && (x || z) == x || (y && z)

W arytmetycznej sumie i iloczynie NIE MA takiej symetrii.

!x && !y == !(x || y)
!x || !y == !(x && y)

Jesli w skomplikowanych wyrazeniach arytmetycznych i logicznych
zastosujemy zasady arytmetyki i logiki, zwykle staja sie krótsze

i prostsze. Podobnie jak liczac na kartce, mozemy zastosowac
zmienne pomocnicze do przechowywania czesto powtarzajacych sie
wyrazen składowych. Wyrazenie

wynik = (x * x) + (x * x);

mozemy przekształcic do postaci

zm_pomocn = x * x;
wynik = zm_pomocn << 1;

Czesto napisane "na logike" wyrazenia da sie łatwo
zoptymalizowac. Jako przykład zastosujmy funkcje biblioteczna
strcmp() (string compare - porównaj łancuchy znaków). Porównanie

łancuchów

if (strcmp(string1, string2) == 0) cout << "identyczne";
else if (strcmp(string1, string2) < 0) cout << "krotszy";
else
cout << "dluzszy";

mozna skrócic tak, by funkcja strcmp() była wywoływana tylko
raz:

wynik = strcmp(string1, string2);
if (wynik == 0)
cout << "identyczne"; break;
else if (wynik < 0)
cout << "krotszy";
else
cout << "dluzszy";

Jesli pracujac nad programem nie bedziemy zapominac, ze PC
operuje arytmetyka dwójkowa, wiele operacji dzielenia i mnozenia

(długich i pracochłonnych) bedziemy mogli zastapic operacjami
przesuniecia w lewo, badz w prawo (ang. shift), które nasz PC
wykonuje znacznie szybciej. Dla liczb całkowitych dodatnich

x * 2 == x << 1; x * 4 == x << 2 itp. ....

[???] UWAGA:
________________________________________________________________
Takich skrótów nie mozna stosowac w stosunku do operandów typu
double, ani float.
________________________________________________________________

Podobnie w przypadku dzielenia przez potege dwójki mozna
zastapic dzielenia znacznie szybsza operacja iloczynu
logicznego.
74

x % 16 == x & 0xF;

Jesli w programie wartosc zmiennej powinna zmieniac sie w sposób

piłokształtny (tj. cyklicznie wzrastac do MAXIMUM i po
osiagnieciu MAXIMUM spadac do zera), najprostszym rozwiazaniem
jest

x = (x + 1) % (MAXIMUM + 1);

ale dzielenie trwa. Ponizszy zapis spowoduje wygenerowanie kodu
znacznie szybszego:

if (x == MAXIMUM) x = 0;
else x++;

stosujac zamiast if-else operator ? : mozemy to zapisac tak:

(x == MAXIMUM) ? (x = 0) : (x++);

Mnozenie jest zwykle troche szybsze niz dzielenie. Zapis

a = b / 10;

mozna zatem zastapic szybszym:

a = b * .1;

Jesli mamy do czynienia ze stała STALA, to zapis w programie

y = x / STALA; --> y = x * (1.0 / STALA);

z pozoru bzdurny spowoduje w wiekszosci implementacji
wyznaczenie wartosci mnoznika 1.0/STALA przez kompilator na
etapie kompilacji programu (compile-time), a w ruchu (run-time)
bedzie obliczany iloczyn zamiast ilorazu.

W programach czesto stosuje sie flagi binarne (jest-nie ma). C++

stosujemy jako flagi zmienne typu int lub char a w Windows BOOL.

Jesli wezmiemy pod uwage fakt, ze operatory relacji generuja
wartosci typu TRUE/FALSE, typowy zapis:

if (a > b)
Flaga = 1;
else
Flaga = 0;

zastapimy krótszym

Flaga = (a > b);

Taki krótszy zapis NIE ZAWSZE powoduje wygenerowanie szybszego
kodu. Jest to zalezne od specyfiki konkretnej implementacji.
Jesli natomiast uproscisz swój program tak:

if (x > 1) a = 3; --> a = 3 * (x > 1);
else a = 0;

spowoduje to wyrazne spowolnienie programu (mnozenie trwa).

Kompilator C++ rozróznia dwa rodzaje wyrazen:

* general expressions - wyrazenia ogólne - zawierajace zmienne i

wywołania funkcji, których wartosci nie jest w stanie okreslic
na etapie kompilacji i
* constant expressions - wyrazenia stałe, których wartosc mozna
wyznaczyc na etapie kompilacji.

Zapis

wynik = 2 * x * 3.14;

mozesz zatem przekształcic do postaci

wynik = 2 * 3.14 * x;

Kompilator przekształci to wyrazenia na etapie kompilacji do
postaci

wynik = 6.28 * x;

co spowoduje zmniejszenie ilosci operacji w ruchu programu. Aby
ułatwic takie działanie kompilatora trzeba umiescic stałe obok
siebie.



LEKCJA 23 - Co nowego w C++?
________________________________________________________________
Z tej lekcji dowiesz sie, jakie mechanizmy C++ pozwalaja na
stosowanie nowoczesnego obiektowego i zdarzeniowego stylu
programowania i co programy robia z pamiecia.
________________________________________________________________

W porównaniu z klasycznym C - C++ posiada:

* rozszerzony zestaw słów kluczowych (ang. keywords):
** nowe słowa kluczowe C++:

class - klasa,
delete - skasuj (dynamicznie utworzony obiekt),
friend - "zaprzyjaznione" funkcje z dostepem do danych,
inline - wpleciony (funkcje przeniesione w formie rozwinietej
do programu wynikowego),
new - utwórz nowy obiekt,
operator - przyporzadkuj operatorowi nowe działanie,
private - dane i funkcje prywatne klasy (obiektu), do których
zewnetrzne funkcje nie maja prawa dostepu,
protected - dane i funkcje "chronione", dostepne z
ograniczeniami,
public - dane i funklcje publiczne, dostepne bez ograniczen,
template - szablon,
this - ten, pointer wskazujacy biezacy obiekt,
virtual - funkcja wirtualna, abstrakcyjna, o zmiennym
działaniu.

* nowe operatory (kilka przykładów juz widzielismy), np.:
<< - wyslij do strumienia wyjsciowego,
>> - pobierz ze strumienia wejsciowego.
* nowe typy danych:
klasy,
obiekty,
abstrakcyjne typy danych (ang. ADT).
* nowe zasady posługiwania sie funkcjami:
funkcje o zmiennej liczbie argumentów,
funkcje "rozwijane" inline,
funkcje wirtualne, itp.;

Przede wszystkim (i od tego własnie rozpoczniemy) zobaczymy
funkcje o nowych mozliwosciach.

ROZSZERZENIE C - FUNKCJE.

Funkcje uzyskuja w C++ znacznie wiecej mozliwosci. Przeglad
rozpoczniemy od sytuacji czesto wystepujacej w praktyce
programowania - wykorzystywania domyslnych (ang. default)
parametrów.

FUNKCJE Z DOMYSLNYMI ARGUMENTAMI.

Prototyp funkcji w C++ pozwala na podanie deklaracji domyslnych
wartosci argumentów funkcji. Jesli w momencie wywołania funkcji
w programie jeden (lub wiecej) argument (ów) zostanie pominiete,

kompilator wstawi w puste miejsce domyslna wartosc argumentu.

Aby uzyskac taki efekt, prototyp funkcji powinien zostac
zadeklarowany w programie np. tak:

void Funkcja(int = 7, float = 1.234);

Efekt takiego działania bedzie nastepujacy:

Wywołanie w programie: Efekt:
________________________________________________________________

Funkcja(99, 5.127); Normalnie: Funkcja(99, 5.127);
Funkcja(99); Funkcja(99, 1.234);
Funkcja(); Funkcja(7, 1.234);
________________________________________________________________


75
[!!!] Argumentów moze ubywac wyłacznie kolejno. Sytuacja:

Funkcja(5.127); //ZLE
Funkcja(99); //DOBRZE

jest w C++ niedopuszczalna. Kompilator potraktuje liczbe 5.127
jako pierwszy argument typu int i wystapi konflikt.

[P079.CPP]

#include <iostream.h>

void fun_show(int = 1234, float = 222.00, long = 333L);

main()
{
fun_show(); // Trzy arg. domyslne
fun_show(1); // Pierwszy parametr
fun_show(11, 2.2); // Dwa parametry
fun_show(111, 2.22, 3L); // Trzy parametry
return 0;
}

void fun_show(int X, float Y, long Z)
{
cout << "\nX = " << X;
cout << ", Y = " << Y;
cout << ", Z = " << Z;
}

Uruchom program i przekonaj sie, czy wstawianie argumentów
domyslnych przebiega poprawnie.

W KTÓRYM MIEJSCU UMIESZCZAC DEKLARACJE ZMIENNYCH.

C++ pozwala deklarowac zmienne w dowolnym miejscu, z
zastrzezeniem, ze deklaracja zmiennej musi nastapic przed jej
uzyciem. Umieszczanie deklaracji zmiennych mozliwie blisko
miejsca ich uzycia znacznie poprawia czytelnosc (szczególnie
duzych "wieloekranowych") programów. Klasyczny sposób deklaracji

zmiennych:

int x, y, z;
...
main()
{
...
z = x + y + 1;
...
}

moze zostac zastapiony deklaracja w miejscu zastosowania (w tym
np. wewnatrz petli):

main()
{
...
for ( int i = 1; i <= 10; i++)
cout << "Biezace i wynosi: " << i;
...
}

Nalezy jednak pamietac o pewnym ograniczeniu. Zmienne
deklarowane poza funkcja main() sa traktowane jako zmienne
globalne i sa widoczne (dostepne) dla wszystkich innych
elementów programu. Zmienne deklarowane wewnatrz bloku/funkcji
sa zmiennymi lokalnymi i moga "przesłaniac" zmienne globalne.
Jesli wielu zmiennym nadamy te same nazwy-identyfikatory, mozemy

przesledzic mechanim przesłaniania zmiennych w C++. W
przykładzie ponizej zastosowano trzy zmienne o tej samej nazwie
"x":

[P080.CPP]
//Program demonstruje przesłanianie zmiennych

#include <iostream.h>

int x = 1; //Zmienna globalna
void daj_x(void); //Prototyp funkcji

main()
{
int x = 22; //Zmienna lokalna funkcji main
cout << ::x << " <-- To jest globalny ::x \n";
cout << x << " <-- A to lokalny x \n";
daj_x();

return 0;
}

void daj_x(void)
{
cout << "To ja funkcja daj_x(): \n";

cout << ::x << " <-- To jest globalny ::x \n";
cout << x << " <-- A to lokalny x \n";

int x = 333;
cout << "A to moja zmienna lokalna - automatyczna ! \n";
cout << x << " <-- tez x ";
}

Program wydrukuje tekst:

1 <-- To jest globalny ::x
22 <-- A to lokalny x
To ja funkcja daj_x():
1 <-- To jest globalny ::x
1 <-- A to lokalny x
A to moja zmienna lokalna - automatyczna !
333 <-- tez x

Zwróc uwage, ze zmienne deklarowane wewnatrz funkcji (tu:
main()) nie sa widoczne dla innych funkcji (tu: daj_x()).
Operator :: (ang. scope) pozwala nam wybierac pomiedzy zmiennymi

globalnymi a lokalnymi.

TYP WYLICZENIOWY enum JAKO ODREBNY TYP ZMIENNYCH.

W C++ od momentu zdefiniowania typu wyliczeniowego enum staje
sie on równoprawnym ze wszystkimi innymi typem danych. Program
ponizej demonstruje przykład wykorzystania typu enum w C++.

[P081.CPP]

# include <iostream.h>

enum ciuchy
{
niewymowne = 1, skarpetka, trampek, koszula, marynarka,
czapa, peruka, koniec
};

main()
{
ciuchy n;
do
{
cout << "\nNumer ciucha ? --> (1-7, 8 = quit): ";
cin >> (int) n;

switch (n)
{
case niewymowne: cout << "niewymowne";
break;
case skarpetka: cout << "skarpetka";
break;
case trampek: cout << "trampek";
break;
case koszula: cout << "koszula";
break;
case marynarka: cout << "marynarka";
break;
case czapa: cout << "czapa";
break;
case peruka: cout << "peruka";
break;
case koniec: break;
default:
cout << "??? Tego chyba nie nosze...";
}
} while (n != koniec);

76
return 0;
}

Zwróc uwage w programie na forsowanie typu (int) przy pobraniu
odpowiedzi-wyboru z klawiatury. Poniewaz w C++ "ciuchy" stanowia

nowy (zdefiniowany przed chwila) typ danych, do utozsamienia ich

z typem int niezbedne jest wydanie takiego polecenia przy
pobieraniu danych ze strumienia cin >> . W opcjach pracy
kompilatora mozesz właczyc/wyłaczyc opcje "Treat enums as int"
(traktuj typ enum jak int) i wtedy pominac forsowanie typu w
programie.

JEDNOCZESNE ZASTOSOWANIE DWU KOMPILATORÓW.

Jak juz wspomnielismy wczesniej kompilator C++ składa sie w
istocie z dwu róznych kompilatorów:

* kompilatora C wywoływanego standardowo dla plików *.C,
* kompilatora C++ wywoływanego standardowo dla plików *.CPP.

Oba kompilatory stosuja RÓZNE metody tworzenia nazw zewnetrznych

(ang. external names). Jesli zatem program zawiera moduł, w
którym funkcje zostały przekompilowane w trybie
charakterystycznym dla klasycznego C - C++ powinien zostac o tym

poinformowany. Dla przykładu, C++

* kategorycznie kontroluje zgodnosc typów argumentów,
* na swój własny uzytek dodaje do nazw funkcji przyrostki (ang.
suffix) pozwalajace na okreslenie typu parametrów,
* pozwala na tworzenie tzw. funkcji polimorficznych (kilka
róznych funkcji o tej samej nazwie), itp.

Zwykły C tego nie potrafi i nie robi. Dlatego tez do
wprowadzenia takiego podziału kompetencji nalezy czasem
zastosowac deklaracje extern "C". Funkcja rand() w programie
ponizej generuje liczbe losowa.

[P081.CPP]

#include <iostream.h>

extern "C"
{
# include <stdlib.h> //Prototyp rand() w STDLIB.H
}

main()
{
cout << rand();
return 0;
}

GENERACJA LICZB LOSOWYCH.

Kompilatory C++ umozliwoaja generacje liczb pseudolosowych
uzytecznych czesto w obliczeniach statystycznych (np. metoda
Monte Carlo) i emulacji "rozmytaj" arytmetyki i logiki
(ang.fuzzy math).

[!!!] UWAGA - Liczby PSEUDO-Losowe.
________________________________________________________________

Funkcja rand() powoduje uruchomienie generatora liczb
pseudolosowych. Jesli chcesz uzyskac liczbe pseudolosowa z
zadanego przedziału wartosci, najlepiej zastosuj dzielenie
modulo:

int n = rand % 10;

powoduje tzw. normalizacje. Reszta z dzielenia przez 10 moze byc

wyłacznie liczba z przedziału 0...9.
Aby przy kazdym urichomieniu aplikacji ciag liczb pseudolosowych

rozpoczynał sie od innej wartosci nalezy uruchomic generator
liczb wczesniej - przed uzyciem funkcji rand() - np.:

randomize();
...
int n = rand() % 100;
...
________________________________________________________________

W programie przykładowym funkcje z STDLIB.H zostana skompilowane

przez kompilator C. Okreslenie trybu kompilacji deklaracja
extern "C" jest umieszczane zwykle nie wewnatrz programu
głównego a w dołaczanych plikach nagłówkowych *.H. Jest to
mozliwosc szczególnie przydatne, jesli dysponujesz bibliotekami
funkcji dla C a nie masz checi, czasu, badz mozliwosci
przerabiania ich na wersje przystosowana do wymagan C++. Drugi
przykład ponizej zajmuje sie sortowaniem krewnych przy pomocy
funkcji C qsort().

[P082.CPP]

# include <iostream.h>
# include <stdlib.h>
# include <string.h>

extern "C" int comp(const void*, const void*);

main()
{
int max;
for(;;)
{
cout << "\n Ilu krewnych chcesz posortowac? (1...6): ";
cin >> max;
if( max > 0 && max < 7) break;
cout << "\n Nic z tego...";
}
static char* krewni[] =
{
"Balbina - ciotka",
"Zenobiusz - kuzyn",
"Kleofas - stryjek",
"Ola - kuzynka (ach)",
"Waleria - tez niby ciotka",
"Halina - stryjenka"
};

qsort(krewni, 6, sizeof(char*), comp);

for (int n = 0; n < max; n++)
cout << "\n" << krewni[n];

return 0;
}

extern "C"
{
int comp(const void *x, const void *y)
{
return strcmp(*(char **)x, *(char **)y);
}
}

Program wykonuje nastepujace czynnosci:

* deklaruje prototyp funkcji typu C,
* deklaruje statyczna tablice wskazników do łancuchów znakowych,

* sortuje wskazniki,
* wyswietla posortowane łancuchy znakowe,
* definiuje funkcje comp() - porównaj,
* wykorzystuje funkcje biblioteczna C - strcmp() - String
Compare do porównania łancuchów znaków.

O PAMIECI.

Program w C++ dzieli dostepna pamiec na kilka obszarów o
okreslonym z góry przeznaczeniu. Dla zaawansowanego programisty
zrozumienie i efektywne wykorzystanie mechanizmów zarzadzania
pamiecia w C++ moze okazac sie wiedza wielce przydatna.
Zaczniemy, jak zwykle od "elementarza".

CO PROGRAM ROBI Z PAMIECIA.

W klasycznym C najczesciej stosowanymi do zarzadzania pamiecia
funkcjami sa:

77
* malloc() - przyporzadkuj pamiec,
* farmalloc() - przyporzadkuj odległa pamiec,
* realloc() - przyporzadkuj powtórnie (zmieniona) ilosc pamieci,
* calloc() - przydziel pamiec i wyzeruj,
* free() - zwolnij pamiec.

Pamiec dzielona jest w obszarze programu na nastepujace bloki:

___________________
niskie adresy --> Ngłówek programu I.
Adres startowy
KOD: Kod programu
___________________
Zmienne statyczne II.
DANE: 1. Zainicjowane Zmienne globalne
___________________
Zmienne statyczne III.
DANE: 2. Niezainicjowane Zmienne globalne
___________________
STERTA: (heap) W miare potrzeby IV.
rosnie w dół.
Tu operuja funkcje
malloc(), free().
___________________
POLE NICZYJE: V.

___________________
W miare potrzeby VI.
STOS: (stack) rosnie w góre.
wysokie adresy --> ___________________

W obszarze kodu (I.) znajduja sie instrukcje. Na stosie
przechowywane sa:

* zmienne lokalne,
* argumenty przekazywane funkcji w momencie jej wywołania,
* adresy powrotne dla funkcji (RET == CS:IP).

Na stercie natomiast przy pomocy funkcji (a jak przekonamy sie
za chwile - takze operatorów C++) mozemy przydzielac pamiec dla
róznych obiektów tworzonych w czasie pracy programu (ang.
run-time memory allocation) - np. tworzyc bufory dla łancuchów,
tablic, struktur itp.. Zwróc uwage, ze obszar V. - POLE NICZYJE
moze byc w czasie pracy programu stopniowo udostepniany dla
stosu (który rozrasta sie "w góre"), albo dla sterty (która
rozrasta sie "w dół"). W przykładowym programie ponizej podano,
w którym obszarze pamieci zostanie umieszczony dany element
programu.

# include <alloc.h>
int a; // III.
int b = 6; // II.

main()
{
char *Dane;
...
float lokalna; // VI.
...
Dane = malloc(16); // IV.
...
}

OPERATORY new I delete.

Operatory new i delete działaja podobnie do pary funkcji
malloc() - free(). Pierwszy przyporzadkowuje - drugi zwalnia
pamiec. Dokładniej rzecz biorac

- operator new moze zostac zastosowany wraz ze wskaznikiem do
bloku danych okreslonego typu:
* struktury danych,
* tablicy, itp. (wkrótce zastosujemy go takze w stosunku do
klas i obiektów);
- przyporzadkowuje pamiec blokowi danych;
- przypisuje poczakowy adres bloku pamieci wskaznikowi.

- operator delete zwalnia pamiec przyporzadkowana poprzednio
blokowi danych,

Operatory new i delete moga współdziałac z danymi wieloma typami

danych (wcale nie tylko ze strukturami), jednakze rozpoczniemy
do struktury Data zawierajacej date urodzenia mojej córki.

[P083.CPP]

# include "iostream.h"

struct Data
{
int dzien;
int miesiac;
int rok;
};

void main()
{
Data *pointer = new Data;
/* Dekl. wskaznik do struct typu Data */
/* Przydziel pamiec dla struktury */

pointer -> miesiac = 11; // pole "miesiac" = 11
pointer -> dzien = 3;
pointer -> rok = 1979;

cout << "\n URODZINY CORKI: ";
cout << pointer -> dzien << '.';
cout << pointer -> miesiac << ". ";
cout << "co rok ! od " << pointer -> rok << " r.";

delete pointer; //Skasuj wskaznik - zwolnij pamiec.
}

Program tworzy w pamieci (dokł. na stercie) strukture typu Data
bez nazwy. O która strukture chodzi i gdzie jej szukac w pamieci

wiemy dzieki wskaznikowi do struktury *pointer. Zapis

Data *pointer = new Data;

oznacza jednoczesna deklaracje i zainicjowanie wskaznika.

TWORZENIE DYNAMICZNYCH TABLIC O ZMIENNEJ WIELKOSCI.

Jesli mamy dane wyłacznie jednego typu (tu: int), zastosowanie
struktury jest własciwie przysłowiowym "strzelaniem z armaty do
wróbli". Trójelementowa tablica typu

int TAB[3];

zupełnie nam wystarczy. Utworzymy ja jednak nie jako tablice
globalna (badz statyczna) w obszarze pamieci danych, lecz
dynamicznie - na stercie.

[P084.CPP]

# include "iostream.h"

main()
{
int *pointer = new int[3]; // Przydziel pamiec

pointer[0] = 3; // Tabl_bez_nazwy[0] - dzien
pointer[1] = 11; // Tabl_bez_nazwy[1] - miesiac
pointer[2] = 1979;

cout << "Data urodzenia: ";
for(int i = 0; i < 3; i++)
cout << pointer[i] << '.';

delete pointer;
}

Uwazny Czytelnik doszedł zapewne do wniosku, ze skoro tablica
tworzona jest dynamicznie w ruchu programu (run-time), to
kompilator nie musi znac na etapie kompilacji programu
(compile-time) wielkosci tablicy! Idac dalej, program powinien
taka technika tworzyc tablice o takiej wielkosci, jakiej w ruchu

zazyczy sobie uzytkownik. Spróbujmy zrealizowac to praktycznie.

[P085.CPP]

# include <conio.h>
# include <stdlib.h>
78
# include <iostream.h>


void main()
{
for(;;)
{
cout << "\nPodaj wielkosc tablicy (1...100) --> ";
int i, size;
cin >> size;
/* Na stercie tworzymy dynamiczna tablica: */
int *pointer = new int[size];

/* Wypelniamy tablice liczbami naturalnymi: */
for (i = 0; i < size; i++)
pointer[i] = i;
cout << "\n TABLICA: \n";

/* Sprawdzamy zawartosc tablicy: */
for (i = 0; i < size; i++)
cout << " " << pointer[i];

char k = getch();
if(k == 'a') break;
delete pointer;
}
}

Twój dialog z programem powinien wygladac nastepujaco:

Podaj wielkosc tablicy (1...100) --> 20
TABLICA:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19
Podaj wielkosc tablicy (1...100) --> 100
TABLICA:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

Skoro dynamiczne tablice o zmiennej wielkosci "chodza", mozemy
wykorzystac to w bardziej interesujacy sposób.

[P086.CPP]

# include <stdlib.h>
# include <string.h>
# include <iostream.h>


extern "C"
{
int Fporownaj(const void* x, const void* y)
{
return (strcmp(*(char **)x, *(char **)y));
}
}

main()
{
cout << "Wpisz maksymalna ilosc imion --> ";

int ilosc, i;
cin >> ilosc;

char **pointer = new char *[ilosc];

for (i = 0; i < ilosc; i++)
{
cout << "Podaj imie Nr: " << i + 1 << "--> ";
char *imie = new char[80];
cin >> imie;
if (strcmp(imie, "stop") == 0) break;
else
pointer[i] = new char[strlen(imie)+1];
strcpy(pointer[i], imie);
delete imie;
}

qsort(pointer, i, sizeof(char *), Fporownaj);

for (i = 0; i < ilosc; i++)
cout << pointer[i] << '\n';
for (i = 0; i < ilosc; i++)
delete pointer[i];

delete pointer;

return 0;
}


Tworzymy dynamicznie przy pomocy operatora new bezimienna
tablice składajaca sie z tablic nizszego rzedu (łancuch znaków
to tez tablica tyle, ze jednowymiarowa - ma tylko długosc).
Zwróc uwage, ze w C++ wskaznik do wskaznika (**pointer)
odpowiada konstrukcji "tablica składajaca sie z tablic". Aby
program uczynic bardziej pogladowym spolszczymy nazwy funkcji
przy pomocy preprocesora.

[P087.CPP]

# define Fporown_string strcmp
# define Fkopiuj_string strcpy
# define Fsortuj qsort

# include <stdlib.h>
# include <string.h>
# include <iostream.h>


extern "C"
{
int Fporownaj(const void* x, const void* y)
{
return (Fporown_string(*(char **)x, *(char **)y));
}
}

main()
{
cout << "Wpisz maksymalna ilosc imion --> ";

int ilosc, i;
cin >> ilosc;

char **pointer = new char *[ilosc];

for (i = 0; i < ilosc; i++)
{
cout << "Podaj imie Nr: " << i + 1 << "--> ";
char *imie = new char[80];
cin >> imie;
if (Fporown_string(imie, "stop") == 0) break;
else
pointer[i] = new char[strlen(imie)+1];
Fkopiuj_string(pointer[i], imie);
delete imie;
}
/* w tym momencie i == ilosc */
Fsortuj(pointer, i, sizeof(char *), Fporownaj);

for (i = 0; i < ilosc; i++)
cout << pointer[i] << '\n';
for (i = 0; i < ilosc; i++)
delete pointer[i];

delete pointer;

return 0;
}

Wskaznik moze wskazywac dane o róznym stopniu złozonosci:
zmienna, tablice, strukture, obiekt (o czym za chwile), ale moze

wskazywac takze funkcje.

JESLI ZABRAKNIE PAMIECI - _new_handler.

Aby obsługiwac błedna sytuacje - brakło pamieci na stercie -
potrzebna nam bedzie funkcja - tzw. HANDLER. Aby jedna było
wiadomo, gdzie szukac handlera, powinnismy operatorowi new
79
przekazac informacje jaka funkcja obsługuje brak pamieci i gdzie

jej szukac.

Mozemy podstawiac na miejsce funkcji stosowanej w programie te
funkcje, która w danym momencie jest nam potrzebna. Jest to
praktyka czesto stosowana w programach obiekktowych, wiec
przypomnijmy raz jeszcze przykładowy program - tym razem w
troche innym kontekscie. Aby wskazac funkcje zastosujemy
wskaznik. . Przypomnijmy deklaracje

double ( *Funkcja ) (double);

[P088.CPP]

#include <conio.h>
#include <math.h>
#include <iostream.h>

double Nasza_F(double); //Deklaracja zwyklej funkcji
double (*Funkcja)(double); //pointer do funkcji

double liczba; //zwyczajna zmienna
int wybor;

int main(void)
{
clrscr();
cout << "\nPodaj Liczbe \n";
cin >> Liczba;
cout << "CO OBLICZYC ?\n________________\n";
cout<<"1 - Sin \n2 - Cos \n3 - Odwrotnosc 1/X\n";

switch(cin >> wybor)
{
case 1: Funkcja = sin; break;
case 2: Funkcja = cos; break;
case 3: Funkcja = Nasza_F; break;
}
cout << "\n\nWYNIK = " << Funkcja(liczba);
return (0);
}

double Nasza_F(double x)
{
if (x != 0)
x = 1/x;
else
cout << "???\n";
return x;
}

Komputer nie jest "z gumy" i nie posiada dowolnie duzej
"rozciagliwej" pamieci. Funkcja malloc(), jesli pamieci
zabraknie, zwraca pusty wskaznik (ang. NULL pointer), co mozna
łatwo przetestowac w programie. Jesli natomiast stosujemy
operator new - konsekwentnie - operator new powinien zwracac
NULL (i próbowac dokonac przypisania pointerowi zero). To tez
mozna sprawdzic w programie.

W C++ istnieje jednak równiez inny, przydatny do tych celów
mechanizm. C++ dysponuje globalnym wskaznikiem _new_handler
(wskaznik do funkcji obsługujacej operator new, jesli zabraknie
pamieci). Dzieki istnieniu tego (predefiniowanego) wskaznika
mozemy przyporzadkowac "handler" - funkcje obsługujaca wyjscie
przez operator new poza dostepna pamiec.

Dopóki nie zazyczymy sobie inaczej, wskaznik

_new_handler == NULL // NULL == 0

i operator new w przypadku niepowodzenia próby przyporzadkowania

pamieci zwróci wartosc NULL inicjujac pusty wskaznik (innymi
słowy "wskaznik do nikad"). Jesli jednak

_new_handler != NULL

to zawartosc wskaznika zostanie przez operator new uznana za
adres startowy funkcji obsługi błednej sytuacji (ang. addres to
call).

[P089.CPP]

# include <stdlib.h>
# include <iostream.h>

static void Funkcja()
{
cout << "\nTo ja ... Funkcja - handler \n";
cout << '\a' << " ! BRAK PAMIECI ! ";
exit (1);
}

extern void (*_new_handler)();
long suma; //Automatycznie suma = 0;

void main()
{
_new_handler = Funkcja; //Inicjujemy wskaznik

for(;;)
{
char *pointer = new char[8192];
suma += 8192;
cout << "\nMam juz " << suma << " znakow w RAM\n";
if (pointer != 0)
cout << "Pointer != NULL";
}
}


[!!!] SPRAWDZ - KONIECZNIE!
________________________________________________________________
W programach uzytkowych, a szczególnie w tych oferowanych
klientom jako produkty o charakterze komercyjnym nalezy ZAWSZE
sprawdzac poprawnosc wykonania newralgicznych operacji - a
szczególnie poprawnosc zarzadzania pamiecia i poprawnosc
operacji dyskowych. Utrata danych, lub nie zauwazone i nie
wykryte przez program przekłamanie moze spowodowac przykre
skutki. Raz utracone dane moga okazac sie nie do odzyskania.




LEKCJA 24 : SKAD WZIEŁY SIE KLASY I OBIEKTY W C++.
________________________________________________________________

W trakcie tej lekcji dowiesz sie, skad w C++ biora sie obiekty i
jak z nich korzystac.
________________________________________________________________


Zajmiemy sie teraz tym, z czego C++ jest najbardziej znany -
zdolnoscia posługiwania sie obiektami. Główna zaleta
programowania obiektowego jest wyzszy stopien "modularyzacji"
programów. "Mudularyzacja" jest tu rozumiana jako mozliwosc
podziału programu na niemal niezalezne fragmenty, które moga
opracowywac rózne osoby (grupy) i które pózniej bez konfliktów
mozna łaczyc w całosc i uruchamiac natychmiast. C++ powstał, gdy
programy stały sie bardzo (zbyt) długie. Mozliwosc skrócenia
programów nie jest jednakze jedyna zaleta C++. W długich,
rozbudowanych programach trudno spamietac szczegóły dotyczace
wszystkich czesci programu. Jesli grupy danych i grupy funkcji
uda sie połaczyc w moduły, do których mozna pózniej siegac, jak
do pewnej odrebnej całosci, znacznie ułatwia to zycie
programiscie. Na tym, w pewnym uproszczeniu, polega idea
programowania obiektowego.

JAK STRUKTURY STAWAŁY SIE OBIEKTAMI.

W C++ struktury uzyskuja "troche wiecej praw" niz w klasycznym
C. Przykładowy program ponizej demonstruje kilka sposobów
posługiwania sie struktura w C++.

[P90.CPP]

#include <iostream.h>

struct Data
{
int dzien;
int miesiac;
int rok;
};
80

Data NaszaStruktura = {3, 11, 1979}; //Inicjujemy strukture
Data daty[16]; //Tablca struktur
Data *p = daty; //Wskaznik do tablicy
void Fdrukuj(Data); //Prototyp funkcji
int i; //Licznik automat. 0

int main()
{
for (; i < 16; i++)
{
*(p + i) = NaszaStruktura;
daty[i].rok += i;
cout << "\nDnia ";
Fdrukuj(daty[i]);
cout << " Patrycja ";
if ( !i ) cout << "urodzila sie, wiek - ";
if (i > 0 && i < 14) cout << "miala ";
if (i > 13) cout << "bedzie miec ";
cout << i;
if (i == 1) cout << " roczek";
else cout << " lat";
if (i > 1 && i < 5) cout << "ka";
cout << '.';
}
return 0;
}

void Fdrukuj(Data Str)
{
char *mon[] =
{
"Stycznia","Lutego","Marca","Kwietnia","Maja","Czerwca",
"Lipca","Sierpnia","Wrzesnia","Pazdziernika","Listopada",
"Grudnia"
};
cout << Str.dzien << ". "
<< mon[Str.miesiac-1] << ". "
<< Str.rok;
}

Prócz danych struktury w C++ moga zawierac takze funkcje. W
przykładzie ponizej struktura Data zawiera wewnatrz funkcje,
która przeznaczona jest do obsługi we własciwy sposób danych
wchodzacych w skład własnej struktury.

[P091.CPP]

#include <iostream.h>

struct Data //Definicja struktury
{
int dzien, miesiac, rok;
void Fdrukuj(); //Prototyp funkcji
Data(); //Konstruktor struktury
};

void Data::Fdrukuj() //Definicja funkcji
{
char *mon[] =
{
"Stycznia","Lutego","Marca","Kwietnia","Maja","Czerwca",
"Lipca","Sierpnia","Wrzesnia","Pazdziernika","Listopada",
"Grudnia"
};
cout << dzien << ". "
<< mon[miesiac-1] << ". "
<< rok;
}

Data::Data(void) //Poczatkowa data - Konstruktor
{
dzien = 3;
miesiac = 11;
rok = 1979;
}

int main()
{
Data NStruktura; //Inicjujemy strukture

cout << "\n Sprawdzamy: ";
NStruktura.Fdrukuj(); //Wywolanie funkcji
cout << " = ";
cout << NStruktura.dzien << " . "
<< NStruktura.miesiac << " . "
<< NStruktura.rok;

for (int i=0; i < 16; i++, NStruktura.rok++)
{
cout << "\nDnia ";
NStruktura.Fdrukuj();
cout << " Patrycja ";
if ( !i ) cout << "urodzila sie, wiek - ";
if (i > 0 && i < 14) cout << "miala ";
if (i > 13) cout << "bedzie miec ";
cout << i;
if (i == 1) cout << " roczek";
else cout << " lat";
if (i > 1 && i < 5) cout << "ka";
cout << '.';
}
return 0;
}

Zwróc uwage, ze
* odkad dane stały sie elementem struktury, zaczelismy odwoływac

sie do nich tak:
nazwa_struktury.nazwa_pola;
* gdy funkcje stały sie elementem struktury, zaczelismy
odwoływac sie do nich tak:
nazwa_struktury.nazwa_funkcji;

Pojawiły sie równiez róznice w sposobie definiowania funkcji:

void Data::Fdrukuj() //Definicja funkcji
{
...
}

oznacza, ze funkcja Fdrukuj() jest upowazniona do operowania na
wewnetrznych danych struktur typu Data i nie zwraca do programu
zadnej wartosci (void). Natomiast zapis:

Data::Data(void) //Poczatkowa data - Konstruktor

oznacza, ze funkcja Data(void) nie pobiera od programu zadnych
parametrów i tworzy (w pamieci komputera) strukture typu Data.
Takie dziwne funkcje konstruujace (inicjujace) strukture (o czym

dokładniej w dalszej czesci ksiazki), nazywane w C++
konstruktorami nie zwracaja do programu zadnej wartosci. Zwróc
uwage, ze konstruktory to specjalne funkcje, które:

-- maja nazwe identyczna z nazwa typu własnej struktury,
-- nie posiadaja wyspecyfikowanego typu wartosci zwracanej do
programu,
-- słuza do zainicjowania w pamieci pól struktury,
-- nie sa wywoływane w programie w sposób jawny, lecz niejawnie,

automatycznie.

Podstawowym praktycznym efektem dodania do struktur funkcji
stała sie mozliwosc skutecznej ochrony danych zawartych na
polach struktury przed dostepem funkcji z zewnatrz struktury.
Przed dodaniem do struktury jej własnych wewnetrznych funkcji -
wszystkie funkcje pochodziły z zewnatrz, wiec "hermetyzacja"
danych wewnatrz była niewykonalna. Zasady dostepu okresla sie w
C++ przy pomocy słów:

public - publiczny, dostepny,
protected - chroniony, dostepny z ograniczeniami,
private - niedostepny spoza struktury.

Przykładowy program ponizej demonstruje tzw. "hermetyzacje"
struktury (ang. encapsulation). W przykładzie ponizej:

* definiujemy strukture;
* definiujemy funkcje;
* przekazujemy i pobieramy dane do/od struktury typu Zwierzak.

Zmienna int schowek powinna sugerowac ukryta przez strukture i
niedostepna dla nieuprawnionych funkcji czesc danych struktury a

nie cechy anatomiczne zwierzaka.
81

[STRUCT.CPP]

# include "iostream.h"

//UWAGA: schowek ma status private, jest niedostepny

struct Zwierzak
{
private:
int schowek; //DANE PRYWATNE - niedostepne
public:
void SCHOWAJ(int Xwe); //Funkcje dostepne zzewnatrz
int ODDAJ(void);
};

void Zwierzak::SCHOWAJ(int Xwe) //definicja funkcji
{
schowek = Xwe;
}

int Zwierzak::ODDAJ(void)
{
return (schowek);
}

main()
{
Zwierzak Ciapek, Azor, Kotek; // Struktury "Zwierzak"
int Piggy; // zwykla zmienna

Ciapek.SCHOWAJ(1);
Azor.SCHOWAJ(22);
Kotek.SCHOWAJ(-333);
Piggy = -4444;

cout << "Ciapek ma: " << Ciapek.ODDAJ() << "\n";
cout << "Azor ma: " << Azor.ODDAJ() << "\n";
cout << "Kotek ma: " << Kotek.ODDAJ() << "\n";
cout << "Panna Piggy ma: " << Piggy << "\n";

return 0;
}

// Proba nieautoryzowanego dostepu do danych prywatnych obiektu:
// cout << Ciapek.schowek;
// printf("%d", Ciapek.schowek);
// nie powiedzie sie

Powiedzie sie natomiast próba dostepu do "zwykłej" zmiennej -
dowolna metoda - np.:

printf("%d", Piggy); //Prototyp ! # include <stdio.h>

Jesli podejmiesz próbe odwołania sie do "zakapsułkowanych"
danych w zwykły sposób - np.:

cout << Ciapek.schowek;

kompilator wyswietli komunikat o błedzie:

Error: 'Zwierzak::schowek' is not accessible in function main()
(pole schowek struktury typu Zwierzak (np. str. Ciapek) nie jest

dostepne z wnetrza funkcji main(). )

Do klas i obiektów juz tylko malenki kroczek. Jak przekonasz sie

za chwile - struktura Ciapek jest juz własciwie obiektem, a typ
danych Zwierzak jest juz własciwie klasa obiektów. Wystarczy
zamienic słowo "struct" na słowo "class".

[CLASS.CPP]

# include "iostream.h"

//w klasach schowek ma status private AUTOMATYCZNIE
//slowo private stalo sie zbedne

class Zwierzak
{
int schowek;
public:
void SCHOWAJ(int Xwe); //Funkcje dostepne zzewnatrz
int ODDAJ(void);
};

void Zwierzak::SCHOWAJ(int Xwe)
{
schowek = Xwe;
}

int Zwierzak::ODDAJ(void)
{
return (schowek);
}

main()
{
Zwierzak Ciapek, Azor, Kotek; // obiekty klasy "Zwierzak"
int Piggy; // zwykla zmienna

Ciapek.SCHOWAJ(1);
Azor.SCHOWAJ(22);
Kotek.SCHOWAJ(-333);
Piggy = -4444;

cout << "Ciapek ma: " << Ciapek.ODDAJ() << "\n";
cout << "Azor ma: " << Azor.ODDAJ() << "\n";
cout << "Kotek ma: " << Kotek.ODDAJ() << "\n";
cout << "Panna Piggy ma: " << Piggy << "\n";

return 0;
}

Kompilator nawet nie mrugnał. Zmiana słowa struct na słowo class

nie sprawiła mu zatem widocznie przykrosci. Mało tego, zwróc
uwage, ze długosc wynikowego pliku STRUCT.EXE i CLASS.EXE jest
IDENTYCZNA. Wynikałoby z tego, ze sposób tworzenia wynikowego
kodu przez kompilator w obu wypadkach był identyczny.

O KLASACH I OBIEKTACH.

Klasy słuza do tworzenia formalnego typu danych. W przypadku
klas wiadomo jednak "z definicji", ze bedzie to bardziej złozony

typ (tzw. agregat) zawierajacy praktycznie zawsze i dane
"tradycyjnych" typów i funkcje (nazywane "metodami"). Podobnie
jak definiujac strukture tworzysz nowy formalny typ danych, tak
i tu - definiujac klase tworzysz nowy typ danych. Jesli
zadeklarujesz uzycie zmiennych danego typu formalnego, takie
zmienne to własnnie obiekty. Innymi słowy, klasy stanowia
definicje formalnego typu, natomiast obiekty - to zmienne danego

typu (danej klasy).

Zamiast słowa struct stosujemy przy klasach słowo class.

class Klasa
{
int prywatna_tab[80]
public:
int dane;
void Inicjuj(void);
int Funkcja(int arg);
};

Nasza pierwsza swiadomie tworzona klasa nazywa sie "Klasa" i
stanowi nowy formalny typ zmiennych. Jesli zadeklarujesz zmienna

takiej klasy (tego typu formalnego), to taka zmienna bedzie
własnie OBIEKTEM.

Nasza pierwsza prawdziwa Klasa zawiera dane:

prywatna_tab[80] - prywatna tablice;
dane - publiczna dana prosta typu int;
oraz funkcje:
Inicjuj() - zainicjuj - utwórz obiekt danej klasy w pamieci;
Funkcja() - jakas funkcja publiczna.

Gdyby była to zwykła struktura, jej definicja w programie
wygladałaby tak:

struct Klasa
82
{
private:
int prywatna_tab[80]
public:
int dane;
void Inicjuj(void);
int Funkcja(int arg);
};


Jezeli w dalszej czesci programu chcielibysmy zastosowac
struktury takiego typu, deklaracja tych struktur musiałaby
wygladac tak:

struct rodzaj_struktur
{
private:
int prywatna_tab[80]
public:
int dane;
void Inicjuj(void);
int Funkcja(int arg);
} str1, str2, .... , nasza_struktura;

badz tak:

struct rodzaj_struktur
{
private:
int prywatna_tab[80]
public:
int dane;
void Inicjuj(void);
int Funkcja(int arg);
};
...
(struct) rodzaj_struktur str1, str2, .... , nasza_struktura;

Słowo kluczowe struct jest opcjonalne. Moglibysmy wiec
zadeklarowac strukture w programie, wewnatrz funkcji main():

struct rodzaj_struktur
{
private:
int prywatna_tab[80]
public:
int dane;
void Inicjuj(void);
int Funkcja(int arg);
};

main()
{
...
struct rodzaj_struktur nasza_struktura;
//lub równowaznie:
rodzaj_struktur nasza_struktura;

Do pól struktury mozemy odwoływac sie przy pomocy operatora
kropki (ang. dot operator). Podobnie dzieje sie w przypadku
klas. Jesli zadeklarujemy zmienna typu Klasa, to ta zmienna
bedzie naszym pierwszym obiektem.

class Klasa
{
int prywatna_tab[80]
public:
int dane;
void Inicjuj(void)
int Funkcja(int our_param);
} Obiekt;

Podobnie jak wyzej, mozemy zadeklarowac nasz obiekt wewnatrz
funkcji main():

class Klasa
{
int prywatna_tab[80]
public:
int dane;
void Inicjuj(void)
int Funkcja(int argument);
};

main()
{
...
Klasa Obiekt;
...

Przypiszemy elementom obiektu wartosci:

main()
{
...
Klasa Obiekt;
Obiekt.dane = 13;
...

Taka sama metoda, jaka stosowalismy do danych - pól struktury,
mozemy odwoływac sie do danych i funkcji w klasach i obiektach.

main()
{
...
Klasa Obiekt;
Obiekt.dane = 13; Obiekt.Funkcja(44);
...

Przyporzadkowalismy obiektowi nie tylko dane, ale takze funkcje
poprzez umieszczenie prototypów funkcji wewnatrz deklaracji
klasy:

class Klasa
{
...
public:
...
void Inicjuj(void) /* Prototypy funkcji */
int Funkcja(int argument);
};

[!!!] UWAGA!
________________________________________________________________
W C++ nie mozemy zainicjowac danych wewnatrz deklaracji klasy:

class Klasa
{
private:
int prywatna_tab[80] = { 1, 2, ... }; //ZLE !
public:
int dane = 123; //ZŁE !
...
________________________________________________________________

Inicjowanie danych odbywa sie w programie głównym przy pomocy
przypisania (dane publiczne), badz za posrednictwem funkcji
nalezacej do danej klasy i majacej dostep do wewnetrznych danych

klasy/obiektu (dane prywatne). Inicjowania danych moga dokonac
takze specjalne funkcje - tzw. konstruktory.

Dane znajdujace sie wewnatrz deklaracji klasy moga miec status
public, private, badz protected. Dopóki nie zazadasz inaczej -
domyslnie wszystkie elementy klasy maja status private. Jezeli
czesc obiektu jest prywatna, to oznacza, ze zaden element
programu spoza obiektu nie ma do niej dostepu. W naszej Klasie
prywatna czesc stanowi tablica złozona z liczb całkowitych:

(default - private:) int prywatna_tab[80];

Do (prywatnych) elementów tablicy dostep moga uzyskac tylko
funkcje zwiazane (ang. associated) z obiektem danej klasy.
Funkcje takie musza zostac zadeklarowane wewnatrz definicji
danej klasy i sa nazywane członkami klasy - ang. member
functions. Funkcje moga miec status private i stac sie dzieki
temu wewnetrznymi funkcjami danej klasy (a w konsekwencji
równiez prywatnymi funkcjami obiektów danej klasy). Jest to
jedna z najwazniejszych cech nowoczesnego stylu programowania w
C++. Na tym polega idea hermetyzacji danych i funkcji wewnatrz
klas i obiektów. Gdyby jednak cała zawartosc (i dane i funkcje)
znajdujace sie w obiekcie zostały dokładnie "zakapsułkowane", to

okazałoby sie, ze obiekt stał sie "slepy i głuchy", a w
konsekwencji - niedostepny i kompletnie nieuzyteczny dla
programu i programisty. Po co nam obiekt, do którego nie mozemy
83
odwołac sie z zewnatrz zadna metoda? W naszym obiekcie, w
dostepnej z zewnatrz czesci publicznej zadeklarowalismy zmienna
całkowita dane oraz dwie funkcje - Inicjuj() oraz Funkcja().
Jesli dane i funkcje maja status public, to oznacza, ze mozemy
sie do nich odwołac z dowolnego miejsca programu i dowolnym
sposobem. Takie odwołania przypominaja sposób odwoływania sie do

elementów struktury:

main()
{
...
Obiekt.dane = 5; //Przypisanie wartosci zmiennej.
Obiekt.Inicjuj(); //Wywołanie funkcji Inicjuj()
...
Obiekt.Funkcja(3); //Wywołanie funkcji z argumentem

[!!!] ZAWSZE PUBLIC !
________________________________________________________________
Dane zawarte w obiekcie, podobnie jak zwykłe zmienne wymagaja
zainicjowania. Funkcja inicjujaca dane - zawartosc obiektu musi
zawsze posiadac status public aby mogła byc dostepna z zewnatrz
i zostac wywołana w programie głównym - funkcji main(). Funkcje
i dane dostepne z zewnatrz stanowia tzw. INTERFEJS OBIEKTU.



LEKCJA 25: PRZYKŁAD OBIEKTU.
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak praktycznie projektuje sie
klasy i obiekty. Twój pierwszy obiekt zacznie działac.
________________________________________________________________

Nasz pierwszy, doswiadczalny obiekt bedzie zliczac ile razy
uzytkownik nacisnał okreslony klawisz - np. litere "A". Najpierw
podejdziemy do problemu "klasycznie". Utworzymy strukture
Licznik, która mozna wykorzystac do przechowywania istotnych dla
nas informacji:

char znak - znak do zliczania
int ile - ile razy wystapił dany znak.

Zwróc uwage, ze Licznik oznacza tu typ struktur (nowy formalny
typ danych) a licznik oznacza nasza robocza zmienna danego typu.

struct Licznik //Licznik - nowy typ struktur
{
public: //Status public jest domyslny dla struktur
char znak;
int ile;
...
} licznik; //Zmienna typu "Licznik"

Do pól struktury licznik.znak i licznik.ile mozemy odwoływac sie

w programie w nastepujacy sposób:

//Przypisanie (zainicjowanie pola struktury)
licznik.znak = 'A';
cin >> licznik.znak;

//Odczyt (wyprowadzenie) biez. zawartosci pola struktury.
cout << licznik.znak;

Potrzebna nam bedzie funkcja, przy pomocy której przekazemy do
struktury informacje, jaki znak powinien byc zliczany. Nazwijmy
te funkcje Inicjuj(). Funkcja Inicjuj() powinna nam zainicjowac
pole struktury tzn. po przekazaniu jej jako argumentu tego
znaku, który ma podlegac zliczaniu, funkcja powinna "przeniesc"
znak i zapisac go w polu licznik.znak naszej roboczej struktury.

Wywołanie funkcji w programie powinno wygladac tak:

main()
{
....
Inicjuj('A');
....
//UWAGA: Nie tak:
//licznik.Inicjuj() - funkcja jest zewnetrzna !

Aby funkcja inicjujaca pole struktury zadziałała prawidłowo, jej

definicja powinna wygladac tak:

void Inicjuj(char x) //Deklaracja zmiennej znak.
{
licznik.znak = x; //x - wewnetrzna zmienna funkcji
licznik.ile = 0;
}

Inicjujac strukture licznik funkcja zeruje pole "ile" struktury.

Przyda nam sie jeszcze jedna funkcja PlusJeden(). Ta funkcja
powinna zwiekszyc zmienna słuzaca do zliczania ile razy wystapił

interesujacy nas znak po kazdym pojawieniu sie odpowiedniego
znaku (w tym przypadku "A").

void PlusJeden(void) //Definicja funkcji
{ //incrementujacej licznik
licznik.ile++;
}

Zbudowalismy licznik, który składa sie z danych rozmieszczonych
na polach struktury oraz dwu stowarzyszonych ze struktura
funkcji. Jesli spróbujemy zastosowac to w programie, gdzie:

char znak_we - znak wczytany z klawiatury;

program bedzie wygladac tak:

void main()
{
char znak_we;
Inicjuj('A');

cout << "\nWpisz tekst zawierajacy litery A"
cout << "\nK - oznacza Koniec zliczania: ";

for(;;) //Wczytujemy znaki
{
cin >> znak_we;
if (znak_we == 'k' || znak_we == 'K') break;
if(znak_we == licznik.znak) PlusJeden();
}
....

W tekscie moga wystapic zarówno duze jak i małe litery. Jesli
zechcemy zliczac i jedne i drugie, mozemy posłuzyc sie funkcja
biblioteczna C zamieniajaca małe litery na duze - toupper().
Najpierw poddamy wczytany zank konwersji a nastepnie porównamy z

"zadanym" na polu licznik.znak:

if(licznik.znak == toupper(znak_we)) PlusJeden();

Po przerwaniu petli przez uzytkownika wystarczy sprawdzic jaka
wartosc jest wpisana w polu licznik.ile i mozemy wydrukowac
wynik zliczania wystapien litery 'A' we wprowadzonym tekscie.

cout << "\nLitera " << licznik.znak
<< " wystapila " << licznik.ile
<< " razy.";

Program w całosci bedzie wygladał tak:

[P092.CPP]

# include <iostream.h>
# include <ctype.h> //Prototyp f. toupper()

struct Licznik
{
char znak;
int ile;
} licznik;

void Inicjuj(char x)
{
licznik.znak = x;
licznik.ile = 0;
}

void PlusJeden(void)
84
{
licznik.ile++;
}

void main()
{
char znak_we;
Inicjuj('A');

cout << "\nWpisz tekst zawierajacy litery A";
cout << "\nPierwzse wytapienie litery k lub K";
cout << "\n - oznacza Koniec zliczania: ";

for(;;)
{
cin >> znak_we;
if (znak_we == 'k' || znak_we == 'K') break;
if(licznik.znak == toupper(znak_we)) PlusJeden();
}

cout << "\nLitera " << licznik.znak
<< " wystapila " << licznik.ile
<< " razy.";
}

Jesli dane i funkcje połaczymy w jedna całosc - powstanie
obiekt. Zawartosc naszego obiektu powinna wygladac tak:

Dane:
char znak;
int ile;
Funkcje:
void Inicjuj(char);
void PlusJeden(void);

Łaczymy w całosc funkcje operujace pewnymi danymi i te własnnie
dane. Co wiecej, jesli zaistnieja takie funkcje, które nie beda
wykorzystywane przez nikogo wiecej poza własnym obiektem i poza
jego składnikami: funkcja Inicjuj() i funkcja PlusJeden(),
funkcje te nie musza byc widoczne, ani dostepne dla reszty
programu. Takie funkcje moga wraz z danymi zostac uznane za
prywatna czesc obiektu. Takie praktyki, szczególnie w programach

przeznaczonych dla srodowiska Windows sa uzasadnione i
pozyteczne. Rozwazmy obiekt, modularyzacje i hermetyzacje
obiektu na konkretnych przykładach.

Zacznijmy od zdefiniowania klasy.

class Licznik
{
char znak;
int ile;
public:
void Inicjuj(char);
void PlusJeden(void);
};

Nastepny krok, to zdefiniowanie obu funkcji. Zwróc uwage, ze
funkcje nie sa juz definiowane "niezaleznie", lecz w stosunku do

własnej klasy:

void Licznik::Inicjuj(char x)
{
znak = x;
ile = 0;
}

void Licznik::PlusJeden(void)
{
ile++;
}

Skoro funkcje widza juz wyłacznie własna klase, zapis

licznik.znak moze zostac uproszczony do --> znak
i
licznik.ile do --> ile

Aby wskazac, ze funkcje sa członkami klasy Licznik stosujemy
operator :: (oper. widocznosci/przesłaniania - ang. scope
resolution operator). Taki sposób zapisu definicji funkcji
oznacza dla C++, ze funkcja jest członkiem klasy (ang. member
function). Logika C++ w tym przypadku wyglada tak:

* Prototypy funkcji nalezy umiescic w definicji klasy.
* Definicje funkcji moga znajdowac sie w dowolnym miejscu
programu, poniewaz operator przesłaniania :: pozwala rozpatrywac

klase podobnie jak zmienne globalne.

* Wstawiajac operator :: pomiedzy nazwe klasy i prototyp funkcji

informujemy C++ ze dana funkcja jest członkiem okreslonej klasy.


Funkcje - członkowie klas nazywane sa czesto METODAMI.
Definicje klas i definicje funkcji - METOD sa czesto umieszczane

razem - w plikach nagłówkowych. Jesli posługujemy sie taka
metoda, wystarczy dołaczyc odpowiedni plik dyrektywa # include.
Kompilator C++ skompiluje wtedy automatycznie wszystkie funkcje,

które znajdzie w dołaczonych plikach nagłówkowych.

Mozemy przystapic do utworzenia programu.

main()
{
char znak_we; //Dekl. zwyklej zmiennej
Licznik licznik; //Deklarujemy obiekt klasy Licznik
licznik.Inicjuj('A'); //Inicjujemy licznik
...

Mozemy teraz okreslic ilosc wprowadzonych z klawiatury liter 'A'

oraz 'a' i wyprowadzic ja na ekran monitora. Pojawia sie jednak

pewien problem. Nie uda sie siegnac z zewnatrz do prywatnych
danych obiektu tak, jak poprzednio:

if(licznik.znak == toupper(znak_we)) ....

Potrzebna nam bedzuie jeszcze jedna metoda autoryzowana do
dostepu do danych obiektu:

char Licznik::Pokaz(void);

która nie bedzie w momencie wywołania pobierac od programu
zadnych argumentów (void), natomiast pobierze znak z pola char
Licznik.znak i przekaze te informacje w postaci zmiennej typu
char do programu. Definicja takiej metody powinna byc
nastepujaca:

char Licznik::Pokaz(void)
{
return znak;
}

Ten sam problem wystapi przy próbie pobrania od obiektu efektów
jego pracy - stanu pola licznik.ile. Do tego tez niezbedna jest
autoryzowana do dostepu metoda. Nazwiemy ja Efekt():

int Licznik::Efekt(void)
{
return ile;
}

Program w wersji obiektowej bedzie wygladac tak:

[P093.CPP]

# include <ctype.h>
# include <iostream.h>

class Licznik
{
char znak;
int ile;
public:
void Inicjuj(char);
void PlusJeden(void);
char Pokaz(void);
int Efekt(void);
};
85

void main()
{
char znak_we;
Licznik licznik;
licznik.Inicjuj('A');

cout << "\nWpisz tekst zawierajacy litery A";
cout << "\nPierwsze wytapienie litery k lub K";
cout << "\n - oznacza Koniec zliczania: ";

for(;;)
{
cin >> znak_we;
if (znak_we == 'k' || znak_we == 'K') break;
if(licznik.Pokaz() == toupper(znak_we))
licznik.PlusJeden();
}

cout << "\nLitera " << licznik.Pokaz()
<< " wystapila " << licznik.Efekt()
<< " razy.";
}

/* Definicje wszystkich funkcji: */

void Licznik::Inicjuj(char x)
{
znak = x;
ile = 0;
}

void Licznik::PlusJeden(void)
{
ile++;
}

char Licznik::Pokaz(void)
{
return znak;
}

int Licznik::Efekt(void)
{
return ile;
}

Przejdziemy teraz do bardziej szczegółowego omówienia
zasygnalizowanego wczesniej problemu inicjowania struktur w
pamieci przy pomocy funkcji o specjalnym przeznaczeniu - tzw.
KONSTRUKTORÓW.

LEKCJA 26: CO TO JEST KONSTRUKTOR.
________________________________________________________________
W trakcie tej lekcji dowiesz sie, w jaki sposób w pamieci
komputera sa tworzone obiekty.
________________________________________________________________


C++ zawiera specjalna kategorie funkcji - konstruktory w celu
automatyzacji inicjowania struktur (i obiektów). Konstruktory to
specjalne funkcje bedace członkami struktur (kategorii member
functions) które sa automatycznie wywoływane i dokonuja
zainicjowania struktury zgodnie z naszymi zyczeniami, po
napotkaniu w programie pierwszej deklaracji struktury/obiektu
danego typu.

PRZYKŁADOWY KONSTRUKTOR.

Struktura Licznik zawiera funkcje inicjujaca obiekt (niech
obiekt bedzie na razie zmienna typu struktura):

struct Licznik //Typ formalny struktur
{
char znak;
int ile;
} licznik; //Przykladowa struktura

void Inicjuj(char x) //Funkcja inicjujaca
{
licznik.znak = x;
licznik.ile = 0;
}

Zdefiniujmy nasza strukture w sposób bardziej
"klasowo-obiektowy":

struct Licznik
{
private:
char znak;
int ile;
public:
void Inicjuj(char);
void PlusJeden(void);
};

Funkcja Inicjuj() wykonuje takie działanie jakie moze wykonac
konstruktor struktury (obiektu), z ta jednak róznica, ze
konstruktor jest wywoływany automatycznie. Jesli wyposazymy
strukture Licznik w konstruktor, to funkcja Inicjuj() okaze sie
zbedna. Aby funkcja Inicjuj() stała sie konstruktorem, musimy
zmienic jej nazwe na nazwe typu struktury, do której konstruktor

ma nalezec. Zwróc uwage, ze konstruktor, w przeciwienstwie do
innych, "zwykłych" funkcji nie ma podanego typu wartosci
zwracanej:

struct Licznik
{
private:
char znak;
int ile;
public:
Licznik(void); //Konstruktor nie pobiera argumentu
void PlusJeden(void);
};

Teraz powinnismy zdefiniowac konstruktor. Zrobimy to tak, jak
wczesniej definiowalismy funkcje Inicjuj().

Licznik::Licznik(void) //Konstruktor nie pobiera argumentu
{
ile = 0;
}

Jesli formalny typ struktur (klasa) posiada kostruktor, to po
rozpoczeciu programu i napotkaniu deklaracji struktur danego
typu konstruktor zostanie wywołany automatycznie. Dzieki temu
nie musimy "recznie" inicjowac struktur na poczatku programu.
Jednakze nasz przykładowy konstruktor nie załatwia wszystkich
problemów - nie ustawia w strukturze zmiennej (pola) int znak -
okreslajacego, który znak powinien byc zliczany w liczniku. W
tak zainicjowanej strukturze zmienna ile jest zerowana, ale
zawartosc pola znak pozostaje przypadkowa. Niby wszystko w
porzadku, ale wyglada to niesolidnie. Czy nie moznaby przekazac
parametru przy pomocy konstruktora? Mozna! Konstruktor
"bezparametrowy"

Licznik::Licznik(void)

taki, jak powyzej to tylko szczególny przypadek - tzw.
konstruktor domyslny (ang. default constructor).

PRZEKAZYWANIE ARGUMENTÓW DO KOSTRUKTORA.

Czasem chcemy zainicjowac nowa strukture juz z pewnymi
ustawionymi parametrami. Te poczatkowe parametry struktury
mozemy przekazac jako argumenty konstruktora.

struct Licznik
{
private:
char znak;
int ile;
public:
Licznik(char); //Konstruktor z argumentem typu char
void PlusJeden(void);
};


Licznik::Licznik(char x) //Konstruktor z jednym argumentem
{
...
}

86
main()
{
Licznik licznik('A'); //Deklaracja struktury typu Licznik
// oznacza to automatyczne wywołanie konstruktora z argumentem
....

Poniewz nowy konstruktor pobiera od programu argument typu
znakowego char, wiec i definicje konstruktora nalezy zmienic:

Licznik::Licznik(char x) //Konstruktor z jednym argumentem
{
ile = 0;
znak = x;
}

Jesli parametrów jest wiecej niz jeden, mozemy je przekazac do
konstruktora, a konstruktor wykorzysta je do zainicjowania
struktury w nastepujacy sposób:

struct Sasiedzi //sasiedzi
{
private:
char Tab_imion[4];
...
public:
Sasiedzi(char *s1, char *s2, char *s3, char s4);
...
};

main()
{
Sasiedzi chopy("Helmut", "Ulrich", "Adolf", "Walter");
....

Przekazanie konstruktorowi argumentów i w efekcie automatyczne
ustawiamie przez konstruktor paramatrów struktury juz w momencie

zadeklarowania struktury w programie rozwiazuje wiele problemów.

W C++ istnieje jednakze pewne dosc istotne ograniczenie - nie
mozemy zadeklarowac tablicy złozonej z obiektów posiadajacych
konstruktory, chyba ze wszystkie konstruktory sa bezparametrowe
(typu default constructors).

Udoskonalmy teraz nasz program zliczajacy wystapienia w tekscie
litery a posługujac sie konstruktorem struktury.

[P094.CPP] /* Wersja ze struktura */

# include <ctype.h>
# include <iostream.h>

struct Licznik
{
private:
char znak;
int ile;
public:
Licznik(char); //Konstruktor
void PlusJeden(void);
char Pokaz(void);
int Efekt(void);
};

Licznik::Licznik(char x) //Def. konstruktora
{
znak = x;
ile = 0;
}

void main()
{
Licznik licznik('A'); //Zainicjowanie przez konstruktor

cout << "Sprawdzamy: znak ile? " << "\n\t\t"
<< licznik.Pokaz() << "\t";
cout << licznik.Efekt();

cout << "\nWpisz tekst zawierajacy litery A";
cout << "\nPierwsze wytapienie litery k lub K";
cout << "\n - oznacza Koniec zliczania: ";
for(;;)
{
char znak_we;
cin >> znak_we;
if (znak_we == 'k' || znak_we == 'K') break;
if(licznik.Pokaz() == toupper(znak_we))
licznik.PlusJeden();
}

cout << "\nLitera " << licznik.Pokaz()
<< " wystapila " << licznik.Efekt() << " razy.";
}

/* Definicje pozostałych funkcji: */

void Licznik::PlusJeden(void) { ile++; }
char Licznik::Pokaz(void) { return (znak); }
int Licznik::Efekt(void) { return (ile); }

Po zamianie słowa kluczowego struct na class (licznik ze
struktury stanie sie obiektem, a Licznik - z formalnego typu
struktur - klasa) wystarczy w programie zlikwidowac zbedne słowo

"private" i wersja obiektowa programu jest gotowa do pracy.

[P095.CPP] /* Wersja z klasa i obiektem */

# include <ctype.h>
# include <iostream.h>

class Licznik
{
char znak;
int ile;
public:
Licznik(char); //Konstruktor
void PlusJeden(void);
char Pokaz(void);
int Efekt(void);
};

Licznik::Licznik(char x) //Def. konstruktora
{
znak = x;
ile = 0;
}

void main()
{
Licznik licznik('A'); //Zainicjowanie obiektu licznik

cout << "Sprawdzamy: znak ile? " << "\n\t\t"
<< licznik.Pokaz() << "\t";
cout << licznik.Efekt();

cout << "\nWpisz tekst zawierajacy litery A";
cout << "\nPierwsze wytapienie litery k lub K";
cout << "\n - oznacza Koniec zliczania: ";
for(;;)
{
char znak_we;
cin >> znak_we;
if (znak_we == 'k' || znak_we == 'K') break;
if(licznik.Pokaz() == toupper(znak_we))
licznik.PlusJeden();
}

cout << "\nLitera " << licznik.Pokaz()
<< " wystapila " << licznik.Efekt()
<< " razy.";
}

void Licznik::PlusJeden(void) { ile++; }
char Licznik::Pokaz(void) { return znak; }
int Licznik::Efekt(void) { return ile; }

Pora w tym miejscu zaznaczyc, ze C++ oferuje nam jeszcze jedno
specjalne narzedzie podobnej kategorii. Podobnie, jak do
tworzenia (struktur) obiektów mozemy zastosowac konstruktor, tak

do skasowania obiektu mozemy zastosowac tzw. desruktor (ang.
destructor). Nazwy konstruktora i destruktora sa identyczne z
nazwa maciezystego typu struktur (maciezystej klasy), z tym, ze
nazwa destruktora poprzedzona jest znakiem "~" (tylda).

87
CO TO JEST DESTRUKTOR.

Specjalna funkcja - destruktor (jesli zadeklarujemy zastosowanie

takiej funkcji) jest wywoływana automatycznie, gdy program
zakonczy korzystanie z obiektu. Konstruktor towrzy, a destruktor

(jak sama nazwa wskazuje) niszczy strukture (obiekt) i zwalnia
przyporzadkowana pamiec. Przykład ponizej to program
manipulujacy stosem, rozbudowany tak, by zawierał i konstruktor
i destruktor struktury (obiektu). Zorganizujmy zarzadzanie
pamiecia przeznaczona dla stosu w taki sposób:

struct Stos
{
private:
int *bufor_danych;
int licznik;
public:
Stos(int ile_RAM); /* Konstruktor
int Pop(int *ze_stosu);
int Push(int na_stos);
};

gdzie:
*bufor_danych - wskaznik do bufora (wypełniajacego role stosu),
licznik - wierzchołek stosu, jesli == -1, stos jest pusty.
Stos::Stos(...) - konstruktor inicjujacy strukture typu Stos
(lub obiekt klasy Stos),
ile_RAM - ilosc pamieci potrzebna do poprawnego działanie stosu,
*ze_stosu - wskaznik do zmiennej, której nalezy przypisac
wartosc zdjeta własnie ze stosu,
na_stos - liczba przeznaczona do zapisu na stos.

Zajmijmy sie teraz definicja konstruktora. Wywołujac konstruktor

w programie (deklarujac uzycie w programie struktury typu Stos)
przekazemy mu jako argument ilosc potrzebnej nam pamieci RAM w
bajtach. Do przyporzadkowznia pamieci na stercie dla naszego
stosu wykorzystamy funkcje malloc().

Stos::Stos(int n_RAM) //Konstruktor - def.
{
licznik = -1;
bufor_danych = (int *) malloc(n_RAM);
}

Posługujac sie funkcja malloc() przyporzadkowujemy buforowi
danych, w oparciu o który organizujemy nasz obiekt (na razie w
formie struktury) - stos 100 bajtów pamieci, co pozwala na
rozmieszczenie 50 liczb typu int (po 2 bajty kazda). Liczbe
potrzebnych bajtów pamieci - 100 przekazujemy jako argument
konstruktorowi w momencie deklaracji struktury typu Stos. Nasza
struktura w programie bedzie sie nazywac nasz_stos.

main()
{
...
Stos nasz_stos(100);
...

Kiedy wykorzystamy nasza strukture w programie, mozemy zwolnic
pamiec przeznaczona dla struktury posługujac sie funkcja
biblioteczna C free(). Przykład przydziału pamieci przy pomocy
pary operatorów new - delete juz był, przedstawimy tu zatem
tradycyjna (coraz rzadziej stosowana metode) oparta na
"klasycznych" funkcjach z biblioteki C. Funkcja free() posłuzymy

sie w destruktorze struktury nasz_stos - ~Stos(). Destruktory sa

wywoływane automatycznie, gdy konczy sie działanie programu, lub

tez, gdy struktura (obiekt) przestaje byc widoczna / dostepna w
programie. Obiekt (struktura) przestaje byc widoczny (podobnie
ja zwykła zmienna lokalna/globalna), jesli opuszczamy te
funkcje, wewnatrz której obiekt został zadeklarowany. Jest to
własciwosc bardzo wazna dla naszego przykładowego stosu. W
naszym programie przykładowym pamiec przydzielona strukturze
stack pozostaje zarezerwowana "na zawsze", nawet wtedy, gdy nasz

stos przestaje byc "widoczny" (ang. out of scope). Obiekt moze
przestac byc widoczny np. wtedy, gdy działa funkcja "nie
widzaca" obiektu. Idac dalej tym torem rozumowania, jesli
destruktor zostanie wywołany automatycznie zawsze wtedy, gdy
obiekt przestanie byc widoczny, istnienie destruktora w
definicji typu struktur Stos pozwala na automatyczne wyzerowanie

stosu. Deklarujemy destruktor podobnie do konstruktora, dodajac
przed nazwa destruktora znak ~ (tylda):

struct Stos
{
...
public:
...
~Stos(void);
...
}

Jesli program zakonczy sie lub struktura przestanie byc
widoczna, zostanie wywołany destruktor struktury nasz_stos i
pamiec zostanie zwolniona. Praktycznie oznacza to, ze mozemy
zwolnic pamiec przyporzadkowana strukturze w taki sposób:

Stos::~Stos(void) //Definicja destruktora
{
free(bufor_danych);
cout << "\n Destruktor: Struktury juz nie ma...";
}

Od momentu zdefiniowania konstruktora i destruktora nie musimy
sie juz przejmowac technicznymi szczegółami ich działania. W
dalszej czesci programu destruktor i konstruktor beda wywoływane

automatycznie. Pozostaje nam pamietac, ze

* stos moze sie nazywac dowolnie, a deklarujemy go tak:

Stos nazwa_struktury;

i dalej stosem mozemy posługiwac sie przy pomocy funkcji:

nazwa_struktury.Push()
nazwa_struktury.Pop()

Wszystkie wewnetrzne sprawy stos bedzie załatwiał samodzielnie.
W tym konkrertnym przypadku czesc "prac organizacyjnych"
zwiazanych z utworzeniem w pamieci struktury i zainicjowaniem
poczatkowych wartosci pól załatwi za nas konstruktor i
destruktor. Na tym własnie polega idea nowoczesnego
programowania w C++. Przykładowy program umieszcza liczby na
stosie a nastepnie pobiera je ze stosu i drukuje na ekranie.
Pełny tekst programu w wersji ze struktura - ponizej.

[P096.CPP]

# include <iostream.h>
# include <alloc.h>

/* -----------------------poczatek pliku STOS.HPP------------ */


# define OK 1

struct Stos
{
private:
int *bufor_danych;
int licznik;
public:
Stos(int); /* Konstruktor */
~Stos(void); /* Destruktor */
int Pop(int*);
int Push(int);
};

Stos::Stos(int n_RAM) //Konstruktor - def.
{
licznik = -1;
bufor_danych = (int *) malloc(n_RAM);
cout << "Konstruktor: Inicjuje strukture. ";
}

Stos::~Stos(void) //Definicja destruktora
{
free(bufor_danych);
88
cout << "\n Destruktor: Struktury juz nie ma...";
}


int Stos::Pop(int* ze_stosu)
{
if(licznik == -1) return 0;
else *ze_stosu = bufor_danych[licznik--];
return OK;
}

int Stos::Push(int na_stos)
{
if(licznik >= 49) return 0;
else bufor_danych[++licznik] = na_stos;
return OK;
}
/* --------------------------koniec pliku STOS.HPP----------- */


void main()
{
Stos nasz_stos(100); //Dekl. struktury typu Stos
int i, Liczba;

cout << "\nZAPISUJE NA STOS LICZBY:\n";

for(i = 0; i < 10; i++)
{
nasz_stos.Push(i + 100);
cout << i + 100 << ", ";
}
cout << "\nKoniec. \n";
cout << "ODCZYTUJE ZE STOSU:\n";
for(i = 0; i < 10; i++)
{
nasz_stos.Pop(&Liczba);
cout << Liczba << ", ";
}
}


W C++ czesta praktyka jest umieszczanie tzw. implementacji
struktur (klas) w plikach nagłówkowych. Szkielet naszego
programu mógłby wygladac wtedy tak:

# include <iostram.h>
# include <alloc.h>
# include <A:\STOS.HPP>

void main()
{
...
}

Wykazemy, ze zamiana struktury na klase odbedzie sie całkiem
bezbolesnie. Mało tego, jesli dokonamy zmian w implementacji w
pliku nagłówkowym (struct --> class i usuniemy słowo private)
nasz program główny nie zmieni sie WCALE !

Oto plik nagłówkowy A:\INCLUDE\STOSCL.HPP:

[P097.CPP]

# include <iostream.h>
# include <alloc.h>

/* ---------------------poczatek pliku STOSCL.HPP------------ */


# define OK 1

class Stos
{
int *bufor_danych;
int licznik;
public:
Stos(int); /* Konstruktor */
~Stos(void); /* Destruktor */
int Pop(int*);
int Push(int);
};

Stos::Stos(int n_RAM) //Konstruktor - def.
{
licznik = -1;
bufor_danych = (int *) malloc(n_RAM);
cout << "Konstruktor: Inicjuje obiekt klasy Stos. ";
}

Stos::~Stos(void) //Definicja destruktora
{
free(bufor_danych);
cout << "\n Destruktor: Obiektu juz nie ma...";
}

int Stos::Pop(int* ze_stosu)
{
if(licznik == -1) return 0;
else *ze_stosu = bufor_danych[licznik--];
return OK;
}

int Stos::Push(int na_stos)
{
if(licznik >= 49) return 0;
else bufor_danych[++licznik] = na_stos;
return OK;
}
/* ------------------------koniec pliku STOSCL.HPP----------- */


void main()
{
Stos nasz_stos(100); //OBIEKT Klasy Stos
int i, Liczba;

cout << "\nZAPISUJE NA STOS LICZBY:\n";

for(i = 0; i < 10; i++)
{
nasz_stos.Push(i + 100);
cout << i + 100 << ", ";
}
cout << "\nKoniec. \n";
cout << "ODCZYTUJE ZE STOSU:\n";
for(i = 0; i < 10; i++)
{
nasz_stos.Pop(&Liczba);
cout << Liczba << ", ";
}
}

Struktury w robia sie coraz bardziej podobne do czegos nowego
jakosciowo, zmienia sie równiez (dzieki tym nowym cechom) styl
programowania.

[!!!] A CO Z UNIAMI ?
________________________________________________________________
Unie sa w C++ traktowane podobnie jak struktury, z tym, ze pola
unii moga sie nakładac (ang. overlap) i wobec tego nie wolno
stosowac słowa kluczowego private w uniach. Wszystkie elementy
unii musza miec status public. Unie moga takze posiadac
konstruktory.
________________________________________________________________

A JESLI BEDZIE WIECEJ KLAS i STRUKTUR ?

Po zdefiniowaniu nowego formalnego typu struktur mozesz
zastosowac w programie wiele zmiennych danego typu. We
wszystkich przykładach powyzej stosowano pojedyncza strukture
WYŁACZNIE DLA ZACHOWANIA JASNOSCI PRZYKŁADU. Mało tego.
W C++
rózne struktury moga korzystac z funkcji o tej samej nazwie W
RÓZNY SPOSÓB. Ta ciekawa zdolnosc nazywa sie rozbudowywalnoscia
funkcji (ang. overloading - dosł. "przeciazanie"). Dokładniej
tym problemem zajmiemy sie w czesci poswieconej klasom i
obiektom. Teraz jedynie prosty przykład na strukturach.

[P098.CPP]

#include <iostream.h>
#include <stdio.h>
#include <time.h>

struct Data
89
{
int miesiac, dzien, rok;
void Display(void); //Metoda "wyswietl"
};

void Data::Display(void)
{
char *mon[] =
{
"Stycznia","Lutego","Marca","Kwietnia","Maja","Czerwca",
"Lipca","Sierpnia","Wrzesnia","Pazdziernika","Listopada",
"Grudnia"
};
cout << dzien << ". "
<< mon[miesiac] << ". "
<< rok;
}

struct Czas
{
int godz, minuty, sekundy;
void Display(void); // znow metoda "wyswietl"
};

void Czas::Display(void)
{
char napis[20];

sprintf(napis, "%d:%02d:%02d %s",
(godz > 12 ? godz - 12 : (godz == 0 ? 12 : godz)),
minuty, sekundy,
godz < 12 ? "rano" : "wieczor");
cout << napis;
}

main()
{

time_t curtime = time(NULL);
struct tm tim = *localtime(&curtime);

Czas teraz;
Data dzis;

teraz.godz = tim.tm_hour;
teraz.minuty = tim.tm_min;
teraz.sekundy = tim.tm_sec;
dzis.miesiac = tim.tm_mon;
dzis.dzien = tim.tm_mday;
dzis.rok = 1900 + tim.tm_year;

cout << "\n Jest teraz --> ";
teraz.Display();
cout << " dnia ";
dzis.Display(); cout << "\a";

return 0;
}

Funkcja Display() wywoływana jest w programie dwukrotnie przy
pomocy tej samej nazwy, ale za kazdym razem działa w inny
sposób. C++ bezbłednie rozpoznaje, która wersja funkcji ma
zostac zastosowana i w stosunku do której struktury (których
danych) funkcja ma zadziałac.

Aby struktura stała sie juz całkowicie klasa, pozostało nam do
omówienia jeszcze kilka ciekawych nowych własnosci.
Najwazniejsza chyba (własnie dlatego, ze tworzaca zdecydowanie
nowa jakosc w programowaniu) jest mozliwosc dziedziczenia cech
(ang. inheritance), która zajmiemy sie w nastepnej lekcji.

[Z]
________________________________________________________________
1. Sprawdz, czy zamiana struktur na klasy nie zmienia sposobu
działania programów, ani długosci kodów wynikowych.
2. Opracuj program zliczajacy wystapienia ciagu znaków - np.
"as" we wprowadzanym tekscie.


LEKCJA 27: O DZIEDZICZENIU.
________________________________________________________________
W trakcie tej lakcji dowiesz sie na czym polega dziedziczenie.
________________________________________________________________

Dziedziczenie (ang inheritance) jest próba nasladowania w
technice programowania najcenniejszego bodaj wynalazku Matki
Natury - zdolnosci przekazywania cech. Jesli wyobrazimy sobie
typy struktur konik, lew, słon, czy krokodyl, to jest oczywiste,
ze struktury te beda posiadac wiele wspólnych cech. Wspólnymi
cechami moga byc zarówno wspólne dane (parametry) - np. nogi =
4; jak i wspólne wykonywane przez nie funkcje - np. jedz(),
spij(), oddychaj() itd.. Moga wystepowac oczywiscie i róznice,
ale wiele danych i funkcji okaze sie wspólnych.

LOGIKA DZIEDZICZENIA.

Rozwijajac dalej mysl naszkicowana we wstepie, w kategoriach
obiegowego jezyka naturalnego mozna rzec, ze słon Trombalski
byłby tu struktura typu formalnego Słon. Funkcjami wewnetrznymi
słonia Trombalskiego i np. krokodyla Eugeniusza mogłyby byc
wspólne czynnosci tych struktur (lub obiektów):

jedz()
spij()
oddychaj()

Projektanci C++ wpadli na pomysł nasladowania mechanizmu
dziedziczenia. Zamiast tworzyc te wszystkie struktury
oddzielnie, mozemy zdefiniowac w C++ jeden ogólny typ struktur
(ang. generic structure), nazywany inaczej STRUKTURA BAZOWA
(ang. base structure). Wszystkie wymienione wyzej struktury
(słon, krokodyl, itp.) stałyby sie wtedy strukturami pochodnymi
(ang. derived structures). Nasza struktura bazowa mogłaby
nazywac sie znów np. Zwierzak.

Poniewaz niektóre funkcje sa wspólne dla wszystkich struktur
(wszystkie Zwierzaki musza jesc, spac, itp.), moglibysmy
przyjac, ze kazda struktura pochodna od bazowego typu Zwierzak
musi zawierac funkcje jedz(), spij() i oddychaj(). Jesli
zdefiniujemy strukture bazowa Zwierzak i zadeklarujemy w tej
klasie funkcje jedz(), spij() i oddychaj(), mozemy spodziewac
sie, ze struktura pochodna słon powinna odziedziczyc funkcje -
cechy po strukturze bazowej Zwierzak. . Słon moze oczywiscie
miec i swoje odrebne cechy - dane i funkcje - np.:

Slon.flaga_ssak
Slon.trabie()
Slon.tupie()

"Gramatyka" C++ przy opisywaniu wzajemnego pokrewienstwa
struktur (i klas) wyglada nastepujaco:

struct NazwaStrukturyPochodnej : NazwaStrukturyBazowej
{
private:
Lista danych i funkcji prywatnych
public:
Lista danych i funkcji publicznych
} Lista struktur danego typu;

a dla klas i obiektów:

class NazwaKlasyPochodnej : dostep NazwaKlasyBazowej
{
Lista danych i funkcji prywatnych
public:
Lista danych i funkcji publicznych
} Lista obiektow danej klasy;

Bazowy typ struktur w C++ wygladałaby tak:

struct Zwierzak
{
void jedz();
void spij();
void oddychaj();
};

Jesli chcemy zasygnalizowac, ze pochodny typ struktur Slon ma
odziedziczyc cos po typie bazowym Zwierzak, musimy w definicji
klasy pochodnej podac nazwe klasy bazowej (jesli mamy
dziedziczyc - nalezy wskazac po kim):

struct Slon : Zwierzak
{
90
int trabie();
int tupie();
};

Przed nazwa typu struktury (klasy) bazowej (tu: Zwierzak) moze
pojawic sie słowo okreslajace zasady dostepu do danych i funkcji

(tu: public).

[!!!] RÓZNIE MOZNA DZIEDZICZYC...
________________________________________________________________
* Jesli uzyjemy w tym miejscu słowa public (przy strukturach
domyslne), to atrybuty dostepu zostana odziedziczone wprost.
Oznacza to, ze to, co było prywatne w strukturze bazowej
zostanie przeniesione jako prywatne do struktury pochodnej, a
to, co było publiczne w strukturze bazowej zostanie przeniesione

jako publiczne do struktury pochodnej.
* Jesli natomiast uzyjemy w tym miejscu słowa private, to
wszystko, co struktura pochodna odziedziczy po strukturze
bazowej stanie sie w strukturze pochodnej prywatne.
________________________________________________________________

Opracowanie przykładowego programu ilustrujacego mechanizm
dziedziczenia rozpoczniemy od zdefiniowania bazowego typu
struktur i struktury pochodnej.

struct Zwierzak
{
int nogi; <-- dane

void jedz(); <-- funkcje
void spij();
void oddychaj();
};

struct Slon : Zwierzak
{
int flaga_ssak;
int trabie();
int tupie();
};

Zdefiniujemy teraz wszystkie funkcje nalezace do powyzszych
struktur. Funkcje beda tylko zgłaszac sie na ekranie napisem, by

przesledzic kolejnosc ich wywołania.

void Zwierzak::jedz(void) { cout << "Jem conieco...\n"; }
void Zwierzak::spij(void) { cout << "Cosik mi sie sni...\n"; }
void Zwierzak::oddychaj(void) { cout << "Dysze ciezko...\n"; }
void Slon::trabi(void) { cout << "Tra-ta-ta...\n"; }
void Slon::tupie(void) { cout << "Kroczem...na zachód\n"; }

Aby przekonac sie, co struktura typu Slon rzeczywiscie
odziedziczy "po przodku", zredagujemy program główny.

# include <iostream.h>
...
void main()
{
Slon Choleryk; //Deklaracja struktury
...
cout << "\nNogi odziedziczylem: " << Choleryk.nogi;
cout << "\nA teraz kolejno funkcje: \n";
Choleryk.jedz();
Choleryk.spij();
Choleryk.oddychaj();
Choleryk.trabi();
Choleryk.tupie();
}

Mimo, ze tworzac strukture Słon nie zadeklarowalismy w jej
składzie ani funkcji jedz(), ani spij(), ani danych nogi, mozemy

zastosowac funkcje Choleryk.jedz(), poniewaz Choleryk
odziedziczył te funkcje po strukturze bazowej Zwierzak. Dzieki
dziedziczeniu mozemy posługiwac sie danymi i funkcjami
nalezacymi do obu typów struktur - bazowego: Zwierzak i
pochodnego: Slon.

[???] A CO Z UNIAMI ?
_______________________________________________________________
Unie nie moga brac udziału w dziedziczeniu. Unia nie moze byc
ani typem bazowym ani typem pochodnym.
_______________________________________________________________

Program w całosci bedzie wygladał tak:

[P099.CPP]

# include <iostream.h>

struct Zwierzak
{
int nogi;
void jedz();
void spij();
void oddychaj();
};
void Zwierzak::jedz(void) { cout << "Jem conieco...\n"; }
void Zwierzak::spij(void) { cout << "Cosik mi sie sni...\n"; }
void Zwierzak::oddychaj(void) { cout << "Dysze ciezko...\n"; }

struct Slon : Zwierzak
{
int flaga_ssak;
void trabi();
void tupie();
};

void Slon::trabi(void) { cout << "Tra-ta-ta...\n"; }
void Slon::tupie(void) { cout << "Kroczem...na wschod\n"; }

void main()
{
Slon Choleryk;
Choleryk.nogi = 4; Choleryk.flaga_ssak = 1;
cout << "\nNogi odziedziczylem: " << Choleryk.nogi;
cout << "\nA teraz kolejno funkcje: \n";
Choleryk.jedz();
Choleryk.spij();
Choleryk.oddychaj();
Choleryk.trabi();
Choleryk.tupie();
if(Choleryk.flaga_ssak == 1) cout << "SSak!";
}



LEKCJA 28: DZIEDZICZENIE ZŁOZONE.
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak mozna odziedziczyc wiele
cech po wielu róznych przodkach.
________________________________________________________________

Jesli zechcemy dziedziczyc dalej według schematu
dziadek-ojciec-syn-wnuk...? Nic nie stoi na przeszkodzie. Przy
okazji zwróc uwage, ze nastepne pokolenia sa coraz bardziej
złozone (tak byc nie musi, ale moze). W przykładzie ponizej
dziedziczymy według schematu Punkt-Okrag-Elipsa.

[P100.CPP]

//Przyklad dziedziczenia "wielopokoleniowego"

#include "stdio.h"
#include "conio.h"

struct punkt //BAZOWY typ struktur - punkt(x, y)
{
int x; //wspolrzedne punktu na ekranie
int y;
};

struct kolo: punkt //Str. pochodna - kolo(x, y, R)
{
int promien; //wspolrzedne srodka x,y dziedziczymy
};

struct elipsa: kolo //dziedziczymy x,y i promien
{
int mniejszy_promien; //Str. pochodna elipsa(x, y, R, r)
};

punkt P; //deklarujemy trzy struktury
91
kolo C;
elipsa E;

main()
{
clrscr();

P.x = C.x = E.x = 1; //Nadajemy wartosci polom struktur
P.y = C.y = E.y = 2;

C.promien = E.promien = 4;
E.mniejszy_promien = 3;
//Sprawdzamy zawartosc pol struktur
printf("%d %d %d %d %d %d \n",
P.x, C.x, E.x, P.y, C.y, E.y);
printf("%d %d %d",
C.promien, E.promien, E.mniejszy_promien );
getch();
return 0;
}

Mozna dziedziczyc po wiecej niz jednym przodku takze w inny
sposób. Kwadrat, dla przykładu, dziedziczy cechy po prostokatach

i po rombach jednoczesnie (jest jednoczesnie szczególnym
przypadkiem prostokata i szczególnym przypadkiem rombu). Typ
pochodny w tym wypadku, zamiast "dziadka" i "ojca" powinien miec

DWU RÓZNYCH OJCÓW (!). W C++ takie dziedziczenie po dwu róznych
typach bazowych jednoczesnie nazywa sie DZIEDZICZENIEM
WIELOBAZOWYM (ang. multi-base inheritance). A oto przykład
takiego dziedziczenia.

[P101.CPP]

#include <iostream.h>

struct BAZOWA1
{ //Struktura bazowa pierwsza
public:
void Funkcja_a(void);
};

struct BAZOWA2
{ //Struktura bazowa druga
public:
void Funkcja_b(void);
};

struct POCHODNA : BAZOWA1, BAZOWA2 //Lista "przodkow"
{
public:
void Funkcja_c(void);
};

void BAZOWA1::Funkcja_a(void){cout << "To ja F_a().\n";}
void BAZOWA2::Funkcja_b(void){cout << "To ja F_b().\n";}
void POCHODNA::Funkcja_c(void){cout << "To ja F_c().\n";}

void main()
{
POCHODNA dziecko; //Dekl. strukt. typu pochodnego

dziecko.Funkcja_a();
dziecko.Funkcja_b();
dziecko.Funkcja_c();
}

Słowo public jest w strukturach zbedne. Zostało uzyte wyłacznie
z pobudek "dydaktycznych" - dla zwrócenia uwagi na status
funkcji - członków struktury.

Zarówno pokolen w schemacie dziadek-ojciec-syn, jak i struktur
(klas) bazowych w schemacie baza_1-baza_2-....-baza_n moze byc
wiecej niz 2.

DZIEDZICZENIE KLAS.

Oto "klasowo-obiektowa" wersja poprzedniego programu
przykładowego ze słonikiem Cholerykiem. Typy struktur Zwierzak i

Slon nazwiemy klasami, (odpowiednio - klasa bazowa i klasa
pochodna) a strukture Slon Choleryk nazwiemy obiektem.

[P102.CPP]

#include <iostream.h>

class Zwierzak //Klasa bazowa (base class)
{
public:
int nogi;

void jedz();
void spij();
void oddychaj();
};

void Zwierzak::jedz(void) { cout << "Jem conieco...\n"; }
void Zwierzak::spij(void) { cout << "Cosik mi sie sni...\n"; }
void Zwierzak::oddychaj(void) { cout << "Dysze ciezko...\n"; }

class Slon : public Zwierzak
{
public:
int flaga_ssak;

void trabi();
void tupie();
};

void Slon::trabi(void) { cout << "Tra-ta-ta...\n"; }
void Slon::tupie(void) { cout << "Kroczem...na wschod\n"; }

void main()
{
Slon Obiekt;
/* obiekt Obiekt klasy Slon */
Obiekt.nogi = 4; Obiekt.flaga_ssak = 1;
cout << "\nNogi odziedziczylem: " << Obiekt.nogi;
cout << "\nA teraz kolejno funkcje: \n";
Obiekt.jedz();
Obiekt.spij();
Obiekt.oddychaj();
Obiekt.trabi();
Obiekt.tupie();
if(Obiekt.flaga_ssak) cout << "Jestem ssakiem !";
}

Pamietajac o problemie domyslnego statusu członków
struktur/public i klas/private) mozemy przejsc do klas i
obiektów.

O KLASACH SZCZEGÓŁOWO.

Aby wykazac mozliwosc modularyzacji programu zaprojektujemy
moduł w postaci pliku nagłówkowego. Moduł bedzie zawierac
definicje naszej prywatnej klasy obiektów ZNAK.

Zaczynamy od danych, które beda nam potrzebne do tworzenia w
programach (róznych !) obiektów typu Znak.

class ZNAK
{
char znak_dany; //Kod ASCII znaku
...

Aby obiekt został zainicjowany (tzn. wiedział jakim znakiem ma
byc w danym programie) dodamy do definicji klasy
jednoparametrowy konstruktor

class ZNAK
{
char znak_dany;
public:
ZNAK(...);
...

Dane moga byc prywatne, natomiast konstruktor i funkcje-metody
powinny byc publiczne, by mozna było wywoływac je w programach.
Konstruktor bedziemy wywoływac w programach tak:

ZNAK Obiekt('a');

Znaczy to: Utwórz w RAM obiekt klasy ZNAK pod nazwa "Obiekt" i
wytłumacz mu, ze jest znakiem 'a'.
92

Konstruktor powinien pobierac od programu jeden argument typu
char i przekazywac go obiektowi klasy ZNAK na jego pole danych
znak_dany. Definicja konstruktora bedzie zatem wygladac tak:

ZNAK::ZNAK(char x)
{
znak_dany = x;
}

Zakres dopuszczalnych znaków zawezimy np. do kodów ASCII 65...90

(od A do Z). Jesli uzytkownik "nie trafi", ustawimy zawsze "*"
(asterisk). Dodatkowo, dla "elegancji" zamienimy ewentualne małe

litery na duze.

ZNAK::ZNAK(char x)
{
znak_dany = x;
if(znak_dany < 65 || znak_dany >122) znak_dany = '*';
if(znak_dany > 97) znak_dany -= 32;
}

A jesli uzytkownik nie zechce podac zadnego znaku i zda sie na
domyslnosc obiektu? Zaden problem, wystarczy do klasy ZNAK dodac

bezparametrowy konstruktor domyslny. Konstruktory domyslne
spełniaja w C++ taka własnie role:

class ZNAK
{
char znak_dany;
public:
ZNAK(char); //Konstruktor zwykly ("jednoznakowy")
ZNAK(void); //Konstruktor domyslny (bezparametrowy)
...

Słowo void (tu opcjonalne) moze nie wystapic. Aby "kłuło w
oczy", który konstruktor jest konstruktorem domyslnym (ang.
default konstructor), wiekszosc programistów zapisuje to tak:

class ZNAK
{
char znak_dany;
public:
ZNAK(char);
ZNAK(); //Z daleka widac, ze nic nie ma !
...

Definicja konstruktora bezparametrowego bedzie wygladac tak:

ZNAK::ZNAK() { znak_dany = 'X'; }

W zaleznosci od sposobu zadeklarowania obiektu w programie C++
wywoła automatycznie albo konstruktor ZNAK(char), albo
konstruktor domyslny ZNAK():

ZNAK obiekt; //Nie sprecyzowano jaki, konstruktor domyslny
ZNAK obiekt('m'); //Wiadomo jaki, konstruktor jednoparametrowy


Dzieki temu, ze C++ "pedantycznie" sprawdza przed wywołaniem
funkcji zgodnosc typów argumentów przekazywanych do funkcji
(konstruktor to tez funkcja) i porównuje typ argumentów z
zyczeniem programisty wyrazonym w prototypie - bezbłednie
rozpozna (mimo identycznej nazwy), która funkcje nalezy
zastosowac.

Dodajmy do klasy ZNAK deklaracje (prototypy) funkcji-metod:

class ZNAK
{
char znak_dany;

public:
ZNAK(char);
ZNAK();
void Pokaz_sie();
void Znikaj();
void Skacz();
};

i zdefiniujmy te metody.

void ZNAK::Pokaz_sie(void)
{
cout << znak_dany << '\a';
}

void ZNAK::Znikaj(void)
{
cout << "\b" << ' '; //'\b' == Back Space
}

void ZNAK::Skacz(void)
{
for(int i = 0; i < 100; i++)
{
gotoxy(rand()%50, rand()%50);
cout << znak_dany;
getch();
}
}

Jesli implementacje klasy ZNAK umiescimy w pliku nagłówkowym

A:\ZNAK.H

//_____________________________________________________________
# include <stdlib.h>
# include <conio.h>
# include <iostream.h>

class ZNAK
{
char znak_dany;

public:
ZNAK(char);
ZNAK();
void Pokaz_sie();
void Znikaj();
void Skacz();
};

ZNAK::ZNAK()
{
znak_dany = 'X';
}

ZNAK::ZNAK(char x)
{
znak_dany = x;
if(znak_dany < 65 && znak_dany >122) znak_dany = '*';
if(znak_dany > 97) znak_dany -= 32;
}

void ZNAK::Pokaz_sie(void)
{
cout << znak_dany << '\a';
}

void ZNAK::Znikaj(void)
{
cout << "\b" << ' '; //'\b' == Back Space
}

void ZNAK::Skacz(void)
{
for(int i = 0; i < 100; i++)
{
gotoxy(rand()%50, rand()%50);
cout << znak_dany;
getch();
}
}
//_____________ koniec pliku A:\INCLUDE\ZNAK.H _________________


to nasz program moze wygladac tak:

[P103.CPP]

# include <a:\znak.h>

93
void main()
{
char litera;

clrscr();
cout << '\n' << "Podaj znak: ";
cin >> litera;

ZNAK Obiekt(litera);
cout << "\nSTART" << "\n\n\n";

getch();
Obiekt.Pokaz_sie();
getch();
Obiekt.Znikaj();
getch();
Obiekt.Skacz();

ZNAK Obiekt2; //To bedzie domyslny 'X'
Obiekt2.Skacz();
}

I tu juz widac pewne cechy nowoczesnego obiektowego stylu
programowania. Tym razem sprwdzenie, czy słowo class mozna
spokojnie zamienic na słowo struct pozostawim dociekliwym
Czytelnikom.


LEKCJA 29: FUNKCJE I OVERLOADING.
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak jeszcze w C++ mozna
wykorzystywac funkcje.
________________________________________________________________

w C++ jedna funkcja moze byc definiowana wielokrotnie a kazda z
wersji funkcji moze byc przystosowana do obsługi innego typu
argumentów. C++ wybiera te własciwa wersje funkcji
automatycznie.

JEDNA NAZWA FUNKCJI - WIELE ZASTOSOWAN.

Overloading funkcji bywa czasem w podrecznikach dzielony na
odrebne zagadnienia:

* funkcja moze tolerowac rózna liczbe argumentów (co dało sie
spokojnie realizowac równiez w klasycznym C - vide printf());
* funkcja moze tolerowac rózne typy argumentów;
* funkcja moze realizowac rózne operacje dla róznych

Wyobrazmy sobie, ze mamy funkcje wydrukuj(), która potrafi
wysłac na ekran otrzymany znak:

void wydrukuj(char znak)
{
cout << znak;
}

Tak zdefiniowana funkcje mozemy wywołac w programie w
nastepujacy sposób:

wydrukuj('Z');

Czasem jednak wygodniej byłoby, gdyby nasza funkcja była
bardziej elastyczna i pozwalała na wykonanie szerszego zakresu
operacji, np.:

wydrukuj('Z');
wydrukuj(75); // 75 to kod ASCII znaku, zamiast znaku bezposr.

wydrukuj("Wiecej niz znak - tekst");

W klasycznym jezyku C wymaga to zdefiniowania nowej funkcji,
natomiast w C++ to, ze funkcja wydrukuj() została juz
zdefiniowana w niczym nie przeszkadza. Ponizej definjujemy taka
funkcje.

...
class KLASA
{
public:
void wydrukuj(char znak);
void wydrukuj(int kod_ASCII);
void wydrukuj(char *string); //wskaznik do lancucha
}

Łancuch znaków jest widziany jako jednowymiarowa tablica
zawierajaca dane typu znakowego, czyli w taki sposób:

char TABLICA[9] ={ "123456789" };

Definice powinny miec nastepujaca postac:

void KLASA::wydrukuj(char znak) {cout << znak;};
void KLASA::wydrukuj(int kodASCII) {cout << (char) kodASCII;};
void KLASA::wydrukuj(char *string) {cout << string;};

Zapis:

cout << (char) kodASCII;

oznacza forsowanie typu - zamien typ int na typ char -
przyporzadkowanie kodowi ASCII - znaku. Wywołanie tej funkcji w
programie moze spowodowac rózne działanie, w zaleznosci od typu
i ilosci argumentów, z którym(i) funkcja zostaje wywołana.
Wywołania funkcji moga wygladac np. tak:

KLASA Obiekt1, Obiekt2;
main() {
...
Obiekt1.wydrukuj('A'); //Wydrukuje sie litera A
Obiekt1.wydrukuj(99); //Wydrukuje sie litera c
Obiekt2.wydrukuj("napis"); //Wydrukuje sie napis.
...
}

Taki sposób postepowania umozliwia funkcjom wieksza elastycznosc

i pozwala operowac bez konfliktów na róznych rodzajach danych.

Jezyk C posiada funkcje słuzace do kopiowania łancuchów
znakowych: strcpy() i strncpy(). Funkcja biblioteczna strncpy()
przerywa proces kopiowania po zakonczeniu łancucha zródłowego,
badz po skopiowaniu zadanej ilosci znaków. Dzieki mechanizmowi
overloadingu mozemy utworzyc nasza własna funkcje
kopiuj_string(), która zaleznie od sytuacji zadziała jak
strcpy(), badz tak jak strncpy().

[P104.CPP]

# include <iostream.h>

/* dwa porototypy - dwie wersje funkcji kopiuj_string() */
/* source: destination: len: */

void kopiuj_string(char*, const char*); //Dwa argumenty
void kopiuj_string(char*, const char*, int); //a tu trzy

static char Piggie[20], Kermit[32];

main()
{
kopiuj_string(Piggie, "Panna Piggie");
kopiuj_string(Kermit, "Kermit - to protokul transmisji", 6);
cout << Kermit << " oraz " << Piggie;

return 0;
}

void kopiuj_string(char *destin, const char *source)
{
while((*destin++ = *source++) != '\0') /* instr. pusta */ ;
}

void kopiuj_string(char *destin, const char *source, int len)
{
while (len && (*destin++ = *source++) != '\0') --len;
while (len--) *destin++ = '\0';
}

[S] Source- Destination.
________________________________________________________________
source - tu: zródłowy łancuch znaków. Ogólnie - zródło. Typowy
skrót src.
destin - tu: łancuch przeznaczenia. Ogólnie destination -
miejsce przeznaczenia. Typowy skrót dest, dst, destin.
len - tu: długosc.
94
________________________________________________________________

O FUNKCJACH WPLECIONYCH - TYPU inline.

Czsami zalezy nam na przyspieszeniu działania programu
obiektowego (zwykle kosztem zwiekszenia długosci pliku). Jesli w

zródłowym tekscie programu nastepuje wywołanie funkcji typu
inline, to kompilator wstawia w to miejsce całe ciało funkcji
(funkcje typu inline nie maja bezposredniego ani wyłacznego
odniesienia do obiektowego stylu programowania). Dla przykładu,
jesli nadalibysmy naszej funkcji wydrukuj() status funkcji
inline, to fragment programu:


obiekt.wydrukuj(65); //Kod ASCII

zostałby zastapiony wstawionym w to miejsce ciałem funkcji
wydrukuj():

....
cout << (char) 65;
....

Jest to skuteczna metoda przyspieszenia działania programów.
Jesli chcemy zastosowac technike funkcji inline w stosunku do
metod nalezacych do danej klasy, powinnismy uzyc słowa
kluczowego "inline" w definicjach funkcji. Zwróc uwge, ze w
samej definicji klasy słowo inline NIE POJAWIA SIE:

[P105.CPP]

# include <iostream.h>

class Klasa
{
public:
void wydrukuj(char* tekst);
void wydrukuj(char Znak);
void wydrukuj(int KodASCII);
};

inline void Klasa::wydrukuj(char* tekst)
{
cout << tekst;
}

inline void Klasa::wydrukuj(char Znak)
{
cout << Znak;
}

inline void Klasa::wydrukuj(int KodASCII)
{
cout << (char) KodASCII;
}

void main()
{
Klasa Obiekt;
cout << "Obiekt wyprowadza dane: " << '\n';
Obiekt.wydrukuj(65);
Obiekt.wydrukuj('B');
Obiekt.wydrukuj("C i juz");
}


Wszystkie wersje funkcji wydrukuj() otrzymały status inline.
Oznacza to, ze funkcje te nie beda w programie wywoływane lecz
całe ciała funkcji zostana wstawione do programu w miejsca
wywołan. Jest to mechanizm podobny do wstawiania do programu
makrorozkazów z ta róznica, ze w przypadku funkcji inline C++
przeprowadza dodatkowo sprawdzenie zgodnosci typów argumentów
(ang. type checking). W naszym przypadku kompilator C++ wstawi
do programu ciało funkcji tyle razy, ile razy funkcja powinna
zostac wywoływana. Zastosowanie funkcji inline jest opłacalne,
jezeli ciało funkcji jest stosunkowo krótkie.

[!!!] A CZY NIE MOZNA WEWNATRZ KLASY ?
________________________________________________________________
Mozna. Jesli umiescimy pełna definicje funkcji wewnatrz
definicji klasy, to taka funkcja staje sie AUTOMATYCZNIE funkcja

typu inline.
________________________________________________________________

Status inline mozemy nadac wszystkim trzem wersjom funkcji
wydrukuj() umieszczajac definicje funkcji bezposrednio wewnatrz
definicji klasy:

class Klasa
{
public:
inline void wydrukuj(char* a) { cout << a; }
inline void wydrukuj(char z) { cout << z; }
inline void wydrukuj(int kod) { cout << (char) kod; }
};

W wiekszosci przypadków daje to efekt pozytywny. Jesli
definiujemy funkcje wewnatrz klasy, sa to zwykle funkcje o
krótkim ciele.

OVERLOADING KONSTRUKTORÓW.

W C++ mozemy poddac overloadingowi takze konstruktory.

UWAGA: destruktorów nie mozna poddac overloadingowi.

Overloading konstruktorów nie wyróznia sie niczym specjalnym.
Wyobrazmy sobie, ze tworzymy obiekt klasy Klasa o nazwie Obiekt.

Jesli chcemy, by konstruktor przy zakładaniu Obiektu przekazał
mu łancuch znaków "zzzz", mozemy to zrobic na dwa sposoby. Raz
polecimy konstruktorowi przekazac do obiektu łancuch znaków
"zzzz", a za drugim razem polecimy przekazac do obiektu
czterokrotnie znak 'z':

Obiekt("zzzz"); /* albo */ Obiekt('z', 4);

Jesli w programie zadeklarujemy obiekt danej klasy, spowoduje to

automatyczne wywołanie konstruktora z parametrem podanym w
momencie deklaracji obiektu.

class Klasa
{
public:
Klasa(char*);
Klasa(char, int);
};

Wersje konstruktora Klasa::Klasa() powinnismy zdefiniowac tak:

Klasa::Klasa(char *tekst) { cout << tekst; }

Klasa::Klasa(char Znak, ile = 4);
{
for(int i = 1; i < ile; i++)
cout << Znak;
}

Dodajmy jeszcze jeden kontruktor domyslny. Konstruktory domyslne

działaja według zasady, która w naturalnym jezyku dałoby sie
przekazac mniej wiecej tak: "dopóki nie zdecydowano inaczej...".

Dopóki nie zdecydowano inaczej - obiekt otrzyma znak 'x'.

class Klasa
{
public:
Klasa();
Klasa(char*);
Klasa(char, int);
};
...
Klasa::Klasa(void)
{
cout << 'x';
}

Praktyczne zastosowanie w programie bedzie wygladac tak:

[P106.CPP]

# include <iostream.h>
95

class Klasa
{
public:
Klasa();
Klasa(char*);
Klasa(char, int);
};

Klasa::Klasa(void)
{
cout << 'x';
}

Klasa::Klasa(char *tekst)
{
cout << tekst;
}

Klasa::Klasa(char Znak, int ile = 4)
{
for(int i = 0; i < ile; i++) cout << Znak;
}

static char *p = "\nJestem Obiekt.";

void main()
{
Klasa Obiekt1; //Konstr. domyslny
Klasa Obiekt2('A'); // ile - domyslnie == 4
Klasa Obiekt3('B', 3);
Klasa Obiekt4(p);
}



LEKCJA 30: WYMIANA DANYCH MIEDZY OBIEKTAMI.
________________________________________________________________

W trakcie tej lekcji dowiesz sie, jak mozna wymieniac dane i
informacje pomiedzy róznymi obiektami.
________________________________________________________________


Hermetyzacja danych jest cenna zdobycza, ale od czasu do czasu
obiekty powinny dokonywac pomiedzy soba wymiany informacji,
takze tych wewnetrznych - prywatnych. Ten problem moze sprawiac
programiscie troche kłopotów - nalezy zatem poswiecic mu troche
uwagi.

DOSTEP DO DANYCH PRZY POMOCY FUNKCJI KATEGORII friend.

Aby wyjasnic mechanizmy dostepu do danych obiektów bedziemy
potrzebowac:

* wielu obiektów;
* danych prywatnych obiektów (dostep do publicznych,
"niezakapsułkowanych" danych jest prosty i oczywisty);
* funkcji o specjalnych uprawnieniach.

Takie funkcje o specjalnych uprawnieniach - z mozliwoscia
odwoływania sie do prywatnych danych wielu obiektów (a nie tylko

swojego) musza w C++ posiadac status "friend" (ang. friend -
przyjaciel).

Nasz przykładowy program bedzie operował tablica złozona z
obiektów klasy Licznik.

class Licznik
{
char moja_litera;
int ile;
public:
void Inicjuj_licznik(char);
void Skok_licznika(void);
void Pokazuj();
};

...
Licznik TAB[MAX];

Obiekty - liczniki beda zliczac wystapienie (kazdy swojego)
okreslonego znaku w strumieniu znaków wejsciowych (wczytywanym z

klawiatury). Tablica bedzie sie składac z MAX == 26 elementów -
obiektów - liczników, po jednym dla kazdej duzej litery
alfabetu. Tablica bedzie nazywac sie TAB[26]. Po zadeklarowaniu:


nazwa_klasy TAB[MAX];

kolejne obiekty beda sie nazywac:

nazwa_klasy Obiekt1 == TAB[0]; //Licznik 1 - 'A'
nazwa_klasy Obiekt2 == TAB[1]; //Licznik 2 - 'B'
... ...
nazwa_klasy ObiektN == TAB[N-1];

Po wprowadzeniu znaku z klawiatury wywołamy wbudowana do kazdego

obiektu funkcje Skok_licznika(), która doda jedynke do
wewnetrznego licznika obiektu. Wywołujac funkcje zastosujemy
zamiast typowej składni

ObiektK.Skok_licznika();

odpowiadajaca jej w tym wypadku notacje

TAB[i].Skok_licznika();

Powinnismy jeszcze przed wywołaniem funkcji sprawdzic, czy znak
jest duza litera alfabetu. W przykładowym programie zrobimy to
tak:

...
cin >> znak; //Pobranie znaku z klawiatury
for(int i = 0; i < 26; i++)
{
if(i == (znak - 'A')) TAB[i].Skok_licznika();
}
...

Dzieki temu wewnetrzny licznik obiektu TAB[2] zostanie
powiekszony tylko wtedy, gdy znak - 'A' == 2 (znak jest litera
C, bo 'C' - 'A' == 2).

Mozna to zapisac skuteczniej.
...
cin >> znak;
TAB[znak - 'A'].Skok_licznika(); //Inkrementacja licznika
...

badz jeszcze krócej:

...
TAB[getch() - 'A'].Skok_licznika();
...

Istnieje tu wszakze niebezpieczenstwo próby odwołania sie do
nieistniejacego elementu tablicy, przed czym powinnismy sie
wystrzegac.

W wyniku działania programu otrzymamy zliczona ilosc
wystepowania danej litery w strumieniu znaków wejsciowych.

[P107.CPP]

# include <ctype.h> //prototyp toupper()
# include <iostream.h>

class Licznik
{
char moja_litera;
int ile;
public:
void Inicjuj(char);
void Skok_licznika();
void Pokazuj();
};

void Licznik::Inicjuj(char z)
{
moja_litera = z;
ile = 0;
}
96

void Licznik::Skok_licznika(void)
{
ile++;
}

void Licznik::Pokazuj(void)
{
cout << "Znak " << moja_litera << " wystapil "
<< ile << " razy" << '\n';
}
main()
{
const MAX = 26;
Licznik TAB[MAX];
register int i;

/* inicjujemy liczniki: -------------------------------*/

for(i = 0; i < MAX; i++)
{
TAB[i].Inicjuj('A' + i);
}
/* pracujemy - zliczamy: -------------------------------*/

cout << "Wpisz ciag zankow zakonczony kropka [.]" << '\n';
for(;;)
{ char znak;
cin >> znak;
if(znak == '.') break;
for(i = 0; i < MAX; i++)
{
if(i == (znak - 'A')) TAB[i].Skok_licznika();
}
}
/* sprawdzamy: ----------------------------------------*/

char sprawdzamy;
cout << '\n' << "Podaj znak do sprawdzenia: " << '\n';
cin >> sprawdzamy;
cout << "Wyswietlam wyniki zliczania: \n";
TAB[toupper(sprawdzamy) - 'A'].Pokazuj();

return 0;
}

Jesli chcielibysmy zliczyc ilosc wszystkich wprowadzonych
znaków, powinnismy zsumowac dane pobrane od wielu obiektów.

Jesli dane przechowywane w obiektach maja status danych
prywatnych, to dostep do tych danych moze byc utrudniony. Do
tego momentu dostep do danych prywatnych obiektu moglismy
uzyskac tylko posługujac sie autoryzowana do tego metoda -
własna funkcja wewnetrzna tegoz obiektu. Ale wtedy nie mielismy
dostepu do danych innych obiektów a tylko do jednego -
"własnego" obiektu funkcji. Jesli zatem chcielibysmy zsumowac
zawartosci wielu obiektów - liczników, to nalezy do tego
zastosowac tzw. funkcje "zaprzyjazniona" - friend function.
Jesli deklarujac funkcje zastosujemy słowo kluczowe friend, to
taka zaprzyjazniona z klasa funkcja uzyska prawo dostepu do
prywatnych elementów danej klasy. Zadeklarujemy taka przykładowa

zaprzyjazniona funkcje o nazwie Suma(). Funkcja bedzie pobierac
jako parametr ilosc obiektów do zsumowania i sumowac zawartosci
wewnetrznych liczników obiektów.

const MAX = 26;

class Licznik
{
char moja_litera;
int ile;
public:
void Inicjuj(char);
void Skok_licznika();
void Pokazuj();
friend int Suma(int);
} TAB[MAX];

Zadeklarowana w taki sposób zaprzyjazniona funkcja ma prawo
dostepu do prywatnych elementów wszystkich obiektów klasy
Licznik. Typowe zastosowanie funkcji typu friend polega własnie
na dostepie do danych wielu róznych obiektów. Powinnismy
zsumowac zawartosc pól

TAB[i].ile

dla wszystkich obiektów (od i = 0 az do i = MAX). Zwróc uwage,
ze definiujac funkcje Suma() nie stosujemy powtórnie słowa
kluczowego friend. A oto definicja:

int Suma(int ilosc_obiektow)
{
int i, suma = 0;
for(i = 0; i < ilosc_obiektow; i++)
suma += TAB[i].ile;

return (suma);
}

Dzieki zastosowaniu słowa "friend", funkcja Suma() jest
zaprzyjazniona ze wszystkimi 26 obiektami, poniewaz wszystkie
obiekty naleza do tej klasy, w której zadeklarowalismy funkcje:

class ...
{
...
friend int Suma(...);
...
} ... ;

Tablica TAB[MAX] złozona z obiektów klasy Licznik została
zadeklarowana nazewnatrz funkcji main() ma wiec status tablicy
GLOBALNEJ. Funkcja Suma() ma dostep do prywatnych danych
wszystkich obiektów, mozemy wiec zastosowac ja w programie w
nastepujacy sposób:

[P108.CPP]

# include <ctype.h>
# include <iostream.h>

class Licznik
{
char moja_litera;
int ile;
public:
void Inicjuj(char);
void Skok_licznika();
void Pokazuj();
friend int Suma(int);
}
const MAX = 26;
Licznik TAB[MAX];
register int i;

main()
{
/* inicjujemy liczniki: -------------------------------*/

for(i = 0; i < MAX; i++)
{
TAB[i].Inicjuj('A' + i);
}
/* pracujemy - zliczamy: -------------------------------*/

cout << "Wpisz ciag zankow zakonczony kropka [.]" << '\n';
for(;;)
{ char znak;
cin >> znak;
if(znak == '.') break;
for(i = 0; i < MAX; i++)
{
if(i == (znak - 'A')) TAB[i].Skok_licznika();
}
}
/* sprawdzamy: ----------------------------------------*/

char sprawdzamy;
cout << '\n' << "Podaj znak do sprawdzenia: " << '\n';
cin >> sprawdzamy;
cout << "Wyswietlam wyniki zliczania: \n";
TAB[toupper(sprawdzamy) - 'A'].Pokazuj();

cout << "\n Wszystkich liter bylo " << Suma(MAX);

97
return 0;
}

void Licznik::Inicjuj(char zn)
{
moja_litera = zn;
ile = 0;
}

void Licznik::Skok_licznika(void) { ile++; }

void Licznik::Pokazuj(void)
{
cout << "Znak " << moja_litera << " wystapil "
<< ile << " razy" << '\n';
}

int Suma(int ilosc_obiektow)
{
int i, suma = 0;
for(i = 0; i < ilosc_obiektow; i++)
suma += TAB[i].ile;

return (suma);
}

Tak działa funkcja typu friend. Zwrócmy tu uwage, ze funkcja
taka nie jest traktowana dokładnie tak samo, jak metoda
wchodzaca w skład klasy i obiektu. Metoda, czyli "własna"
funkcja obiektu odwołuje sie do jego pola (danych) w taki
sposób:

void Licznik::Skok_licznika(void)
{
ile++; //Wiadomo o ktory obiekt chodzi
}

Funkcja klasy friend odwołuje sie do pól obiektów tak:

int Suma(int liczba)
{
...
suma += TAB[i].ile;
/* - wymaga dodatkowo wskazania, o który obiekt chodzi - */
}

Nalezy pamietac, ze dla funkcji kategorii friend wszystkie
obiekty nalezace do danej klasy maja status public - sa
dostepne.

O ZAPRZYJAZNIONYCH KLASACH.

W C++ moga byc zaprzyjaznione ze soba wzajemnie takze klasy.
Pozwala to metodom zdefiniowanym wewnatrz jednej z klas na
dostep do prywatnych danych obiektów innych klas. W przypadku
zaprzyjaznionych klas słowem kluczowym friend poprzedzamy nazwe
klasy (a nie kazdej zaprzyjaznionej metody z osobna, choc
zamierzony skutek własnie na tym polega). Oto praktyczny
przykład zaprzyjaznionych klas.

[P109.CPP]

# include <iostream.h>

class Data1; //Deklaracja (a nie definicja!) klasy

class TEZ_DATA
{
int dz, rok;
public:
TEZ_DATA() {}
TEZ_DATA(int d, int y) { dz = d; rok = y;}
void Pokazuj() {cout << '\n' << rok << '-' << dz;}
friend Data1; //"zaprzyjazniona" klasa
};

class Data1 //Tu DEFINICJA klasy
{
int mc, dz, rok;
public:
Data1(int m, int d, int y) { mc = m; dz = d; rok = y; }
operator TEZ_DATA();
};

static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31};

/* ---- funkcja - metoda konwersji - definicja ----------- */

Data1::operator TEZ_DATA(void)
{
TEZ_DATA DT_Obiekt(0, rok);
for (int i = 0; i < mc-1; i++)
DT_Obiekt.dz += TAB[i];
DT_Obiekt.dz += dz;
return DT_Obiekt;
}

main()
{
Data1 dt_Obiekt(11,17,89);
TEZ_DATA DT_Obiekt;
DT_Obiekt = dt_Obiekt;
DT_Obiekt.Pokazuj();

return 0;
}

Zaprzyjaznione sa klasy Data1 i TEZ_DATA. Dzieki temu metody
zadeklarowane wewnatrz zaprzyjaznionej klasy Data1 maja dostep
do prywatnych danych obiektów klasy TEZ_DATA. Poniewaz klasa to
nowy formalny typ danych, a obiekt to dane takiego nowego typu,
nic nie stoi na przeszkodzie, by obiekty przekazywac do funkcji
jako argumenty (tak jak wczesniej obiekty typów typowych - int,
float itp.).

W C++ mamy jeszcze jedna metode wymiany danych. Mozemy nadac
elementom klas i obiektów status static (statyczny).

WYMIANA INFORMACJI PRZY POMOCY DANYCH STATYCZNYCH.

Jesli element klasy został zadeklarowany jako element statyczny
(przy pomocy słowa kluczowego static), to bez wzgledu na to jak
wiele obiektów danej klasy utworzymy, w pamieci bedzie istniec
TYLKO JEDEN EGZEMPLARZ (kopia) tego elementu. W przykładowym
programie z obiektami-licznikami mozemy osiagnac taki efekt
nadajac zmiennej ile (stan licznika) status static int ile:

class Licznik
{
char moja_litera;
static int ile;
...
};

Jesli utworzymy wiele obiektów takiej klasy, to wszystkie te
obiekty beda posługiwac sie ta sama (wspólna!) zmienna ile. Dla
przykładu, jesli zechcemy zliczac ile razy w strumieniu danych
wejsciowych pojawiły sie np. znaki 'a' , 'b' i 'c', mozemy
utworzyc trzy obiekty - liczniki: licznik_a, licznik_b i
licznik_c. wszystkie te liczniki beda posługiwac sie wspólna
zmienna statyczna ile:

class Licznik
{
public:
char moja_litera;
static int ile;
Licznik(char); //Konstruktor
...
};

Do zainicjownia obiektów posłuzymy sie konstruktorem. Deklaracja

obiektu spowoduje automatyczne wywołanie kostruktora i
zainicjowanie obiektu w pamieci. Przy okazji przekazujemy
obiektom znaki do zliczania.

Licznik licznik_a('a'), licznik_b('b'), licznik_c('c');

Jesli teraz w strumieniu wejsciowym pojawi sie któras z
interesujacych nas liter (a, b, badz c), zostanie wywołana
własciwa wersja metody Skok_licznika():

int main(void)
{
char litera;
98
...
cin >> litera;
...
if(litera == licznik_a.moja_litera) licznik_a.Skok_licznika();

if(litera == licznik_b.moja_litera) licznik_b.Skok_licznika();

...
}

Zmienna ile jest zmienna statyczna, wiec wsztstkie trzy funkcje
dokonaja inkrementacji zmiennej znajdujacej sie pod tym samym
fizycznym adresem pamieci. Jesli dla wszystkich obiektów danej
klasy jakas zmienna oznacza zawartosc tego samego adresu
pamieci, mozemy sie odwołac do tej zmiennej równiez tak:

nazwa_klasy::nazwa_zmiennej

Ten sposób mozna jednakze stosowac wyłacznie wobec statycznych
elementów klasy o statusie danych publicznych. Jesli sa to dane
prywatne nie mozna jeszcze dodatkowo zapominac o hermetyzacji i
zasadach dostepu. Jezeli pole danej klasy jest polem statycznym,

mozemy do niego odwoływac sie na dwa sposoby. Za posrednictwem
obiektów w taki sposób:

identyfikator_obiektu.identyfikator_pola

A za posrednictwem nazwy klasy (podobnie jak do zmiennych
globalnych), taka metoda:

identyfikator_klasy::identyfikator_pola

Mozemy zmodyfikowac program przykładowy posługujac sie
(globalna) zmienna statyczna. Zamiast wszystkich liter bedziemy
zliczac tylko wystapienia 'a', 'b' i 'c'.

[P110.CPP]

# include "ctype.h"
# include "iostream.h"

class Licznik
{
public:
char moja_litera;
static int ile;
Licznik(char); //Konstruktor
void Skok_licznika();
void Pokazuj();
};

void main()
{
/* inicjujemy liczniki: -------------------------------*/

Licznik licznik_a('a'), licznik_b('b'), licznik_c('c');

/* pracujemy - zliczamy: -------------------------------*/

cout << "Wpisz ciag zankow zakonczony kropka [.]" << '\n';
for(;;)
{ char znak;
cin >> znak;
if(znak == '.') break;
if (znak == licznik_a.moja_litera) licznik_a.Skok_licznika();
if (znak == licznik_b.moja_litera) licznik_b.Skok_licznika();
if (znak == licznik_c.moja_litera) licznik_c.Skok_licznika();
}
/* sprawdzamy: ----------------------------------------*/


cout << "Wyswietlam wyniki zliczania: \n";
licznik_a.Pokazuj();
licznik_b.Pokazuj();
licznik_c.Pokazuj();

}

Licznik::Licznik(char z)
{
moja_litera = z;
ile = 0;
}

void Licznik::Skok_licznika(void)
{
ile++;
}

void Licznik::Pokazuj(void)
{
cout << "Znak " << moja_litera << " wystapil "
<< ile << " razy" << '\n';
}

Tym razem Twój dialog z programem moze wygladac np. tak:

C:\>program
Wpisz ciag zankow zakonczony kropka [.]
aaa bbb cccc qwertyQWERTYPOLIPOLIpijesz? nie ojojojojoj.
Wyswietlam wyniki zliczania:
Znak a wystapil 10 razy
Znak b wystapil 10 razy
Znak c wystapil 10 razy

Jak widac, program sie myli. Wszystkie funkcje wyswietlaja
(odwołuja sie do) zawartosci tego samego wspólnego pola.

Charakter (status) statyczny mozemy nadac równiez funkcji
(metodzie) nalezacej do danej klasy. Jesli funkcja otrzyma
status static, w pamieci bedzie istniec tylko jeden egzemplarz
danej funkcji i do takiej funkcji mozna bedzie odwoływac sie
podobnie jak do zmiennej statycznej posługujac sie nazwa obiektu

lub nazwa klasy:

nazwa_obiektu.Funkcja(...); /* lub */
nazwa_klasy::Funkcja(...);

Jezeli funkcja jest tylko jedna, jej działanie nie zalezy od
tego ile obiektów danej klasy zostało utworzone i jakie nazwy
nadamy tym obiektom. W przykładowym programie powyzej "az sie
prosi", by nadac status funkcji statycznej metodzie
wyswietlajacej wyniki zliczania:

class Licznik
{
...
static void Pokazuj(void);
...
}

Sprawdzenie, czy wtedy program przestanie "robic błedy"
pozostawiamy bardziej dociekliwym Czytelnikom jako zadanie
domowe.



LEKCJA 31: PRZEKAZANIE OBIEKTÓW JAKO ARGUMENTÓW DO
FUNKCJI.
________________________________________________________________
W trakcie tej lekcji poznasz sposoby manipulowania obiektami
przy pomocy funkcji. Poznasz takze troche dokładniej referencje.
________________________________________________________________

Typowy sposób przekazywania argumentów do funkcji w C++ to
przekazanie przez wartosc (ang. by value). W przypadku obiektów
oznacza to w praktyce przekazanie do funkcji kopii obiektu. Jako

przykład zastosujemy program zliczajacy wystapienia znaków w
strumieniu wejsciowym. Zmienimy w tym programie sposób
wyprowadzenia wyników. Funkcji Pokazuj() przekazemy jako
argument obiekt. Obiekt-licznik zawiera w srodku te informacje,
której potrzebuje funkcja - ilosc zliczonych znaków. Zacznijmy
od zdefiniowania klasy.

class Licznik
{
public:
char moja_litera;
int ile;
Licznik(char litera);
void Skok_licznika();
};

99
W programie głównym mozemy zastosowac konstruktor do
zainicjowania obiektu np. tak:

main()
{
Licznik licznik_a('a');
...

Zdefiniujmy funkcje. Obiekt licznik_a bedzie argumentem funkcji
Pokazuj(). Funkcja powinna wyprowadzic na ekran zawartosc pola
licznik_a.ile. Deklaracja - prototyp takiej pobierajacej obiekt
funkcji bedzie wygladac tak:

wart_zwracana Nazwa_funkcji(nazwa_klasy nazwa_obiektu);

Nazwa klasy spełnia dokładnie taka sama role jak kazdy inny typ
danych. W naszym przypadku bedzie to wygladac tak:

void Pokazuj(Licznik obiekt);

Poniewaz "obiekt" jest parametrem formalnym i jego nazwa nie
jest tu istotna, mozemy pominac ja w prototypie funkcji (w
definicji juz nie!) i skrócic zapis do postaci:

void Pokazuj(Licznik);

Funkcja Pokazuj() otrzyma w momencie wywołania jako swój
argument kopie obiektu, która jako argument formalny funkcji
nazwalismy "obiekt". W naszym programie wywołanie tej funkcji
bedzie wygladac tak:

Pokazuj(licznik_a);

Obiekt "licznik_a" jest tu BIEZACYM ARGUMENTEM FAKTYCZNYM.
Typ
(tzn. tu: klasa) argumentu faktycznego musi byc oczywiscie
zgodny z zadeklarowanym wczesniej typem argumentu formalnego
funkcji.

Jesli funkcja dostała własna kopie obiektu, moze odwołac sie do
elementów tego obiektu w taki sposób:

void Pokazuj(Licznik obiekt)
{
cout << obiekt.ile;
}

albo np. tak:

int Pokazuj(Licznik obiekt)
{
return (obiekt.ile);
}

Nalezy podkreslic, ze funkcja Pokazuj() NIE MA DOSTEPU do
oryginalnego obiektu i jego danych. Podobnie jak było to w
przypadku przekazania zmiennej do funkcji i tu funkcja ma do
dyspozycji WYŁACZNIE SWOJA "PRYWATNA" KOPIE obiektu. Funkcja
nie

moze zmienic zawartosci pól oryginalnego obiektu.

Podobnie, jak w przypadku "zwykłych" zmiennych, jesli chcemy by
funkcja działała na polach oryginalnego obiektu, musimy funkcji
przekazac nie kopie obiektu a wskaznik (pointer) do tego
obiektu. Oto program przykładowy w całosci:

[P110.CPP]

//UWAGA: Program moze wymagac modelu wiekszego niz SMALL !

# include "ctype.h"
# include "iostream.h"

class Licznik
{
public:
char moja_litera;
int ile;
Licznik(char);
void Skok_licznika();
};

/* Prototypy funkcji (dwie wersje): ---------------- */

void Pokazuj1(Licznik);

int Pokazuj2(Licznik);


void main()
{
/* inicjujemy licznik: -------------------------------*/

Licznik licznik_a('a');

/* pracujemy - zliczamy: -------------------------------*/

cout << "Wpisz ciag zankow zakonczony kropka [.]" << '\n';
for(;;)
{
char znak;
cin >> znak;
if(znak == '.') break;
if (znak == licznik_a.moja_litera) licznik_a.Skok_licznika();
}

/* sprawdzamy: ----------------------------------------*/

cout << "Wyswietlam wyniki zliczania litery a: \n";

Pokazuj1(licznik_a);
cout << '\n' << Pokazuj2(licznik_a);

}

Licznik::Licznik(char z)
{
moja_litera = z;
ile = 0;
}

void Licznik::Skok_licznika(void)
{
ile++;
}

/* ------------ Definicje funkcji: ---------------- */

void Pokazuj1(Licznik Obiekt)
{
cout << Obiekt.ile;
}

int Pokazuj2(Licznik Obiekt)
{
return (Obiekt.ile);
}

[!!!]UWAGA:
________________________________________________________________
Programy manipulujace obiektami w taki sposób moga wymagac
modelu pamieci wiekszego niz przyjmowany domyslnie model SMALL.
Typowy komunikat pojawiajacy sie przy zbyt małym modelu pamieci
to:

Error 43: Type mismatch in parameter to call to
Pokazuj1(Licznik)...
(Zły typ argumentu przy wywołaniu funkcji Pokazuj(...)...)

Programy obiektowe sa z reguły szybke, ale niestety dosc
"pamieciochłonne". W IDE BORLAND C++ masz do dyspozycji opcje:

Options | Compiler | Code generation | Model

Dokładniejsze informacje o modelach pamieci znajdziesz w dalszej

czesci ksiazki.
________________________________________________________________


O PROBLEMIE REFERENCJI.

Typowy (domyslny) sposób przekazywania argumentów do funkcji w
C++ polega na tzw. "przekazaniu przez wartosc" i jest inny niz
Pascalu, czy Basicu. Poniewaz w polskich warunkach do C/C++
100
wiekszosc adeptów "dojrzewa" po przebrnieciu przez Basic i/lub
Pascal, programisci ci obciazeni sa juz pewnymi nawykami i
pewnym schematyzmem myslenia, który do C++ niestety nie da sie
zastosowac i jest powodem wielu pomyłek. To, co w Basicu wyglada

zrozumiale (uwaga, tu własnie pojawia sie automatyzm myslenia):

PRINT X REM Wyprowadz biezaca wartosc zmiennej X
INPUT X REM Pobierz wartosc zmiennej X

a w Pascalu:

writeln(X); { Wyprowadz biezaca wartosc zmiennej X }
readln(X); { Pobierz wartosc zmiennej X }

przyjmuje w C/C++ forme zapisu wyraznie dualnego:

printf("%d", X); //Wyprowadz wartosc zmiennej X
scanf("%d", &X); //Pobierz wartosc zmiennej X

Na czym polega róznica? Jesli odrzucimy na chwile automatyzm i
zastanowimy sie nad ta sytuacja, zauwazymy, ze w pierwszym
przypadku (wyprowadzanie istniejacych juz danych - PRINT,
wrilteln, printf()) w celu poprawnego działania funkcji
powinnismy przekazac jej BIEZACA WARTOSC ARGUMENTU X (adres
zmiennej w pamieci nie jest funkcji potrzebny). Dla Basica,
Pascala i C++ biezaca wartosc zmiennej kojarzoana jest z jej
identyfikatorem - tu: "X". W drugim jednakze przypadku (pobranie

danych i umieszczenie ich pod własciwym adresem pamieci) jest
inaczej. Funkcji zupełnie nie interesuje biezaca wartsc zmiennej

X, jest jej natomiast do poprawnego działania potrzebny adres
zarezerwowany dla zmiennej X w pamieci. Ale tu okazuje sie, ze
Basic i Pascal postepuja dokładnie tak samo, jak poprzednio:

INPUT X i read(X);

Oznacza to, ze X nie oznacza dla Pascala i Basica biezacej
wartosci zmiennej, lecz oznacza (DOMYSLNIE) przekazanie do
funkcji adresu zmiennej X w pamieci. Funkcje oczywiscie
"wiedza", co dostały i dalej juz one same manipuluja danymi we
własciwy sposób.

W C++ jest inaczej. Zapis:

Funkcja(X);

oznacza w praktyce, ze zostana wykonane nastepujace operacje:

* spod adresu pamieci przeznaczonego dla zmiennej X zostanie
(zgodnie z zadeklarowanym formatem) odczytana biezaca wartosc
zmiennej X;
* wartosc X zostanie zapisana na stos (PUSH X);
* zostanie wywołana funkcja Funkcja();
* Funkcja() pobierze sobie wartosc argumentu ze stosu (zgodnie z

formatem zadeklarowanym w prototypie Funkcji()).
* Funkcja() zadziała zgodnie ze swoja definicja i jesli ma cos
do pozostawienia (np. return (wynik); ) pozostawi wynik.

Jak widac:

* funkcja "nie wie", gdzie w pamieci umieszczony był przekazany
jej argument;
* funkcja komunikuje sie "ze swiatem zewnetrznym" (czyli własnym

programem, badz funkcja wyzszego rzedu - wywołujaca) tylko za
posrednictwem stosu;
* funkcja dostaje swoja "kopie" argumentu z którym działa;
* funkcja nie ma wpływu na "oryginał" argumentu, który pozostaje

bez zmian.

REFERENCJA - CO TO TAKIEGO ?

Zastanówmy sie, czym własciwie jest referencja zmiennej w C++.
Pewne jest, ze jest to alternatywny sposób odwołania sie do
zmiennej. Zacznijmy od trywialnego przykładu odwołania sie do
tej samej zmiennej majacej swoja własciwa nazwe "zmienna" i
referencje "ksywa".

# include "iostream.h"

main()
{
int zmienna;
int& ksywa;
...

Aby "ksywa" oznaczała te sama zmienna, referencje nalezy
zainicjowac:

int& ksywa = zmienna;

Zainicjujemy nasza zmienna "zmienna" i bedziemy robic z nia
cokolwiek (np. inkrementowac). Równoczesnie bedziemy sprawdzac,
czy odwołania do zmiennej przy pomocy nazwy i referencji beda
pozostawac równowazne.

[P111.CPP]

/* UWAGA: Program moze potrzebowac modelu wiekszego niz
domyslnie ustawiany MODEL SMALL */

# include "iostream.h"

main()
{
int zmienna = 6666;
int& ksywa = zmienna;

cout << '\n' << "Zmienna" << " Ksywa";
cout << '\n' << zmienna << '\t' << ksywa;

for (register int i = 0; i < 5; i++, zmienna += 100)
cout << '\n' << zmienna << '\t' << ksywa;

return 0;
}

Dialog (a własciwie monolog) powinien wygladac tak:

C:\>program

Zmienna Ksywa
6666 6666
6666 6666
6766 6766
6866 6866
6966 6966
7066 7066

Referencje i wskazniki mozna stosowac a C++ niemal wymiennie
(dokładniej - nie jest to wymiennosc wprost, a uzupełnianie na
zasadzie odwrotnosci-komplementarnosci).

[!!!] TO NIE WSZYSTKO JEDNO!.
________________________________________________________________
Mogłoby sie wydawac, ze operator adresowy & zyskał dwa RÓZNE
zastosowania: okreslenie adresu w pameci oraz tworzenie
wskazania. Aby rozróznic te dwie sytuacje zwróc uwage na
"gramatyke" zapisu. Jesli identyfikator zminnej jest poprzedzony

okresleniem typu zminnej:

int &zmienna; /* lub */ int &zmienna = ... ;

to zmienna nazywamy "zmienna referencyjna". Jesli natomiast
identyfikator nie został poprzedzony okresleniem typu:

p = &zmienna;

to mówimy wtedy o adresie zmiennej.
Przekazanie argumentu do funkcji poprzez referencje jest w
istocie zblizone do przekazania wskaznika do argumentu. Zwróc
uwage, ze przekazanie wskaznika do obiektu moze zwykle odbyc sie

szybciej niz sporzadzenie kopii obiektu i przekazanie tej kopii
do funkcji. Zastosowanie w deklaracji funkcji operatora
adresowego & pozwala nam stosowac syntaktyke zapisu taka "jak
zwykle" - przy przekazaniu przez wartosc. Jesli nie chcemy
ryzykowac zmian wprowadzonych do oryginalnego parametru
przekazanego funkcji poprzez wskazanie, mozemy zadeklarowac
oryginalny parametr jako stała (kompilator "dopilnuje" i
uniemozliwi zmiane wartosci):
101

nazwa_funkcji(const &nazwa_obiektu);
________________________________________________________________

Poprosimy C++ by pokazał nam konkretne fizyczne adresy
skojarzone z identyfikatorami "zmienna" i "ksywa". Operator &
oznacza dla C++

&X --> adres w pamieci zmiennej X

[P112.CPP]

/* UWAGA: Program moze potrzebowac modelu wiekszego niz
domyslnie ustawiany MODEL SMALL */

# include "iostream.h"

main()
{
int zmienna = 6666;
int& ksywa = zmienna;

cout << "Zmienna (ADR-hex) Ksywa (ADR-hex): \n\n";
cout << hex << &zmienna << "\t\t" << &ksywa;

return 0;
}

Monolog programu powinien wygladac tak:

Zmienna (ADR-hex) Ksywa (ADR-hex):

0x287efff4 0x287efff4

Fizyczny adres pamieci, który "kojarzy sie" C++ ze zmienna i
ksywa jest identyczny. Referencja nie oznacza zatem ani
sporzadzania dodatkowej kopii zmiennej, ani wskazania do
zmiennej w rozumieniu wskaznika (pointer). Jest to inna metoda
odwołania sie do tej samej pojedynczej zmiennej.

LEKCJA 33: WSKAZNIKI DO OBIEKTÓW.
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak posługiwac sie obiektami
za posrednictwem wskazników.
________________________________________________________________

Wskazniki do obiektów funkcjonuja podobnie jak wskazniki do
struktur. Operator -> pozwala na dostep zarówno do danych jak i
do funkcji. Dla przykładu wykorzystamy obiekt naszej prywatnej
klasy Licznik.

class Licznik
{
public:
char moja_litera;
int ile;
Licznik(char znak) { moja_litera = z; ile = 0; }
void Skok_licznika(void) { ile++; }
};

Aby w programie mozna było odwołac sie do obiektu nie poprzez
nazwe a przy pomocy wskaznika, zadeklarujemy wskaznik do
obiektów klasy Licznik:

Licznik *p;

Wskaznik w programie mozemy zastosowac np. tak:

p->Skok_licznika();

(czytaj: Wywołaj metode "Skok_licznika()" w stosunku do obiektu
wskazywanego w danym momencie przez wskaznik p)

Trzeba pamietac, ze sama deklaracja w przypadku referencji i
wskazników nie wystarcza. Przed uzyciem nalezy jeszcze
zainicjowac wskaznik w taki sposób, by wskazywał na nasz
obiekt-licznik. Wskaznik do obiektu inicjujemy w taki sam sposób

jak kazdy inny pointer:

p = &Obiekt;

Mozemy przystapic do utworzenia programu przykładowego.

[P119.CPP]

# include "ctype.h"
# include "iostream.h"

class Licznik
{
public:
char moja_litera;
int ile;
Licznik(char z) { moja_litera = z; ile = 0; }
void Skok_licznika(void) { ile++; }
};

void main()
{
char znak;
cout << "\nPodaj litere do zliczania: ";
cin >> znak;

Licznik Obiekt1(znak), Obiekt2('a'), *p1, *p2;
p1 = &Obiekt1;
p2 = &Obiekt2;
cout << "\n Wpisz ciag znakow";
cout << "zakonczony kropka [.] i [Enter] \n";
for(;;)
{
cin >> znak;
if(znak == '.') break;
if(znak == p1->moja_litera) p1->Skok_licznika();
if(znak == p2->moja_litera) p2->Skok_licznika();
}
cout << "\nBylo " << p1->ile;
cout << " liter: " << p1->moja_litera;
p1 = p2;
cout << "\nBylo " << p1->ile;
cout << " liter: " << p1->moja_litera;
}


Mozemy oczywiscie np. stosowac przypisanie, inkrementowac i
dekrementowac pointer oraz realizowac arytmetyke na wskaznikach
dokładnie tak samo, jak w przypadku innych zmiennych.

this - WSKAZNIK SPECJALNY.

Poswiecimy teraz chwile uwagi pewnemu specjalnemu wskaznikowi.
Specjalnemu (i waznemu) na tyle, ze az "dorobił sie" w C++
własnego słowa kluczowego "this".

Kazdej funkcji - metodzie zadeklarowanej wewnatrz klasy zostaje
w momencie wywołania w niejawny sposób (ang. implicitly)
przekazany wskaznik do obiektu (w stosunku do którego funkcja ma

zadziałac). Pointer wskazuje funkcji w pamieci ten obiekt,
którego członkiem jest dana funkcja. Bez istnienia takiego
własnie wskaznika nie moglibysmy stosowac spokojnie funkcji, nie

moglibysmy odwoływac sie do pola obiektu, gdybysmy nie wiedzieli

jednoznacznie, o który obiekt chodzi. Program posługuje sie
automatycznie niejawnym wskaznikiem do obiektu (ang. implicit
pointer). Mozemy wykorzystac ten istniejacy, choc do tej pory
nie widoczny dla nas pointer posługujac sie słowem kluczowym
this (ten). This pointer wskazuje na obiekt, do którego nalezy
funkcja. Korzystajac z tego wskaznika funkcja moze bez cienia
watpliwosci zidentyfikowac własnie ten obiekt, z którym pracuje
a nie obiekt przypadkowy.

[!!!] FUNKCJE KATEGORII static NIE OTRZYMUJA POINTERA this.
Nalezy pamietac, ze wskaznik this istnieje wyłacznie podczas
wykonywania metod (ang. class member function execution), za
wyjatkiem funkcji statycznych.

Jesli w programie zadeklarujemy klase Klasa:

class Klasa
{
int dane;
...
}

102
a wewnatrz tej klasy metode Pokazuj():

class Klasa
{
int dane;
public:
void Pokazuj();
...
}

void Klasa::Pokazuj(void)
{
cout << dane;
}

To zdefiniowanie funkcji Pokazuj() z zastosowaniem pointera this

i notacji wskaznikowej (p->), jak ponizej, bedzie równowazne:

void Klasa::Pokazuj(void)
{
cout << this->dane;
}

Przypomnijmy, ze taka notacja wskaznikowa oznacza:
"Wyprowadz zawartosc pola "dane" obiektu, na który wskazuje
wskaznik" (poniewaz jest to wskaznik this, wiec chodzi o własny
obiekt).



LEKCJA 34 OVERLOADING OPERATORÓW.
________________________________________________________________
Podczas tej lekcji poznasz mozliwosci dostosowania operatorów
C++ do własnego "widzimisie" i do potrzeb własnych obiektów.
________________________________________________________________

Niemal od poczatku niniejszej ksiazki korzystamy z operatorów
poddanych overloadingowi. Sa to operatory << i >> , które
pierwotnie wykonywały bitowe przesuniecie w lewo i w prawo.
Owerloading tych operatorów "załatwił" za nas producent
(Borland, Microsoft, czy inny). Jak widzisz, nie powoduje to w
dalszym uzytkowaniu tych operatorów zadnych zauwazalnych
komplikacji, a czesto ułatwia tworzenie programów. Zwróc uwage,
ze overloading operatorów (jak i definicje klas) moze znajdowac
sie w dołaczonych plikach nagłówkowych i po jednorazowym
wykonaniu moze byc "niewidoczny" dla programistów tworzacych
programy aplikacyjne.

Jesli projektujemy (definiujemy) nowa klase, dodajemy do C++
nowy, lecz pełnoprawny typ danych. Autorzy C++ nie byli w stanie

przewidziec jakie klasy i jakie obiekty moga wymyslic kolejne
pokolenia programistów w ramach swojej radosnej twórczosci.
Wprowadzili zatem do C++ jasne i jednoznaczne algorytmy
postepowania z typami "typowymi". C++ doskonale wie jak dodawac,

mnozyc, czy odejmowac np. liczby int, long, float itp., nie wie
jednak jak dodac do siebie obiekty klas CString (CString = Class

String = klasa "łancuch znaków"), TOdcinek (to taki kawałek
prostej) itp.. A przeciez miło byłoby, gdyby rozbudowac
działanie operatorów tak, by było mozliwe ich typowe
zastosowanie w stosunku do naszych własnych, "nietypowych"
obiektów:

int x, y; int z = x + y; //To operator + załatwia sam
float x, y; float z = x + y;

Zanim jednak stanie sie mozliwe postepowanie takie:

class CString x, y, z; z = x + y;

class Nasza_Klasa obiekt1, obiekt2, obiekt3;
obiekt3 = obiekt1 + obiekt2;

itp., itd. ...

musimy "uzupełnic" C++ i "wyjasnic" operatorom, co własciwie ma
w praktyce oznaczac operacja obiekt1 = obiekt2 + obiekt3; .
Jest wyczuwalne intuicyjnie, ze działanie operatorów w stosunku
do róznych obiektów moze byc rózne. Dla przykładu - wiesz
zapewne, ze inaczej wyglada algorytm mnozenia liczb zespolonych,

a inaczej liczb całkowitych rzeczywistych. Dlatego tez wykonanie

operacji mnozenia wymaga od operatora * podjecia róznych
działan:

class Liczba_zespolona x, y, z; z = x * y;

int x, y, z; z = x * y;

Czasem moze sie zdarzyc, ze dla dwu róznych klas działanie
jakiegos operatora jest identyczne, czesciej jednak (i tak
nalezy sie spodziewac) działanie operatora dla kazdej klasy
bedzie odrebne i unikalne.

Pójdzmy w tym rozumowaniu o krok dalej. Skoro rozszerzenie
obszaru zastosowan jakiegos operatora na obiekty nowej
(nieznanej wczesniej klasy) wymaga zdefiniowania nowego
algorytmu działania operatora, C++ bedzie potrzebował do tego
celu specjalnych srodków, które powinny byc łatwo rozpoznawalne.

Do opisu algorytmów słuza generalnie w C++ funkcje i tu Autorzy
nie wprowadzili wyjatku. Zastrzegli jednak dla tych specjalnych
funkcji specjalna nazwe: operator ...();

I tak funkcja precyzujaca nowy algorytm dodawania (nowy sposób
działania operatora + ) bedzie sie nazywac:

operator+();

a np. funkcja okreslajaca nowy algorytm mnozenia (nowy sposób
działania operatora * ) bedzie sie nazywac:

operator*();

Spróbujmy zastosowac taka filozofie w praktyce programowania.

[!!!] NIESTETY NIE WSZYSTKIE OPERATORY MOZNA
ROZBUDOWAC.
________________________________________________________________
Sa w C++ operatory, których nie mozemy poddac overloadingowi. Sa

to:

. :: .* ?:

. operator kropki umozliwia dostep do pól struktur i obiektów;

:: operator "widocznosci-przesłaniania" (ang. scope);
.* wskazanie członka klasy (ang. pointer-to-member);
?: operator warunkowy.
________________________________________________________________

Wszystkie pozostałe operatory mozemy poddac overloadingowi i
przypisywac im potrzebne nam działanie.

OVERLOADING OPERATORA [+] (DWUARGUMENTOWEGO).

Zaczniemy od operatora + nalezacego do grupy "dwuargumentowych
operatorów arytmetycznych" (ang. binary arithmetic operator).
Zwracamy tu juz na poczatku rozwazan uwage na przynaleznosc
operatora do okreslonej grupy, poniewaz overloading róznych
opertorów nalezacych do tej samej grupy przebiega podobnie.
Poniewaz znak + moze byc takze operatorem jednoargumentowym
(ang. unary plus, o czym za chwile), podkreslamy, ze tym razem
chodzi o plus jako operator dodawania. Overloading operatora
przeprowadzimy w stosunku do obiektów prostej, znanej Ci juz z
poprzednich przykładów klasy Data, która (w celu upodobnienia
sie do maniery stosowanej w Windows i bibliotekach klas)
nazwiemy tym razem CData. "Namówimy" operator + do
przeprowadzenia operacji na obiektach (dokładniej na polach
obiektów):

CData nowadata = staradata + 7; // W tydzien pozniej

Operator + musi oczywiscie "wiedziec", na którym polu obiekty
klasy CData przechowuja liczbe dni i jak zwiazane sa (logicznie)

pola obiektu dz, mc, rok. Jest rzecza zrozumiała, ze samo
dodanie dni do pola dz moze nie wystarczyc, poniewaz data
37.11.93 jest niedopuszczalna.

Jesli staradata jest obiektem klasy CData z zawartymi wewnatrz
103
danymi, to w wyniku działania "nowego" operatora + powinien
powstac obiekt nowadata klasy CData, którego pola zostana w
sensowny sposób powiekszone o dodana liczbe dni. Rozwaz
działanie programu (najlepiej skompiluj i uruchom).

[P120.CPP]

/* Overloading operatora dwuargumentowego + */

# include <iostream.h>

class CData
{
int dz, mc, rok;
public:
CData() {} //Konstruktor domyslny (pusty)
CData(int d, int m, int y) { mc = m; dz = d; rok = y; }
void Pokazuj() { cout << dz << '.' << mc << '.' << rok; }
CData operator+(int); //TU! overloading operatora +
};

static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31};

/* Definicja funkcji operatorowej: ------------------------ */

CData CData::operator+(int n)
{
CData kopia_obiektu = *this;
n += kopia_obiektu.dz;
while (n > TAB[kopia_obiektu.mc-1])
{
n -= TAB[kopia_obiektu.mc-1];
if (++kopia_obiektu.mc == 13)
{ kopia_obiektu.mc = 1; kopia_obiektu.rok++; }
}
kopia_obiektu.dz = n;
return (kopia_obiektu);
}

main()
{
CData staradata(31, 1, 94); //Kostruktor z argumentami
CData nowadata; //Pusty konstruktor
cout << "\n Stara data: ";
staradata.Pokazuj();
cout << "\n Podaj ile minelo dni --> ";
int n;
cin >> n;
nowadata = staradata + n;
cout << "\n Jest zatem --> ";
nowadata.Pokazuj();
return 0;
}

Do tej pory do danych prywatnych obiektu moglismy siegnac
wyłacznie przy pomocy zdefiniowanej wewnatrz klasy
funkcji-metody. Metoda umozliwiajaca nam dostep do prywatnych
danych obiektu jest tu zadeklarowana wewnatrz klasy (a wiec
majaca "status prawny" metody) funkcja operatorowa. Przyjrzyjmy
sie tej funkcji dokładniej:

CData CData::operator+(int n)
{
CData kopia_obiektu = *this;
...
return (kopia_obiektu);
}

Funkcja
* została zdefiniowana dla obiektów klasy CData (z innymi
postepowac nie potrafi);
Jesli operator + zostanie umieszczony pomiedzy obiektem klasy
CData, a liczba typu int:
.... staradata + n;
* funkcja pobiera liczbe n jako argument (jawnie);
* funkcja pobiera obiekt klasy CData jako swój drugi argument
(niejawnie, dzieki pointerowi this);
* funkcja zwróci obiekt klasy CData (ze zmodyfikowanym polem);

Nowy obiekt zwrócony przez funkcje zostanie przypisany

nowadata = ... ; // <-- return(kopia_obiektu);

W prawym polu operatora (operator jest dwuargumentowy, ma wiec
swoje lewe i prawe pole) moze pojawic takze stała. Operacja:

nowadata = staradata + 14;

zostanie wykonana poprawnie.

Ale to nie wszystko. Jesli wystapi układ odwrotny - np.:

nowadata = 14 + staradata;

nasz operator "zgłupieje". Doszedłszy do operatora + C++ "nie
bedzie jeszcze wiedział" (analizuje wyrazenia arytmetyczne od
lewej do prawej), KTÓRY obiekt wystapi za chwile. Jedno jest
pewne, nie zawsze musi byc to "własny" obiekt funkcji, do
którego mamy pointer this. Aby uzyskac jednoznacznosc sytuacji,
funkcja operatorowa powinna tu w jawny sposób pobierac przed
zadziałaniem dwa argumenty:

CData operator+(int n, CData obiekt);

aby działanie:

CData obiekt_wynik; obiekt_wynik = n + obiekt;

stało sie wykonalne. Pojawia sie tu wszakze pewien problem.
Wskaznik this wskazuje własny obiekt funkcji-metody, a tym razem

funkcja potrzebuje dostepu nie do pola własnego obiektu, lecz do

pola "obcego" obiektu przekazanego jej jako argument. Ale w C++
mozemy:

* zdefiniowac dwie (i wiecej) funkcji o tej samej nazwie (kazda
na inna ewentualnosc);
* mozemy nadac funkcji status friend (wtedy nie bedac metoda tez

uzyska dostep do danych obiektu).

Definicja naszej klasy CData zawierajaca deklaracje dwu funkcji
operatorowych operator+() rózniacych sie zastosowaniem i (po
czym rozpozna je C++) liczba argumentów, bedzie wygladac tak:

class CData
{
int dz, mc, rok;
public:
CData() {}
CData(int d, int m, int y) { mc = m; dz = d; rok = y; }
void Pokazuj() { cout << dz << '.' << mc << '.' << rok; }
/* Dwie funkcje operatorowe: ------------------------------ */
CData operator+(int);
friend CData operator+(int, CData&);
};

Zastosowalismy zamiast kopii obiektu bezposrednio przekazywanej
funkcji - referencje do obiektu klasy CData - CData&. Klasa
zawiera:
* prywatne dane;
* dwa konstruktory;
* własna metode - funkcje operatorowa operator+();
* deklaracje zaprzyjaznionej z klasa funkcji kategorii friend
(choc jest to funkcja o tej samej nazwie, jej status i
uprawnienia sa nieco inne).

[!!!] NIE WSZYSTKO, CO WEWNATRZ JEST METODA.
________________________________________________________________
Nawet, jesli wewnatrz definicji klasy zdefiniujemy w pełni
funkcje (nadajac jej status inline), nie stanie sie ona metoda!
Słowo kluczowe friend okresla status funkcji jednoznacznie, bez
wzgledu na to, w którym miejscu w tekscie programu umiescimy
definicje ciała funkcji.
________________________________________________________________


W zasadzie ciało funkcji jest na tyle proste (wymagamy od niej
tylko zwrotu obiektu ze zmodyfikowanym polem danych), ze mozemy
skorzystac z rozbudowanego wczesniej operatora + i całe ciało
zdefiniowac tak:

class CData
{
int dz, mc, rok;
104
public:
...
CData operator+(int);
friend CData operator+(int n, CData& x) { return (x + n); }
};

Jesli w operacji dodawania argumenty zastosujemy we
wczesniejszej kolejnosci:

return (obiekt + liczba);

to zostanie tu wykorzystany operator + rozbudowany poprzednio
przez metode CData::operator+(int). Program w całosci moze
zatem wygladac tak:

[P121.CPP]

# include "iostream.h"

class CData
{
int dz, mc, rok;
public:
CData() {}
CData(int d, int m, int y) { mc = m; dz = d; rok = y; }
void Pokazuj() { cout << dz << '.' << mc << '.' << rok; }
CData operator+(int);
friend CData operator+(int n, CData& x) { return (x + n); }
};

static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31};

CData CData::operator+(int n)
{
CData kopia_obiektu = *this;
n += kopia_obiektu.dz;
while (n > TAB[kopia_obiektu.mc-1])
{
n -= TAB[kopia_obiektu.mc-1];
if (++kopia_obiektu.mc == 13)
{ kopia_obiektu.mc = 1; kopia_obiektu.rok++; }
}
kopia_obiektu.dz = n;
return (kopia_obiektu);
}

main()
{
CData staradata(31, 1, 94); //Kostruktor z argumentami
CData nowadata, jeszczejednadata;
cout << "\n Stara data: ";
staradata.Pokazuj();
cout << "\n Podaj ile minelo dni --> ";
int n;
cin >> n;
nowadata = staradata + n;
cout << "\n Jest zatem --> ";
nowadata.Pokazuj();
cout << "\n Testuje nowy operator: ";
jeszczejednadata = (1+n) + staradata;
jeszczejednadata.Pokazuj();
return 0;
}

Operator + w obu sytuacjach działa poprawnie. Byc moze wpadłes
na pomysł, ze operator - (minus) tez mamy juz z głowy. Niby tak,

ale tylko w takim zakresie, w jakim nasza funkcja operatorowa
poprawnie bedzie obsługiwac ujemne liczby dni. Jesli zechcesz
podac ujemna liczbe dni (zmuszajac funkcje do odejmowania
zamiast dodawania), twój dialog z programem bedzie wygladał np.
tak:

C:\>program
Stara data: 31.1.94
Podaj ile minelo dni --> -10
Jest zatem --> 21.1.94
Testuje nowy operator: 22.1.94

lub tak:

C:\>program
Stara data: 31.1.94
Podaj ile minelo dni --> -150
Jest zatem --> -119.1.94
Testuje nowy operator: -118.1.94

Funkcja operatorowa została napisana w taki sposób, ze po
przekroczeniu wartosci -31 program bedzie wypisywał bzdury. Jako

zadanie domowe - spróbuj zmodyfikowac algorytm w taki sposób, by

rozszerzyc zakres poprawnych wartosci.

[!!!] Mozesz dodawac obiekty minusem.
________________________________________________________________
* Nalezy tu zwrócic uwage, ze dodawanie obiektów moze wykonywac
nie tylko i nie koniecznie operator + . Jesli zechcesz, mozesz
do tego celu zastosowac dowolnie wybrany operator (np. -, *
itp.). W celu ułatwienia zrozumienia zapisu (i tylko dlatego)
wiekszosc programistów rozbudowuje działanie operatorów zgodnie
z ich pierwotnym zastosowaniem.
* DOWOLNOSC, ALE NIE PEŁNA!
O tyle, o ile działanie operatora moze byc zmienione, to ilosc
argumentów potrzebnych operatorowi pozostaje w C++ "sztywna"
(patrz przykład z n!).
________________________________________________________________

W bardzo podobny sposób mozesz rozbudowywac inne arytmetyczne
operatory dwuargumentowe (*, /, -, itp.) w stosunku takze do
innych klas.

OVERLOADING OPERATORÓW JEDNOARGUMENTOWYCH ++ I -- .

Typowe operatory jednoargumentowe to ++ i --. Jako przykładem
posłuzymy sie problemem zlicznia znaków pobieranych ze
strumienia wejsciowego.

Zaczniemy od redefinicji postinkrementacji licznika. Musimy
zastosowac funkcje operatorowa. Funkcja, chcac operowac na
obiektach musi w stosunku do tych obiektów posiadac status
friend, lub byc metoda. Prototyp funkcji operatorowej potrzebnej

do wykonania overloadingu operatora jednoargumentowego ++
wyglada w postaci ogólnej tak:

typ_zwracany nazwa_klasy::operator++(lista argumentów);

Funkcje operatorowe zwracaja zwykle wartosc zgodna co do typu z
typem obiektów z którymi współpracuja. Jesli identyfikatory b, c

i d reprezentuja obiekty, nic nie stoi na przeszkodzie, by stał
sie mozliwy zapis:

class Klasa
{
...
} x, y, z;
...
z = x + y;

Dodajemy dwa obiekty x i y tego samego typu (tej samej klasy), a

wynik przypisujemy obiektowi z, który takze jest obiektem tego
samego typu. Jesli moznaby jeszcze zastosowac operator
przypisania tak:

z = q = x + y;

operator przypisania = zwracałby nam w efekcie obiekt tego
samego typu. Funkcje operatorowe musza przestrzegac tych samych
zasad, które obowiazuja wyrazenia: typ argumentów x, y, z, q,
... powinien byc zgodny, rezultat operacji (x + y) powinien byc
obiektem tego samego typu, co obiekty x, y, z, q. Dokonujac
overloadingu operatorów powinnismy precyzyjnie okreslic typ
wartosci zwracanej w wyniku działania operatora.

Stosowana poprzednio do inkrementacji liczników metode
Skok_licznika() zastapimy w definicji klasy funkcja operatorowa:


class Licznik
{
public:
char moja_litera;
int ile;
105
Licznik(char);
Licznik operator++();
};

Powinnismy teraz zdefiniowac funkcje operatorowa. Poniewaz pole
obiektu, które zamierzamy inkrementowac nazywa sie:

obiekt.ile // Licznik::ile;

funkcja powinna zadziałac tak:

Licznik Licznik::operator++(void)
{
this->ile++;
return (*this);
}

Przetłumaczmy te notacje na "ludzki jezyk". Funkcja operatorowa:


* nie pobiera zadnych jawnych argumentów (void);
* jest metoda, zatem w momencie wywołania otrzymuje w niejawny
sposób wskaznik *this do "własnego" obiektu;
* posługujac sie wsakznikiem this inkrementuje zawartosc pola
int ile własnego obiektu;
* zwraca obiekt (zmodyfikowany) klasy Licznik (tj. dokładniej -
zwraca wskaznik this do własnego-zmodyfikowanego obiektu.

Poniewaz funkcja operatorowa jest metoda zadeklarowana wewnatrz
klasy, bez problemu uzyska dostep do wewnetrznych pól obiektów
tej klasy i wykona inkrementacje licznika. Mozemy zatem
zastosowac wyrazenie typu:

Licznik licznik_a; licznik_a++;

Funkcja jest metoda wraz ze wszystkimi własciwymi metodom
przywilejami. Zapis mozemy zatem uproscic do postaci:

Licznik Licznik::operator++(void)
{
ile++;
return (*this);
}

a tak skrócone ciało funkcji umiescic w definicji klasy obok
definicji konstruktora:

class Licznik
{
public:
char moja_litera;
int ile;
Licznik(char z) { ile = 0; moja_litera = z; }
Licznik operator++() { ile++; return (this); }
};

Aby nie zaciemniac obrazu, przy pomocy licznika bedziemy tym
razem zliczac wszystkie znaki za wyjatkiem kropki. Poniewaz
licznik nie bedzie miał swojej ulubionej litery, mozemy
zastosowac pusty konstruktor.

[P121.CPP]
/* --------------------- POST - inkrementacja ----------- */
# include "iostream.h"

class Licznik
{
public:
int ile;
Licznik() { ile = 0;}
Licznik operator++() { ile++; return (*this); }
} obiekt;

void main()
{
cout << "\n Wpisz kilka znakow: ";
char znak;
for(;;)
{
cin >> znak;
if(znak == '.') break;
obiekt++;
}
cout << "\n Wpisales " << obiekt.ile << " znakow";
}

Podobnie jak wczesniej, preinkrementacja i postinkrementacja
wymagaja odrebnego overloadingu. Dokładnie rzecz ujmujac,
zgodnie ze standardem ANSI C, odrebny overloading nie jest juz
niezbedny, wykorzystamy to jednak jako pretekst do wykonania go
dwiema róznymi technikami. Poniewaz logika jest bardzo podobna,
pomijamy tu (chyba juz zbedny) komemtarz. Dla ułatwienia Ci
porównania, zestawilismy obok siebie rózne funkcje operatorowe
napisane róznymi technikami (notacja wskaznikowa i
referencyjna).

[P122.CPP]

/* -------- PRE - inkrementacja ------------------------- */
# include "iostream.h"

class Licznik
{
public:
int ile;
Licznik() { ile = 0;}
Licznik operator+(int n = 1)
{ this->ile += n; return (*this); }
Licznik friend operator++(Licznik& x)
{ x + 1; return (x); }
} obiekt;

void main()
{
cout << "\n Wpisz kilka znakow: ";
char znak;
for(;;)
{
cin >> znak;
if(znak == '.') break;
++obiekt;
}
cout << "\n Wpisales " << obiekt.ile << " znakow";
cout << "\n I dodamy jeszcze sto! --> ";
obiekt + 100;
cout << obiekt.ile;
}


Ponizej inny przykład tego samego overloadingu odnosnie tej
samej klasy Licznik (w troche inny sposób).

[P123.CPP]

# include "conio.h"
# include "iostream.h"

class Licznik
{
public:
char moja_litera;
int ile;
Licznik() { ile = 0; } //Pusty konstruktor
Licznik(char);
Licznik operator++(); //Funkcja pre/post-inkrementacji
Licznik operator--(); //Funkcja pre/post-dekrementacji
};

Licznik::Licznik(char z) { moja_litera = z; ile = 10; }
Licznik Licznik::operator++(void) { ile++; return *this; }
Licznik Licznik::operator--(void) { ile--; return *this; }


void main()
{
Licznik obiekt1('A'), obiekt2; //obiekt2 - "pusty"

cout << "\n Wpisz napis z max. 10 literami [A]: \n ";
for(;;)
{
char litera = getch(); cout << litera;

if(obiekt1.ile == 0) break;

if(litera == obiekt1.moja_litera) obiekt1--;

106
++obiekt2; //Ten zlicza wszystkie znaki
//metoda PRE - inkrementacji
if(obiekt2.ile > 30) cout << "\n NIE PRZESADZAJ \n";
}

cout << "\n Koniec: " << obiekt1.ile;
cout << " liter " << obiekt1.moja_litera;
cout << "\n Wszystkich znakow bylo: " << obiekt2.ile;
}

Overloading "siostrzanych" operatorów ++ i -- jest blizniaczo
podobny.

OVERLOADING OPERATORA !

Z matematyki jestesmy przyzwyczajenu do zapisu silni n! i
wydawałoby sie, ze majac w C++ do dyspozycji operator ! nie
powinnismy miec z tym zadaniem najmniejszego kłopotu. Operujac
znana Ci klasa Liczba i wyposazajac program w funkcje
operatorowa mozemy załatwic ten problem np. tak:

[P124.CPP]

# include <iostream.h>

class Liczba
{
public:
long wartosc;
Liczba(int x) { wartosc = (long) x; }
friend void operator!(Liczba&);
};

void operator!(Liczba& obiekt)
{
long wynik = 1;
for(int i = 1; i <= obiekt.wartosc; i++)
{
wynik *= i;
}
cout << '\n' << wynik;
}

int x;

main()
{
for(int k = 0; k < 5; k++)
{
cout << "\n Podaj liczbe --> ";
cin >> x;
Liczba a(x);
cout << "\n Silnia wynosi: ";
!a;
}
return 0;
}

Program działa, wyniki kolejnych kilku silni sa poprawne. Gdy
jednak spróbujemy zastosowac operator ! zgodnie z tradycyjnym
matematycznym zapisem: a!; okaze sie, ze C++ zacznie miec
watpliwosci. Komunikaty o błedzie spowoduja watpliwosci
kompilatora, czy chodzi nam o operator "!=", w którym
zapomnielismy znaku "=". Jesli w funkcji operatorowej spróbujemy

zmienic operator ! na != , a zapis w programie:

z !a; na a!=a;

C++ zarzada dwuargumentowej funkcji operatorowej (bo taki
operator jest tradycyjnie dwuargumentowy). Mozemy oczywiscie
próbowac oszukac C++ przy pomocy argumentu pozornego. Jesli
podamy w funkcji operatorowej dwa argumenty

void operator!=(Liczba& obiekt1, Liczba& obiekt2)
{
long wynik = 1;
for(int i = 1; i <= obiekt.wartosc; i++)
{
wynik *= i;
}
cout << '\n' << wynik;
}

program uda sie skompilowac i kod wynikowy bedzie działał
poprawnie, C++ zaprotestuje jedynie przy pomocy ostrzezenia

Warning: obiekt2 is never used...

Chcac uniknac ostrzezen nalezy uzyc argument pozorny w dowolny
sposób. Zwracamy na to uwage, poniewaz C++ jest pedantem i:

[!!!] DZIAŁANIE OPERATORÓW MOZE BYC DALECE DOWOLNE, ALE
LICZBA
ARGUMENTÓW MUSI POZOSTAC ZGODNA Z "TRADYCJAMI" C++.

Stosowanie podczas overloadingu operatorów argumentów pozornych
jest technika czesto stosowana przez programistów.

Aby wykazac, ze korzystanie z gotowych "fabrycznych" zasobów
ułatwia zycie programiscie czasami w zaskakujaco skuteczny
sposób, przytocze przykładowy program, który posługujac sie
"fabryczna" klasa ofstream (obiekty - strumien danych do pliku
wyjsciowego - Output File STREAM):

* zakłada w biezacym katalogu plik dyskowy DANE.TST;
* otwiera plik dla zapisu;
* zapisuje do pliku tekst "to jest zawartosc pliku";
* zamyka plik;

[P125.CPP]

# include "fstream.h"

void main()
{
ofstream plik("dane.tst");
plik << "To jest zawartosc pliku";
}

I juz. O wszystkie szczegóły techniczne tych (wcale przeciez nie

najprostszych) operacji zadbał producent w bibliotekach klas
Wejscia/Wyjscia. Jesli zechcemy do pliku dopisac cos jeszcze,
wystarczy dodac:

[P126.CPP]

# include "fstream.h"

void main()
{
ofstream plik("dane.tst");
plik << "To jest zawartosc pliku" << " i jeszcze cosik.";
}

Urzekajaca prostota, nieprawdaz? I to wszystko załatwia poddany
overloadingowi operator << . Niedowiarek mógłby w tym momencie
zapytac "a jesli plik juz istnieje, to chyba nie jest takie
proste?". Rzeczywiscie, nalezałoby tu rozbudowac program w C++
do postaci:

# include "fstream.h"
void main()
{
ofstream plik("dane.tst", ios::app);
plik << " Dopiszemy do pliku jeszcze i to...";
}

Korzystamy tu dodatkowo z globalnej zmiennej ios::app (ang.
append - dołacz) okreslajacej inny niz typowy tryb dostepu do
pliku dyskowego i w dalszym ciagu z operatora << . Tworzenie
obiektu - pliku dyskowego jest takie proste, dzieki istnieniu
konstruktora, który jest tu automatycznie wywoływany po
deklaracji: ofstream plik( ... );

[Z]
________________________________________________________________
1. Wykonaj samodzielnie overloading dowolnego operatora.
________________________________________________________________



LEKCJA 35: O ZASTOSOWANIU DZIEDZICZENIA.
________________________________________________________________
Z tej lekcji dowiesz sie, do czego w praktyce programowania
107
szczególnie przydaje sie dziedziczenie.
________________________________________________________________

Dzieki dziedziczeniu programista moze w pełni wykorzystac gotowe

biblioteki klas, tworzac własne klasy i obiekty, jako klasy
pochodne wazgledem "fabrycznych" klas bazowych. Jesli bazowy
zestw danych i funkcji nie jest adekwatny do potrzeb, mozna np.
przesłonic, rozbudowac, badz przebudowac bazowa metode dzieki
elastycznosci C++. Zdecydowana wiekszosc standardowych klas
bazowych wyposazana jest w konstruktory. Tworzac klase pochodna
powinnismy pamietac o istnieniu konstruktorów i rozumiec sposoby

przekazywania argumentów obowiazujace konstruktory w przypadku
bardziej złozonej struktury klas bazowych-pochodnych.

PRZEKAZANIE PARAMETRÓW DO WIELU KONSTRUKTORÓW.

Klasy bazowe moga byc wyposazone w kilka wersji konstruktora.
Dopóki nie przekazemy konstruktorowi klasy bazowej zadnych
argumentów - zostanie wywołany (domyslny) pusty konstruktor i
klasa bazowa bedzie utworzona z parametrami domyslnymi. Nie
zawsze jest to dla nas najwygodniejsza sytuacja.

Jezeli wszystkie, badz chocby niektóre z parametrów, które
przekazujemy konstruktorowi obiektu klasy pochodnej powinny
zostac przekazane takze konstruktorowi (konstruktorom) klas
bazowych, powinnismy wytłumaczyc to C++. Z tego tez powodu,
jesli konstruktor jakiejs klasy ma jeden, badz wiecej
parametrów, to wszystkie klasy pochodne wzgledem tej klasy
bazowej musza posiadac konstruktory. Dla przykładu dodajmy
konstruktor do naszej klasy pochodnej Cpochodna:


class CBazowa1
{
public:
CBazowa1(...); //Konstruktor
};

class CBazowa2
{
public:
CBazowa2(...); //Konstruktor
};

class Cpochodna : public CBazowa1, CBazowa2 //Lista klas
{
public:
Cpochodna(...); //Konstruktor
};

main()
{
Cpochodna Obiekt(...); //Wywolanie konstruktora
...

W momencie wywołania kostruktora obiektu klasy pochodnej
Cpochodna() przekazujemy kostruktorowi argumenty. Mozemy (jesli
chcemy, nie koniecznie) przekazac te argumenty konstruktorom
"wczesniejszym" - konstruktorom klas bazowych. Ta mozliwosc
okazuje sie bardzo przydatna (niezbedna) w srodowisku obiektowym

- np. OWL i TVL. Oto prosty przykład definiowania konstruktora w

przypadku dziedziczenia. Rola konstruktorów bedzie polegac na
trywialnej operacji przekazania pojedynczego znaku.

class CBazowa1
{
public:
CBazowa1(char znak) { cout << znak; }
};

class CBazowa2
{
public:
CBazowa2(char znak) { cout << znak; }
};

class Cpochodna : public CBazowa1, CBazowa2
{
public:
Cpochodna(char c1, char c2, char c3);
};

Cpochodna::Cpochodna(char c1,char c2,char c3) : CBazowa1(c2),
CBazowa2(c3)
{
cout << c1;
}

Konstruktor klasy pochodnej pobiera trzy argumenty i dwa z nich:

c2 --> przekazuje do konstruktora klasy CBazowa1
c3 --> przekazuje do konstruktora klasy CBazowa2
Sposób zapisu w C++ wyglada tak:

Cpochodna::Cpochodna(char c1,char c2,char c3) : CBazowa1(c2),
CBazowa2(c3)

Mozemy zatem przekazac parametry "w tył" do konstruktorów klas
bazowych w taki sposób:

kl_pochodna::kl_pochodna(lista):baza1(lista), baza2(lista), ...

gdzie:
lista - oznacza liste parametrów odpowiedniego konstruktora.

W takiej sytuacji na liscie argumentów konstruktorów klas
bazowych moga znajdowac sie takze wyrazenia, przy załozeniu, ze
elementy tych wyrazen sa widoczne i dostepne (np. globalne
stałe, globalne zmienne, dynamicznie inicjowane zmienne globalne

itp.). Konstruktory beda wykonywane w kolejnosci:

CBazowa1 --> CBazowa2 --> Cpochodna

Dzieki tym mechanizmom mozemy łatwo przekazywac argumenty
"wstecz" od konstruktorów klas pochodnych do konstruktorów klas
bazowych.

FUNKCJE WIRTUALNE.

Działanie funkcji wirtualnych przypomina rozbudowe funkcji
dzieki mechanizmowi overloadingu. Jesli, zdefiniowalismy w
klasie bazowej funkcje wirtualna, to w klasie pochodnej mozemy
definicje tej funkcji zastapic nowa definicja. Przekonajmy sie o

tym na przykładzie. Zacznijmy od zadeklarowania funkcji
wirtualnej (przy pomocy słowa kluczowego virtual) w klasie
bazowej. Zadeklarujemy jako funkcje wirtualna funkcje oddychaj()

w klasie CZwierzak:

class CZwierzak
{
public:
void Jedz();
virtual void Oddychaj();
};

Wyobrazmy sobie, ze chcemy zdefiniowac klase pochodna CRybka
Rybki nie oddychaja w taki sam sposób, jak inne obiekty klasy
CZwierzak. Funkcje Oddychaj() trzeba zatem bedzie napisac w dwu
róznych wariantach. Obiekt Ciapek moze te funkcje odziedziczyc
bez zmian i sapac spokojnie, z Sardynka gorzej:

class CZwierzak
{
public:
void Jedz();
virtual void Oddychaj() { cout << "Sapie..."; }
};

class CPiesek : public CZwierzak
{
char imie[30];
} Ciapek;

class CRybka
char imie[30];
public:
void Oddychaj() { cout << "Nie moge sapac..."; }
} Sardynka;

108

Zwróc uwage, ze w klasie pochodnej w deklaracji funkcji słowo
kluczowe virtual juz nie wystepuje. W klasie pochodnej funkcja
CRybka::Oddychaj() robi wiecej niz w przypadku "zwykłego"
overloadingu funkcji. Funkcja CZwierzak::Oddychaj() zostaje
"przesłonieta" (ang. overwrite), mimo, ze ilosc i typ
argumentów. pozostaje bez zmian. Taki proces - bardziej
drastyczny, niz overloading nazywany jest przesłanianiem lub
nadpisywaniem funkcji (ang. function overriding). W programie
przykładowym Ciapek bedzie oddychał a Sardynka nie.

[P127.CPP]

# include <iostream.h>

class CZwierzak
{
public:
void Jedz();
virtual void Oddychaj() {cout << "\nSapie...";}
};

class CPiesek : public CZwierzak
{
char imie[30];
} Ciapek;

class CRybka
char imie[30];
public:
void Oddychaj() {cout << "\nSardynka: A ja nie oddycham.";}
} Sardynka;

void main()
{
Ciapek.Oddychaj();
Sardynka.Oddychaj();
}

Funkcja CZwierzak::Oddychaj() została w obiekcie Sardynka
przesłonieta przez funkcje CRybka::Oddychaj() - nowsza wersje
funkcji-metody pochodzaca z klasy pochodnej.

Overloading funkcji zasadzał sie na "typologicznym pedantyzmie"
C++ i na dodatkowych informacjach, które C++ dołacza przy
kompilacji do funkcji, a które dotycza licznby i typów
argumentów danej wersji funkcji. W przypadku funkcji wirtualnych

jest inaczej. Aby wykonac przesłanianie kolejnych wersji funkcji

wirtualnej w taki sposób, funkcja we wszystkich "pokoleniach"
musi miec taki sam prototyp, tj. pobierac taka sama liczbe
parametrów tych samych typów oraz zwracac wartosc tego samego
typu. Jesli tak sie nie stanie, C++ potraktuje rózne prototypy
tej samej funkcji w kolejnych pokoleniach zgodnie z zasadami
overloadingu funkcji. Zwrócmy tu uwage, ze w przypadku funkcji
wirtualnych o wyborze wersji funkcji decyduje to, wobec którego
obiektu (której klasy) funkcja została wywołana. Jesli wywołamy
funkcje dla obiektu Ciapek, C++ wybierze wersje
CZwierzak::Oddychaj(), natomiast wobec obiektu Sardynka zostanie

zastosowana wersja CRybka::Oddychaj().

W C++ wskaznik do klasy bazowej moze takze wskazywac na klasy
pochodne, wiec zastosowanie funkcji wirtualnych moze dac pewne
ciekawe efekty "uboczne". Jesli zadeklarujemy wskaznik *p do
obiektów klasy bazowej CZwierzak *p; a nastepnie zastosujemy ten

sam wskaznik do wskazania na obiekt klasy pochodnej:

p = &Ciapek; p->Oddychaj();
...
p = &Sardynka; p->Oddychaj();

zarzadamy w taki sposób od C++ rozpoznania własciwej wersji
wirtualnej metody Oddychaj() i jej wywołania we własciwym
momencie. C++ moze rozpoznac, która wersje funkcji nalezałoby
zastosowac tylko na podstawie typu obiektu, wobec którego
funkcja została wywołana. I tu pojawia sie pewien problem.
Kompilator wykonujac kompilcje programu nie wie, co bedzie
wskazywał pointer. Ustawienie pointera na konkretny adres
nastapi dopiero w czasie wykonania programu (run-time).
Kompilator "wie" zatem tylko tyle:

p->Oddychaj()(); //która wersja Oddychaj() ???

Aby miec pewnosc, co w tym momencie bedzie wskazywał pointer,
kompilator musiałby wiedziec w jaki sposób bedzie przebiegac
wykonanie programu. Takie wyrazenie moze zostac wykonane "w
ruchu programu" dwojako: raz, gdy pointer bedzie wskazywał
Ciapka (inaczej), a drugi raz - Sardynke (inaczej):

CZwierzak *p;
...
for(p = &Ciapek, int i = 0; i < 2; i++)
{
p->Oddychaj();
p = &Sardynka;
}

lub inaczej:

if(p == &Ciapek) CZwierzak::Oddychaj();
else CRybka::Oddychaj();

Taki efekt nazywa sie polimorfizmem uruchomieniowym (ang.
run-time polymorphism).

Overloading funkcji i operatorów daje efekt tzw. polimorfizmu
kompilacji (ang. compile-time), to funkcje wirtualne daja efekt
polimorfizmu uruchomieniowego (run-time). Poniewaz wszystkie
wersje funkcji wirtualnej maja taki sam prototyp, nie ma innej
metody stwierdzenia, która wersje funkcji nalezy zastosowac.
Wybór własciwej wersji funkcji moze byc dokonany tylko na
podstawie typu obiektu, do którego nalezy wersja funkcji-metody.

Róznica pomiedzy polimorfizmem przejawiajacym sie na etapie
kompilacji i poliformizmem przejawiajacym sie na etapie
uruchomienia programu jest nazywana równiez wszesnym albo póznym

polimorfizmem (ang. early/late binding). W przypadku wystapienia

wczesnego polimorfizmu (compile-time, early binding) C++ wybiera

wersje funkcji (poddanej overloadingowi) do zastosowania juz
tworzac plik .OBJ. W przypadku póznego polimorfizmu (run-time,
late binding) C++ wybiera wersje funkcji (poddanej przesłanianiu

- overriding) do zastosowania po sprawdzeniu biezacego kontekstu

i zgodnie z biezacym wskazaniem pointera.

Przyjrzyjmy sie dokładniej zastosowaniu wskazników do obiektów w

przykładowym programie. Utworzymy hierarchie złozona z klasy
bazowej i pochodnej w taki sposób, by klasa pochodna zawierała
jakis unikalny element - np. nie wystepujaca w klasie bazowej
funkcje.

class CZwierzak
{
public:
void Jedz();
virtual void Oddychaj() {cout << "\nSapie...";}
};

class CPiesek : public CZwierzak
{
char imie[20];
void Szczekaj() { cout << "Szczekam !!!"; }
} Ciapek;

Jesli teraz zadeklarujemy wskaznik do obiektów klasy bazowej:

CZwierzak *p;

to przy pomocy tego wskaznika mozemy odwołac sie takze do
obiektów klasy pochodnej oraz do elementów obiektu klasy
pochodnej - np. do funkcji p->Oddychaj(). Ale pojawia sie tu
pewien problem. Jesli zechcelibysmy wskazac przy pomocy pointera

taki element klasy pochodnej, który nie został odziedziczony i
którego nie ma w klasie bazowej? Rozwiazanie jest proste -
wystarczy zarzadac od C++, by chwilowo zmienił typ wskaznika z
obiektów klasy bazowej na obiekty klasy pochodnej. W przypadku
funkcji Szczekaj() w naszym programie wygladałoby to tak:
109

CZwierzak *p;
...
p->Oddychaj();
p->Szczekaj(); //ZLE !
(CPiesek*)p->Szczekaj(); //Poprawnie
...

Dzieki funkcjom wirtualnym tworzac klasy bazowe pozwalamy
pózniejszym uzytkownikom na rozbudowe funkcji-metod w
najwłasciwszy ich zdaniem sposób. Dzieki tej "nieokreslonosci"
dziedziczac mozemy przejmowac z klasy bazowej tylko to, co nam
odpowiada. Funkcje w C++ moga byc jeszcze bardziej
"nieokreslone" i rozbudowywalne. Nazywaja sie wtedy funkcjami w
pełni wirtualnymi.
LEKCJA 36: FUNKCJE WIRTUALNE i KLASY ABSTRAKCYJNE.
________________________________________________________________
W trakcie tej lekcji dowiesz sie, co mawia zona programisty, gdy

nie chce byc obiektem klasy abstrakcyjnej.
________________________________________________________________

FUNKCJE W PEŁNI WIRTUALNE (PURE VIRTUAL).

W skrajnych przypadkach wolno nam umiescic funkcje wirtualna w
klasie bazowej nie definiujac jej wcale. W klasie bazowej
umieszczamy wtedy tylko deklaracje-prototyp funkcji. W
nastepnych pokoleniach klas pochodnych mamy wtedy pełna swobode
i mozemy zdefiniowac funkcje wirtualna w dowolny sposób -
adekwatny dla potrzeb danej klasy pochodnej. Mozemy np. do klasy

bazowej (ang. generic class) dodac prototyp funkcji wirtualnej
funkcja_eksperymentalna() nie definiujac jej w (ani wobec)
klasie bazowej. Sens umieszczenia takiej funkcji w klasie
bazowej polege na uzyskaniu pewnosci, iz wszystkie klasy
pochodne odziedzicza funkcje funkcja_eksperymentalna(), ale
kazda z klas pochodnych wyposazy te funkcje we własna definicje.

Takie postepowanie moze okazac sie szczególnie uzasadnione przy
tworzeniu biblioteki klas (class library) przeznaczonej dla
innych uzytkowników. C++ w wersji instalacyjnej posiada juz
kilka gotowych bibliotek klas. Funkcje wirtuale, które nie
zostaja zdefiniowane - nie posiadaja zatem ciała funkcji -
nazywane sa funkcjami w pełni wirtualnymi (ang. pure virtual
function).

O KLASACH ABSTRAKCYJNYCH.

Jesli zadeklarujemy funkcje CZwierzak::Oddychaj() jako funkcje w

pełni wirtualna, oprócz słowa kluczowego virtual, trzeba te
informacje w jakis sposób przekazac kompilatorowi C++. Aby C++
wiedział, ze nasza intencja jest funkcja w pełni wirtalna, nie
mozemy zadeklarowac jej tak:

class CZwierzak
{
...
public:
virtual void Oddychaj();
...
};

a nastepnie pominac definicje (ciało) funkcji. Takie
postepowanie C++ uznałby za bład, a funkcje - za zwykła funkcje
wirtualna, tyle, ze "niedorobiona" przez programiste. Nasza
intencje musimy zaznaczyc juz w definicji klasy w taki sposób:

class CZwierzak
{
...
public:
virtual void Oddychaj() = 0;
...
};

Informacja dla kompilatora, ze chodzi nam o funkcje w pełni
wirtualna, jest dodanie po prototypie funkcji "= 0". Definiujac
klase pochodna mozemy rozbudowac funkcje wirtualna np.:

class CZwierzak
{
...
public:
virtual void Oddychaj() = 0;
...
};

class CPiesek : public CZwierzak
{
...
public:
void Oddychaj() { cout << "Oddycham..."; }
...
};

Przykładem takiej funkcji jest funkcja Mów() z przedstawionego
ponizej programu. Zostawiamy ja w pełni wirtualna, poniewaz
rózne obiekty klasy CZLOWIEK i klas pochodnych

class CZLOWIEK
{
public:
void Jedz(void);
virtual void Mow(void) = 0; //funkcja WIRTUALNA
};

class NIEMOWLE : public CZLOWIEK
{
public:
void Mow(void); // Tym razem BEZ slowa virtual
};
/* Tu definiujemy metode wirtualna: -------------------- */
void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; };


moga mówic na rózne sposoby... Obiekt Niemowle, dla przykładu,
nie chce mówic wcale, ale z innymi obiektami moze byc inaczej.
Wyobraz sobie np. obiekt klasy Zona (zona to przeciez tez
człowiek !).

class Zona : public CZLOWIEK
{
public:
void Mow(void);
}

W tym pokoleniu definicja wirtualnej metody Mow() mogłaby
wygladac np. tak:

void Zona::Mow(void)
{
cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!! ";
cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY
?!!!";

//... itd., itd., itd...
}

[P128.CPP]

#include "iostream.h"

class CZLOWIEK
{
public:
void Jedz(void);
virtual void Mow(void) = 0;
};

void CZLOWIEK::Jedz(void) { cout << "MNIAM, MNIAM..."; };

class Zona : public CZLOWIEK
{
public:
void Mow(void); //Zona mowi swoje
}; //bez wzgledu na argumenty (typ void)

void Zona::Mow(void)
{
cout << "JA NIE MAM CO NA SIEBIE WLOZYC !!!";
cout << "DLACZEGO KOWALSKI ZARABIA ZAWSZE WIECEJ NIZ TY
?!!!";

}

110
class NIEMOWLE : public CZLOWIEK
{
public:
void Mow(void);
};

void NIEMOWLE::Mow(void) { cout << "Nie Umiem Mowic! \n"; };

main()
{
NIEMOWLE Dziecko;
Zona Moja_Zona;

Dziecko.Jedz();
Dziecko.Mow();
Moja_Zona.Mow()

return 0;
}

Przykładowa klasa CZŁOWIEK jest klasa ABSTRAKCYJNA. Jesli
spróbujesz dodac do powyzszego programu np.:

CZLOWIEK Facet;
Facet.Jedz();

uzyskasz komunikat o błedzie:

Cannot create a variable for abstract class "CZLOWIEK"
(Nie moge utworzyc zmiennych dla klasy abstrakcyjnej "CZLOWIEK"

[???] KLASY ABSTRAKCYJNE.
________________________________________________________________
* Po klasach abstrakcyjnych MOZNA dziedziczyc!
* Obiektów klas abstrakcyjnych NIE MOZNA stosowac bezposrednio!
________________________________________________________________

Poniewaz wyjasnilismy, dlaczego klasy sa nowymi typami danych,
wiec logika (i sens) innej rozpowszechnionej nazwy klas
abstrakcyjnych - ADT - Abstract Data Type (Abstrakcyjne Typy
Danych) jest chyba zrozumiała i oczywista.

ZAGNIEZDZANIE KLAS I OBIEKTÓW.

Moze sie np. zdarzyc, ze klasa stanie sie wewnetrznym elementem
(ang. member) innej klasy i odpowiednio - obiekt - elementem
innego obiektu. Nazywa sie to fachowo "zagniezdzaniem" (ang.
nesting). Jesli, dla przykładu klasa CB bedzie zawierac obiekt
klasy CA:

class CA
{
int liczba;
public:
CA() { liczba = 0; } //Konstruktor domyslny
CA(int x) { liczba = x; }
void operator=(int n) { liczba = n }
};

class CB
{
CA obiekt;
public:
CB() { obiekt = 1; }
};

Nasze klasy wyposazylismy w konstruktory i od razu poddalismy
overloadingowi operator przypisania = . Aby przesledzic
kolejnosc wywoływania funkcji i sposób przekazywania parametrów
pomiedzy tak powiazanymi obiektami rozbudujemy kazda funkcje o
zgłoszenie na ekranie.

class CA
{
int liczba;
public:
CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; }
CA(int x) { liczba = x; cout << "->CA(int) "; }
void operator=(int n) { liczba = n; cout << "->operator "; }
};

class CB
{
CA obiekt;
public:
CB() { obiekt = 1; cout << "->Konstruktor CB() "; }
};

Mozemy teraz sprawdzic, co stanie sie w programie po
zadeklarowaniu obiektu klasy CB:

[P129.CPP]

# include "iostream.h"

class CA
{
int liczba;
public:
CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; }
CA(int x) { liczba = x; cout << "->CA(int) "; }
void operator=(int n) { liczba = n; cout << "->operator "; }
};

class CB
{
CA obiekt;
public:
CB() { obiekt = 1; cout << "->Konstruktor CB() "; }
};

main()
{
CB Obiekt;
return 0;
}

Po uruchomieniu programu mozesz przekonac sie, ze kolejnosc
działan bedzie nastepujaca:

C:\>program
-> CA(), CA_O::liczba = 0 ->operator ->Konstruktor CB()

Skoro oprócz zainicjowania obiektu klasy pochodnej nie robimy w
programie dokładnie nic, nie dziwmy sie ostrzezeniu

Warning: Obiekt is never used...

Jest to sytuacja troche podobna do komunikacji pomiedzy
konstruktorami klas bazowych i pochodnych. Jesli zaprojektujemy
prosta strukture klas:

class CBazowa
{
private:
int liczba;
public:
CBazowa() { liczba = 0}
CBazowa(int n) { liczba = n; }
};

class CPochodna : public CBazowa
{
public:
CPochodna() { liczba = 0; }
CPochodna(int x) { liczba = x; }
};

problem przekazywania parametrów miedzy konstruktorami klas
mozemy w C++ rozstrzygnac i tak:

class CPochodna : public CBazowa
{
public:
CPochodna() : CBazowa(0) { liczba = 0; }
CPochodna(int x) { liczba = x; }
};

Bedzie to w praktyce oznaczac wywołanie konstruktora klasy
bazowej z przekazanym mu argumentem 0. Podobnie mozemy postapic
w stosunku do klas zagniezdzonych:

[P130.CPP]

#include "iostream.h"

111
class CA
{
int liczba;
public:
CA() { liczba = 0; cout << "-> CA(), CA_O::liczba = 0 "; }
CA(int x) { liczba = x; cout << "->CA(int) "; }
void operator=(int n) { liczba = n; cout << "->operator "; }
};

class CB
{
CA obiekt;
public:
CB() : CA(1) {}
};

main()
{
CB Obiekt;
return 0;
}

Eksperymentujac z dwoma powyzszymi programami mozesz przekonac
sie, jak przebiega przekazywanie parametrów pomiedzy
konstruktorami i obiektami klas bazowych i pochodnych.

JESZCZE RAZ O WSKAZNIKU *this.

Szczególnie waznym wskaznikiem przy tworzeniu klas pochodnych i
funkcji operatorowych moze okazac sie pointer *this. Oto
przykład listy.

[P131.CPP]

# include "string.h"
# include "iostream.h"

class CLista
{
private:
char *poz_listy;
CLista *poprzednia;
public:
CLista(char*);
CLista* Poprzednia() { return (poprzednia); };
void Pokazuj() { cout << '\n' << poz_listy; }
void Dodaj(CLista&);
~CLista() { delete poz_listy; }
};

CLista::CLista(char *s)
{
poz_listy = new char[strlen(s)+1];
strcpy(poz_listy, s);
poprzednia = NULL;
}

void CLista::Dodaj(CLista& obiekt)
{
obiekt.poprzednia = this;
}

main()
{
CLista *ostatni = NULL;
cout << '\n' << "Wpisanie kropki [.]+[Enter] = Quit \n";
for(;;)
{
cout << "\n Wpisz nazwe (bez spacji): ";
char TAB[70];
cin >> TAB;
if (strncmp(TAB, ".", 1) == 0) break;
CLista *lista = new CLista(TAB);
if (ostatni != NULL)
ostatni->Dodaj(*lista);
ostatni = lista;
}

for(; ostatni != NULL;)
{
ostatni->Pokazuj();
CLista *temp = ostatni;
ostatni = ostatni->Poprzednia();
delete (temp);
}
return 0;
}

Z reguły to kompilator nadaje wartosc wskaznikowi this i to on
automatycznie dba o przyporzadkowanie pamieci obiektom. Pointer
this jest zwykle inicjowany w trakcie działania konstruktora
obiektu.






LEKCJA 37: KAZDY DYSK JEST ZA MAŁY, A KAZDY PROCESOR
ZBYT WOLNY...
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak komputer dysponuje swoimi
zasobami w srodowisku tekstowym (DOS).
________________________________________________________________

Truizmy uzyte w tytule maja znaczyc, ze "zasoby najlepszego
nawet komputera sa ograniczone" i zwykle okazuja sie
wystarczajace tylko do pewnego momentu. Najbardziej newralgiczne

zasoby to:

* czas mikroprocesora i
* miejsce w pamieci operacyjnej.

Tworzone przez nas programy powinny wystrzegac sie zatem
najciezszych grzechów:

* nie pozwalac mikroprocesorowi na słodkie nieróbstwo;

Rzadko uzmysławiamy sobie, ze oczekiwanie na nacisniecie
klawisza przez uzytkownika (czasem po przeczytaniu napisu na
ekranie) trwa sekundy (1, 2, .... czasem 20), a kazda sekunda
lenistwa PC to stracone miliony cykli mikroprocesora.

* oszczednie korzystac z pamieci dyskowej, a szczególnie
oszczednie z pamieci operacyjnej RAM.

MODELE PAMIECI IBM PC.

Jak zapewne wiesz, Twój PC moze miec:

* pamiec ROM (tylko do odczytu),
* konwencjonalna pamiec RAM (640 KB),
* pamiec rozszerzona EMS i XMS,
* pamiec karty sterownika graficznego ekranu (np. SVGA-RAM),
* pamiec Cache dla buforowania operacji dyskowych.

Najczesciej stosowane modele pamieci to:

* Small - mały,
* Medium - sredni,
* Compact - niewielki (tu mam watpliwosc, moze "taki sobie" ?),
* Large - duzy,
* Huge - jeszcze wiekszy, odległy.

Dodatkowo moze wystapic

* Tiny - najmniejszy.

Taki podział został spowodowany segmentacja pamieci komputera
przez procesory Intel 8086 i podziałem pamieci na bloki o
wielkosci 64 KB. Model Small (Tiny, jesli jest) jest najszybszy,

ale najmniej pojemny. Model Huge - odwrotnie - najpojemniejszy,
za to najwolniejszy. Model Tiny powoduje ustawienia wszystkich
rejestrów segmentowych mikroprocesora na te sama wartosc
(poczatek tej samej stronicy pamieci) i umieszczenie wszystkich
zasobów programu wewnatrz wspólnego obszaru pamieci o wielkosci
nie przekraczajacej 64 KB. Wszystkie skoki sa wtedy "krótkie", a

wszystkie pointery (adresy) 16-bitowe. Kompilacja z
zastosowaniem modelu Tiny pozwala uzyskac program wykonywalny w
wersji *.COM (a nie *.EXE). Ale niestety nie wszystkie programy
mieszcza sie w 64 KB. W modelu Small segment kodu jest jeden
(kod max. 64 K) i segment danych tez tylko jeden (dane max. 64
K), ale sa to juz dwa rózne segmenty. Zestawienia
112
najwazniejszych parametrów poszczególnych modeli pamieci
przedstawia tabelka ponizej:

Modele pamieci komputera IBM PC.
________________________________________________________________

Model Segment kodu Segment danych *dp *cp
________________________________________________________________

Tiny 1 1 (CS = DS) 16 bit 16 bit
Small 1 1 16 bit 16 bit
Medium wiele 1 16 bit 32 bit
Compact 1 wiele 32 bit 16 bit
Large wiele wiele 32 bit 32 bit
Huge wiele wiele 32 bit 32 bit
________________________________________________________________

*dp - data pointer - wskaznik do danych (near/far)
*cp - code pointer - wskaznik do kodu.
Large - kod + dane = max. 1 MB.
Huge - kod = max. 1 MB, wiele segmentów danych po 64 K kazdy.

Wynikajace z takich modeli pamieci kwalifikatory near, far, huge

dotyczace pointerów w C++ nie sa akceptowane przez standard ANSI

C (poniewaz odnosza sie tylko do IBM PC i nie maja charakteru
uniwersalnego). Trzeba tu zaznaczyc, ze typ wskaznika jest przez

kompilator przyjmowany domyslnie (automatycznie) zgodnie z
wybranym do kompilacji modelem pamieci. Jesli poruszamy sie
wewnatrz niewielkiego obszaru pamieci, mozesz "forsowac" blizszy

typ pointera, przyspieszajac tym samym działanie programów:

huge *p;
...
near *ptr; //Bliski pointer
...
near int Funkcja(...) //Bliska funkcja
{
...
}
#define ILE (1024*640)

near unsigned int Funkcja(void)
{
huge char *ptr; // tu długi pointer jest niezbedny
long suma = 0;
for (p = 0; p < ILE; p++) suma += *p;
return (suma);
}

Zarówno zadeklarowanie funkcji jako bliskiej (near), jak i jako
statycznej (static) powoduje wygenerowanie uproszczonej
sekwencji wywołania funkcji przez kompilator. Daje to w efekcie
mniejszy i szybszy kod wynikowy.

IDENTYFIKACJA KLAWISZY.

Znane Ci z pliku <stdio.h> i <conio.h> "klasyczne" funkcje
obsługi konsoli maja pewne zalety. Korzystanie z klasycznych,
nieobiektowych mechanizmów powoduje z reguły wygenerowanie
znacznie krótszego kodu wynikowego. Funkcje scanf() i gets()
wymagaja wcisniecia klawisza [Enter]. Dla szybkiego dialogu z
komputerem znacznie bardziej nadaja sie szybsze getch() i
kbhit(). Poniewaz klawiatura zawiera takze klawisze specjalne
(F1 ... F10, [Shift], [Del], itp.), pełna informacje o stanie
klawiatury mozna uzyskac za posrednictwem funkcji bioskey(),
korzystajacej z przerywania BIOS Nr 16. Oto krótki przykład
zastosowania funkcji bioskey():

#include "bios.h"
#include "ctype.h"
#include "stdio.h"
#include "conio.h"

# define CTRL 0x04
# define ALT 0x08
# define RIGHT 0x01
# define LEFT 0x02

int klawisz, modyfikatory;
void main()
{
clrscr();
printf("Funkcja zwraca : %d", bioskey(1));
printf("\n Nacisnij klawisz ! \n");
while (!bioskey(1));
printf("Funkcja zwrocila: %c", bioskey(1));
printf("\nKod: %d", (char)bioskey(1));
...

A to jeszcze inny sposób korzystania z tej bardzo przydatnej
funkcji, tym razem z innymi parametrami:

/* Funkcja z parametrem (0) zwraca kod klawisza: ------ */

klawisz = bioskey(0);

/* Funkcja sprawdza stan klawiszy specjalnych --------- */

modyfikatory = bioskey(2);
if (modyfikatory)
{
printf("\n");
if (modyfikatory & RIGHT) printf("RIGHT");
if (modyfikatory & LEFT) printf("LEFT");
if (modyfikatory & CTRL) printf("CTRL");
if (modyfikatory & ALT) printf("ALT");
printf("\n");
}
/* drukujemy pobrany klawisz */
if (isalnum(klawisz & 0xFF))
printf("'%c'\n", klawisz);
else
printf("%#02x\n", klawisz);
}

Nalezy tu zwrócic uwage, ze funkcje kbhit() i bioskey() nie
dokonuja czyszczenia bufora klawiatury. Identyfikuja znak
(znaki) w buforze, ale pozostawiaja bufor w stanie niezmienionym

do dalszej obróbki. Zwróc uwage, ze funkcja getch() moze
oczekiwac na klawisz w nieskonczonosc. Sprawdzic szybciej, czy
uzytkownik nacisnał juz cokolwiek mozesz np. tak:

if (kbhit()) ...; if (!kbhit()) ...;

while (!bioskey(1)) ... if (bioskey(1)) ...;

Inna wielce przydatna "szybka" funkcja jest getch(). Oto
praktyczny przykład pobierania i testowania nacisnietych
klawiszy klawiatury.

[P131.CPP]

# include "stdio.h"
# include "conio.h"

char z1, z2;

void Odczyt(void)
{
z2 = '\0';
z1 = getch();
if (z1 == '\0') z2 = getch();
}

main()
{
clrscr();
printf("\nKropka [.] = Quit");
printf("\nRozpoznaje klawisze [F1] ... [F3] \n\n");

for (;;)
{
while(!kbhit());
Odczyt();
if (z1 == '.') break;
if (z1 != '\0') printf("\nZnak: %c", z1);
else
switch (z2)
{
case ';' : printf("\n F1"); break;
case '<' : printf("\n F2"); break;
113
case '=' : printf("\n F3"); break;
default : printf("\n Inny klawisz specjalny!");
}
}
return 0;
}

Klawisze specjalne powoduja wygenerowanie dwubajtowego kodu
(widzianego w powyzszym przykładowym programie jako dwa
jednobajtowe znaki z1 i z2). Funkcja getch() pobiera te bajty z
bufora klawiatury kolejno jednoczesnie czyszczac bufor. W
przypadku klawiszy specjalnych pierwszy bajt jest zerowy (NULL,
'\0', 00h), co jest sprawdzane w programie. A oto tabela kodów
poszczególnych klawiszy:

Kody klawiszy klawiatury IBM PC.
________________________________________________________________

Klawisze Kody ASCII (dec)
________________________________________________________________

Home G 71 (00:47h) '\0', 'G'
End O 79 (00:4Fh) '\0', 'O'
PgUp I 73
PgDn Q 81
Ins R 82
Del S 83
F1 ; 59
F2 ... F10 <, ... D 60, ... 68
Shift + F1 T 84
...
Shift + F10 ] 93
Ctrl + F1 ^ 94
...
Ctrl + F10 f 103
Alt + F1...F10 h, ... q 104, ... 113
Alt + 1...9 x, ... A (?) 120, ... 128
Alt + 0 C (?) 129

Strzałki kursora:
LeftArrow K 75
RightArrow M 77
UpArrow H 72
DownArrow P 80

Ctrl + PgDn v 118
Ctrl + PgUp N (?) 132
Ctrl + Home w 119
Ctrl + End u 117
________________________________________________________________


Wyprowadzanie znaków na ekran mozna przeprowadzic szybciej
posługujac sie przerywaniem DOS INT 29H. Drukowanie na ekranie w

trybie tekstowym przebiega wtedy szybciej niz robia to
standardowe funkcje <stdio.h>, <conio.h>, czy <iostream.h>.
Ponizej prosty przykład praktyczny wykorzystania przerywania
29H:

[P132.CPP]

# include <stdlib.h>
# include <conio.h>
# pragma inline

void SpeedBox(int, int, int, int, char);

main()
{
clrscr();
for (; !kbhit(); )
{
int x = rand() % 40;
int y = rand() % 12;
SpeedBox(x, y, (80 - x), (24 - y), ('t' + x % 50));
}
return 0;
}

void SpeedBox(int x1, int y1, int x2, int y2, char znak)
{
int k;

for (; y1 < y2; y1++) { gotoxy(x1, y1);
for (k = x1; k < x2; k++)
{
asm MOV AL, znak
asm INT 29H
}
}
}

[Z]
________________________________________________________________
1. Opracuj program pozwalajacy porównac szybkosc wyprowadzania
danych na ekran monitora róznymi technikami (cout, puts(),
printf(), asm).
2. Porównaj wielkosc plików wynikowych .EXE powstajacych w
róznych wariantach z poprzedniego zadania.
________________________________________________________________


LEKCJA 38: O C++, Windows i małym Chinczyku.
czyli:
KTO POWIEDZIAŁ, ZE PROGRAMOWANIE DLA WINDOWS JEST
TRUDNE?!!!

Jak swiat swiatem ludzie przekazuja sobie sady, opinie,
poglady... W ciagu naszej nowozytnej ery wymyslono juz wiele
opinii, które krazyły przez dziesiecio- i stulecia gwarantujac
jednym komfort psychiczny (- Ja przeciez mam swoje zdanie na ten

temat!), innym dajac pozory wiedzy (- Tak, ja cos o tym wiem,
słyszałem, ze...). Zywotnosc takich cwiercprawd, uproszczen,
uogólnien, czy wrecz kompletnie bzdurnych mitów była i jest
zadziwiajaca.

Podejme tu próbe obalenia funkcjonujacego powszechnie przesadu,
ze

- Programowanie dla Windows jest trudne. (BZDURA!!!)

Aby nie zostac całkowicie posadzonym o herezje, przyznaje na
wstepie dwa bezsporne fakty.
Po pierwsze, wielu powszechnie szanowanych ludzi zrobiło wiele,
by juz pierwszymi przykładami (zwykle na co najmniej dwie
strony) skutecznie odstraszyc adeptów programowania dla Windows.

No bo jak tu nie stracic zapału, gdy program piszacy tradycyjne
"Hello World." w okienku ma 2 - 3 stronice i jeszcze zawiera
kilkadziesiat zupełnie nieznanych i niezrozumiałych słów
(skrótów? szyfrów?).
Po drugie, wszystko jest trudne, gdy brak odpowiednich narzedzi.

Nawet odkrecenie małej srubki bywa niezwykle trudne, gdy do
dyspozycji mamy tylko młotek... Napisanie aplikacji okienkowej
przy pomocy Turbo Pascal 6, Turbo C, Quick C, czy QBASIC
rzeczywiscie BYŁO nadwyraz trudne.

I tu własnie dochodzimy do sedna sprawy:

(!!!) Programowanie dla Windows BYŁO trudne (!!!)


UWAGA!
Pierwsza typowa aplikacja dla Windows napisana w BORLAND C++ 3/4

moze wygladac np. tak:

#include <iostream.h>

void main()
{
cout <<"Pierwsza Aplikacja dla Windows";
}

I juz!
Niedowiarek zapyta: - I TAKIE COS CHODZI POD Windows???
TAK!.

W BORLAND C++ 3+ ... 4+ wystarczy dobrac parametry pracy
kompilatora i zamiast aplikacji DOS-owskiej otrzymamy program
wyposazony we własne okienko, paski przewijania w okienku,
klawisze, menu, ikonke, itp., itd.!

114
O MAŁYM CHINCZYKU, czyli - NAJLEPIEJ ZACZAC OD POCZATKU...

Istnieje jedyny sprawdzony sposób rozwiazywania zagadnien
takiego typu - tzw. METODA MAŁEGO CHINCZYKA.
WSZYSCY DOSKONALE WIEDZA, ze jezyk chinski jest szalenie trudny.

Dlatego tez mimo ogromnego wysiłku prawie NIKOMU nie udaje sie
biegle nauczyc chinskiego - z jednym wyjatkiem - wyjatkiem
małego Chinczyka. Dlaczego? To proste. Mały Chinczyk po prostu o

tym nie wie! I dlatego juz po kilku latach doskonale swobodnie
włada tym bodaj najtrudniejszym jezykiem swiata!

Jesli zatem komus udało sie przekonac Cie, szanowny Czytelniku,
ze programowanie dla Windows jest trudne, namawiam Cie na
dokonanie pewnego eksperymentu intelektualnego. Spróbuj
zapomniec, ze masz juz na ten temat jakies zdanie i wczuj sie w
role małego Chinczyka. Co roku udaje sie to wielu milionom
przyszłych ekspertów od wszystkich mozliwych jezyków swiata (C++

jest chyba znacznie łatwiejszy do chinskiego).

BORLAND C++ aby dopomóc programiscie w jego ciezkiej pracy
tworzy (czesto automatycznie) wiele plików pomocniczych. Krótkie

zestawienie plików pomocniczych zawiera tabela ponizej.

Najwazniejsze pliki pomocnicze w kompilatorach Borland/Turbo
C++.
________________________________________________________________

Rozszerzenie Przeznaczenie Gdzie/Uwagi
________________________________________________________________

.C .CPP Teksty zródłowe \EXAMPLES \SOURCE
(ASCII) (przykłady) (kod zródł.)
.H .HPP .CAS Pliki nagłówkowe \INCLUDE
(ASCII)
.PRJ .DPR .IDE Projekty \EXAMPLES \SOURCE

.TAH .TCH .TDH Help
.TFH .HLP .HPJ
.RTF

.DSK .TC .CFG Konfiguracyjne
.DSW .BCW

.DEF .RC .RES Zasoby i definicje
.RH .ICO .BMP

.BGI .CHR .RTF Grafika DOS, fonty

.MAK .NMK .GEN Pliki instruktazowe dla
MAKEFILE MAKE.EXE

.ASM .INC .ASI Do asemblacji (ASCII)

.RSP Instruktazowy dla TLINK

.LIB .DLL Biblioteki

.TOK Lista słów zastrzezonych (reserved words)
(ASCII)
.DRV Sterowniki (drivery)

.OVL Nakładki (overlay)

.SYM Plik ze skompilowanymi (Pre - compiled)
plikami nagłówkowymi.
________________________________________________________________


Swiadome i umiejetne wykorzystanie tych plików moze znacznie
ułatwic i przyspieszyc prace.

Po wprowadzeniu na rynek polskiej wersji Windows 3.1 okienka
zaczeły coraz czesciej pojawiac sie w biurach i domach, i
stanowia coraz czesciej naturalne (własnie tak, jak chinski dla
Chinczyków) srodowisko pracy dla polskich uzytkowników PC. Nie
pozostaje nam nic innego, jak po prostu zauwazyc i uznac ten
fakt.

Po uruchomieniu Borland C++ (2 * klik myszka, lub rozkaz Uruchom

z menu Plik) zobaczymy tradycyjny pulpit (desktop)
zintegrowanego srodowiska IDE - podobny do Turbo Pascala, z
tradycyjnym układem głównego menu i okien roboczych.

Skoro mamy zajac sie tworzeniem aplikacji dla Windows- zaczynamy

od rozwiniecia menu Options i wybieramy z menu rozkaz
Application... . Rozwinie sie okienko dialogowe. Przy pomocy
klawiszy mozemy wybrac sposób generowania aplikacji - dla DOS,
dla Windows lub tworzenie bibliotek statycznych .LIB, czy tez
dynamicznych .DLL. Wybieramy oczywiscie wariant [Windows EXE].

[!!!]UWAGA!
________________________________________________________________

Struktura podkatalogów i wewnetrzna organizacja pakietów 3.0,
3.1, 4 i 4.5 ZNACZNIE SIE RÓZNI.
________________________________________________________________


Skoro ustawilismy juz poprawnie najwazniejsze dla nas parametry
konfiguracyjne - mozemy przystapic do uruchomienia pierwszej
aplikacji dla Windows.

PIERWSZA APLIKACJA "specjalnie dla Windows".

Tryb postepowania z kompilatorem BORLAND C++ 3.0/3.1 bedzie w
tym przypadku dokładnie taki sam, jak np. z Turbo Pascalem.
Wszystkich niezbednych zmian w konfiguracji kompilatora juz
dokonalismy. Kompilator "wie" juz, ze chcemy uzyskac w efekcie
aplikacje dla Windows w postaci programu .EXE. Mozemy zatem

* Wydac rozkaz File | New

Pojawi sie nowe okienko robocze. Zwróc uwage, ze domyslne
rozszerzenie jest .CPP, co powoduje domyslne zastosowanie
kompilatora C++ (a nie kompilatora C - jak w przypadku plików z
rozszerzeniem .C). Mozesz to oczywiscie zmienic, jesli zechcesz,

posługujac sie menu Options | Compiler | C++ options... (Opcje |

Kompilator | Kompilator C albo C++). W tym okienku dialogowym
masz sekcje:

Use C++ Compiler: Zastosuj Kompilator C++
(zamiast kompilatora C)
(.) CPP extention - tylko dla rozszerzenia .CPP
( ) C++ always - zawsze

* Wybierz rozkaz Save as... z menu File

Pojawi sie okienko dialogowe "Save File As" (zapis pliku pod
wybrana nazwa i w wybranym miejscu).

* Do okienka edycyjnego wpisz nazwe pliku i pełna sciezke
dostepu - np. A:\WIN1.CPP lub C:\C-BELFER\WIN1.CPP

Zmieni sie tytuł roboczego okna z NONAME00 na wybrana nazwe

Mozemy wpisac tekst pierwszego programu:

[P133.CPP]

#include <iostream.h>

void main()
{
cout << " Pierwsza Aplikacja " << " Dla MS Windows ";
}

Po wpisaniu tekstu dokonujemy kompilacji.

* Wybierz rozkaz Compile to OBJ z menu Compile.
* Wybierz rozkaz Link lub Make z menu Compile.
W okienku komunikatów (Messages) powinien pojawic sie w trakcie
konsolidacji komunikat ostrzegawczy:

*Linker Warning: No module definition file specified:
using defaults

Oznacza to: Konsolidator ostrzega, ze brak specjalnego
stowarzyszonego z plikiem .CPP tzw. pliku definicji sposobu
115
wykorzystania zasobów Windows - .DEF. Program linkujacy
zastosuje wartosci domyslne.

Jesli w IDE wersji kompilatora przeznaczonej dla srodowiska DOS
spróbujesz uruchomic program WIN1.EXE w tradycyjny sposób -
rozkazem Run z menu Run - na ekranie pojawi sie okienko z
komunikatem o błedzie (Error message box):

Can't run a Windows EXE file
D:\WIN1.EXE

[ OK ]

czyli: "Nie moge uruchomic pliku EXE dla Windows".

Jak juz napisałem wczesniej, kompilatory C++ w pakietach 3.0/3.1

maja swoje ulubione specjalnosci:

Borland C++ - jest zorientowany na współprace z DOS
Turbo C++ - jest zorientowany na współprace z Windows

w wersji 3.1:

BCW - dla Windows
BC - dla DOS

nie oznacza to jednak, ze beda kłopoty z praca naszego programu!

Wyjdz z IDE BC/BCW.

Z poziomu Menedzera Programów mozesz uruchomic swój program
rozkazem Plik | Uruchom. Do okienka musisz oczywiscie wpisac
poprawna sciezke do pliku WIN1.EXE (czyli katalog wyjsciowy
kompilatora Borland C++).

*** Wybierz z menu głównego Menedzera Programów (pasek w górnej
czesci ekranu) rozkaz Plik. Rozwinie sie menu Plik.
*** Wybierz z menu Plik rozkaz Uruchom. Pojawi sie okienko
dialogowe uruchamiania programów. Wpisz pełna sciezke
dostepu do programu - np.:

D:\KATALOG\WIN1.EXE

i "kliknij" myszka na klawiszu [OK] w okienku.

Na ekranie pojawi sie okno naszej aplikacji. Okno jest
wyposazone w:

- Pasek z tytułem (Caption) - np.: A:\WIN1.EXE ;
- Klawisz zamykania okna i rozwiniecia standardowego menu (tzw.
menu systemowego Windows) - [-] ;
- Paski przewijania poziomego i pionowego;
- Klawisze MINIMIZE i MAXIMIZE (zmniejsz do ikonki | powieksz na

cały ekran) w prawym górnym narozniku okna;

Program znajduje sie w wersji .EXE na dyskietce dołaczonej do
ksiazki. Mozesz uruchomic go z poziomu Menedzera Plików (Windows

File Manager), Menedzera Programów (Windows Program Manager) lub

z DOS-owskiego wiersza rozkazów (DOS Command Line):

C\>WIN A:\WIN1.EXE[Enter]

Co moze nasza pierwsza aplikacja?

- Typowa dla Windows technika drag and drop - pociagnij i upusc
mozesz przy pomocy myszki przesuwac okno naszej pierwszej
aplikacji po ekranie ("ciagnac" okno za pasek tytułowy).

- Ciagnac ramki badz narozniki mozesz zmieniac wymiary okna w
sposób dowolny.

- Posługujac sie paskami przewijania mozesz przewijac tekst w
oknie w pionie i w poziomie.

- Mizesz zredukowac okno do ikonki.

- Mozesz uruchomic nasza aplikacje wielokrotnie i miec na
ekranie kilka okien programu WIN1.EXE.

- Nasza aplikacja wyposazona jest w menu systemowe. Mozesz
rozwinac menu i wybrac z menu jeden z kilku rozkazów.

Jesli nie pisałes jeszcze programów dla Windows - mozesz byc
troche zaskoczony. Gdzie w naszym programie jest napisane np. -
co powinno znalesc sie w menu??? Odpowiedz jest prosta -
nigdzie. Podobnie jak programy tworzone dla DOS korzystaja w
niejawny sposób z zasobów systemu - standardowych funkcji DOS,
standardowych funkcji BIOS, przerywan, itp - tak programy
tworzone dla Windows moga w niejawny sposób korzystac z zasobów
srodowiska Windows - standardowego menu, standardowych okien,
standardowych klawiszy, itp.. Takie zasoby udostepniane przez
srodowisko programom aplikacyjnym nazywaja sie interfejsem API
(Application Program Interface). Poznałes juz API DOS'a - jego
przerywania i funkcje. Interfejs Windows nazywa sie "Windows
API" i to z jego gotowych funkcji własnie korzystamy.

Uruchom program wielokrotnie (min. 4 razy). Wykonaj 4 - 6 razy
czynnosci oznaczone powyzej trzema gwiazdkami *** . Poniewaz nie

zazadalismy, by okno programu było zawsze "na wierzchu" (on top)

- po kazdym kolejnym uruchomieniu (nie musisz nic robic -
nastapi to automatycznie - zadba o to Menedzer Windows)
poprzednie okno programu zniknie. Jesli po czwartym (piatym)
uruchomieniu programu zredukujesz okno Menedzera Programów do
ikony (np. [-] i "do ikony" z menu systemowego) - okaze sie, ze
"pod spodem" stale widac kaskade okien naszej aplikacji WIN1.EXE

(patrz rys. ponizej). Na rysunkach ponizej przedstawiono kolejne

stadia pracy z nasza PIERWSZA APLIKACJA.

Aplikacja WIN1.EXE została wyposazona w ikonke systemowa (znane
Ci okienko). Ikonka jest transparentna (półprzezroczysta) i
mozemy ja takze metoda drag and drop przeniesc w dowolne miejsce

- np. do roboczego okna naszej aplikacji. Zwróc uwage takze na
towarzyszacy nazwie programu napis "inactive" (nieaktywna).
Chodzi o to, ze program zrobił juz wszystko, co miał do
zrobienia i zakonczył sie. DOS dołozyłby standardowo funkcje
zwolnienia pamieci i zakonczył program. W Windows niestety
okienko nie zamknie sie samo w sposób standardowy. W Windows,
jak wiesz, mozemy miec otwarte jednoczesnie wiele okien
programów a aktywne jest (tylko jedno) zawsze to okno, do
którego przekazemy tzw. focus. Okno to mozna rozpoznac po
ciemnym pasku tytułowym. Własnie z przyjecia takiego sposobu
podziału zasobów Windows pomiedzy aplikacje wynika skutek
praktyczny - okno nie zamknie sie automatycznie po zakonczeniu
programu - lecz wyłacznie na wyrazne zyczenie uzytkownika. API
Windows zawiera wiele gotowych funkcji (np. CloseWindow() -
zamknij okno, DestroyWindow() - skasuj okno i in.), z których
moze skorzystac programista piszac aplikacje. Nie jestesmy wiec
całkiem bezradni.

Spróbuj skompilowac w opisany wyzej sposób i uruchomic pierwsza
aplikacje w takiej wersji:

#include <stdio.h>

void main()
{
printf(" Pierwsza Aplikacja \n Dla MS Windows ");
}

Jak łatwo sie przekonac, całkowicie klasyczny, w pełni
nieobiektowy program WIN1.C bedzie w Windows działac dokładnie
tak samo. Nasze aplikacje nie musza bynajmniej byc całkowicie
obiektowe, chociaz zastosowanie obiektowej techniki
programowania pozwala zmusic nasz komputer do zdecydowanie
wydajniejszego działania.

PODSUMUJMY:

* Jesli korzystamy wyłacznie ze standardowych zasobów srodowiska

Windows, tworzenie aplikacji dla Windows nie musi byc wcale
trudniejsze od tworzenia aplikacji DOS'owskich.
* Czy aplikacja ma byc przeznaczona dla DOS, czy dla Windows
mozemy zdecydowac "w ostatniej chwili" ustawiajac odpowiednio
robocze parametry kompilatora C++:
Options | Applications... | DOS standard
albo
116
Options | Applications... | Windows EXE
* Aplikacje skompilowane do wersji DOS'owskiej mozemy uruchamiac

wewnatrz kompilatora DOS'owskiego rozkazem Run | Run.
* Aplikacje skompilowane (scislej - skonsolidowane) do wersji
okienkowej mozemy uruchamiac wewnatrz Windows z poziomu
Menedzera Plików badz Menedzera Programów rozkazem Uruchom z
menu Plik.
* Dodatkowe pliki nagłówkowe .H i biblioteki .LIB .DLL znajduja
sie w katalogach
\BORLANDC\INCLUDE
\BORLANDC\OWL\INCLUDE
\BORLANDC\LIB
\BORLANDC\OWL\LIB
Sciezki dostepu do tych katalogów nalezy dodac do roboczych
katalogów kompilatora w okienku Options | Directories...
* Aplikacje nie korzystajace z funkcji Windows API nie musza
dołaczac okienkowych plików nagłówkowych. Jesli jednak
zechcemy zastosowac funkcje i dane (stałe, struktury,
obiekty, itp.) wchodzace w skład:
- Windows API
- Windows Stock Objects - obiekty "ze składu Windows"
- biblioteki klas Object Windows Library
nalezy dołaczyc odpowiedni plik nagłówkowy:
#include <windows.h>
#include <windowsx.h>
#include <owl.h>

TYPOWE BŁEDY I KŁOPOTLIWE SYTUACJE:

* Nalezy pamietac o ustawieniu własciwych katalogów roboczych
kompilatora Options | Directories...
* Przy bardziej skomplikowanych aplikacjach moze wystapic
potrzeba dobrania innego (zwykle wiekszego) modelu pamieci.
Modelem domyslnym jest model Small. Inne parametry pracy
kompilatora ustawia sie podobnie za pomoca menu Options.



LEKCJA 39: KORZYSTAMY ZE STANDARDOWYCH ZASOBÓW
Windows.
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak korzystac z zasobów
Windows bez potrzeby wnikania w wiele szczególów technicznych
interfejsu aplikacji - Windows API.
________________________________________________________________

Poniewaz nasze programy moga korzystac ze standardowych zasobów
Windows, na poczatku bedziemy posługiwac sie okienkami
standardowymi. Poczawszy od aplikacji WIN3.EXE "rozszerzymy
oferte" do dwu podstawowych typów:

* Standardowe główne okno programu (Default main window).
To takie własnie okno, jakie dostały nasze pierwsze aplikacje
WIN1.EXE.
* Okienkiem dialogowym (Dialog box),
a dokładniej najprostszym rodzajem okienek dialogowych - tzw.
okienkami komunikatów - Message Box.

Zastosowanie okienka dialogowego pozwoli nam na wprowadzenie do
akcji klawiszy (buttons).

________________________________________________________________

UWAGA:
Niestety nie wszystkie tradycyjne funkcje typu printf(),
scanf(), gets() itp. zostały zaimplementowane dla Windows!
Piszac własne programy mozesz przekonac sie o tym dzieki opisowi

funkcji w Help. Funkcje nalezy odszukac w Help | Index. Oprócz
przykładu zastosowania znajdziesz tam tabelke typu:

DOS Unix Windows ANSI C C++ Only
cscanf Yes
fscanf Yes Yes Yes Yes
scanf Yes Yes Yes
sscanf Yes Yes Yes Yes

[Yes] oznacza "zaimplementowana". Dlatego własnie w dalszych
programach przykładowych dla wersji 3.0 nalezy np. stosowac np.
makro getchar() zamiast tradycyjnego getch() zaimplementowane
dla Windows juz w wersji BC++ 3.0.
________________________________________________________________


Dla przykładu spróbujmy skompilowac i uruchomic w srodowisku
Windows jeden z wczesniejszych programów - tabliczke mnozenia.
Zwróc uwage na dołaczony dodatkowy plik WINDOWS.H i nowy typ
wskaznika. Zamiast zwykłego

char *p ...
LPSTR p ...

LPSTR - to Long Pointer to STRing - daleki wskaznik do łancucha
tekstowego. Jest to jeden z "ulubionych" typów Windows.

/* WIN2.CPP: */
/* - Tablica dwuwymiarowa
- Wskazniki do elementów tablicy */

#include <windows.h>
#include <iostream.h>
#include <stdio.h>

int T[10][10], *pT, i, j, k;
char spacja = ' ';

LPSTR p1 = " TABLICZKA MNOZENIA (ineksy)\n";
LPSTR p2 = " Inicjujemy i INKREMENTUJEMY wskaznik:\n";
LPSTR p3 = "... nacisnij cokolwiek (koniec)...";

void main()
{
printf(p1);
for (i = 0; i < 10; i++)
{
for (j = 0; j < 10; j++)
{ T[i][j] = (i + 1)*(j + 1);
if (T[i][j] < 10) cout << T[i][j] << spacja << spacja;
else
cout << T[i][j] << spacja;
}
cout << '\n';
}
printf(p2);
pT = &T[0][0];
for (k = 0; k < 10*10; k++)
{
if (*(pT+k) < 10) cout << *(pT + k) << spacja << spacja;
else
cout << *(pT + k) << spacja;
if ((k + 1)%10 == 0) cout << '\n';
}
printf(p3);
getchar();
}

Wybralismy dla aplikacji standardowe główne okno (Main Window),
poniewaz istnieje potrzeba pionowego przewijania okna w celu
przejrzenia pełnego wydruku obu tablic.

[???] Dlaczego ten tekst jest nierówny???
________________________________________________________________
Niestety, znaki w trybie graficznym Windows nie maja stałej
szerokosci (jak było to w trybie tekstowym DOS). Niektóre
aplikacje przeniesione ze srodowiska DOS beda sprawiac kłopoty.
________________________________________________________________

APLIKACJE DWUPOZIOMOWE.

Zastosujemy teraz najprostszy typ okienka dialogowego - okienko
kamunikatów (Message Box), nasze nastepne aplikacje moga byc juz

nie jedno- a dwupoziomowe. Typowe postepowanie okienkowych
aplikacji bywa takie:

* program wyswietla w głównym oknie to, co ma do powiedzenia;
* aby zadawac pytania stosuje okienka dialogowe, badz okienka
komunikatów;
* funkcja okienkowa (u nas MessageBox()) zwraca do programu
decyzje uzytkownika;
* program główny analizuje odpowiedz i podejmuje w głównym oknie

stosowne działania.

117
Przesledzmy ewolucje powstajacej w taki sposób aplikacji.

STADIUM 1. Tekst w głównym oknie.

Zaczniemy tworzenie naszej aplikacji tak:

/* WINR1.CPP: */
/* Stadium 1: Dwa okienka w jednym programie */

# include <stdio.h>
# include <windows.h>

char *p1 = "Teraz dziala \n funkcja \n MessageBox()";
char *p2 = "START";
int wynik;

void main()
{
printf(" Start: Piszemy w glownym oknie \n");
printf(" ...nacisnij cosik...");
getchar();
MessageBox(0, p1, p2, 0);
printf("\n\n\n Hello World dla WINDOWS!");
printf("\n\t...dowolny klawisz... ");
getchar();
}

Moglibysmy zrezygnowac z metod typowych dla aplikacji DOSowskich

i zatrzymania (i zapytania) makrem getchar() (odpowiednik
getch() dla Windows). To działanie mozemy z powodzeniem
powierzyc funkcji okienkowej MessageBox(). Funkcja MessageBox()
pobiera cztery parametry:

int Message Box(hwndParent, lpszText, lpszTitle, Style)

HWND hwndParent - identyfikator maciezystego okna (głównego okna

aplikacji). Poniewaz nie wiemy póki co pod jakim numerem
(identyfikatorem) Windows zarejestruja nasza aplikacje -
wpisujemy 0
LPCSTR lpszText - daleki wskaznik do łancucha tekstowego
wewnatrz okienka.
LPCSTR lpszTitle - daleki wskaznik do łancucha tekstowego -
tytułu okienka komunikatu.
UINT Style - UINT = unsigned int; numer okreslajacy zawartosc
okienka.
int Return Value - identyfikator klawisza, który wybrał
uzytkownik w okienku komunikatu.

[!!!] UWAGA
________________________________________________________________
Deklaracje wskazników do tekstów powinny wygladac tak:
LPCSTR p1 = "Napis1", p2 = "Tekst2";
ale C++ moze samodzielnie dokonac forsowania typów i zamienic
typ char* na typ LPCSTR (lub LPSTR).
________________________________________________________________

/* WINR2.CPP: */
/* Stadium 2: Dwa okienka ze zmienna zawaroscia */

# include <windows.h>
# include <stdio.h>

char *p2, *p1 = "Dopisywanie:";
char napisy[4][20] = { "Borland ", "C++ ", "dla ", "Windows" };

void main()
{
printf("\n\n\n Hello World dla WINDOWS!");
printf("\n AUTOR: ...................");

for( int i = 0; i < 4; i++)
{
p2 = &napisy[i][0];
MessageBox(0, p2, p1, MB_OK);
printf("\n %s", napisy[i]);
}
MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK);
}

W tym stadium stosujemy:

- główne okno aplikacji
- dwa okienka komunikatów (Dopisywanie i KONIEC)
- jeden klawisz - [OK]

Łancuchy tekstowe przeznaczone do pola tekstowego okienka
pobierane sa z tablicy napisy[4][20] (cztery napisy po max. 20
znaków) przy pomocy wskaznika p2. MB_OK to predefiniowana stała
(Message Box OK - key identifier - identyfikator klawisza [OK]
dla okienek komunikatów).

/* WINR3.CPP: */
/* Stadium 3: Dwa okienka steruja petla */

# include <windows.h>
# include <stdio.h>

char *p2, *p1 = "Dopisywanie:";
char napisy[4][20] = { "Borland ", "C++ ", "dla ", "Windows" };

void main()
{
printf("\n\n\n Hello World dla WINDOWS!");
printf("\n AUTOR: ...................");

for( int i = 0; i < 4; i++)
{
p2 = &napisy[i][0];
if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK)
printf("\n %s", napisy[i]);
else
printf("\n ...?");
}
MessageBox(0, "I to juz \n wszystko...", "KONIEC", MB_OK);
}


W tym stadium stosujemy:

- główne okno aplikacji
- dwa okienka komunikatów (Dopisywanie i KONIEC)
- dwa klawisze - [OK] i [Anuluj] (OK/Cancel)
- jedna ikone [STOP]

Zwróc uwage, ze tym razem sprawdzamy, który klawisz wybrał
uzytkownik w okienku. Odbywa sie to tak:

if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK)

IDOK jest predefiniowana stała - kodem klawisza [OK] (ang.
OK-key IDentifier - identyfikator klawisza OK). Identyfikatory
róznych zasobów Windows sa liczbami całkowitymi. Jesli jestes
dociekliwy Czytelniku, mozesz sprawdzic - jaki numer ma klawisz
[OK] rozbudowujac tekst aplikacji np. tak:

int Numer;
...
Numer = MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL);
printf("\nKlawisz [OK] ma numer: %d", Numer);
if(Numer == IDOK) ...

Zwróc uwage na sposób wykorzystania zasobów w funkcji
MessageBox(). Identyfikatory zasobów, które chcemy umiescic w
okienku sa wpisywane jako ostatni czwarty argument funkcji i
moga byc sumowane przy pomocy znaku | (ang. ORing), np.:

MessageBox(0,..,.., MB_ICONSTOP | MB_OKCANCEL);

oznacza umieszczenie ikony STOP i klawiszy [OK] i [Anuluj]. Kod
zwracany przez funkcje moze byc wykorzystywany we wszelkich
konstrukcjach warunkowych (switch, case, for, while, if-else,
itp.).

/* WINR4.CPP: */
/* Stadium 4: Okienka steruja 2 petlami, przybywa zasobów. */

# include <windows.h>
# include <stdio.h>

char *p2, *p1 = "Dopisywanie:";
char *p3 = "I to by bylo na tyle...\n Konczymy ???";
char *p4 = "UWAGA: KONIEC ?";
char napisy[5][20] = { "Borland ", "C++ ", "dla ", "Microsoft",
"Windows" };
118

main()
{
printf("\n\n\n Grafoman dla WINDOWS!");
printf("\n AUTOR: (jak wyzej)");
puts("_____________________________\n");

do
{
for( int i = 0; i < 5; i++)
{
p2 = &napisy[i][0];
if( MessageBox(0, p2, p1, MB_ICONSTOP | MB_OKCANCEL) == IDOK)
printf("\n %s", napisy[i]);
else
printf("\n ...?");
}
} while
(MessageBox(0,p3,p4,MB_ICONQUESTION |
MB_OKCANCEL)==IDCANCEL);

return 0;
}

W tym stadium stosujemy:

- główne okno aplikacji
- dwa okienka komunikatów (Dopisywanie i KONIEC)
- dwa klawisze - [OK] i [Anuluj] (OK/Cancel)
- dwie ikonki [STOP] i [PYTAJNIK]

Tekst jest przewijany w głównym oknie programu i po zakonczeniu
roboczej czesci programu i przejsciu w stan nieaktywny
(inactive) mozesz przy pomocy paska przewijania pionowego
obejrzec napisy - historie Twoich zmagan z programem. Zwróc
uwage, ze pojemnosc głównego okna jest ograniczona. Jesli
napisów bedzie zbyt duzo, tekst przewiniety poza okno moze
ulegac obcieciu (ang clip on). Zwróc równiez uwage na
naprzemienne przekazywanie aktywnosci (focus) pomiedzy oknami
aplikacji:

MainWindow <-----> MessageBox

Warto w tym momencie zwrócic uwage na kilka typowych dla
okienkowych aplikacji mechanizmów.

* Jesli nacisniemy klawisz na klawiaturze, badz klawisz myszki,
obsługa takiego zdarzenia moze nastepowac na dwa sposoby.
Najpierw Windows pobieraja kod klawisza i dokonuja
"kolejkowania" (podobnie jak DOS-owski bufor klawiatury).
Nastepnie przekazuja kod klawisza aplikacji do obsługi. Jesli
aplikacja czeka na klawisz i potrafi obsłuzyc takie zdarzenie
(np. funkcja MessageBox(), badz makro getchar(), czy operator
cin >> w programie głównym), obsługa zdarzenia zostaje
zakonczona. Jesli aplikacja nie potrafi obsłuzyc zdarzenia -
obsługa przekazywaba jest stadardowym funkcjom obsługi (Event
Handler) Windows.
* Kiedy na ekranie pojawia sie okienko dialogowe (tu:
komunikatów) zostaje mu przekazany tzw. focus - czyli aktywnosc.

Nacisniecie [Entera] spowoduje zadziałanie tego klawisza w
okienku, który własnie ten focus otrzymał (tu zwykle pierwszego
z lewej).
* jesli nacisniemy klawisz specjalny, którego obsługe w sposób
standardowy powinny załatwiac Windows - obsługa takiego
zdarzenia zostaje przekazana domyslnej funkcji Windows (ang.
Default Event Handler). Tak jest w przypadku klawiszy ze
strzałkami (przewijanie w oknie), [Tab], [Alt]+[F4], itp.

/* WINR5.CPP: */
/* Stadium 5: Zmiana wielkosci i nazwy okienka. */

# include <windows.h>
# include <iostream.h>
# include <string.h>

char tytul[80] = "Dopisywanie: ";
char *p0, *p2;
char *p1 = "UWAGA: Ponawianie proby \n oznacza: WYDRUKUJE I
ZAPYTAM";
char *p3 = "I to by bylo na tyle...\n Konczymy ???";
char *p4 = "UWAGA: KONIEC ?";
char napisy[5][20] = { "Borland ", "C++ ", "dla ", "Microsoft",
"Windows" };

main()
{
cout << "\n\n\n Grafoman dla WINDOWS!";
cout << "\n AUTOR: (jak wyzej)";
cout << "\n_____________________________\n";

p0 = &tytul[0];
do
{
for( int i = 0; i < 5; i++)
{
p2 = &napisy[i][0];
strcat(p0, p2);
int decyzja = MessageBox(0, p1, p0, MB_ICONHAND |
MB_ABORTRETRYIGNORE);
if (decyzja == IDABORT) break;
else
if (decyzja == IDRETRY)
{
cout << "\n " << napisy[i];
i--;
}
else
if (decyzja == IDIGNORE)
{
cout << "\n ...?";
continue;
}
}
} while
(MessageBox(0, p3, p4, MB_ICONQUESTION | MB_OKCANCEL) ==
IDCANCEL);
return 0;
}

W Stadium 5 zmienia sie (rosnie) nagłówek okienka komunikatów.

UWAGA: Po wyjsciu za ekran nastapi załamanie programu. Program
nie zawiera handlera obsługujacego przekroczenia
dopuszczalnej długosci.

Rysunek ponizej przedstawia rózne stadia działania opisanych
powyzej aplikacji.

Jesli postanowisz napisac praktyczna aplikacje dla Windows, jest

to zwykle program znacznie dłuzszy, w którym trzeba przemyslec
sposób organizacji petli, wyrazen warunkowych i sposoby
wykorzystania zasobów.

[!!!]UWAGA
________________________________________________________________
Okienka moga byc "modalne" i "nie-modlane". Okienko "modalne" to

takie okienko, które do momentu jego zamkniecia uniemozliwia
uzytkownikowi działania w innych oknach (tej samej aplikacji,
badz innych aplikacji) znajdujacych sie na ekranie. W ramach
parametru Styl mozesz stosowac predefiniowane stałe
MB_APPMODAL
MB_TASKMODAL
itp.
okreslajace stopien "modalnosci" okienka (na poziomie zadania -
TASK, aplikacji - APP, itp.).
________________________________________________________________


LEKCJA 40: STRUKTURA PROGRAMU PROCEDURALNO -
ZDARZENIOWEGO
PRZEZNACZONEGO DLA WINDOWS.
________________________________________________________________
W trakcie tej lekcji poznasz ogólna budowe interfejsu API
Windows i dowiesz sie, co z tego wynika dla nas - autorów
programów przeznaczonych dla Windows.
________________________________________________________________

W przeciwienstwie do długich liniowych programów przeznaczonych
dla DOS, w naszych programach dla Windows bedziemy pisac cos
na kształt krótkich odcinków programu i przekazywac sterowanie
Windows. Jest to bardzo wazna cecha - kod programu jest zwykle
silnie zwiazany z Windows w taki sposób, ze uzytkownik moze w
duzym stopniu decydowac o sposobie (kolejnosci) wykonywania
119
programu. Praktycznie robi to poprzez wybór opcji-klawiszy w
dowolnej kolejnosci. Przy takiej filozofii w dowolnym momencie
powinnismy miec mozliwosc przełaczenia sie do innego programu
(innego okna) i nasz program powinien (bez zauwazalnej zwłoki)
przekazac sterowanie, nie zagarniajac i nie marnujac czasu CPU.
Z tego powodu kod programu powinien byc bardzo
"zmodularyzowany". Kazda sekcja kodu powinna byc odseparowana i
kazda, po wykonaniu powinna przekazywac sterowanie do Windows.

NOTACJA WEGIERSKA I NOWE TYPY DANYCH.

Tworzenie zdarzeniowych programów dla Windows wymaga kilku
wstepnych uwag na temat nowych typów danych. Okienkowe typy sa
definiowane w plikach nagłówkowych (WINDOWS.H, WINDOWSX.H,
OWL.H

itp) i maja postac najczesciej struktury, badz klasy. Typowe
sposoby deklaracji w programach okienkowych sa nastepujace:

HWND hWnd - WiNDow Handle - identyfikator okna
HWND hWnd - typ (predefiniowany), hWnd - zmienna
HINSTANCE hInstance - Instance Handle - identyfikator danego
wystapienia (uruchomienia) programu
PAINTSTRUCT - struktura graficzna typu PAINTSTRUCT
ps - nasza robocza struktura (zmienna)
WNDCLASS - struktura (a nie klasa wbrew mylacej nazwie)
POINT - struktura (współrzedne punktu - piksela na ekranie)
RECT - struktura (współrzedne prostokata)
BOOL - typ signed int wykorzystywany jako flaga (TRUE/FALSE)
WORD - unsigned int
DWORD - unsigned long int
LONG - long int
HANDLE, HWND, HINSTANCE - unsigned int (jako nr - identyfikator)

UINT - j. w. - unsigned int.

W programach okienkowych stosuje sie wiele predefiniowanych
stałych, których znaczenie sugeruje przedrostek i nazwa, np:

WM_CREATE - Windows Message: Create! - Komunikat Windows:
Utworzyc! (np. okno)
WS_VISIBLE - Window Style: Visible - Styl Okna: Widoczne
ID_... - IDentifier - IDentyfikator
MB_... - Message Box - elementy okienka komunikatów

W srodowisku Windows stosuje sie specjalna notacje nazwana od
narodowosci swojego wynalazcy Karoja Szimoni - notacja
wegierska. Sens notacji wegierskiej polega na dodaniu do nazwy
zmiennej okreslonych liter jako przedrostka (prefix).
Litery-przedrostki stosowane w notacji wegierskiej zebrano w
Tabeli ponizej. Pomiedzy nazewnictwem Microsofta a Borlanda
istnieja wprawdzie drobne rozbieznosci, ale ogólne zasady mozna
odniesc zarówno do BORLAND C++ 3+...4+, jak i Microsoft C++
6...7, czy Visual C++.

Notacja wegierska
________________________________________________________________
Prefix Skrót ang. Znaczenie
________________________________________________________________

a array tablica
b bool zmienna logiczna (0 lub 1)
by unsigned char znak (bajt)
c char znak
cb count of bytes liczba bajtów
cr color reference value okreslenie koloru
cx, cy short (count x, y len.) x-ilosc, y-długosc (short)
dw unsigned long liczba długa bez znaku
double word podwójne słowo
fn function funkcja
pfn pointer to function wsk. do funkcji
h handle "uchwyt" - identyfikator
i integer całkowity
id identifier identyfikator
n short or int krótki lub całkowity
np near pointer wskaznik bliski
p pointer wskaznik
l long długi
lp long pointer wskaznik typu long int
lpfn l. p. to function daleki wskazn. do funkcji
s string łancuch znaków
sz string terminated '\0' łancuch ASCIIZ
tm text metric miara tekstowa
w unsigned int (word) słowo
x,y short x,y coordinate współrzedne x,y (typ: short)
________________________________________________________________

O PROGRAMOWANIU PROCEDURALNO - ZDARZENIOWYM DLA
WINDOWS.

W proceduralno-sekwencyjnych programach DOS'owskich sterowanie
jest przekazywane mniej lub bardziej kolejno kolejnym
instrukcjom w taki sposób, jak zyczył sobie tego programista. W
Windows program-aplikacja prezentuje uzytkownikowi wszystkie
dostepne opcje w formie widocznych na ekranie obiektów (visual
objects) do wyboru przez uzytkownika. Program funkcjonuje zatem
według zupełnie innej koncepcji nazywanej "programowaniem
zdarzeniowym" (ang. event-driven programming). Mozna powiedziec,

ze za przebieg wykonania programu nie jest odpowiedzialny tylko
programista lecz czesc tej odpowiedzialnosci przejmuje
uzytkownik i to on decyduje w jaki sposób przebiega wykonanie
programu. Uzytkownik moze wybrac w dowolnym momencie dowolna
sposród wszystkich oferowanych mu opcji a program powinien
zawsze zareagowac poprawnie i równie szybko. Jest oczywiste, ze
piszac program nie mozemy przewidziec w jakiej kolejnosci
uzytkownik bedzie wybierał opcje/rozkazy z menu. Przeciwnie
powinismy napisac program w taki sposób by dla kazdego rozkazu
istniał oddzielny kod. Jest to ogólna koncepcja, na której
opiera sie programowanie zdarzeniowe.

W przeciwienstwie do programów proceduralno - sekwencyjnych,
które nalezy czytac od poczatku do konca, programy dla Windows
musza zostac pociete na na mniejsze fragmenty - sekcje - na
zasadzie jedna sekcja - obsługa jednego zdarzenia. Jesli
zechcesz wyswietlic napis "Hello, World", sekcja zdarzeniowego
programu obsługujaca takie zdarzenie moze wygladac np. tak:

Funkcja_Obsługi_Komunikatów_o_Zdarzeniach(komunikat)
{
switch (komunikat_od_Windows)
{
case WM_CREATE:
...
TextOut(0, 0, "Napis: np. Hello world.", dlugosc_tekstu);
break;

...
case WM_CLOSE: // CLOSE - zamknac okno
.... break;
..................... itd.
}

a w przypadku obiektowego stylu programowania - metoda
obsługujaca to zdarzenie (nalezaca np. do obiektu
Obiekt_Główne_Okno - TMainWindow) moze wygladac np. tak:

void TMainWindow::RysujOkno()
{
TString Obiekt_napis = "Hello, World";
int dlugosc_tekstu = sizeof(Obiekt_napis);
TextOut(DC, 10, 10, Obiekt-napis, dlugosc_tekstu);
}

Taki fragment kodu programu jest specjalnie przeznaczony do
obsługi jednego zdarzenia (ewent-ualnosci). W okienku wykonuje
sie operacja PAINT (maluj). "Malowanie" okna moze sie odbywac
albo po raz pierwszy, albo na skutek przesuniecia. Programy
zdarzeniowe tworzone w C++ dla Windows beda zbiorem podobnych
"kawałków" nastepujacych w tekscie programu sekcja za sekcja.
Oto jak działa program zdarzeniowy: kod programu, podzielony na
sekcje obsługujace poszczególne zdarzenia koncentruje sie wokół
interfejsu.

FUNKCJE WinMain() i WindowProc().

W programach pisanych w standardowym C dla Windows uzywane sa
dwie najwazniejsze funkcje: WinMain() i WindowProc().

________________________________________________________________

UWAGA:
Funkcji WindowProc() mozna nadac dowolna nazwe, ale WinMain()
musi sie zawsze nazywac WinMain(). Jest to nazwa zastrzezona
podobnie jak main() dla aplikacji DOSowskich.
________________________________________________________________
120


Funkcja WinMain() powoduje utworzenie okna programu umozliwiajac

zdefiniowanie i zarejestrowanie struktury "okno" (struct
WNDCLASS) a nastepnie powoduje wyswietlenie okna na ekranie. Od
tego momentu zarzadzanie przejmuje funkcja WindowProc(). W
typowej proceduralno - zdarzeniowej aplikacji dla Windows to
własnie funkcja WindowProc() obsługuje pobieranie informacji od
uzytkownika (np. nacisniecie klawisza lub wybór z menu). Funkcja

WindowProc() robi to dzieki otrzymywaniu tzw. komunikatów (ang.
Windows message).

W Windows zawsze po wystapieniu jakiegos zdarzenia (event)
nastepuje przesłanie komunikatu (message) o tym zdarzeniu do
biezacego aktywnego w danym momencie programu w celu
poinformowania go, co sie stało. Jesli został nacisniety
klawisz, komunikat o tym zdarzeniu zostanie przesłany do funkcji

WindowProc(). Tak funkcjonuje interfejs pomiedzy aplikacja a
Windows. W programach tworzonych w C prototyp funkcji
WindowProc() wyglada nastepujaco:

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message,
WORD wParam, LONG lParam);

Słowa FAR i PASCAL oznaczaja, ze:

FAR - kod funkcji znajduje sie w innym segmencie niz kod
programu;
PASCAL - kolejnosc odkładania argumentów na stos - odwrotna (jak

w Pascalu).

________________________________________________________________

UWAGA:
Prototyp funkcji moze zostac podany równiez tak:

LONG FAR PASCAL WndProc(HWND, unsigned, WORD, LONG);
________________________________________________________________


Pierwszy parametr hWnd jest to tzw. identyfikator okna (ang.
window handle). Ten parametr zawiera informacje, dla którego
okna przeznaczony jest komunikat. Zastosowanie takiego
identyfikatora jest celowe, poniewaz funkcje typu WindowProc()
moga obsługiwac przesyłanie komunikatów do wielu okien. Jesli
okien jest wiele, okno jest identyfikowane przy pomocy tego
własnie identyfikatora (numeru).

Nastepny parametr to sam komunikat o długosci jednego słowa
(word). Ten parametr przechowuje wartosc z zakresu
zdefiniowanego w pliku nagłówkowym WINDOWS.H. W zaleznosci od
tego co sie zdarzyło, Windows moga nam przekazac ok. 150 róznych

komunikatów a w tym np.:

WM_CREATE Utworzono okno
WM_KEYDOWN Nacisnieto klawisz
WM_SIZE Zostały zmienione wymiary okna
WM_MOVE Okno zostało przesuniete
WM_PAINT Okno nalezy narysowac (powtórnie) - (re)draw
WM_QUIT Koniec pracy aplikacji
itp.

Przedrostek WM_ to skrót od Windows Message - komunikat Windows.

Wymiana komunikatów w srodowisku Windows moze przebiegac w rózny

sposób - zaleznie od zródła wywołujacego generacje komunikatu i
od charakteru zdarzenia. Ze wzgledu na zródło mozna komuniakty
umownie podzielic na nastepujace grupy:

1. Działanie uzytkownika (np. nacisniecie klawisza) powoduje
wygenerowanie komunikatu.
2. Program - aplikacja wywołuje funkcje Windows i powoduje
przesłanie komunikatu do aplikacji.
3. Srodowisko Windows przesyła komunikat do programu.
4. Dwie aplikacje zwiazane mechanizmem dynamicznej wymiany
danych (Dinamic Data Exchange - DDE) wymieniaja komunikaty.

Komunikaty Windows mozna takze podzielic umownie na nastepujace
kategorie:

1. Komunikaty dotyczace zarzadzania oknami (Windows Managenent
Msg.):
WM_ACTIVATE (zaktywizuj lub zdezaktywizuj okno), WM_PAINT,
WM_MOVE, WM_SIZE, WM_CLOSE, WM_QUIT.

Bardzo istotnym szczegółem technicznym jest problem
przekazywania aktywnosci pomiedzy oknami. Szczególnie czesto
wystepuje potrzeba przekazania aktywnosci do elementu
sterujacego. Jesli hEditWnd bedzie identyfikatorem (window
handle) okienka edycyjnego:

case WM_SETFOCUS:
SetFocus(hEditWnd);
break;

funkcja SetFocus() spowoduje, ze wszystkie komunikaty dotyczace
zdarzen klawiatury beda kierowane do okna sterujacego, jezeli
okno maciezyste jest aktywne. Poniewaz zmiana rozmiaru okna
głównego nie pociaga za soba automatycznej zmiany rozmiaru okna
sterujacego, potrzebna jest dodatkowo obsługa wiadomosci
WM_SIZE wobec okna elementu sterujacego.

2. Komunikaty inicjacyjne dotyczace konstrukcji np. menu
aplikacji:
WM_INITMENU - zainicjuj menu (wysyłany przed zainicjowaniem),
WM_INITDIALOG - zainicjuj okienko dialogowe.

3. Komunikaty generowane przez Windows w odpowiedzi na wybór
rozkazu z menu, zegar, badz nacisniecie klawisza:
WM_COMMAND - wybrano rozkaz z menu,
WM_KEYDOWN - nacisnieto klawisz,
WM_MOUSEMOVE - przesunieto myszke,
WM_TIMER - czas minał.

4. Komunikaty systemowe. Aplikacja nie musi odpowiadac na
rozkazy obsługiwane przez domyslna procedure Windows -
DefWindowProc(). Szczególnie dotyczy to rozkazów nie odnoszacych

sie do roboczego obszaru okna - Non Client Area Messages.

5. Komunikaty schowka (Clipborad Messages).

Sens działania funkcji WindowProc() w C/C++ polega na
przeprowadzeniu analizy, co sie stało i podjeciu stosownej
akcji. Mozna to realizowac przy pomocy drabinki if-else-if, ale
najwygodniejsze jest stosowanie instrukcji switch.

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message,
WORD wParam, LONG lParam)
{
switch (Message)
{
case WM_CREATE:
.....
break; /* Koniec obsługi komunikatu WM_CREATE */
case WM_MOVE:
.... /* Kod obsługi komunikatu WM_MOVE */
break; /* Koniec obsługi WM_MOVE. */
case WM_SIZE:
.... /* Kod obsługi sytuacji WM_SIZE */
break; /* Koniec obsługi WM_SIZE */

.......... /* Inne, pozostałe mozliwe sytuacje */

case WM_CLOSE: /* Zamkniecie okna */
....
break;
default: /* wariant domyslny: standardowa obsługa
.... przez standardowa funkcje Windows */
}
}
________________________________________________________________
UWAGA:
Poniewaz komunikatów "interesujacych" dana aplikacje moze byc
ponad 100 a sposobów reakcji uzytkownika jeszcze wiecej, w
"powaznych" aplikacjach tworzone sa czesto struktury decyzyjne o

wiekszym stopniu złozonosci. Jesli istnieje potrzeba
optymalizacji działania programów stosuje sie struktury dwu
typów:
121
* hierarchia wartosci (Value Tree) i
* drzewo analizy zdarzen (Event Tree).
Utworzone w taki sposób tzw. "Drzewo decyzyjne" nazywane takze
"Drzewem analizy zdarzen" moze byc wielopoziomowe. Widoczny
powyzej pierwszy poziom drzewa (pierwszy przesiew) realizowany
jest zwykle przy pomocy instrukcji switch a nastepne przy pomocy

drabinek typu if-else-if-break. Schemat if-else-if-break czesto
bywa zastepowany okienkami dialogowymi.
________________________________________________________________


Parametry wParam i lParam przechowuja parametry istotne dla
danego komunikatu. wParam ma długosc pojedynczego słowa (word) a

lParam ma długosc podwójnego słowa (long). Jesli, dla przykładu,

okno zostało przesuniete, te parametry zawieraja nowe
współrzedne okna.

Jezeli program ma byc programem zdarzeniowym, powinnismy przed
podjeciem jakiejkolwiek akcji zaczekac az Windows przysla nam
komunikat o tym, jakie zdarzenie nastapiło. Wewnatrz Windows
tworzona jest dla komunikatów kolejka (ang message queue).
Dzieki istnieniu kolejkowania otrzymujemy komunikaty pobierane z

kolejki pojedynczo. Jesli uzytkownik przesunie okno a nastepnie
przycisnie klawisz, to Windows wywołaja funkcje WindowProc()
najpierw z parametrem WM_MOVE a nastepnie z parametrem
WM_KEYDOWN.

Jednym z najwazniejszych zadan funkcji WinMain() jest utworzenie

kolejki dla komunikatów i poinformowanie Windows, ze komunikaty
do naszego programu nalezy kierowac pod adresem funkcji
WindowProc(). W tym celu stosuje sie daleki wskaznik do
procedury okienkowej lpfn (Long Pointer to Function). Poza tym
funkcja WinMain() tworzy okno (okna) i wyswietla je na ekranie w

pozycji poczatkowej. Kiedy program zostaje po raz pierwszy
załadowany i uruchomiony - Windows najpierw wywołuja funkcje
WinMain().

Windows manipuluja komunikatami posługujac sie struktura MSG (od

messages - komunikaty). Struktura MSG jest zdefiniowana w pliku
WINDOWS.H w nastepujacy sposób:

typedef struct tagMSG
{
HWND hwnd;
WORD message;
WORD wParam;
LONG lParam;
DWORD time;
POINT pt;
} MSG;

Na pierwszym polu tej struktury znajduje sie "identyfikator"
(kod) okna, dla którego przeznaczony jest komunikat (kazdy
komunikat moze byc przesłany tylko do jednego okna). Na drugim
polu struktury przechowywany jest sam komunikat. Komunikat jest
zakodowany przy pomocy predefiniowanych stałych w rodzaju
WM_SIZE, WM_PAINT czy WM_MOUSEMOVE. Kolejne dwa pola słuza do
przechowania danych-parametrów towarzyszacych kazdemu
komunikatowi: wParam i lParam. Na nastepnym polu przechowywany
jest w zakodowanej postaci czas - moment, w którym wystapiło
zdarzenie. Na polu pt przechowywane sa współrzedne kursora
myszki na ekranie w momencie w którym został wygenerowany
komunikat o wystapieniu zdarzenia. Nalezy zwrócic tu uwage, ze
typ POINT oznacza strukture. Struktura POINT (punkt) w Windows
wyglada tak:

typedef struct tagPOINT
{
int x;
int y;
} POINT;

Aby miec pewnosc, ze otrzymalismy wszystkie komunikaty, które
zostały do nas skierowane, w programie wykonywana jest petla
pobierania komunikatów (message loop) wewnatrz funkcji
WinMain(). Na poczatek wywoływana jest zwykle okienkowa (czyli
nalezaca do Windows API) funkcja GetMessage(). Ta funkcja
wypełnia strukture komunikatów i zwraca wartosc. Zwracana przez
funkcje wartosc jest rózna od zera, jezeli otrzymany własnie
komunikat był czymkolwiek za wyjatkiem WM_QUIT. Komunikat
WM_QUIT jest komunikatem konczacym prace kazdej aplikacji dla
Windows. Jesli otrzymamy komunikat WM_QUIT powinnismy przerwac
petle pobierania komunikatów i zakonczyc prace funkcji
WinMain(). Taka sytuacja oznacza, ze wiecej komunikatów nie
bedzie. Po uwzglednieniu tych warunków petla moze wygladac tak:

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \
LPSTR lpszCmdLine, int nCmdShow)
....
while(GetMessage(&msg,NULL,0,0)) //Poki nie otrzymamy WM_QUIT
{
....
}

Po nacisnieciu przez uzytkownika klawisza generowany jest
komunikat WM_KEYDOWN. Jednakze z faktu otrzymania komunikatu
WM_KEYDOWN nie wynika, który klawisz został przycisniety, czy
była to duza, czy mała litera. Funkcje TranslateMessage()
(PrzetłumaczKomunikat) stosuje sie do przetłumaczenia komunikatu

WM_KEYDOWN na komunikat WM_CHAR. Komunikat WM_CHAR
przekazuje
przy pomocy parametru wParam kod ASCII nacisnietego klawisza.
Funkcje TranslateMessage() stosujemy w petli pobierania
komunikatów tak:

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, \
LPSTR lpszCmdLine, int nCmdShow)
....
while(GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
....
}

W tym stadium program jest gotów do przesłania komunikatu do
funkcji - procedury okienkowej WindowProc(). Posłuzymy sie w tym

celu funkcja DispatchMessage() (ang. dispatch - odpraw, przekaz,

DispatchMessage = OtprawKomunikat). Funkcja WinMain()
poinformowała wczesniej Windows, ze odprawiane komunikaty
powinny trafic własnie do WindowProc().

while(GetMessage(&msg, NULL, NULL, NULL))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

Tak funkcjonuje petla pobierajaca komunikaty od Windows i
przekazujaca je funkcji WindowProc(). Petla działa do momentu
pobrania komunikatu WM_QUIT (Koniec!). Otrzymanie komunikatu
WM_QUIT powoduje przerwanie petli i zakonczenie pracy programu.
Komunikaty systemowe (system messages), które sa kierowane do
Windows takze trafiaja do tej petli i sa przekazywane do
WindowProc(), ale ich obsługa powinna sie zajac specjalna
funkcja DefWindowProc() - Default Window Procedure, umieszczona
na koncu (wariant default).
Jest to standardowa dla aplikacji okienkowych postac petli
pobierania komunikatów.

Jak widac, wymiana informacji pomiedzy uzytkownikiem,
srodowiskiem a aplikacja przebiega tu troche inaczej niz w DOS.
Program pracujacy w srodowisku tekstowym DOS nie musi np.
rysowac własnego okna.

[Z]
________________________________________________________________
1. Uruchom Windows i popatrz swiadomym, fachowym okiem, jak
przebiega przekazywanie aktywnosci (focus) miedzy okienkami
aplikacji.
________________________________________________________________



LEKCJA 41: JAK TWORZY SIE APLIKACJE DLA Windows?
________________________________________________________________
W trakcie tej lekcji dowiesz sie, jak "poskładac" aplikacje dla
122
Windows z podstawowych funkcji interfejsu API i jakie komunikaty
sa najwazniejsze dla naszych aplikacji.
________________________________________________________________

Przy tworzeniu programu zwrócmy szczególna uwage na to, co
dzieje sie w programie po otrzymaniu komunikatu WM_PAINT (nalezy
narysowac okno). Jest to zadanie ze strony Windows, by program
narysował obszar roboczy (client area) swojego okna. Program
otrzyma komunikat WM_PAINT zawsze na poczatku, kiedy powinien
narysowac swoje okno po raz pierwszy i pózniej powtórnie, za
kazdym razem, gdy trzeba bedzie odtworzyc okno na ekranie. Jesli
inne okno przesuwane po ekranie przysłoni okno naszego programu,
po odsłonieciu naszego okna Windows przesla do programu
komunikat WM_PAINT - odtwórz swoje okno - narysuj go powtórnie
(redraw, repaint). Jesli zechcemy wyprowadzic na ekran napis
"Hello World" takze bedziemy musieli narysowac okno od nowa. Nie
zawsze "odswiezenia" wymaga całe okno. W kazdej z sytuacji:

- całe okno zostało przysłoniete i odsłoniete
- czesc okna wymaga odswiezenia
- okno jest rysowane po raz pierwszy

Windows przesla do programu ten sam komunikat - WM_PAINT.
Jesli odtworzenia wymaga tylko czesc okna, taka czesc okna
nazywa sie niewazna-nieaktualna (ang. invalid). W Windows takie
nieaktualne fragmenty okna zawsze maja kształt prostokatów.
Wyobrazmy sobie, ze jakies inne okno przesłoniło naroznik okna
naszego programu. Jesli uzytkownik usunie to przesłaniajace
okno, odsłoniety obszar bedzie potraktowany przez Windows jako
nieaktualny. Windows przesla do aplikacji komunikat WM_PAINT
zadajacy odtworzenia okna. Zadajac odtworzenia okna Windows
powinny nas poinformowac która czesc naszego okna została na
ekranie "zepsuta". Współrzedne prostokata na ekranie Windows
przekaza przy pomocy specjalnej struktury nazywanej struktura
rysunku (ang. paint structure - PAINTSTRUCT).

Strukture rysunku mozemy nazwac w programie np.:

PAINSTRUCT ps;

W funkcji WindowProc() obsługa komunikatu WM_PAINT rozpoczyna
sie od wyczyszczenia pól struktury rysunku ps. Struktura
predefiniowanego typu PAINTSTRUCT (w WINDOWS.H) zawiera
informacje o rysunku.

PAINTSTRUCT ps;
{
switch (Message)
{
case WM_CREATE:
..... break;
case WM_MOVE:
.... break;
case WM_SIZE:
.... break;

case WM_PAINT: /* Obsługa rysowania okna */
memset(&ps, 0x00, sizeof(PAINTSTRUCT);
....
break; //Koniec obsługi WM_PAINT

case WM_CLOSE:
.... break;

default: .....
}
}

Nastepnie pola struktury rysunku zostaja wypełnione poprzez
okienkowa funkcja BeginPaint() - RozpocznijRysowanie. Zwróc
uwage, ze do poprawnego działania funkcji potrzebne sa
informacje o tym, które okno trzeba odswiezyc (Windows powinny
wiedziec wobec którego okna zadamy informacji o "zepsutym"
prostokacie) i adres naszej struktury rysunku. Aby przekazac te
informacje postepujemy tak:

case WM_PAINT:
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
....

Teraz funkcja BeginPaint() moze wypełnic nasza strukture rysunku

ps danymi. Pola struktury typu PAINTSTRUCT wygladaja
nastepujaco:

typedef struct tagPAINTSTRUCT
{
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BYTE rgbReserved[16];
} PAINTSTRUCT;

Przy pomocy pola typu RECT (ang. rectangle - prostokat) Windows
przekazuja do programu współrzedne wymiary (ang. dimensions)
"zepsutego" na ekranie prostokata. Typ RECT oznacza nastepujaca
strukture:

typedef struct tagRECT
{
int left; //współrzedna lewa - x
int top; //współrzedna górna - y
int right; //współrzedna prawa - x
int bottom; //współrzedna dolna - y
} RECT;

Górny lewy róg nieaktualnego prostokata (invalid rectangle) ma
dwie współrzedne (left, top) a dolny prawy róg prostokata ma
współrzedne (right, bottom). Te współrzedne ekranowe mierzone sa

w pikselach i sa to współrzedne wzgledne - wzgledem lewego
górnego naroznika okna aplikacji. Lewy górny naroznik okna
aplikacji ma wiec współrzedne (0,0).

Zwrócmy uwage na wartosc zwracana przez funkcje BeginPaint() -
zmienna hDC:

case WM_PAINT:
memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);
....

Wszystnie operacje graficzne beda wymagac nie kodu okna hWnd a
własnie kodu-identyfikatora kontekstowego hDC.

Na poczatku pracy programu, gdy okno jest rysowane po raz
pierwszy, Windows generuja komunikat WM_PAINT i cały obszar
roboczy okna jest uznawany za nieaktualny. Kiedy program otrzyma

ten pierwszy komunikat, mozemy wykorzystac to do umieszczenia w
oknie np. napisu. Jesli tekst ma rozpoczynac sie od lewego
górnego naroznika okna aplikacji, funkcja TextOut() uzywana w
Windows do wykreslania tekstu (w trybie graficznym) powinna
rozpoczynac wyprowadzanie tekstu od punktu o (pikselowych)
współrzednych (0,0).

case WM_PAINT:
...
TextOut(hDC, 0, 0, (LPSTR) "Tekst", strlen("Tekst"));
EndPaint(hWnd, &ps);
break;

Funkcja TextOut() (wyprowadz tekst) pobiera piec parametrów:

hDC - identyfikator-kod prostokata, który nalezy narysowac
x - współrzedna pozioma (w pikselach)
y - współrzedna pionowa poczatku naszego napisu
W tym przypadku współrzedne wynosza (0,0).
LPSTR - wskaznik do łancucha znaków "Hello world."
LPSTR = long pointer to string (wskaznik typu far).

Wskaznk ten przekazujemy do funkcji poprzez forsowanie typu:
... (LPSTR) "Tekst";
Zgodnie z definicja typu w pliku WINDOWS.H spowoduje to zamiane
wskaznika do łancucha typu near char* (bliski) na wskaznik typu
far (daleki). Ostatni parametr funkcji to długosc wyprowadzanego

tekstu - tu obliczana przez funkcje strlen().

Przesledzmy etapy powstawania aplikacji.

Funkcja MainWin() rejestruje i tworzy główne okno programu oraz
inicjuje globalne zmienne i struktury. Funkcja WinMain() zawiera

123
petle pobierania komunikatów. Kazdy komunikat przeznaczony dla
głównego okna (lub ewentualnych nastepnych okien potomnych) jest

pobierany, ewentualnie poddawany translacji i przekazywany do
funkcji obsługujacej dialog z Windows. Przed zakonczeniem
programu funkcja WinMain() kasuje utworzone wczesniej obiekty,
zwalnia pamiec i pozostałe zasoby.

UWAGA: "Obiekty" nie sa tu uzyte w sensie stosowanym w OOP.
"Obiekt" oznacza tu np. strukture.

int PASCAL WinMain(HANDLE hInstance, hPrevInstance,
LPSTR lpszCmLine, int nCmdShow)
{ ...

HANDLE hInstance - identyfikator biezacego pojawienia sie danej
aplikacji. Poniewaz w Windows program moze byc uruchamiany
wielokrotnie, stosuje sie pojecie tzw. "Instancji" - wystapienia

- uruchomienia programu.

HANDLE hPrevInstance - identyfikator poprzedniego wystapienia

danej aplikacji
LPSTR lpszCmdLine - daleki wskaznik do parametrów wywołania
programu z linii rozkazu
int nCmdShow - sposób poczatkowego wyswietlenia okna

(pełne okno, badz ikona)

Deklaracja struktury typu MSG (Message) do przechowywania
komunikatów.

MSG msg;

Nadanie nazwy aplikacji:

strcpy(szAppName, "Nazwa Aplikacji");

Rejestrujemy struktury okien jesli jest to pierwsze uruchomienie

danej aplikacji i sprawdzamy, czy rejestracja powiodła sie:

if(!PrevInstance)
{
if((int nRc = RegisterClass() ...

Utworzenie głównego okna programu (moze sie nie udac):

hWndMain = CreateWindow(....);
if(hWndMain == NULL)
{
MessageBox(0, "Klops", "Koniec", MB_OK);
return (FALSE);
}

Wyswietlenie głównego okna na ekranie:

ShowWindow(hWndMain, nCmdShow);

Petla komunikatów wykrywajaca komunikat WM_QUIT:

while(GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

Główna procedura obsługi okna WindowProc().

Instrukcja switch przełacza do odpowiedniego wariantu działania
- obsługi odpowiedniego komunikatu. Musza tu znajdowac sie
procedury obsługi wszystkich interesujacych nas działan
uzytkownika i ogólnych komunikatow Windows (np. WM_CLOSE). Jesli

wystapi taki komunikat, którego obsługa nie została
przewidziana, obsługa jest przekazywana, do funkcji okienkowej
DefWindowProc() - obsługe przejmuja Windows.
Komunikaty inicjowane przez uzytkownika sa rozpatrywane
zasadniczo jako WM_COMMAND. Rozkaz wybrany z menu lub
odpowiadajaca mu kombinacja klawiszy jest przekazywana przy
pomocy pierwszego parametru komunikatu - wParam. Kod
odpowiadajacy rozkazowi z menu nazywa sie "control menu ID", a
identyfikator kombinacji klawiszy - "accelerator ID". Procedura
obsługi komunikatów powinna zawierac

case (WM_COMMAND): ..... break;

Wewnatrz przy pomocy instrukcji switch{...} nalezałoby
rozpatrywac kolejne warianty, wykorzystujac identyfikator
wybranego z menu rozkazu - ID. Obsługa komunikatow swiadczacych
o wyborze przez uzytkownika rozkazu z menu stanowi zwykle główna

robocza czesc programu.

LONG FAR PASCAL WindowProc(HWND hWnd, WORD Message, WORD
wParam,

LONG lParam)
{
HMENU hMenu=0; /* Identyfikator menu */
HBITMAP hBitmap=0; /* Identyfikator mapy bitowej */
HDC hDC; /* Identyfikator kontekstowy */
PAINSTRUCT ps; /* Struktura rysunku */
int nRc=0; /* Zwrot kodu przez funkcje */

switch (message)
{
case WM_CREATE:

Gdy okno jest tworzone Windows przesyłaja jeden raz komunikat
WM_CREATE do okna. Procedura obsługi nowego okna (new window
procedure) otrzymuje ten komunikat po utworzeniu okna, ale
jeszcze zanim okno pojawi sie na ekranie.

lParam - Wskaznik do struktury CREATESTRUCT o postaci:

typedef struct {
LPSTR lpCreateParams;
HANDLE hInst;
HANDLE hMenu;
HWND hwndParent;
int cy;
int cx;
int y;
int x;
LONG style;
LPSTR lpszName;
LPSTR lpszClass;
DWORD dwExStyle;
} CREATESTRUCT; */


Kod obsługi powiekszania/zmniejszania case WM_SIZE.

wParam zawiera kod operacji - zmniejsz/powieksz
lParam zawiera nowa wysokosc i szerokosc okna

case WM_PAINT:

Pobranie kontekstowego identyfikatora urzadzenia. Funkcja
BeginPaint() spowoduje w razie potrzeby wysłanie komunikatu
WM_ERASEBKGND (Erase Background - skasuj tło).

memset(&ps, 0x00, sizeof(PAINTSTRUCT));
hDC = BeginPaint(hWnd, &ps);

Set Background Mode - ustaw rodzaj tła (tu: przezroczyste):

SetBkMode(hDC, TRANSPARENT);

Aplikacja powinna wykreslic obszar roboczy okna posługujac sie
grafika GDI i (Graficzny Interfejs Urzadzenia - analogia do
graficznego standardu BGI w srodowisku DOS). Struktura ps typu
PAINSTRUCT zwrócona przez BeginPaint() wskazuje prostokat do
zamalowania.

Wypisanie tekstu w głównym oknie aplikacji:

TextOut(hDC, 0, 0, (LPSTR) "Hello, world.", strlen("Hello,
world."));

Funkcja TextOut() pracuje w trybie graficznym, wiec (podobnie
jak inne funkcje graficzne Windows API) otrzymuje jako argument
tzw. "kontekst urzadzenia" - hDC.

124
Zamykanie okna:

case WM_CLOSE:
DestroyWindow(hWnd);
if (hWnd == hWndMain)
PostQuitMessage(0);

Jesli zamkniete zostało główne okno aplikacji, funkcja
PostQuitMessage() wysyła do Windows komunikat, ze aplikacja
zakonczyła działanie i okno aplikacji zostało usuniete. W tym
stadium stosuje sie funkcje PostQuitMessage() i
PostAppMessage(). Pozostale przypadki sa obsługiwane przez
wariant domyslny - default. Przekazanie komunikatu do obsługi
przez Windows.

default:
return (DefWindowProc(hWnd, Message, wParam, lParam));

Funkcja rejestrujaca wszystkie klasy wszystkich okien zwiazanych

z biezaca aplikacja (nazwiemy ja roboczo FRegisterClasses()).
Jesli operacja sie powiodła - funkcja zwraca kod błedu.

int FRegisterClasses(void)
{
WNDCLASS wndclass; /* Struktura do definiowania klas okien. */
memset(&wndclass, 0x00, sizeof(WNDCLASS));

Ustawienie parametrów okna w strukturze:

wndclass.style = CS_HRDRAW | CS_VRDRAW;
wndclass.lpfnWindowProc = WindowProc;

Dodatkowa pamiec dla klasy Window i obiektów klasy Window.
Dołaczanie innych zasobów odbywa sie przy pomocy funkcji:

LoadBitmap() - załaduj mape bitowa
LoadIcon() - załaduj ikonke
LoadCurcor(), LoadMenu(), itp. ...

wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInst;
wndclass.hIcon = LoadIcon(NULL, ID_ICON);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

Utworzenie pedzla (brush) dla tła:

wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
wndclass.lpszMenuName = szAppName;
wndclass.lpszClassName = szAppName;

if (!RegisterClass(&wndclass)) return -1;
}

Typowe obiekty ze składu Windows to

HBRUSH Pedzel; i
HPEN Ołówek;

Nalezy tu zwrócic uwage jeszcze na dwa szczegóły techniczne. DC
i GDI - Device Context, Graphics Device Interface - to tzw.
kontekst urzadzenia i graficzny interfejs urzadzenia. Pozwala to
Windows działac skutecznie w trybie "Device Independent"
(niezaleznym od sprzetu).


LEKCJA 42: KOMPILATORY "SPECJALNIE DLA Windows".
________________________________________________________________
Z tej lekcji dowiesz sie, czym róznia sie kompilatory
przeznaczone dla pracy w srodowisku Windows.
________________________________________________________________

W IDE i w sposobie zachowania zaszły istotne zmiany. Posługujac
sie Turbo C++ z pakietu BORLAND C++ 3.0 lub BCW z pakietu 3.1
mozemy korzystac z uroków i usług Windows szerzej niz do tej
pory. Mozemy otwierac wiele okien i uruchamiac bezposrednio z
poziomu IDE okienkowe aplikacje. W głównym menu kompilatora
zaszły pewne zmiany (sygnalizujace obiektowo- i okienkowo -
zorientowana ewolucje pakietów Borlanda), na które warto zwrócic
uwage.

Znikneło menu Debug (co wcale nie oznacza, ze nie mozemy
korzystac z Debuggera), pojawiło sie natomiast nowe menu Browse
(przegladanie). Rozkazy, których tradycyjnie szukalismy w menu
Debug zostały rozrzucone do innych menu. I tak:

Menu Compile zawiera:

Compile (kompilacja do *.OBJ),
Make (kompilacja i konsolidacja do *.EXE),
Link (konsolidacja bez powtórnej kompilacji),
Build all (konsolidacja wszystkich modułów),
Information... (informacja o przebiegu kompilacji),
Remove messages (usuwanie komunikatów z pliku wynikowego)

Menu Run zawiera:

Run (uruchomienie i ewentualna rekompilcja),
Arguments... (argumenty uruchomieniowe z wiersza rozkazu),
Debugger (zamiast w Debug - TU!)
Debugger arguments... (argumenty dla Debuggera)

Menu Project zawiera:

Open project - otwórz (nowy lub istniejacy) plik projektu,

Close project - zamknij projekt,
Add item... - dodaj element (plik) do projektu,
Delete item - usun element (plik) z projektu,
Include ˙˙files... ˙˙- ˙˙podaj ˙katalog ˙zawierajacy ˙dodatkowe

dołaczane do programu pliki nagłówkowe *.H

W menu Options (zestaw znany juz z Borland C++) warto zwrócic
uwage na pewna dodatkowa mozliwosc. Jak wiemy z doswiadczenia,
uruchamiajac program czesto dokonujemy zmian i korekt w pliku
zródłowym *.C, czy *.CPP. Znacznie rzadziej jednak zmieniamy
zestaw dołaczanych do programu plików nagłówkowych *.H. Wiemy
równiez, ze kompilacja tych własnie plików nagłówkowych zajmuje
czesto lwia czesc czasu całej kompilacji i konsolidacji
programu. Borland zauwazył to i w okienku dialogowym:

Options | Compiler | Code generation --> Code Generation Options


umiescił opcje Pre-compiled headers (pliki nagłówkowe wstepnie
skompilowane wczesniej - i tylko jeden raz). Szczególnie w
przypadku aplikacji okienkowych moze to znacznie przyspieszyc
proces uruchamiania i "szlifowania" naszych programów. Nie ma
jednak nic za darmo. Borland/Turbo C++ po skompilowaniu plików
nagłówkowych tworzy na dysku roboczy plik *.SYM nadajac mu nazwe

zgodna z nazwa biezacego projektu (jest to zwykle nazwa głównego

modułu *.CPP) i do poprawnego działania wymaga kilkadziesiat lub

nawet kilkaset kilobajtów dodatkowej przestrzeni na dysku.

[!!!]UWAGA
________________________________________________________________

Jesli przenosisz projekt na dyskietke i tam kontynuujesz prace
nad projektem, pamietaj, ze moze zabraknac miejsca na
prekompilowany plik .SYM.
________________________________________________________________


Czytelnik zechce sam sprawdzic w jakim stopniu przyspieszy to
kompilacje naszego własnego programu proceduralno -
zdarzeniowego WINPZ1.CPP:

WINZ1.CPP. Jednomodułowa aplikacja proceduralno - zdarzeniowa
dla Windows.
________________________________________________________________
#include <windows.h>
#pragma argused

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow )
{
WNDCLASS Okno1;
MSG komunikaty;
HWND NrOkna;
125
LPSTR LongPtr1 = "Okno 1";
LPSTR lp2 = "AM: BC++ 3..4/Reczne sterowanie (1)";

if (hPrevInstance == 0)
{
Okno1.style= CS_HREDRAW | CS_VREDRAW ;
Okno1.lpfnWndProc= WndProc;
Okno1.cbClsExtra = 0;
Okno1.cbWndExtra= 0;
Okno1.hInstance = hInstance;
Okno1.hCursor = LoadCursor(0, IDC_CROSS );
Okno1.hbrBackground= GetStockObject(WHITE_BRUSH );
Okno1.lpszMenuName= 0;
Okno1.lpszClassName= LongPtr1;

if (!RegisterClass(&Okno1))
return 0;
}

NrOkna = CreateWindow(LongPtr1, lp2, WS_VISIBLE |
WS_SYSMENU |
WS_MINIMIZEBOX | WS_VSCROLL | WS_MAXIMIZEBOX,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
0, 0, hInstance, 0);

ShowWindow(NrOkna, nCmdShow);
UpdateWindow(NrOkna);

while (GetMessage(&komunikaty, 0, 0, 0))
{
TranslateMessage(&komunikaty );
DispatchMessage(&komunikaty );
}
return 0;
}

long FAR PASCAL WndProc (HWND NrOkna, unsigned
KomunikatWindows,

WORD wParam, LONG lParam)
{
HDC NrKontekstu;
PAINTSTRUCT struktura_graficzna;
RECT prostokat;

switch(KomunikatWindows)
{
case WM_PAINT:
{
NrKontekstu = BeginPaint(NrOkna, &struktura_graficzna);
GetClientRect(NrOkna, &prostokat);
TextOut(NrKontekstu,80,50, ": Reczne sterowanie:", 20 );
TextOut(NrKontekstu, 5,70, "Tu -->", 6);
TextOut(NrKontekstu, 5, 85, "Blad:", 5);
TextOut(NrKontekstu,75,70, "-----------------------------",

40);
TextOut(NrKontekstu,30,110, "Programowanie proceduralno -
zdarzeniowe.", 41 );
TextOut(NrKontekstu,30,135, "Szablon moze zostac rozbudowany

o inne funkcje.", 47 );
TextOut(NrKontekstu,30,180, "RECZNIE panujemy np. nad:", 25
);
TextOut(NrKontekstu,20,220, "paskiem tytulowym okna, tytulem

ikonki...", 41);
TextOut(NrKontekstu, 100, 250, "!KONIEC - [Alt]+[F4]", 20);

EndPaint(NrOkna,&struktura_graficzna);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
return DefWindowProc(NrOkna,KomunikatWindows,wParam,lParam);

}
return 0;
}

Program demonstruje opisane wyzej mechanizmy, moze byc
uruchamiany wielokrotnie i sprowadzony do ikony. Z uwagi na brak

zdefiniowanych dodatkowych zasobów (brak w projekcie plików:
.RC - resources - zasoby
.ICO - ikona
.DEF - definicji
.PRJ lub .IDE - projektu
.DSK - konfiguracyjnego
itp.)

podczas kompilacji programu wystapia dwa komunikaty
ostrzegawcze. Komunikaty te mozna zignorowac.

A oto druga przykładowa aplikacja w tym samym stylu. Tym razem
funkcja okienkowa reaguje na nacisniecie lewego klawisza myszki,

co powoduje wygenerowanie komunikatu WM_LEFTBUTTONDOWN.

Program WINZ-2.CPP
________________________________________________________________
#include <windows.h>
#include <string.h>
#pragma argused

char napis[10];
int X, Y;

LONG FAR PASCAL WndProc (HWND, WORD, WORD, LONG);

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow )
{
WNDCLASSwndClass;
MSGmsg;
HWNDhWnd;
LPSTR Lp1 = "Mysza1";
LPSTR lp2 = "WINPZ2: Wykrywanie Lewego Klawisza
Myszki";

if (!hPrevInstance)
{
wndClass.style= CS_HREDRAW | CS_VREDRAW ;
wndClass.lpfnWndProc= WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra= 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = 0;
wndClass.hCursor= LoadCursor(0, IDC_ARROW );
wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );
wndClass.lpszMenuName= 0;
wndClass.lpszClassName= Lp1;

if (!RegisterClass(&wndClass))
exit(1);
}

hWnd = CreateWindow(Lp1, lp2, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
0, 0, hInstance, 0);

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg );
DispatchMessage(&msg );
}
return 0;
}

LONG FAR PASCAL WndProc (HWND hWnd, WORD Message,
WORD wParam, LONG lParam)
{
HDC hDC;
PAINTSTRUCT ps;
RECT rect;
switch(Message)
{
case WM_SIZE:
hDC = GetDC( hWnd );
126
TextOut(hDC, 50, 100, "Wykrywanie nacisniecia", 22);
TextOut(hDC, 50, 120, "lewego klawisza myszki.", 23);
TextOut(hDC, 20, 140, "Komunikat o zdarzeniu: ", 22);
TextOut(hDC, 20, 156, "Left Button Down - LBUTTONDOWN", 31);

TextOut(hDC, 50, 170, "Po wcisnieciu klawisza,", 23);
TextOut(hDC, 50, 190,"w biezacej pozycji kursora, pojawi sie

napis <-- Tu!.", 52);
ReleaseDC(hWnd, hDC);
break;

case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
TextOut(hDC, X,Y, napis, strlen(napis));
EndPaint(hWnd, &ps);
break;

case WM_LBUTTONDOWN:
strcpy(napis,"<-- Tu !");
X = LOWORD(lParam);
Y = HIWORD(lParam);
InvalidateRect(hWnd, 0, TRUE);
UpdateWindow(hWnd);
break;

case WM_DESTROY:
PostQuitMessage(0); break;

default:
return DefWindowProc(hWnd, Message, wParam, lParam);
}
return 0;
}

Plik nagłówkowy STRING.H pojawia sie ze wzgledu na obecnosc
funkcji strlen() wyznaczajacej długosc napisu. Zmienne X i Y to
biezace (wzgledne) współrzedne kursora myszki w momencie
nacisniecia klawisza. Program demonstruje nastepujace efekty:

X = LOWORD(lParam);

- przekazanie współrzednej X przy pomocy parametru lParam
(LOWORD to LOw WORD of the double word - młodsze słowo podójnego

słowa).

Y = HIWORD(lParam);

Analogicznie - przekazanie współrzednej Y (HIgh WORD of the
double word). Funkcja InvalidateRect() powoduje uznanie
prostokanego pola za nieaktualne. Funkcja UpdateWindow()
"odswieza" okno. Dzieki temu tandemowi napis znika i pojawia sie

w nowym miejscu.

PROJEKT.

Aby skompilowac powyzsze programy przykładowe nalezy:

1. Uruchomic kompilator C++.
2. Załadowac do okienka edycyjnego (File | Open) plik z tekstem
zródłowym programu.
3. Wybrac rozkaz Compile z menu Compile.
Przed kompilacja i konsolidacja (jesli był inny) ustawic sposób
tworzenia kodu wynikowego [Windows EXE].

Kompilacja przebiegnie poprawnie (pamietaj o Opcjach i
Katalogach), mimo to pojawia sie jednak dwa komunikaty
ostrzegawcze. W okienku "Compile Status" (stan/przebieg
kompilacji) pojawi sie zawartosc:

Lines 3832 (znakomita wiekszosc to WINDOWS.H,
prekompilacja byłaby celowa)
Warnings: 1
Errors: 0

Jesli wybierzesz klawisz [OK] w okienku "focus" (aktywnosc)
zostanie przekazana do okienka komunikatów "Message" a tam
pojawi sie napis:

Warning: Parameter 'lspzCmdLine' is never used.

Wskaznik do parametrów uruchomieniowych programu (Arguments)
pobieranych z wiersza rozkazu nie został ani raz uzyty w
programie. Na to nic nie mozemy poradzic. Po prostu argumenty
uruchomieniowe nie sa nam potrzebne. Wykonujemy wiec "klik"
(przekazanie "focusa") w okienku edycyjnym i mozemy przejsc do
nastepnej czynnosci:

4. Konsolidacja: Compile | Link.

W okienku "Message" znów pojawi sie ostrzezenie:

Linker Warning: No module definition file specified:
using defaults.
(brak wyspecyfikowanego pliku definicji .DEF; stosuje wartosci
domyslne)

I tu juz mozemy cos zaradzic. Mozemy zatem pokusic sie o
stworzenie naszego pierwszego pliku definicji (nazwa jest troche

mylaca - chodzi o zdefiniowanie sposobu wykorzystania zasobów
srodowiska Windows).

Aby utworzyc plik .DEF (jest to plik ASCII) nalezy:

1. Otworzyc nowe okienko edycyjne (nie wychodzac z IDE):
File | New
Otworzy sie okienko NONAMExx.CPP. Ta nazwa nie jest oczywiscie
najodpowiedniejsza, wiec umieszczamy plik we własciwym katalogu
(tym samym, co główny program *.CPP) przy pomocy rozkazu File |
Save as...
i nadajemy plikowi stosowna nazwe i rozszerzenie *.DEF. Okieno
pozostaje puste, ma jednak "focus" i nowa nazwe, np.
C:\..\PR.DEF.

3. Redagujemy nasz pierwszy plik definicji, np. tak:

NAME JAKAKOLWIEK // <-- nazwa aplikacji
DESCRIPTION 'Opis: A. MAJCZAK, BC C++ 3...4'
EXETYPE WINDOWS // <-- EXE dla Windows
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 4096 // <-- sterta 4 KB
STACKSIZE 5120 // <-- stos 5 KB

_______________________________________________________________
UWAGA:
W przypadku tworzenia bibliotek .DLL dane musza miec status
SINGLE (pojedyncze) zamiast MULTIPLE (wielokrotne). Uzycie tu
słowa MULTIPLE pozwoli nam na wielokrotne uruchamianie
aplikacji.
________________________________________________________________

Moznaby tu zapytac - po co to robic, skoro uzywamy standardowych
wartosci i obecnosc tego pliku nie wnosi nic nowego do sposobu
działania naszego programu?
Odpowiedz jest prosta. Majac taki plik bedziemy mogli
przesledzic stadia tworzenia tzw. projektu (w BC++ 4 bez tego
ani rusz). Zapisujemy zatem plik na dysk:

4. File | Save. (plik .DEF zostaje zapisany na dysku).
Poniewaz pracujemy w srodowisku Windows, okno edycji pliku *.DEF

mozemy traktowac podobnie jak kazde inne okno. Najwygodniej
zatem przejsc do okna edycji głównego pliku zródłowego *.CPP
przy pomocy własnego menu systemowego tegoz okna.

5. Menu Systemowe [-] | Zamknij.

I mozemy przystapic do tworzenia projektu składajacego sie z dwu

plików: *.CPP i *.DEF. Jesli, dla przykładu, przyjmiemy w tym
miejscu, ze nasze dwa moduły nazywaja sie: WINZ2.CPP i WINZ2.DEF

i sa przechowywane w katalogu głównym dysku C:\ , kolejnosc
czynnosci powinna byc nastepujaca:

1. Rozwijamy menu Project ([Alt]+[P] lub myszka).
2. Wybieramy z menu rozkaz Open Project... (Utwórz projekt).
Pojawia sie okienko dialogowe Open Project File z domyslnym
rozszerzeniem *.PRJ (w BC 4+ - *.IDE).
3. Do okienka File Name:
wpisujemy nazwe pliku z opisem projektu: np. WINZ2.PRJ.
W dolnej czesci ekranu otwiera sie okienko
127
Project: WINZ2
4. Wybieramy z menu Project rozkaz Add item... (dodaj element do

projektu). Pojawia sie okienko dialogowe "Add to Project List"
(dodawanie do listy elementów projektu).
5. Do okienka File Name: wpisujemy nazwe głównego pliku
projektu: WINZ2.CPP (*.cpp jest domyslnym rozszerzeniem).
Plik mozemy wybrac takze z listy w okienku Files: .
6. Wybieramy w okienku dialogowym klawisz [+Add] (dodaj do
projektu).
7. Wpisujemy nazwe kolejnego pliku wchodzacego w skład projektu
(w tym przypadku WINZ2.DEF).
8. Wybieramy klawisz [+Add] w okienku.
UWAGA: Czynnosci 7) i 8) w przypadku bardziej złozonych
projektów beda powtarzane wielokrotnie.
9. Wybieramy klawisz [Done] w okienku (zrobione/gotowe).
Konfigurowanie projektu zostało zakonczone.
10. Przy pomocy rozkazów Compile, Link, Make, Build all, Run
mozemy teraz skompilowac, skonsolidowac i uruchomic nasz program

w postaci projektu. Ostrzezenie Linkera zniknie.

[!!!]UWAGA
________________________________________________________________

W dolnej czesci ekranu w stadium tworzenia projektów ( i pózniej

po załadowaniu pliku projektu [Open Project] pojawi sie lista
plików. Do trybu edycji pliku mozesz przjsc poprzez dwukrotne
kliniecie pliku na tej liscie.
Zwróc uwage, ze pliki projektów .PRJ ( w Borland 4+ .IDE)
przechowuja równiez informacje o konfiguracji. Najwazniejsza z
nich to informacja o katalogach, z których korzysta kompilator:

Options | Directories... | Include
Options | Directories... | Library
Options | Directories... | Output
________________________________________________________________

Najwygodniej przechowywac wszystkie pliki wchodzace w skład
jednego projektu w odrebnym katalogu dyskowym. Dla wprawy załóz
odrebny katalog i zapisz tam pliki:

*.CPP
*.DEF
*.PRJ (lub *.IDE)

dla swoich pierwszych dwóch projektów, które własnie powstały.

[!!!] UWAGA
________________________________________________________________

Ten sam plik definicji mozesz wykorzystywac do tworzenia
nastepnych przykładowych aplikacji typu Windows EXE.
________________________________________________________________



LEKCJA 43: Elementy sterujace i zarzadzanie programem.
________________________________________________________________
Jak sterowac praca aplikacji. Jak umieszczac elementy
graficzne-sterujace w oknie aplikacji. Najczesciej stosowane
funkcje API Windows.
________________________________________________________________

Elementy sterujace praca aplikacji w Windows (ang. controls) sa
równiez swoistymi okienkami (tyle, ze potomnymi - Child Window
wobec głównego okna aplikacji - Parent Window).
Do utworzenia takiego specjalnego okna równiez mozna uzyc
funkcji CreateWindow(). Jesli okno ma stac sie nie głównym oknem
aplikacji, lecz oknem sterujacym przebiegiem programu, funkcja
wymaga podania nastepujacych argumentów:

- rodzaj klasy sterujacej (ang. control class)
- rodzaj elementu sterujacego (ang. control style)

Typowe rodzaje elementów (obiektów) starujacych w srodowisku
Windows:

BUTTON - klawisz rozkazu, prostokatne okno typu
Child, reprezentujace przycisk, który
uzytkownik moze właczyc; przycisk moze
byc opatrzony etykieta (text label).

COMBOBOX - okienko dialogowe kombinowane. Jest
złozeniem klasy EDIT i LISTBOX;

LISTBOX - oknienko z lista (zwykle element
składowy okienka dialogowego typu
Combo Box.

STATIC - pole statyczne (bez prawa edycji).
Niewielkie okno zawierajace tekst lub

grafike; słuzy z reguły do oznaczania

innych okien sterujacych.

SCROLLBAR - pasek przewijania (pionowy - Vertical

Scroll Bar; poziomy - Horizontal
Scroll Bar).

Style klawiszy sterujacych (Button Styles):

BS_PUSHBUTTON - Klawisz. Okno sterujace wysyła, po
kazdym wyborze klawisza
(klikniecie), wiadomosc do okna
maciezystego (Parent Window).

BS_RADIOBUTTON - Okragły przełacznik działajacy
zwykle na zasadzie @tylko jeden
z grupy".

BS_CHECKBOX - - prostokatny przełacznik [X]
właczajacy (aktywna) lub
wyłaczajacy (nieaktywna)
opcje. Działa niezaleznie od
pozostałych.

Inne style okreslaja np. sposób edycji tekstu (ES_LEFT,
ES_MULTILINE, itp.) Szczegóły - patrz system Help - Windows API.
Oto przykład utworzenia okna elementu sterujacego typu "Klawisz"

(BUTTON to nazwa typu):

hControlWnd = CreateWindow ("BUTTON", " Napis_na_Klawiszu ",
BS_PUSHBUTTON |WS_CHILD | WS_VISIBLE,
10, 20, 30, 40,
hWnd, ID_Elem, hInstance, 0);

Identyfikator ID_Elem jest potrzebny, gdy w jednym oknie
znajduje sie kilka elementów sterujacych - pozwala na ich
rozpoznawanie w programie. Sposób przekazywania informacji o
kliknieciu klawisza przypomne na przykładzie okienka
komunikatów:

if(IDOK==MessageBox(0, "", "", MB_OK)) ...

IDOK to predefiniowany w Windows identyfikator klawisza [OK].
Oto krótkie wyjasnienie pozostałych elementów:

10, 10, 30, 20, - współrzedne. x, y, szerokosc, wysokosc
hWnd, - oznacznik okna maciezystego

Przesuwanie i zmiana wielkosci elementu sterujacego.

Funkcja MoveWindow() przesuwa okno we wskazane miejsce:

MoveWindow(hKlawisz, 10, 10, 20, 30, TRUE);

Poniewaz okno elementu sterujacego ma zadane wzgledne
współrzedne w oknie maciezystym, gdy okno macierzyste zostanie
przesuniete - element sterujacy bedzie przesuniety
automatycznie. Równiez po zmianie rozmiarów okna maciezystego
okno elementu sterujacego zmienia połozenie, zawsze jednakowe
wzgledem lewego górnego rogu.

Usuwanie okna sterujacego

Okienko elementu sterujacego mozemy usunac (jak i kazde inne
okna) przy pomocy funkcji:

DestroyWindow(hKlawisz);


128
Przekazywanie informacji do- i z- okna elementu sterujacego

Zdarzenie w oknie elementu sterujacego - np. klikniecie klawisza

- powoduje wygenerowanie komunikatu WM_COMMAND. Towarzyszace
komunikatowi parametry przenosza istotne informacje:

wParam - identyfikator elementu sterujacego,
lParam - dla wcisnietego klawisza bedzie to BN_CLICKED.

Niektóre komunikaty Windows moga byc kierowane do okna elementu
sterujacego i wymuszac pewne operacje. Dla przykładu komunikat
WM_GETTEXTLENGTH przesłany do okienka edycyjnego typu Text Edit
Box (element sterujacy klasy EDIT) jest zadaniem podania
długosci tekstu wpisanego własnie do okienka. Aby Windows
wygenerowały komunikat i przesłały go do naszego elementu
sterujacego - musimy "poprosic" przy pomocy funkcji
SendMessage() (WyslijKomunikat):

DlugTekstu = SendMessage(hEditWnd, WM_GETTEXTLENGHT, 0, 0);
gdzie:
hEditWnd jest identyfikatorem elementu - okienka edycyjnego

[???]Robi na "szaro'?
________________________________________________________________
Podobnie jak opcje w menu - klawisze takze moga zostac
udostepnione (ang. enable), badz zablokowane (ang. disable).
Jesli hKlawisz bedzie identyfikatorem elementu sterujacego,
mozna go udostepnic (1), badz zablokowac (0) przy pomocy
funkcji:

EnableWindow(hKlawisz, 0);
EnableWindow(hKlawisz, 1);
________________________________________________________________

Typowy projekt dla srodowiska Windows składa sie z kilku (czasem

kilkunastu) plików: .H, .MNU, .DLG, .RC, .DEF, .PRJ, .ICO, .BMP,

itp. Kompilator zasobów generuje na podstawie tego "składu"
koncowy plik aplikacji.

------------------Plik MEDYT-01.H-------------------------------
#define szAppName "MEDYT-01"
#define ID_EDIT 200

------------------Plik główny: MEDYT-01.CPP---------------------

#include <windows.h>
#include "EDIT.H"
#pragma argused

HWND hEditWnd;

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
WNDCLASS wndClass;
MSG msg;
HWND hWnd;
RECT rect;

if ( !hPrevInstance )
{
wndClass.style= CS_HREDRAW | CS_VREDRAW ;
wndClass.lpfnWndProc= WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra= 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = LoadIcon(NULL, szAppName);
wndClass.hCursor= LoadCursor(NULL, IDC_CROSS);
wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );
wndClass.lpszMenuName= NULL;
wndClass.lpszClassName= szAppName;

if (!RegisterClass(&wndClass))
return 0;
}

hWnd = CreateWindow(szAppName,
"MEDYT-01", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
0, 0, hInstance, 0);

GetClientRect(hWnd, (LPRECT) &rect);
hEditWnd = CreateWindow ("Edit",NULL, WS_CHILD | WS_VISIBLE |
ES_MULTILINE | WS_VSCROLL |
WS_HSCROLL | ES_AUTOHSCROLL |
ES_AUTOVSCROLL, 0, 0,(rect. right -
rect. left),
(rect. bottom - rect.
top),hWnd,IDC_EDIT, hIstance,NULL);
if( ! hEditWnd )
{
DestroyWindow(hWnd);
return 0;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg );
DispatchMessage(&msg );
}
return 0;
}

long FAR PASCAL WndProc (HWND hWnd, unsigned Message,
WORD wParam, LONG lParam)
{
switch(Message)
{
case ID_EDIT:
if(HIWORD(lParam)==EN_ERRSPACE)

/* starsze słowo lParam zawiera własciwe dla okna edycyjnego
wiadomosci, jezeli jest to EN_ERRSPACE - okno sterujace nie
moze alokowac dodatkowego obszaru pamieci */

{
MessageBox (GetFocus(), "Brak pamieci", "MEDYT-01",
MB_ICONSTOP | MB_OK);
}
break;
case WM_SETFOCUS:
SetFocus(hEditWnd);
break;

/* Pierwsze dwa parametry funkcji MoveWindow sa ustawione na
zero, dzieki temu po zastosowaniu tej funkcji nie zmieni sie

wzajemne połozenie obu okien, a jedynie uaktualnianiu
ulegnie okno sterujace. */

case WM_SIZE:
MoveWindows(hEditWnd, 0, 0, LOWORD(lParam));
HIWORD(lParam), TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;

default:
return (DefWindowProc(hWnd,Message,wParam,lParam));
}
return 0;
}

Jak sterowac praca aplikacji. Jak umieszczac elementy
graficzne-sterujace w oknie aplikacji. Najczesciej stosowane
funkcje API Windows.
________________________________________________________________

Elementy sterujace praca aplikacji w Windows (ang. controls) sa
równiez swoistymi okienkami (tyle, ze potomnymi - Child Window
wobec głównego okna aplikacji - Parent Window).
Do utworzenia takiego specjalnego okna równiez mozna uzyc
funkcji CreateWindow(). Jesli okno ma stac sie nie głównym oknem

aplikacji, lecz oknem sterujacym przebiegiem programu, funkcja
wymaga podania nastepujacych argumentów:

- rodzaj klasy sterujacej (ang. control class)
- rodzaj elementu sterujacego (ang. control style)
129

Typowe rodzaje elementów (obiektów) starujacych w srodowisku
Windows:

BUTTON - klawisz rozkazu, prostokatne okno typu
Child, reprezentujace przycisk, który
uzytkownik moze właczyc; przycisk moze
byc opatrzony etykieta (text label).

COMBOBOX - okienko dialogowe kombinowane. Jest
złozeniem klasy EDIT i LISTBOX;

LISTBOX - oknienko z lista (zwykle element
składowy okienka dialogowego typu
Combo Box.

STATIC - pole statyczne (bez prawa edycji).
Niewielkie okno zawierajace tekst lub

grafike; słuzy z reguły do oznaczania

innych okien sterujacych.

SCROLLBAR - pasek przewijania (pionowy - Vertical

Scroll Bar; poziomy - Horizontal
Scroll Bar).

Style klawiszy sterujacych (Button Styles):

BS_PUSHBUTTON - Klawisz. Okno sterujace wysyła, po
kazdym wyborze klawisza
(klikniecie), wiadomosc do okna
maciezystego (Parent Window).

BS_RADIOBUTTON - Okragły przełacznik działajacy
zwykle na zasadzie @tylko jeden
z grupy".

BS_CHECKBOX - - prostokatny przełacznik [X]
właczajacy (aktywna) lub
wyłaczajacy (nieaktywna)
opcje. Działa niezaleznie od
pozostałych.

Inne style okreslaja np. sposób edycji tekstu (ES_LEFT,
ES_MULTILINE, itp.) Szczegóły - patrz system Help - Windows API.
Oto przykład utworzenia okna elementu sterujacego typu "Klawisz"

(BUTTON to nazwa typu):

hControlWnd = CreateWindow ("BUTTON", " Napis_na_Klawiszu ",
BS_PUSHBUTTON |WS_CHILD | WS_VISIBLE,
10, 20, 30, 40,
hWnd, ID_Elem, hInstance, 0);

Identyfikator ID_Elem jest potrzebny, gdy w jednym oknie
znajduje sie kilka elementów sterujacych - pozwala na ich
rozpoznawanie w programie. Sposób przekazywania informacji o
kliknieciu klawisza przypomne na przykładzie okienka
komunikatów:

if(IDOK==MessageBox(0, "", "", MB_OK)) ...

IDOK to predefiniowany w Windows identyfikator klawisza [OK].
Oto krótkie wyjasnienie pozostałych elementów:

10, 10, 30, 20, - współrzedne. x, y, szerokosc, wysokosc
hWnd, - oznacznik okna maciezystego

Przesuwanie i zmiana wielkosci elementu sterujacego.

Funkcja MoveWindow() przesuwa okno we wskazane miejsce:

MoveWindow(hKlawisz, 10, 10, 20, 30, TRUE);

Poniewaz okno elementu sterujacego ma zadane wzgledne
współrzedne w oknie maciezystym, gdy okno macierzyste zostanie
przesuniete - element sterujacy bedzie przesuniety
automatycznie. Równiez po zmianie rozmiarów okna maciezystego
okno elementu sterujacego zmienia połozenie, zawsze jednakowe
wzgledem lewego górnego rogu.

Usuwanie okna sterujacego

Okienko elementu sterujacego mozemy usunac (jak i kazde inne
okna) przy pomocy funkcji:

DestroyWindow(hKlawisz);


Przekazywanie informacji do- i z- okna elementu sterujacego

Zdarzenie w oknie elementu sterujacego - np. klikniecie klawisza

- powoduje wygenerowanie komunikatu WM_COMMAND. Towarzyszace
komunikatowi parametry przenosza istotne informacje:

wParam - identyfikator elementu sterujacego,
lParam - dla wcisnietego klawisza bedzie to BN_CLICKED.

Niektóre komunikaty Windows moga byc kierowane do okna elementu
sterujacego i wymuszac pewne operacje. Dla przykładu komunikat
WM_GETTEXTLENGTH przesłany do okienka edycyjnego typu Text Edit
Box (element sterujacy klasy EDIT) jest zadaniem podania
długosci tekstu wpisanego własnie do okienka. Aby Windows
wygenerowały komunikat i przesłały go do naszego elementu
sterujacego - musimy "poprosic" przy pomocy funkcji
SendMessage() (WyslijKomunikat):

DlugTekstu = SendMessage(hEditWnd, WM_GETTEXTLENGHT, 0, 0);
gdzie:
hEditWnd jest identyfikatorem elementu - okienka edycyjnego

[???]Robi na "szaro'?
________________________________________________________________
Podobnie jak opcje w menu - klawisze takze moga zostac
udostepnione (ang. enable), badz zablokowane (ang. disable).
Jesli hKlawisz bedzie identyfikatorem elementu sterujacego,
mozna go udostepnic (1), badz zablokowac (0) przy pomocy
funkcji:

EnableWindow(hKlawisz, 0);
EnableWindow(hKlawisz, 1);
________________________________________________________________

Typowy projekt dla srodowiska Windows składa sie z kilku (czasem

kilkunastu) plików: .H, .MNU, .DLG, .RC, .DEF, .PRJ, .ICO, .BMP,

itp. Kompilator zasobów generuje na podstawie tego "składu"
koncowy plik aplikacji.

------------------Plik MEDYT-01.H-------------------------------
#define szAppName "MEDYT-01"
#define ID_EDIT 200

------------------Plik główny: MEDYT-01.CPP---------------------

#include <windows.h>
#include "EDIT.H"
#pragma argused

HWND hEditWnd;

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
WNDCLASS wndClass;
MSG msg;
HWND hWnd;
RECT rect;

if ( !hPrevInstance )
{
wndClass.style= CS_HREDRAW | CS_VREDRAW ;
wndClass.lpfnWndProc= WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra= 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = LoadIcon(NULL, szAppName);
wndClass.hCursor= LoadCursor(NULL, IDC_CROSS);
wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );
wndClass.lpszMenuName= NULL;
130
wndClass.lpszClassName= szAppName;

if (!RegisterClass(&wndClass))
return 0;
}

hWnd = CreateWindow(szAppName,
"MEDYT-01", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
0, 0, hInstance, 0);

GetClientRect(hWnd, (LPRECT) &rect);
hEditWnd = CreateWindow ("Edit",NULL, WS_CHILD | WS_VISIBLE |
ES_MULTILINE | WS_VSCROLL |
WS_HSCROLL | ES_AUTOHSCROLL |
ES_AUTOVSCROLL, 0, 0,(rect. right -
rect. left),
(rect. bottom - rect.
top),hWnd,IDC_EDIT, hIstance,NULL);
if( ! hEditWnd )
{
DestroyWindow(hWnd);
return 0;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg );
DispatchMessage(&msg );
}
return 0;
}

long FAR PASCAL WndProc (HWND hWnd, unsigned Message,
WORD wParam, LONG lParam)
{
switch(Message)
{
case ID_EDIT:
if(HIWORD(lParam)==EN_ERRSPACE)

/* starsze słowo lParam zawiera własciwe dla okna edycyjnego
wiadomosci, jezeli jest to EN_ERRSPACE - okno sterujace nie
moze alokowac dodatkowego obszaru pamieci */

{
MessageBox (GetFocus(), "Brak pamieci", "MEDYT-01",
MB_ICONSTOP | MB_OK);
}
break;
case WM_SETFOCUS:
SetFocus(hEditWnd);
break;

/* Pierwsze dwa parametry funkcji MoveWindow sa ustawione na
zero, dzieki temu po zastosowaniu tej funkcji nie zmieni sie

wzajemne połozenie obu okien, a jedynie uaktualnianiu
ulegnie okno sterujace. */

case WM_SIZE:
MoveWindows(hEditWnd, 0, 0, LOWORD(lParam));
HIWORD(lParam), TRUE);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;

default:
return (DefWindowProc(hWnd,Message,wParam,lParam));
}
return 0;
}



LEKCJA 44: O Okienkach dialogowych.
________________________________________________________________
O tym, jak konstruuje sie okienka dialogowe.
________________________________________________________________

Do wyswietlania okienek dialogowych w Windows API słuzy funkcja
DialogBox(), a do zakonczenia ich "zycia na ekranie" -
EndDialog(). Podobnie jak kazde okno, równiez okno dialogowe
musi miec swoja funkcje, obsługi komunikatów Windows. Zamiast
WindowProc() nazywa sie ja tradycyjnie DlgProc():

BOOL FAR PASCAL DlgProc(HWND hDLG, unsigned Message, WORD
wParam, LONG lParam);
{
switch (message)
{
...
default: return (0);
}
}

Za wyjatkiem braku domyslnego handlera Windows -
DefWindowProc(), który jest zbedny, w zwiazku z wewnetrznie
przyjmowanymi wartosciami domyslnymi, funkcja podobna jest
bardzo w swojej konstrukcji do WindowProc(). Funkcja zwraca
wartosc FALSE (czyli 0), jesli przesłany komunikat nie został
obsłuzony. Typowymi komunikatami, które rozpatruje wiekszosc
okienek dialogowych, sa WM_INITDIALOG oraz WM_COMMAND.

Przykład okienka dialogowego:

------------------Plik: DLGBOX1.H-------------------------------

#define szAppName "DLGBOX1"
#define IDM_DLG1 100

------------------Plik zasobów: DLGBOX1.RC----------------------

#include "DLGBOX1.H"
#include <windows.h>

IDI_ICON ICON CONTROL.ICO

DLGBOX1 MENU
BEGIN
MENUITEM "&O DlgBox" IDM_DLG1
/* to menu pojawi sie w oknie maciezystym */
END

DLGBOX1 DIALOG 30,30,200,100

/* Pierwsze liczby to współrzedne lewego-górnego rogu okna, dwie

nastepne - to szerokosc i długosc. Współrzedne sa wzgledne.
Punkt (0,0) to naroznik okna maciezystego */

STYLE WS_POPUP | WS_DLGFRAME

BEGIN
LTEXT "Przyklad" -1, 0, 12, 160, 8
CTEXT "DLGBOX1 - Przyklad" -1, 0, 36, 160, 8
DEFPUSHBUTTON "OK" IDOK, 64, 60, 32,14, WS_GROUP
END
----------------------------------------------------------------

Pomiedzy para słów kluczowych BEGIN-END mozna umieszczac rózne
instrukcje sterujace. Definiuja one, jaki rodzaj okna
sterujacego ukaze sie w okienku dialogowym. Instrukcje te mozna
stosowac w nastepujacym formacie:

typ_okna "tekst" ID, x, y, szerokosc, wysokosc [styl]

Parametr styl jest opcjonalny. Styl okna okreslaja
identyfikatory predefiniowane w API Windows (WS_...). Parametr
ID jest odpowiednikiem identyfikatora dla okien potomnych typu
Child Window; dla okien sterujacych, które nie zwracaja
komunikatów do okna macierzystego, ma wartosc -1. IDOK
wykorzystalismy jako identyfikator dla okna sterujacego typu
BUTTON. Zostanie on wysłany do funkcji okienkowej jako wartosc
parametru wParam, gdy uzytkownik kliknie klawisz.

------------------Plik główny: DLGBOX1.CPP----------------------


#include <windows.h>
#include <stdio.h>
#include <string.h>
#include "DLGBOX1.H"
#pragma argused
131

HANDLE hInst;

long FAR PASCAL WndProc (HWND, unsigned, WORD, LONG) ;
BOOL FAR PASCAL ControlProc (HWND, unsigned, WORD, LONG) ;

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow )
{
WNDCLASS wndClass;
MSG msg;
HWND hWnd;
if ( !hPrevInstance )
{
wndClass.style= CS_HREDRAW | CS_VREDRAW ;
wndClass.lpfnWndProc= WndProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra= 0;
wndClass.hInstance = hInstance;
wndClass.hIcon = LoadIcon(NULL, szAppName);
wndClass.hCursor= LoadCursor(NULL, IDC_ARROW );
wndClass.hbrBackground= GetStockObject(WHITE_BRUSH );
wndClass.lpszMenuName= szAppName;
wndClass.lpszClassName= szAppName;

if (!RegisterClass(&wndClass))
return 0;
}

hInst = hInstance;
hWnd = CreateWindow(szAppName, "DLGBOX1",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0);

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg );
DispatchMessage(&msg );
}
return 0;
}

BOOL FAR PASCAL ControlProc (HWND hDlg, unsigned Message,
WORD wParam, LONG lParam)
{
switch(msg)
{
case WM_INITDIALOG:
return TRUE;
break;
case WM_COMMAND:
switch(wParam)
{
case IDOK:
case IDCANCEL:
EndDialog(hDlg,0);
return TRUE;
}
break;
}
return (0);
}

long FAR PASCAL WndProc (HWND hWnd, unsigned msg,
WORD wParam, LONG lParam)
{
FARPROC lpControlProc;
switch(Message)
{
case WM_COMMAND:
switch(wParam)
{
case IDM_ABOUT:
lpControlProc = MakeProcInstance((FARPROC) ControlProc, hInst);

DialogBox(hInst, "DLGBOX1", hWnd, lpControlProc);
return 0;
}
break;
case WM_DESTROY:
hDC = BeginPaint(hWnd , &ps);
TextOut(hDC, 30, 50,"Demo okienka dialogowego", 25);
TextOut(hDC, 30, 70,"Zastosuj menu...", 17);
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;

default:
return (DefWindowProc(hWnd,Message,wParam,lParam));
}
return 0;
}

Stosujac okienko edycyjne mozemy uzyc nastepujacych
predefiniowanych parametrów:

CONTROL - okresla okno elementu sterujacego zdefiniowane

przez uzytkownika.

CONTROL "tekst", klasa, styl, x, y, szerokosc, wysokosc

LTEXT - element sterujacy: okienko tekstowe
Wyrównywanie tesktu: do lewej.
RTEXT - j. w. Wyrównywanie tesktu: do prawej
CTEXT - j. w. Wyrównywanie tesktu: centrowanie w okienku
CHECKBOX - pole tekstowe po prawej stronie przełacznika typu
Check Box.
PUSHBUTTON - Klawisz z napisem.
LISTBOX - okienko z lista
GROUPBOX - grupa elementów sterujacych typu BUTTON; zgrupowanie

kilku elementów sterujacych i otoczenie ramka. Tekst

zostanie umieszczony w lewym górnym rogu.
DEFPUSHBUTTON - Klawisz domyslny w stylu BS_DEFPUSHBUTTON.
RADIOBUTTON - analogicznie jak dla stylu BS_RADIOBUTTON.
EDITTEXT - tworzy okno oparte na klasie EDIT.
COMBOBOX - tworz okno oparte na klasie COMBOBOX.
ICON - definiuje ikone oparta na klasie STATIC; w
okienku dialogowym.
SCROLLBAR - tworzy okno oparte na klasie SCROLLBAR.

[!!!]UWAGA
________________________________________________________________
W niektórych przypadkach okienko dialogowe moze byc głównym
oknem aplikacji.
________________________________________________________________


LEKCJA 45: Dołaczanie zasobów - menu i okienka dialogowe.
________________________________________________________________

Jak dodac menu i okienka dialogowe do aplikacji.
________________________________________________________________

Aby dodac do aplikacji menu nalezy utworzyc plik (ASCII) zasobów
*.RC, który zostanie uzyty w projekcie. Pierwsza instrukcja jest
MENU, "NazwaMenu",
MENU i para słów kluczowych (znanych z Pascala) BEGIN oraz END,
miedzy którymi znajdzie sie kombinacja instrukcji MENUITEM oraz
POPUP.

MENUITEM definiuje pozycje na głównym pasku menu - okresla - jak
bedzie wygladac i jaki identyfikator bedzie ja reprezentował.
Instrukcja POPUP pozwala, rozwinac liste pozycji wchodzacych w
skład danego menu. Nazwa menu moze byc uzyta podczas rejestracji
klasy danego okna jako wpisana w odpowiednie pole struktury na
której oparte jest okno. W ten sposób uzyskamy menu dla
wszystkich okien danej klasy.

BEGIN
POPUP "Rozkaz"
BEGIN
MENUITEM "Rozkaz 1", IDM_R1
MENUITEM "Rozkaz 2", IDM_R2
MENUITEM "Rozkaz 3", IDM_R3
END
POPUP "Kolor"
BEGIN
MENUITEM "Czarny", IDM_BLACK
132
MENUITEM "Niebieski", IDM_BLUE
MENUITEM "Zielony", IDM_GREEN
END
MENUITEM "Koniec", IDM_EXIT
END

Kazda pozycja ma własny identyfikator, np. IDM_EXIT, IDM_BLUE,
który Windows przekazuja do aplikacji, gdy zostaje ona wybrana
przez uzytkownika z systemu menu. Poniewaz kazdy identyfikator
powinien byc unikalny, najlepiej jest go zdefiniowac w pliku
zasobów .RC lub własnym pliku nagłówkowym .H:

#define IDM_EXIT 100
#define IDM_BLUE 101
#define IDM_R1 102
...

Mamy juz zdefiniowane menu w pliku zasobów, nalezy je teraz
dołaczyc do aplikacji na jeden z dwóch sposobów:

- Mozna okreslic menu jako menu danej klasy okien, gdy klasa ta

jest rejestrowana. W ten sposób dołaczymy menu do kazdego
okna opartego na tej klasie. Aby to wykonac, wystarczy
przypisac odpowiedniemu polu struktury nazwe naszego menu.
Jezeli obiekt klasy WNDCLASS nazwiemy Window1, to:

Window1.lpszMenuName = "NazwaMenu";

Gdy klasa zostanie zarejestrowana, kazde okno tej klasy bedzie
miało to samo menu, chyba ze dostarczymy odpowiedni
identyfikator menu w momencie tworzenia okna funkcja
CreateWindow().

- Drugim sposobem jest dołaczenie menu w momencie tworzenia
okna, wtedy tylko tworzone okno bedzie miało dane menu.

Nalezy najpierw załadowac menu przy uzyciu funkcji LoadMenu(),
która zwraca jego identyfikator:

HMENU hMenu = LoadMenu(hInstance, "NazwaMenu");

hWnd = CreateWindow(szAppName,
"Nazwa Aplikacji",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
hMenu, <-- tu
hIstance,
NULL );

Typowa praktyka jest dołaczenie pozycji menu do instrukcji
switch w funkcji okienkowej. Poniewaz Windows wysyła komunikat
WM_COMMAND do odpowiedniej funkcji okienkowej w odpowiedzi na
wybór pozycji przez uzytkownika, a parametr wParam zawiera
identyfikator tejze pozycji - mozna napisac tak:

case WM_COMMAND:
switch (wParam)
{
case IDM_R1:
... obsługa ...; break;
case IDM_R2:
... obsługa ...; break
case IDM_QUIT:
...DestroyWindow(...);
}

Jak rozbudowuje sie menu.

API Windows zawiera funkcje, umozliwiajace rozbudowe menu nawet
w ruchu aplikacji (run-time). Rozbudowa menu w konkretnym oknie
nie pociaga za soba zmian w innych, opartych na tej samej klasie

oknach. Jest to mozliwe, poniewaz w chwili tworzenia okna
otrzymuje ono swoja kopie menu (tradycyjne w C/C++ przekazywanie

kopii zmiennej do funkcji).

Nie wszystkie pozycje w menu sa w danym stadium pracy aplikacji
sensowne (mozliwe do wykonania). Zaraz przekonasz sie, jak to
sie dzieje, ze niektóre pozycje "robi sie na szaro". W API
Windows słuzy do tego funkcja:

EnableMenuItem (hMenu, IDM_R1, MF_DISABLED);
EnableMenuItem (hMenu, IDM_R1, MF_GRAYED);
EnableMenuItem (hMenu, IDM_R1, MF_ENABLED);

Rozkaz R1 skojarzony z identyfikatorem IDM_R1 i znajdujacy sie w

systemie menu o oznaczniku hMenu stanie sie kolejno zablokowany,

widoczny-lecz-niedostepny, dostepny.

Dodawanie i usuwanie pozycji w menu

Dodawanie pozycji do menu moze byc wykonane dwoma sposobami:
przez wstawienie pomiedzy istniejace pozycje lub na koncu listy.

W pierwszym przypadku nalezy uzyc funkcji InsertMenu(). Funkcja
ta pozwala jednoczesnie okreslic status pozycji, miedzy innymi
czy bedzie umieszczone nowe pole mozna okreslic dwoma sposobami:

przez identyfikator pozycji majacej byc przed nowa lub przez
numeracje poszczególnych, liczac id lewej skrajnej pozycji (C++
tradycyjnie liczy od zera). Sposób "odliczania" pozycji w
systemie menu okresla tryb (BYCOMMAND lub BYPOSITION - rozkaz,
badz pozycja):

InsertMenu(hMenu, IDM_R1, MF_BYCOMMAND |MF_DISABLED,
IDM_R5,
"Rozkaz 5");
InsertMenu(hMenu, 1, MF_ENABLED, IDM_R5, "Rozkaz 5");

Funkcja wstawi za pozycja "Rozkaz 1" nowa pozycje "Rozkaz 5",
jednoczesnie ustawia jej status. Druga funkcja dodajaca pozycje
do utworzonego systemu menu jest:

AppendMenu(hMenu, MF_ENABLED, IDM_R4, "Rozkaz 4");

Ponizej przykład zdefiniowania menu aplikacji w taki własnie
sposób:

case WM_CREATE:
hMenu = CreateMenu(); //Utworzenie menu
AppendMenu(hMenu, MF_ENABLED, IDM_R1, "Rozkaz 1");
AppendMenu(hMenu, MF_ENABLED, IDM_R2, "Rozkaz 2");
AppendMenu(hMenu, MF_ENABLED, IDM_R3, "Rozkaz 3");
SetMenu(hWnd, hMenu); //Wyswietlenie menu
...
break;

Usuwanie pozycji z menu mozna przeprowadzic dwoma sposobami:

- poprzez wskazanie numeru pozycji w systemie menu:

DeleteMenu(hMenu, 1, MF_BYPOSITION); //usunieta zostanie druga
//pozycja z systemu menu

- przez wyszczególnienie identyfikatorem pozycji

DeleteMenu(hMenu, IDM_R3, MF_BYCOMMAND);

Po usunieciu pozycji z menu Window usunie równiez wszystkie
zwiazane z nia submenu.

Zaznaczanie pozycji w menu (mark).

Obok pozycji w menu mozna umiescic znak markujacy ("ptaszek").
Znak markujacy mozna zainicjowac w pliku zasobów .RC. Dzieki
temu, uzytkownik w momencie otwarcia okna dowie sie z wygladu
menu o poczatkowym ustawieniu opcji.

MENUITEM "Rozkaz 2", IDM_R2, CHECKED

W trakcie pracy aplikacji nalezy posłuzyc sie funkcja
CheckMenuItem(). Zwykle najpierw kasujemy "ptaszka" przy
poprzedniej pozycji:

CheckMenuItem( hMenu, IDM_R2, MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_R3, MF_CHECKED);

Zmiany pozycji menu
133

Funkcja ModyfyMenu() pozwala na zmiane nazwy pozycji i jej
atrybutów. Oto przykłady uzycia tej funkcji:

ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND, IDM_R2, "Polecenie
2");

Identyfikator pozycji nie ulegnie zmianie, jedynie nazwa pola z
"Rozkaz 2" na "Polecenie 2". Mozemy zmienic jednoczesnie i
identyfikator, by nie pomylic sie w programie:

ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND, IDM_P2, "Polecenie
2");

Dodatkowo mozna ustawic za jednym zamachem i atrybuty:

ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND | MF_CHECKED |
MF_GRAYED,

IDM_R2, "Polecenie 2");

Uzycie grafiki w systemie menu.

W systemie menu aplikacji mozemy zamiast łancucha znaków "Rozkaz

2" umiescic element graficzny - np. w postaci mapy bitowej.
Zamiast pola o nazwie "Pole", wprowadza mape bitowa:

HMENU hMenu = GetMenu(hWnd);
HBITMAP hBitmap = LoadBitmap (hIstance, "Pole");
ModifyMenu(hMenu, IDM_R2, MF_BYCOMMAND | MF_BITMAP,
IDM_R2,
(LPSTR) MAKELONG (hBitmap, 0));

GetMenu() zwraca oznacznik aktualnego menu, potrzebny jako
pierwszy parametr funkcji ModifyMenu(). Drugim parametrem tej
funkcji jest identyfikator pozycji, która chcemy zmienic.
Trzecia okresla, ze zmiana ma byc wykonana przez wyszukanie
pozycji za posrednictwem jej identyfikatora oraz ze nowa pozycje

ma reprezentowac mapa bitowa. Czwarty parametr okresla
identyfikator nowej pozycji. Poniewaz ostatnim parametrem nie
jest juz wskaznik do łancucha znakowego, nalezy przesłac
oznacznik mapy bitowej jako mniej znaczace słowo tego parametru.

W tym celu 16-bitowy oznacznik jest łaczony z 16-bitowa stała, a

nastepnie poddawany konwersji do typu Long Pointer to STRing.

Zmiana menu aplikacji na kolejne.

Aplikacja w róznych stadiach pracy moze miec na ekranie rózne
(kilka czasem kilkanascie) menu. Wymiany menu w oknie aplikacji
mozna dokonac, załadowujac nowe menu funkcja LoadMenu() i
ustawiajac je jako aktualne funkcja SetMenu():

...
hMenu2 = LoadMenu (hIstance, "Menu2");
SetMenu (hWnd, hMenu2);
DrawMenuBar(...);
...

Menu i Menu2 powinny byc zdefiniowane w pliku zasobów *.RC.
Po kazdej zmianie menu nalezy uzyc funkcji DrawMenuBar(), aby
wprowadzone zmiany pojawiły sie na ekranie. Oto przykład
stosownego pliku zasobów:

Menu1 MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New" , IDM_NEW
MENUITEM "&Save", IDM_SAVE
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Options"
BEGIN
MENUITEM "Menu&1", IDM_M1,CHECKED
MENUITEM "Menu&2" , IDM_M2
END
END

Menu2 MENU
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open", IDM_OPEN
MENUITEM "&New" , IDM_NEW
MENUITEM "&Save", IDM_SAVE
MENUITEM "Save &As", IDM_SAVEAS
MENUITEM "&DOS shell", IDM_DOSSHELL
MENUITEM "E&xit", IDM_EXIT
END
POPUP "&Options"
BEGIN
MENUITEM "Menu&1", IDM_M1,
MENUITEM "Menu&2" , IDM_M2, CHECKED
END
END

ZASTOSOWANIE Resource Worshop

Takie pliki zasobów w Borland C++ mało kto tworzy dzis "na
piechote". BORLAND C++ oferuje do tego celu dwa
narzedzia:
Edytor zasobów - Resource Workshop
Automatyczny generator - DialogExpert (wersje 4+)

Najwygodniejszym sposobem jest zastosowanie edytora zasobów
Resource Workshop. Jest to tym wygodniejsze, ze Resource
Workshop pozwala jednoczesnie obserwowac i zródłowy plik *.RC
(ASCII) i efekt - menu w ruchu.
W srodowisku Borland C++ okienka dialogowe tworzy sie takze
zwykle przy pomocy Resource Worshop.
Tworzenie okienek dialogowych przy pomocy Resource Workshop
przypomina składanie budowli z gotowych klocków.

Kolejne elementy sterujace mozemy umieszczac w okienku
dialogowym poprzez wybranie ich z palety narzedzi i
przeniesienie do projektowanego okienka technika "pociagnij i
upusc" (drag & drop).

Po skróconym omówieniu najwazniejszych funkcji z API Windows
przejdzmy to niemniej krótkiej prezentacji zasad tworzenia
aplikacji przy pomocy biblioteki obiektów OWL.

[Z]
________________________________________________________________
1. Przeanalizuj program w pełnej wersji (na dyskietce).
2. Zmodyfikuj dowolna aplikacje przykładowa tak, by dołaczyc do
niej inna ikone.
3. Opracuj własne menu i własna ikone przy pomocy Resource
Workshop.
________________________________________________________________

Krótka instrukcja do Resource Workshop.

________________________________________________________________

1. Uruchomienie: Ikonka Worshop w oknie grupowym Borland C++.
2. Poczatek pracy: File | New Project...
3. Rodzaje zasobów do wyboru w okienku dialogowym "New project":

[ ] RC - plik zasobów
[ ] CUR - kursor
[ ] BMP - mapa bitowa
[ ] RES - plik zasobów w formie skompilowanej
[ ] ICO - ikonka
[ ] FNT - czcionki (Fonts)
Wybieramy odpowiednio: RC
4. Zmieni sie menu w głównym oknie Resource Workshop. Z menu
wybieramy Resource | New
W okienku dialogowym z listy Resource Type (rodzaj zasobów):
ACCELERATORS, BITMAP, CURSOR, DIALOG, FONT, ICON, MENU,
RCDATA,
STRINGTABLE, VERSINFO
wybieramy odpowiednio MENU lub DILOG.

Kolejny raz zmieni sie menu. W przypadku menu wybieramy:
Menu:
New pop-up - nowa pozycja POPUP
New menu item - nowa pozycja MENUITEM
Zwróc uwage, ze typowe menu File, Edit, Help jesy juz gotowe do
wstawienia (ukryte pod pozycjami New file pop-up, New edit
pop-up...).

134
W przypadku okienka dialogowego najwazniejsze jest menu Control.

Sa tam wszyskie rodzaje podstawowych elementów sterujacych (Push

button, Radio button, scroll bar, List box, Combo box, Edit box,

itd.). Projektujac okienko mozesz równiez wyswietlic siatke
(Grid).

Przy pomocy Resource Workshop mozesz poddawac edycji i
modyfikowac pliki zasobów zarówno nalezace do programów
przykładowych zawartoch na dyskietce, jak i zasoby "firmowych"
przykładów Borlanda. W katalogach \SOURCE (kody zródłowe .CPP) i

\EXAMPLES (przykłady - projekty) znajdziesz wiele rozmaitych
przykładów. Mozesz takze poddawac edycji pliki .BMP, .ICO i inne

niekoniecznie nalezace do pakietu Borland C++.
________________________________________________________________



LEKCJA 46: O PROGRAMACH OBIEKTOWO - ZDARZENIOWYCH.
________________________________________________________________
Po aplikacjach sekwencyjnych, proceduralno-zdarzeniowych, jedno-

i dwupoziomowych, pora rozwazyc dokładniej stosowanie technik
obiektowych.
________________________________________________________________

Programy pracujace w srodowisku Windows tworzone sa w oparciu o
tzw. model trójwarstwowy. Pierwsza warstwa to warstwa
wizualizacji, druga - interfejs, a trzecia - to własciwa
maszyneria programu. W tej lekcji zajmiemy sie "anatomia"
aplikacji wielowarstwowych a nastepnie sposobami wykorzystania
bogatego instrumentarium oferowanego przez Borlanda wraz z
kompilatorami BC++ 3+...4+.

Biblioteka OWL w wersjach BORLAND C++ 3, 3.1, 4 i 4.5 zawiera
definicje klas potrzebnych do tworzenia aplikacji dla Windows.
Fundamentalne znaczenie dla wiekszosci typowych aplikacji maja
nastepujace klasy:

TModule (moduł - program lub biblioteka DLL)
TApplication (program - aplikacja)
TWindow (Okno)

Rozpoczne od krótkiego opisu dwu podstawowych klas.

KLASA TApplication.

Tworzac obiekt klasy TNaszProgram bedziemy wykorzystywac
dziedziczenie od tej własnie klasy bazowej:

class TNaszProgram : public TApplication

Podstawowym celem zastosowania tej własnie klasy bazowej jest
odziedziczenie gotowej funkcji - metody virtual InitMainWindow()

(zainicjuj główne okno programu). Utworzenie obiektu klasy
TNaszProgram nastepuje zwykle w czterech etapach:

* Windows uruchamiaja program wywołujac główna funkcje WinMain()

lub OwlMain() wchodzaca w skład kazdej aplikacji.
* Funkcja WinMain() tworzy przy pomocy operatora new nowy obiekt

- aplikacje.
* Obiekt - aplikacja zaczyna funkcjonowac. Konstruktor obiektu
(własny, badz odziedziczony po klasie TApplication) wywołuje
funkcje - wirtualna metode InitMainWindow().
* Funkcja przy pomocy operatora new tworzy obiekt - okno
aplikacji.

Wskaznik do utworzonego obiektu zwraca funkcja GetApplication().

Dla zobrazowania mechanizmów ponizej przedstawiamy uproszczony
"wyciag" z dwu opisywanych klas. Nie jest to dokładna kopia kodu

zródłowego Borlanda, lecz skrót tego kodu pozwalajacy na
zrozumienie metod implementacji okienkowych mechanizmów wewnatrz

klas biblioteki OWL i tym samym wewnatrz obiektów obiektowo -
zdarzeniowych aplikacji.
A oto najwazniejsze elementy implementacji klasy TApplication:

- Konstruktor obiektu "Aplikacja":

TApplication::TApplication(const char far* name,
HINSTANCE Instance,
HINSTANCE prevInstance,
const char far* CmdLine,
int CmdShow,
TModule*& gModule)
{
hPrevInstance = prevInstance;
nCmdShow = CmdShow;
MainWindow = 0;
HAccTable = 0; //Accelerator Keys Table Handle
BreakMessageLoop = FALSE;
AddApplicationObject(this); //this to wskaznik do własnego
gModule = this; //obiektu, czyli do biez. aplikacji
}

Funkcja - metoda "Zainicjuj Instancje":

void TApplication::InitInstance()
{
InitMainWindow();
if (MainWindow)
{
MainWindow->SetFlag(wfMainWindow);
MainWindow->Create();
MainWindow->Show(nCmdShow);
}

Metoda "Zainicjuj główne okno aplikacji":

void TApplication::InitMainWindow()
{
SetMainWindow(new TFrameWindow(0, GetName()));
}

Metoda Run() - "Uruchom program":

int TApplication::Run()
{
int status;
{
if (!hPrevInstance) InitApplication();
InitInstance();
status = MessageLoop();
}

A oto petla pobierania komunikatów w uproszczeniu. "Pump" to po
prostu "pompowanie" komunikatów (message) oczekujacych (waiting)

w kolejce. PeekMessage() to sprawdzenie, czy w kolejce oczekuje
komunikat. PM_REMOWE to "brak komunikatu".

BOOL TApplication::PumpWaitingMessages()
{
MSG msg;
BOOL foundOne = FALSE;

while (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
foundOne = TRUE;
if (msg.message == WM_QUIT)
{
BreakMessageLoop = TRUE;
MessageLoopResult = msg.wParam;
::PostQuitMessage(msg.wParam);
break;
}

if (!ProcessAppMsg(msg))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
return foundOne;
}

int TApplication::MessageLoop()
135
{
long idleCount = 0;
MessageLoopResult = 0;
while (!BreakMessageLoop) {
TRY {
if (!IdleAction(idleCount++))
::WaitMessage();
if (PumpWaitingMessages())
idleCount = 0;
}
if (MessageLoopResult != 0) {
::PostQuitMessage(MessageLoopResult);
break;
}
})
}
BreakMessageLoop = FALSE;
return MessageLoopResult;
}

else if (::IsWindowEnabled(wnd)) {
*(info->Wnds++) = wnd;
::EnableWindow(wnd, FALSE);
}
}
return TRUE;
}


KLASA TWindow.

Klasa TWindow (Okno) zawiera implementacje wielu przydatnych
przy tworzeniu aplikacji "cegiełek". Ponizej przedstawiono
fragment pliku zródłowego (patrz \SOURCE\OWL\WINDOW.CPP). Łatwo
mozna rozpoznac pewne znane juz elementy.
...
extern LRESULT FAR PASCAL _export InitWndProc(HWND, UINT,
WPARAM, LPARAM);
...
struct TCurrentEvent //Struktura BiezaceZdarzenie
{
TWindow* win; //Wskaznik do okna
UINT message; //Komunikat
WPARAM wParam;
LPARAM lParam;
};
...
DEFINE_RESPONSE_TABLE(TWindow)
//Makro: Zdefiniuj tablice odpowiedzi na zdarzenia
//EV_WM_SIZE - Zdarzenie (EVent)-nadszedł komunikat WM_SIZE
...
EV_WM_SETCURSOR,
EV_WM_SIZE,
EV_WM_MOVE,
EV_WM_PAINT,
EV_WM_LBUTTONDOWN,
EV_WM_KILLFOCUS,
EV_WM_CREATE,
EV_WM_CLOSE,
EV_WM_DESTROY,
EV_COMMAND(CM_EXIT, CmExit),
...
END_RESPONSE_TABLE;

Funkcje - metody obsługujace komunikaty zaimplementowane zostały

wewnatrz klasy TWindow tak:

TWindow::EvCreate(CREATESTRUCT far&)
{
SetupWindow();
return (int)DefaultProcessing();
}

void TWindow::EvSize(UINT sizeType, TSize&)
{
if (Scroller && sizeType != SIZE_MINIMIZED)
{
Scroller->SetPageSize();
Scroller->SetSBarRange();
}
}

Metoda GetWindowClass() bardzo przypomina klasyczne
zainicjowanie zanej juz struktury WNDCLASS:

void TWindow::GetWindowClass(WNDCLASS& wndClass)
{
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = *GetModule();
wndClass.hIcon = 0;
wndClass.hCursor = ::LoadCursor(0, IDC_ARROW);
wndClass.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
wndClass.lpszMenuName = 0;
wndClass.lpszClassName = GetClassName();
wndClass.style = CS_DBLCLKS;
wndClass.lpfnWndProc = InitWndProc;
}

Skoro te wszystkie "klocki" zostały juz zaimplementowane
wewnatrz definicji klas, nasze programy powinny tylko umiejetnie

z nich korzystac a teksty zródłowe programów powinny ulec
skróceniu i uproszczeniu.

STADIA TWORZENIA OBIEKTOWEJ APLIKACJI.

Poniewaz znakomita wiekszosc dzisiejszych uzytkowników pracuje z

Windows 3.1, 3.11, i NT - zaczniemy tworzenie aplikacji od
umieszczenia na poczatku informacji dla OWL, ze nasz docelowy
program ma byc przeznaczony własnie dla tego srodowiska:

#define WIN31

Jak juz wiemy dzieki krótkiemu przegladowi struktury bazowych
klas przeprowadzonemu powyzej - funkcje API Windows sa w istocie

klasycznymi funkcjami posługujacymi sie mechanizmami jezyka C.
C++ jest "pedantem typologicznym" i przeprowadza dodatkowe
testowanie typów parametrów przekazywanych do funkcji (patrz
"Technika programowania w C++"). Aby ułatwic współprace,
zwiekszyc poziom bezpieczenstwa i "uregulowac" potencjalne
konflikty - dodamy do programu:

#define STRICT

Chcac korzystac z biblioteki OWL wypada dołaczyc własciwy plik
nagłówkowy:

#include <owl.h>

Plik OWL.H zawiera juz wewnatrz dołaczony WINDOWS.H, który
wystepował we wczesniejszych aplikacjach proceduralno -
zdarzeniowych i jeszcze pare innych plików.
Poniewaz chcemy skorzystac z gotowych zasobów - odziedziczymy
pewne cechy po klasie bazowej TApplication. Zgodnie z zasadami
programowania obiektowego chcac utworzyc obiekt musimy najpierw
zdefiniowac klase:

class TOkno ...

i wskazac po której klasie bazowej chcemy dziedziczyc:

class TOkno : public TApplication
{
...

Konstruktor obiektu klasy TOkno powinien tylko przekazac
parametry konstruktorowi klasy bazowej - i juz.

class TOkno : public TApplication
{
public:
TOkno(LPSTR name, HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nShow) : TApplication(name,
hInstance, hPrevInstance, lpCmdLine, nShow)
{
return;
}
virtual void InitMainWindow();
};

Umiescilismy w definicji klasy jeszcze jedna funkcje inicjujaca
główne okno aplikacji. Mozemy ja zdefiniowac np. tak:
136

void TOkno::InitMainWindow(void)
{
MainWindow = new (TWindow(0, "Napis - Tytul Okna"));
}

Działanie funkcji polega na utworzeniu nowego obiektu (operator
new) klasy bazowej TWindow. Główne okno stanie sie zatem
obiektem klasy TWindow (Niektóre specyficzne aplikacje posługuja

sie okienkiem dialogowym jako głównym oknem programu. W takiej
sytuacji dziedziczenie powinno nastepowac po klasie TDialog).
Konstruktorowi tego obiektu przekazujemy jako parametr napis,
który zostanie umieszczony w nagłówku głównego okna aplikacji.
Pierwszy argument (tu ZERO) to wskaznik do maciezystego okna,
poniewaz w bardziej złozonych aplikacjach wystepuja okna
maciezyste (parent) i okna potomne (child). Okno maciezyste to
zwykle obiekt klasy "główne okno" a okno potomne to najczesciej
okienko dialogowe, badz okienko komunikatów. W tym przypadku
wpisujemy zero, poniewaz program nie posiada w tym stadium
wczesniejszego okna maciezystego.
Pozostało nam jeszcze dodac funkcje WinMain() i pierwszy program

obiektowy w wersji "Maszyna do robienia nic" jest gotów.

Listing . Obiektowa "Maszyna do robienia nic"
________________________________________________________________
#define STRICT
#define WIN31
#include <owl.h>

class TOkno : public TApplication
{
public:
TOkno(LPSTR AName, HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
: TApplication(AName, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
void InitMainWindow(){MainWindow = new TWindow(NULL, Name);};
};

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TOkno OBIEKT("Windows - Program PW1", hInstance,
hPrevInstance, lpCmdLine, nCmdShow);
OBIEKT.Run();
return 0;
}
________________________________________________________________

Wykonanie takiej aplikacji przebiega nastepujaco. Windows
wywołuja główna funkcje WinMain(), która przekazuje swoje
parametry do konstruktora klasy TOkno::TOkno(). Konstruktor
przekazuje parametry do konstruktora klasy bazowej
TApplication(). Po skonstruowaniu obiektu w pamieci funkcja
wywołuje odziedziczona metode Run(). Funkcja Run() wywołuje
metody InitApplication() (zainicjuj aplikacje) i InitInstance()
(zainicjuj dane wystapienie programu). Metoda InitInstance()
wywołuje funkcje InitMainWindow(), która buduje główne okno
aplikacji na ekranie. Po pojawieniu sie okna rozpoczyna
działanie petla pobierania komunikatów (message loop). Petla
komunikatów działa az do otrzymania komunikatu WM_QUIT.

Rozbudujmy aplikacje o okienko komunikatów. Zastosujemy do tego
funkcje MessageBox(). Funkcja zostanie uzyta nie jako metoda
(składnik obiektu), lecz jako "wolny strzelec" (stand alone
function).

Listing B. Maszyna rozszerzona o okienka komunikatów.
________________________________________________________________

#define WIN31
#define STRICT
#include <owl.h>

class TOkno : public TApplication
{
public:
TOkno(LPSTR Nazwa, HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
: TApplication(Nazwa, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
void InitMainWindow(){MainWindow = new TWindow(NULL, "Okno
PW2" );};
};

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TOkno OBIEKT("Okno PW2", hInstance, hPrevInstance,
lpCmdLine, nCmdShow);
LPSTR p1 = "Jesli wybierzesz [Anuluj]\n- aplikacja nie
ruszy!";
LPSTR p2 = "START";

if (MessageBox(NULL, p1, p2, MB_OKCANCEL) == IDCANCEL)
MessageBox(NULL, "I juz..." , "KONIEC" , MB_OK);
else
OBIEKT.Run();
return 0;
}
________________________________________________________________
Uwagi techniczne.
Sciezki do katalogów:
..\INCLUDE;..\CLASSLIB\INCLUDE;..\OWL\INCLUDE;
..\LIB;..\CLASSLIB\LIB;..\OWL\LIB;
Konsolidacja:
Options | Linker | Settings: Windows EXE (typ aplikacji)
Options | Linker | Libraries:
- Container class Libraries: Static (bibl. klas CLASSLIB)
- OWL: Static (bibl. OWL statycze .LIB)
- Standard Run-time Lib: Static (bibl. uruchomieniowe .LIB)

(.) None - oznacza zadne (nie zostana dołaczone);
(.) Static - oznacza statyczne .LIB
(.) Dinamic - oznacza dynamiczne .DLL
________________________________________________________________

JAK ROZBUDOWYWAC OBIEKTOWE APLIKACJE?

Mimo całego uroku obiektowych aplikacji pojawia sie tu wszakze
drobny problem. Skoro komunikacja klawiatura/myszka -> program
-> ekran nie odbywa sie wprost, lecz przy pomocy wymiany danych
pomiedzy obiektami róznych warstw - w jaki sposób (w którym
miejscu programu) umiescic "zwyczajne" funkcje i procedury i jak

zorganizowac wymiane informacji. "Zwyczajne" funkcje beda
przeciez wchodzic w skład roboczych czesci naszych programów
(Engine). Rozwazmy to na przykładzie aplikacji reagujacej na
nacisniecie klawisza myszki. Najbardziej istotny -
"newralgiczny" punkt programu został zaznaczony w tekscie "<--
TU". Od Windows przejmiemy obsługe komunikatów
WM_LBUTTONDOWN,
WM_RBUTTONDOWN. Aby wiedziec, w którym miejscu ekranu jest
kursor myszki, wykorzystamy informacje przenoszone przez
parametr lParam.

Rozpoczniemy tworzenie programu od zdefiniowania klasy.

#define WIN31
#define STRICT
#include <stdio.h>
#include <string.h>
#include <owl.h>

class TNAplikacja : public TApplication
{
public:
TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE
hPrevInstance, LPSTR lpCmdLine, int nCmdShow) :
TApplication(AName, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
virtual void InitMainWindow();
};

Wykorzystamy okienko komunikatu do swiadomego zakonczenia pracy
aplikacji. Klasa TApplication jest wyposazona w metode
CanClose() (czy mozna zamknac?) słuzaca do zamykania głównego
okna aplikacji. Metoda została zaimplementowana tak:

BOOL TApplication::CanClose()
{
if (MainWindow)
return (MainWindow->CanClose());
else
137
return (TRUE);
}

Bedzie nam wiec potrzebna własna wersja metody CanClose() i
wskaznik do obiektu MainWindow. Wskaznik (typu far utworzony
przez składowe makro _FAR) wygenerujemy przy pomocy makra
_CLASSDEF(nazwa_klasy):

_CLASSDEF(TGOkno)

Implementujemy teraz klase główne okno aplikacji. Jako klase
bazowa stosujemy TWindow.

class TGOkno : public TWindow
{
public:
TGOkno(PTWindowsObject AParent, LPSTR ATitle)
: TWindow(AParent, ATitle) {};

Konstruktor tradycyjnie wykorzystujemy do przekazania parametrów

do konstruktora klasy bazowej. PTWindowsObject AParent to
wskaznik (PoinTer) do obiektu "okno" a ATitle to string - tytuł.

Obsługa komunikatów kierowanych do tego okna moze byc
realizowana przy pomocy metod zaimplementowanych jako elemeny
składowe klasy Główne Okno - TGOkno.
Program graficzny powinien reagowac raczej na myszke niz na
klawiature. Windows rozpoznaja zdarzenia zwiazane z myszka i
generuja komunikaty o tych zdarzeniach.

Zdarzenia myszki (mouse events).
________________________________________________________________

Komunikat Zdarzenie
________________________________________________________________
WM_MOUSEMOWE - przesunieto myszke (wewnatrz obszaru
roboczego - inside the client area -
ICA)
WM_LBUTTONDOWN - nacisnieto LEWY klawisz myszki (ICA)
WM_LBUTTONDBLCLK - nacisnieto dwukrotnie LEWY klaw. (ICA)
WM_LBUTTONUP - puszczono LEWY klawisz (ICA)
WM_RBUTTONDOWN - nacisnieto PRAWY klawisz myszki (ICA)
WM_RBUTTONDBLCLK - nacisnieto dwukrotnie PRAWY klaw. (ICA)
WM_RBUTTONUP - puszczono PRAWY klawisz (ICA)
WM_MBUTTONDOWN - nacisnieto SRODK. klawisz myszki (ICA)
WM_MBUTTONDBLCLK - nacisnieto dwukrotnie SROD. klaw. (ICA)
WM_MBUTTONUP - puszczono SRODKOWY klawisz (ICA)
WM_NCMOUSEMOVE - ruch myszki poza client area (NCA)
WM_NLBUTTONDOWN - nacisnieto LEWY klawisz myszki poza
obszarem roboczym - non-client area
(NCA)
WM_NCLBUTTONDBLCLK - nacisnieto dwukrotnie LEWY klaw. (NCA)
WM_NCLBUTTONUP - puszczono LEWY klawisz (NCA)
WM_NCRBUTTONDOWN - nacisnieto PRAWY klawisz myszki (NCA)
WM_NCRBUTTONDBLCLK - nacisnieto dwukrotnie PRAWY klaw.
(NCA)
WM_NCRBUTTONUP - puszczono PRAWY klawisz (NCA)
WM_NCMBUTTONDOWN - nacisnieto SR. klawisz myszki (NCA)
WM_NCMBUTTONDBLCLK - nacisnieto dwukrotnie SRODK. klaw.
(NCA)
WM_LBUTTONUP - puszczono SRODKOWY klawisz (NCA)
________________________________________________________________

Nastepna tabelka zawiera (znacznie skromniejszy) zestaw
komunikatów generowanych pod wpływem zdarzen zwiazanych z
klawiatura. Chocby z wizualnego porównaia wielkosci tych tabel
wyraznie widac, ze Windows znacznie bardziej "lubia" współprace
z myszka.

Komunikaty Windows w odpowiedzi na zdarzenia zwiazane z
klawiatura.
_______________________________________________________________
Komunikat Zdarzenie
_______________________________________________________________
WM_KEYDOWN Nacisnieto (jakis) klawisz.
WM_KEYUP Puszczono klawisz.
WM_SYSKEYDOWN Nacisnieto klawisz "systemowy".
WM_SYSKEYUP Puszczono klawisz "systemowy".
WM_CHAR Kod ASCII klawisza.
________________________________________________________________

Klawisz systemowy to np. [Alt]+[Esc], [Alt]+[F4] itp.
________________________________________________________________


Komunikaty Windows mozemy wykorzystac w programie.
...
BOOL CanClose();
void WMLButtonDown(RTMessage Msg)= [WM_FIRST +
WM_LBUTTONDOWN];
void WMRButtonDown(RTMessage Msg)= [WM_FIRST +
WM_RBUTTONDOWN];
};

Nasze Główne_Okno potrafi obsługiwac nastepujace zdarzenia:
* Funkcja CanClose() zwróciła wynik TRUE/FALSE,
* Nacisnieto lewy/prawy klawisz myszki.
Komunikat Msg zadeklarowany jako zmienna typu RTMessage jest w
klasie maciezystej TWindow wykorzystywany tak:

_CLASSDEF(TWindow)

class _EXPORT TWindow : public TWindowsObject
{
...
protected:
virtual LPSTR GetClassName()
{ return "OWLWindow"; }
virtual void GetWindowClass(WNDCLASS _FAR & AWndClass);
virtual void SetupWindow();

virtual void WMCreate(RTMessage Msg) = [WM_FIRST +
WM_CREATE];

virtual void WMMDIActivate(RTMessage Msg) =
[WM_FIRST + WM_MDIACTIVATE];
...
virtual void WMSize(RTMessage Msg) = [WM_FIRST + WM_SIZE];
virtual void WMMove(RTMessage Msg) = [WM_FIRST + WM_MOVE];
virtual void WMLButtonDown(RTMessage Msg) = [WM_FIRST +
WM_LBUTTONDOWN];

Zwróc uwage na notacje. Zamiast WM_CREATE pojawiło sie [WM_FIRST

+ WM_CREATE]. Komunikat WM_FIRST jest predefiniowany w
OWLDEF.H
i musi wystapic w obiektowych aplikacjach w dowolnej klasie
okienkowej, badz sterujacej (window class/controll class), która

winna odpowiadac na okreslony komunikat. Oto fragment pliku
OWLDEF.H zawierajacy definicje stałych tej grupy:

#define WM_FIRST 0x0000
/* 0x0000- 0x7FFF window messages */
#define WM_INTERNAL 0x7F00
/* 0x7F00- 0x7FFF reserved for internal use */
#define ID_FIRST 0x8000
/* 0x8000- 0x8FFF child id messages */
#define NF_FIRST 0x9000
/* 0x9000- 0x9FFF notification messages */
#define CM_FIRST 0xA000
/* 0xA000- 0xFFFF command messages */
#define WM_RESERVED WM_INTERNAL - WM_FIRST
#define ID_RESERVED ID_INTERNAL - ID_FIRST
#define ID_FIRSTMDICHILD ID_RESERVED + 1
#define ID_MDICLIENT ID_RESERVED + 2
#define CM_RESERVED CM_INTERNAL - CM_FIRST

W tym momencie zwrócmy jeszcze uwage, ze funkcje z grupy
MessageHandlers sa typu void i zwykle sa metodami wirtualnymi -
przeznaczonymi "z definicji" do nadpisywania przez programistów
w klasach potomnych. Wszystkie te metody maja zawsze jedyny
argument - referencje do struktury TMessage zdefiniowanej
nastepujaco:

struct TMessage
{
HWND Receiver; //Identyfikator okna - odbiorcy
WORD Message; //sam komunikat
union
{
WORD WParam; //Parametr WParam stowarzyszony z
//komunikatem; ALBO (dlatego unia!)
struct tagWP
{
138
BYTE Lo;
BYTE Hi;
} WP;
union
{
DWORD lParam;
struct tagLP
{
WORD Lo;
WORD Hi;
} LP;
};
long Result;
};

Po tych wyjasnieniach mozemy zaimplementowac poszczególne
funkcje.

void TAplikacja::InitMainWindow()
{
MainWindow = new (0, Name);
}

Jesli wybrano klawisz [Yes] funkcja zwróci IDYES. Jesli funkcja
zwróciła IDYES - operator porównania zwróci TRUE (prawda) i ta
tez wartosc zostanie zwrócona przez metode CanClose:

BOOL TMyWindow::CanClose()
{
return (MessageBox(HWindow, "Wychodzimy?",
"Koniec", MB_YESNO | MB_ICONQUESTION) == IDYES);
}

Stosunkowo najciekawsza kombinacja odbywa sie wewnatrz handlera
komunikatu WM_LBUTTONDOWN. Ze struktury komunikatów pobierana
jest zawartosc młodszego słowa parametru lParam - Msg.LP.Lo i
starszego słowa Msg.LP.Hi. Sa to wzgledne współrzedne graficzne
kursora myszki (wzgledem naroznika okna) w momencie nacisniecia
lewego klawisza myszki. Funkcja sprintf() zapisuje je w postaci
dwu liczb dziesietnych %d, %d do bufora znakowego char
string[20]. Funkcja GetDC() (Get Device Context) okresla
kontekst urzadzenia (warstwa sterownika urzadzenia) i dalej
obiekt moze juz stosujac funkcje kontekstowa "czuc sie"
niezalezny od sprzetu. Dane te w postaci znakowej sa pobierane
przez funkcje kontekstowa OutText() jako string a równoczesnie
pobierane sa w formie liczbowej: Msg.LP.Hi. Msg.LP.Lo, aby
wyznaczyc współrzedne tekstu na ekranie. Funkcja strlen()
oblicza długosc łancucha znakowego - i to juz ostatni potrzebny
nam parametr.

void TMyWindow::WMLButtonDown(RTMessage Msg)
{
HDC DC;
char string[20];
sprintf(string, "(%d, %d)", Msg.LP.Lo, Msg.LP.Hi); <-- TU
DC = GetDC(HWindow);
TextOut(DC, Msg.LP.Lo, Msg.LP.Hi, string, strlen(string));

/* Mozna zwolnic kontekst */
ReleaseDC(HWindow, DC);
}

Ewentualna metoda uniewazniajaca prostokat (invalid rectangle) i

kasujaca w ten sposób zawartosc okna w odpowiedzi na
WM_RBUTTONDOWN moze zostac zaimplementowana np. tak:

void TMyWindow::WMRButtonDown(RTMessage)
{
InvalidateRect(HWindow, 0, 1);
}

Główny program to juz tylko wywołanie metody Run() wobec
obiektu.

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TNAplikacja OBIEKT("Wspolrzedne w oknie", hInstance,
hPrevInstance, lpCmdLine, nCmdShow);
OBIEKT.Run();
return (OBIEKT.Status);
}

Wyswietlanie współrzednych jakkolwiek wartosciowe z
dydaktycznego punktu widzenia jest mało interesujace. Pokusimy
sie o obiektowa aplikacje umozliwiajaca odreczne rysowanie w
oknie (freehand drawing).


[!!!]UWAGA
________________________________________________________________

Pakiety Borland C++ 3..4.5 zawieraja wiele gotowych "klocków" do

wykorzystania. Oto przykład wykorzystania w pliku zasobów .RC
standardowego okienka wejsciowego (Input Dialog Box) i
standardowego okienka typu Plik (File Dialog Box):

#include <windows.h>
#include <owlrc.h>

rcinclude INPUTDIA.DLG
rcinclude FILEDIAL.DLG

ROZKAZY MENU LOADONCALL MOVEABLE PURE DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&New" CM_FILENEW
MENUITEM "&Open" CM_FILEOPEN
MENUITEM "&Save" CM_FILESAVE
END
END

Takie menu mozna zastosowac w programie obiektowym umieszcajac
je w konstruktorze i dokonujac nadpisania metody AssignMenu()
(przypisz menu):

TGOkno::TGOkno(PTWindowsObject AParent, LPSTR ATitle) :
TWindow(AParent, ATitle)
{
AssignMenu("ROZKAZY");
...
}

[S]
rcinclude - dołacz zasoby
LOADONCALL - załaduj po wywołaniu
owlrc - zasoby biblioteki klas OWL

Gotowe "klocki" mozna wykorzystac nawet wtedy, gdy nie pasuja w
100%. Inne niz typowe odpowiedzi na wybór rozkazu implementujemy

w programie głównym poprzez nadpisanie wirtualnej metody

virtual void CMFileOpen(RTMessage msg) =
[CM_FIRST + CM_FILEOPEN]

TGOkno GOkno;

void TGOkno::CMFileOpen(RTMessage)
{
... obsługa zdarzenia ...
}
________________________________________________________________


[Z]
________________________________________________________________
1. Przeanalizuj gotowe zasoby dołaczone do Twojej wersji Borland

C++.
2. Uruchom kilka projektów "firmowych" dołaczonych w katalogu
\EXAMPLES. Zwróc szczególna uwage na projekty STEPS (kolejne
kroki w tworzeniu aplikacji obiektowej).
________________________________________________________________


LEKCJA 47: APLIKACJA OBIEKTOWA - RYSOWANIE W OKNIE.
________________________________________________________________
W trakcie tej lekcji opracujemy obiektowa aplikacje psoługujac
sie biblioteka klas Object Windows Library.
________________________________________________________________

Zaczniemy oczywiscie od standardowych "klocków". Definicja klasy
139

Nasza_Aplikacja i moduł prezentacyjno - uruchomieniowy beda
wygladac standardowo, nie musimy im zatem poswiecac zbytniej
uwagi. Przytoczymy je jedynie. Pointer do napisu inicjujemy po
to, by okienko komunikatu zawierało jakas bardziej konkretna
informacje dla uzytkownika. Rysunki z wnetrza tej aplikacji
mozna przy pomocy Schowka przeniesc jako pliki .CLP, badz za
pomoca PAINTBRUSH - jako .BMP, .PCX i drukowac.

#include <owl.h>

LPSTR Ptr = "Jesli chcesz zapamietac rysunek, \
powinienes przeniesc go do Clipboard'u \
klawiszami [Print Screen] \
lub [Alt]+[PrtScr].";

class TNAplikacja : public TApplication
{
public:
TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE
hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
: TApplication(AName, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
virtual void InitMainWindow();
};
...

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TNAplikacja OBIEKT("Rysownik. Prawy klawisz umozliwia wyjscie.",

hInstance, hPrevInstance, lpCmdLine, nCmdShow);
OBIEKT.Run();
return (OBIEKT.Status);
}

Nic specjalnie ciekawego nie dzieje sie w funkcji inicjujacej
główne okno, ani w funkcji zamykajacej aplikacje. Zmieniły sie
tylko napisy w okienku komunikatów.

void TNAplikacja::InitMainWindow()
{
MainWindow = new TGOkno(0, Name);
}

BOOL TGOkno::CanClose()
{
return (MessageBox(HWindow, Ptr, "KONIEC",
MB_YESNO | MB_ICONQUESTION) == IDYES);
}

Zajmiemy sie teraz główna "maszyneria" programu. Rozbudujemy
obsługe komunikatów przez handlery zaimplenmentowane w klasie
Główne_Okno.

_CLASSDEF(TGOkno)
class TGOkno : public TWindow
{
public:
HDC dc;
BOOL ButtonDown;
BOOL Flaga_Start;

TGOkno(PTWindowsObject AParent, LPSTR ATitle);
//Konstruktor

virtual void WMLButtonDown(RTMessage Msg)
= [WM_FIRST + WM_LBUTTONDOWN];
virtual void WMLButtonUp(RTMessage Msg)
= [WM_FIRST + WM_LBUTTONUP];
virtual void WMMouseMove(RTMessage Msg)
= [WM_FIRST + WM_MOUSEMOVE];
virtual void WMRButtonDown(RTMessage Msg)
= [WM_FIRST + WM_RBUTTONDOWN];
virtual BOOL CanClose();
};

Konstruktor przekazuje parametry do konstruktora klasy bazowej i

zeruje flage ButtonDown - lewy klawisz myszki przycisniety.

TGOkno::TGOkno(PTWindowsObject AParent, LPSTR ATitle)
: TWindow(AParent, ATitle)
{
ButtonDown = FALSE;
}

Funkcja obsługujaca zdarzenie WM_LBUTTONDOWN jeden raz inicjuje
obsługe myszki i ustawia flage. Funkcje SetCapture() i GetDC()
załatwija problem relacji kontekstowych i okreslaja obszar
roboczy (client area). Jesli umiescimy te funkcje w
konstruktorze za obszar client area uznany zostanie cały ekran.
Po zadziałaniu tych funkcji komunikaty od myszki beda dotyczyc
wyłacznie obszaru roboczego. Do nacisniecia prawego klawisza nie

bedzie dostepu do "ramki" okna.

void TGOkno::WMLButtonDown(RTMessage Msg)
{
if (!Flaga_Start)
{
Flaga_Start = TRUE; //UWAGA:
SetCapture(HWindow); //Jesli zainicjujemy SetCapture()
dc = GetDC(HWindow); //w konstruktorze - mamy caly ekran
}
MoveTo(dc, Msg.LP.Lo, Msg.LP.Hi);
ButtonDown = TRUE;
}

Funkcja MoweTo() powoduje przesuniecie kursora graficznego do
aktualnej pozycji myszki (juz wzglednej - z uwzglednieniem dc)
bez rysowania linii. Flaga ButtnDown została ustawiona.
Rysowanie scedujemy na metode obsługujaca WM_MOUSEMOVE -
przesuniecie myszki.

void TGOkno::WMMouseMove(RTMessage Msg)
{
if (ButtonDown)
LineTo(dc, Msg.LP.Lo, Msg.LP.Hi);
}

Jesli lewy klawisz jest nacisniety - funkcja LineTo() bedzie
kreslic linie do kolejnych punktów "sledzac" ruch myszki. Jesli
uzytkownik pusci lewy klawisz - zerujemy flage stanu klawisza
ButtonDown <== FALSE.

void TGOkno::WMLButtonUp(RTMessage)
{
if (ButtonDown) ButtonDown = FALSE;
}

Jak juz nabazgrzemy po ekranie, prawy klawisz umozliwi nam
skasowanie zawartosci przy pomocy InvalidateRect().

void TGOkno::WMRButtonDown(RTMessage)
{
InvalidateRect(HWindow, 0, 1);
ReleaseCapture();
ReleaseDC(HWindow, dc);
Flaga_Start = FALSE;
}

Para funkcji ReleaseDC() i ReleaseCapture() pozwala przekazac
komunikaty od myszki do "ramki okna". Dzieki temu mozna po
skasowaniu ekranu np. rozwinac menu systemowe i zakonczyc
aplikacje. A oto program w całosci.

Listing. Odreczne rysowanie.
________________________________________________________________

#define STRICT
#define WIN31
#include <owl.h>

LPSTR Ptr = "Jesli chcesz zapamietac rysunek, \
powinienes przeniesc go do Clipboard'u \
klawiszami [Print Screen] \
lub [Alt]+[PrtScr].";

class TNAplikacja : public TApplication
{
public:
TNAplikacja(LPSTR AName, HANDLE hInstance, HANDLE
hPrevInstance,
140
LPSTR lpCmdLine, int nCmdShow)
: TApplication(AName, hInstance, hPrevInstance, lpCmdLine,
nCmdShow) {};
virtual void InitMainWindow();
};

_CLASSDEF(TMyWindow)
class TMyWindow : public TWindow
{
public:
HDC dc;
BOOL ButtonDown;
BOOL Flaga_Start;

TMyWindow(PTWindowsObject AParent, LPSTR ATitle);
//Konstruktor

virtual void WMLButtonDown(RTMessage Msg)
= [WM_FIRST + WM_LBUTTONDOWN];
virtual void WMLButtonUp(RTMessage Msg)
= [WM_FIRST + WM_LBUTTONUP];
virtual void WMMouseMove(RTMessage Msg)
= [WM_FIRST + WM_MOUSEMOVE];
virtual void WMRButtonDown(RTMessage Msg)
= [WM_FIRST + WM_RBUTTONDOWN];
virtual BOOL CanClose();
};

TMyWindow::TMyWindow(PTWindowsObject AParent, LPSTR ATitle)
: TWindow(AParent, ATitle)
{
ButtonDown = FALSE;
}

void TMyWindow::WMLButtonDown(RTMessage Msg)
{
if ( !Flaga_Start )
{
Flaga_Start = TRUE; //UWAGA:
SetCapture(HWindow); //Jesli zainicjujemy SetCapture()
dc = GetDC(HWindow); //w konstruktorze - mamy caly ekran
}
MoveTo(dc, Msg.LP.Lo, Msg.LP.Hi);
ButtonDown = TRUE;
}

void TMyWindow::WMMouseMove(RTMessage Msg)
{
if ( ButtonDown )
LineTo(dc, Msg.LP.Lo, Msg.LP.Hi);
}

void TMyWindow::WMLButtonUp(RTMessage)
{
if (ButtonDown) ButtonDown = FALSE;
}

void TMyWindow::WMRButtonDown(RTMessage)
{
InvalidateRect(HWindow, NULL, TRUE);
ReleaseCapture();
ReleaseDC(HWindow, dc);
Flaga_Start = FALSE;
}

void TNAplikacja::InitMainWindow()
{
MainWindow = new TMyWindow(0, Name);
}

BOOL TMyWindow::CanClose()
{
return (MessageBox(HWindow, Ptr, "KONIEC",
MB_YESNO | MB_ICONQUESTION) == IDYES);
}

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
TNAplikacja OBIEKT("Rysownik. Prawy klawisz umozliwia
wyjscie.", hInstance, hPrevInstance,
lpCmdLine, nCmdShow);
OBIEKT.Run();
return (OBIEKT.Status);
}
________________________________________________________________




LEKCJA 48: O PAKIETACH BORLAND C++ 4/4.5.
________________________________________________________________
Z tej lekcji dowiesz sie, czy warto kupic nowsza wersje Borland
C++ 4/4.5 i jakie niespodzianki czekaja Cie po zamianie
kompilatora na nowszy.
________________________________________________________________

Czy warto sprawic sobie BORLAND C++ 4/4.5 ?

Kilka słów o tym, co oferuje Borland w pakietach "Borland C++
4/4.5" i jakie niespodzianki czekaja nowych uzytkowników przy
instalacji i uruchamianiu.

Wymagania sprzetowe i instalacja

Aby instalacja i uzytkowanie pakietu przebiegało poprawnie,
zaleca sie nastepujaca konfiguracje sprzetu:

Wymagania sprzetowe Borland C++ 4.
________________________________________________________________
Parametr minimum zalecane (pełna konfig.)
________________________________________________________________
* procesor 80386/33 MHZ 486 DX (lub 386 + 387)
* miejsce na dysku 8 MB 80 MB (bez kompresji)
* pamiec RAM 4 MB 8 MB i wiecej
* system DOS 4.01 DOS 6.0...6.22
* MS Windows 3.1 Windows NT
________________________________________________________________



Czesciowa instalacja Borland C++ 4.
________________________________________________________________

Konfiguracja Dysk
________________________________________________________________
1. Kompilator BCC 16 bitowy (D+W) 9 MB
2. Kompilator BCC 32 bitowy (D+W) 13 MB
3. Srodowisko IDE 16 bitowe 26 MB
4. Srodowisko IDE 32 bitowe 30 MB
5. Tylko dla DOS (minimum) 8 MB
________________________________________________________________
* D+W - dla DOS i Windows

Mozna próbowac zainstalowac Borland C++ 4 na małym dysku, mozna
takze ograniczyc sie do 4 MB RAM, ale generowanie 32-bitowych
aplikacji bedzie wtedy znacznie utrudnione a praca kompilatora
wolniejsza. W przypadku stosowania kompresorów (np. SUPERSTOR,
DOUBLE SPACE) nalezy pamietac, ze wtórna kompresja plików jest
mało skuteczna i dysk zgłaszany jako 80 MB moze okazac sie
"ciasny".

Borland C++ 4 mozna instalowac z dyskietek, badz z CD-ROM.
Poniewaz pakiet BC++ 4 jest "okienkowo - zorientowany", nawet
program instalacyjny wymaga obecnosci Windows. Uruchomienie
programu instalacyjnego nastepuje zatem z poziomu Menedzera
programów rozkazem File | Run... (w spolszczonej wersji Windows
- Plik | Uruchom...) lub z DOS-owskiego wiersza rozkazu:

C:\>WIN X:INSTALL

Opcji jest troche wiecej - o najciekawszych z nich - kilka słów
ponizej.

Warto zwrócic uwage na tzw. "rozszerzenie dla Windows"
(extention to MS Windows) - Win32s. W programie INSTALL.EXE do
zainstalowania tego pakietu (pakiet jest oryginalnym produktem
Microsofta i wymaga 8 MB przestrzeni dyskowej) słuzy opcja
[Install Win32s]. Najwazniejszy plik-driver instaluje sie w
pliku SYSTEM.INI:

device=X:\WINDOWS\SYSTEM\WIN32S\W32S.386

Pozwala to na uruchamianie 32 - bitowych aplikacji pod Windows
3.1. Jesli masz Windows NT - jest to zbedne - o ten "drobiazg"
zadbał juz Microsoft.
141

W przypadku instalacji w sieci, gdzie Windows zainstalowane sa
na serwerze nalezy pamietac, ze BC++ 4 w trakcie instalacji
modyfikuje nie tylko klasyczne parametry systemu:

FILES=40
BUFFERS=40
PATH=...;X:\BC4\BIN;

ale takze pliki konfiguracyjne i inicjacyjne w katalogu WINDOWS:


WIN.INI, PROGMAN.INI, SYSTEM.INI,

oraz tworzy nowe własne pliki, które bedzie próbował zapisac w
katalogach \WINDOWS i \WINDOWS\SYSTEM, np. BCW.INI, TDW.INI,
HELP.ICO, OWL.INI, BWCC.DLL, itp. (łacznie 18 nowych plików).
Brak prawa zapisu na dysk serwera moze uniemozliwic poprawna
instalacje i skonfigurowanie BC++ 4/4.5 w sieci.

Borland wraz z wersjami bibliotek dostarcza komplet kodów
zródłowych. Jesli chcesz - mozesz sam wygenerowac sobie cała
biblioteke, jesli chcesz - mozesz na własne oczy przekonac sie
jak to wszystko działa i jak jest zbudowane. Oprócz
teoretycznych mozliwosci poznawczych daje to praktyczna
mozliwosc dostosowania bibliotek do nowej wersji kompilatora, co

w przypadku "czwórki" moze okazac sie dla wielu uzytkowników
bardzo przydatne (o czy dokładniej za chwile).

Oprócz klasycznego paska głównego menu zintegrowane srodowisko
(IDE) zostało wyposazone w rozbudowana listwe narzedziowa.

W skład pakietu wchodza miedzy innymi:

* BCW - zintegrowane srodowisko (IDE) dla srodowiska Windows
* TDW - Turbo Debugger for Windows
* BCC - kompilator uruchamiany z DOS'owskiego wiersza rozkazu
* BCC32 - kompilator 32 - bitowy (odpowiednik BCC)
* BRCC - kompilator zasobów do kompilacji plików *.RC z zasobami

do postaci binarnej *.RES
* RLINK - konsolidator słuzacy do dołaczania plików z zasobami
przy tworzeniu plików wykonywalnych *.EXE
* TLINK - "zwykły" konsolidator
* MAKE - program narzedziowy do automatyzacji kompilacji i
konsolidacji, korzystajacy z tzw. plików instruktazowych
(emulujacy NMAKE Microsofta)
* WINSIGHT - przegladanie informacji o okienkach (dla Windows) i

komunikatach
* TDUMP - bezposrednie przegladanie informacji zawartych w
plikach *.EXE i *.OBJ
* TDSTRIP - narzedzie do usuwania tablicy symboli z plików
wykonywalnych
* IMPLIB - importer bibliotek z DLL
* TDMEM - wyswietlanie informacji o zajetosci pamieci
* MAKESWAP - zarzadzanie swapowaniem (tworzenie plików
tymczasowych EDPMI.SWP o zadanej wielkosci)

i jeszcze pare narzedzi (np. tradycyjny bibliotekarz TLIB,
TOUCH, GREP, itp.), o których tu nie wspominam.

Czego robic nie nalezy?

Przede wszystkim nie nalezy traktowac Borland C++ 4/4.5 jako
"upgrade" do wczesniejszych wersji (3, czy 3.1). W kompilatorze
dokonano sporych zmian (np. inaczej działa operator new). Nie
wolno zatem "nadpisac" zawartosci poprzednich katalogów i plików

o tych samych nazwach. Szczególnie dotyczy to plików
konfiguracyjnych BCCONFIG.BCW i TDCONFIG.TDW. Jesli stare wersje

tych plików nie zostana przemianowane, badz usuniete z pola
widzenia (PATH) - pojawia sie konflikty przy uruchamianiu BC++.

Ze wzgledu na wprowadzone zmiany pliki .OBJ tworzone przez
wczesniejsze kompilatory C beda w zasadzie przenosne, natomiast
pliki .OBJ i biblioteki utworzone przez wczesniejsze wersje
kompilatorów C++ (szczególnie Borland C++ 3.1) beda sprawiac
kłopoty (nie beda np. poprawnie wywoływane destruktory). Przy
konsolidacji "starych" plików mozna stosowac opcje -K2
konsolidatora, co pozwoli zmniejszyc do minimum ryzyko
konfliktów.

Jesli jest juz Borland Pascal 7...

Jesli masz juz zainstalowany Borland Pascal 7 nalezy pamietac,
ze poprawna praca obu kompilatorów w jednym systemie wymaga
"uregulowania stosunków":

1. Kazdy kompilator musi miec własna kopie debuggera TDW. Aby
uniknac konfliktu pascalowski debugger mozna przemianowac np.:

TDW.EXE --> PASTDW.EXE

2. Nalezy usunac stare pliki inicjujace TDW.INI. Mozna tu
posłuzyc sie narzedziem TDWINI.EXE.
3. Nalezy sprawdzic poprawnosc instalacji driverów w pliku
SYSTEM.INI:

DEVICE=X:\BC4\BIN\WINDPMI.386
DEVICE=X:\BC4\BIN\TDDEBUG.386 <-- tu mozliwy konflikt z BP 7

Nalezy usunac dublujace sie instalacje pozostawiajac tylko te z
BC++ 4 oraz usunac pascalowskie TDDEBUG.386 (pas) i TDWIN.DLL by

uniemozliwic omyłkowe zainstalowanie.

Przy poprawnym skonfigurowaniu systemu pozostałe zasoby ( w tym
np. Resource Workshop 4) beda poprawnie współpracowac z BP 7.

Stare zasoby C++

Zapewne wiekszosc uzytkowników Borland C++ 4 "przesiadzie sie" z

BC++ 3/3.1 lub Turbo C++. I tu takze czychaja pewne
niebezpieczenstwa. Stare projekty - tradycyjnie .PRJ w BC++ 4
zyskuja nowe domyslne rozszerzenie .IDE. W okienku dialogowym
zarzadzania projektem: Project | Open... przy pomocy opcji

[3.1 Project Files (*.prj)]

mozna dokonac automatycznej konwersji do formatu .IDE, przy czym

stara wersja pliku *.PRJ pozostanie bez zmian. Niektóre stare
kody zródłowe beda wymagac drobnych modyfikacji. Szczególnie
nalezy zwrócic uwage na:

- nakładki (overlay support)
- zarzadzanie pamiecia (new - delete)
- informacje diagnostyczne w plikach (debug info)
- zmianne pseudorejestrowe (dostepne teraz tylko w niektórych
trybach)

O bibliotece Turbo Vision.

Biblioteka Turbo Vision Library - TV.LIB współpracujaca
poprawnie z BC++ 3.0/3.1 powinna zostac powtórnie skompilowana,
poniewaz BC++ 4 stosuje inny format:

- informacji diagnostycznych (debug info format)
- inna długosc identyfikatorów (symbol length)
- inna biblioteke Runtime Library

Kod zródłowy biblioteki znajduje sie w katalogu:

\BIN\TVISION\SOURCE

Po (Uwaga!) wprowadzeniu kilku niewielkich zmian

- do plików zródłowych .CPP
- do pliku instruktazowego MAKEFILE

oraz po skompilowaniu przy pomocy BCC 4 w DWU WERSJACH: TVO.LIB
(z nakładka - Overlay) i TVNO.LIB (bez nakładki - No Overlay)
biblioteka TVL moze byc nadal z powodzeniem stosowana z Borland
C++ 4. Podobnie rekompilacji wymaga bibiloteka klas dołaczona w
wersji zródłowej w katalogu X:\BC4\SOURCE\CLASSLIB.

O AUTOMATYZACJI - CASE.

Prócz znanego juz od dosc dawna (w komputerologii kilka lat to
cała epoka) tradycyjnego narzedzia Resource Worshop, w wersji
BC4 wystepuja jeszcze inne narzedzia CASE kategorii "wizard"
(kreator aplikacji):
142

- ClassExpert
- ApplicationExpert
- DialogExpert
- TargetExpert

Nazwa TargetExpert pochodzi od ang. "Target platform" - docelowa

platforma pracy aplikacji (DOS, Win16, Win32).
Biblioteka OWL 2.0 została wzbogacona o dodatkowe klasy VBX
umozliwiajace współprace z Visual Basic i wykorzystanie
elementów utworzonych przy pomocy VB.
Wspomaganie tworzenie programu przy pomocy tych narzedzi
(AppExpert podobnie jak inne narzedzie typu Wizard jest
automatycznym generatorem aplikacji) wymaga od uzytkownika
wyboru z listy "zagadnienia" a z okienek docelowych cech
programu. Przytocze tu dla przykładu liste opcji z pojedynczego
okienka AppExperta z krótkim wyjasnieniem:

________________________________________________________________
Topics: (okienko z lista: Zagadnienia)
Application (program docelowy)
-- Basic Opttions (wybór opcji podstawowych)
-- Advanced Options (opcje zaawansowane)
-- Code Gen Control (sposób generacji kodu)
-- Admin Options (opcje "administracyjne")
Main Window (główne okno programu)
-- Basic Options (podstawowe opcje)
-- SDI Client (interf. jednego dokumentu)
-- MDI Client (interf. wielu dokumentów)
MDI Child/View (okna potomne, widok/edycja)
-- Basic Options (opcje podstawowe)

Model: (Szkielet programu)
[X] Multiple document interface - interfejs MDI
[ ] Single document interface - interfejs SDI

Features: (cechy)
[.] SpeedBar (ma pasek narzedzi)
[.] Status line (ma wiersz statusowy)
[.] Drag/drop (obsługuje ciagnij/upusc)
[.] Printing (obsługuje drukarke)
________________________________________________________________

Po wybraniu w okienku klawisza [Generate] (wygeneruj) AppExpert
generuje szkielet programu aplikacji o podanych własnosciach.
Wygenerowane zostaje od szesciu do dziewieciu (zaleznie od
ustawienia opcji i Twoich zyczen) plików projektu:

*.IDE - plik projektu (lub .PRJ)
*.APX - plik roboczy AppExpert'a (odpowiednik
.PRJ)
*.RC - plik zasobów
*.RH - plik nagłówkowy zasobów
*.H - plik nagłówkowy, zródłowy
*.CPP - moduł główny zródłowy
*.HPJ - plik pomocy
*.RTF - zródłowy pomocy kontekstowej
*.ICO - ikonka projektu

Przy pomocy rozkazu Generate makefile mozna równiez
automatycznie utworzyc plik instruktazowy MAKEFILE dla
generatora MAKE.EXE.

Uzyskany plik szkieletowy *.CPP nalezy tylko uzupełnic o obsługe

interesujacych nas zdarzen/komunikatów. Przyspiesza to znacznie
tworzenie typowych aplikacji.

Programisci wszystkich krajów...

BC++ 4 zawiera biblioteke LOCALE.DLL umozliwiajaca obsługe
angielsko- francusko- i niemiecko- jezyczna. Borland zapowiada,
ze nastepne wersje beda coraz bogatsze. Doczekalismy sie
spolszczenia Windows - moze i Borland C++ po polsku juz tuz tuz?

Póki co, najwygodniej podmienic czcionki.
________________________________________________________________

ZAKONCZENIE.

I to juz niestety koniec. Po przeanalizowaniu historii:
programowania sekwencyjnego i strukturalnego
oraz nowoczesnych styli programowania:
obiektowego i zdarzeniowego
pozostał Ci juz tylko wykonanie trzech rzeczy. Powinienes teraz:


1. Pisac własne aplikacje
2. Pisac własne aplikacje
3. Pisac własne aplikacje

Tak naprawde - jest to jedyny sposób, by zostac dobrym
programista.

Przez pewien czas okaze Ci sie zapewne przydatna dyskietka
dołaczona do ksiazki. Znajdziesz tam jeszcze sporo programów
przykładowych, które nie zmiesciły sie w ksiazce.

Przyjemnej pracy z programem MEDYT.

You're Reading a Free Preview

Pobierz