Lekcja Turbo Pascala

Pierwszy program w Pascalu Jakub Radoszewski


Teraz, kiedy masz już zainstalowane całe oprogramowanie potrzebne do programowania w Pascalu, możesz napisać swój pierwszy program. W tym celu otwórz środowisko graficzne Pascala (uruchom plik fp.exe) i w okienku edycji wpisz dokładnie taką treść programu (oczywiście bez numerów linii):


1 program pierwszy;

2

3 begin

4 Write('Hello world!');

5 end.


Jeżeli już wpisałeś treść programu, będziesz musiał przejść kilka kroków (które Ci zresztą będą towarzyszyły w dalszym ciągu nauki programowania), zanim zobaczysz wynik działania swojego programu. Oto one:


1. Naciśnij klawisz F2 (lub wejdź do menu File i tam kliknij na opcję Save) w celu zachowania treści dopiero co wpisanego programu. Zostaniesz poproszony o podanie nazwy, pod którą zostanie zapamiętany Twój kod, podaj więc dowolną nazwę i kliknij na przycisk Save. Domyślnie plik z Twoim kodem zostanie zapisany w katalogu, który był wybrany przy instalacji pakietu Pascala (domyślny i polecany to: c:\pp\bin\win32) i dostanie on standardowo rozszerzenie .pas.

2. Teraz przyszła pora na kompilację Twojego programu. Żeby to uczynić, naciśnij kombinację klawiszy Alt+F9 (lub wejdź za pomocą myszki do menu Compile i tam kliknij na opcję Compile). Zostanie wyświetlony komunikat, że Twój program skompilował się pomyślnie, który zniknie po naciśnięciu klawisza Enter (jeżeli pojawił Ci się inny komunikat, to sprawdź, czy dobrze przepisałeś podany wyżej kod - jeżeli nie, to popraw ewentualne błędy i powróć do kroku 1.). Teraz Twój program został przetworzony przez kompilator na język maszynowy, czyli zrozumiały dla procesora i przygotowany przez to do działania.

3. Żeby uruchomić program, naciśnij kombinację klawiszy Ctrl+F9 (lub wejdź do menu Run i kliknij na opcję Run). Jeżeli wszystko przebiegło pomyślnie, to ... nic się nie powinno wydarzyć. Żeby zobaczyć wynik działania swojego programu, naciśnij kombinację klawiszy Alt+F5 (lub wejdź do menu File i kliknij na opcję User screen). Wtedy otworzy Ci się czarne okienko, a w nim pojawi Ci się napis "Hello world!" (ang. Witaj świecie!). Jest to efekt działania programu - wypisuje on na ekran tekst "Hello world!", po czym kończy działanie. Możesz teraz wyjść z czarnego okienka, korzystając albo z kliknięcia myszką w prawym górnym rogu tego okienka, albo - szybciej - naciskając kombinację klawiszy Alt+F3.


Twój program zakończył działanie; teraz pora na to, by zrozumieć, na jakiej zasadzie został utworzony, żeby móc potem samemu pisać tego typu programy. Na początku programu napisaliśmy instrukcję "program pierwszy;", w której nadaliśmy nazwę programowi - nazywa się "pierwszy". Przed nazwą programu napisaliśmy słowo "program", które jest przykładem tzw. słowa kluczowego, czyli słowa, którego NIE WOLNO zmieniać, gdyż kompilator na jego podstawie wnioskuje, że po nim zostaje mu podana nazwa programu. Dlatego moglibyśmy na przykład zmienić nazwę naszego programu na "drugi":


1 program drugi;

2

3 begin

4 Write('Hello world!');

5 end.


natomiast nie moglibyśmy zmienić słowa kluczowego "program" na np. programik. Nie wolno także nie postawić średnika po tej instrukcji - jego brak, tak jak i zmianę słowa "program", kompilator potraktuje jako błąd i zgłosi go w procesie kompilacji.


Sposoby radzenia sobie z takimi zgłoszeniami błędów poznasz wkrótce.


Dalej następuje instrukcja "Write('Hello world!');", otoczona słowami "begin" oraz "end". Słowa te to również słowa kluczowe i one również muszą się znajdować niezmienione w programie. Oznaczają one początek i koniec części głównej programu i MUSZĄ się znajdować w każdym programie. Poza tym po słowie end koniecznie musi się znajdować kropka.


Wreszcie mamy samą instrukcję "Write('Hello world!');", która - jak się zapewne domyślasz - jest odpowiedzialna za wypisanie tekstu "Hello world!" na ekranie - jest to tzw. instrukcja wypisywania. Przy wypisywaniu nie można zmieniać słowa "Write", natomiast można zmieniać treść komunikatu, jaki instrukcja ta ma wypisać (tym niemniej, musi on być otoczony apostrofami). Tak na przykład, możemy napisać nowy program tak:


1 program trzeci;

2

3 begin

4 Write('Jestem Twoim trzecim programem');

5 end.


Jeżeli przejdziesz kolejno przez kroki 1-3 podane wyżej, zobaczysz tym razem w czarnym okienku tekst "Jestem Twoim trzecim programem".

Jeżeli chcesz nadpisać nowo napisany program nad starym, czyli zapisać go w miejscu starego, to wystarczy zmienić jego treść zgodnie z uznaniem i nacisnąć klawisz F2. Jeżeli jednak chciałbyś zapisać go osobno pod inną nazwą - i tym samym pozostawić stary program - musisz wejść do menu File i tam wybrać opcję Save As, podać nową nazwę i zatwierdzić - wówczas oba kody będą zachowane na dysku.


Możesz również dalej zmieniać treść wypisywanego komunikatu czy też nazwę programu (tę drugą nie dowolnie - szczegóły wkrótce). Warto, byś zapamiętał mniej więcej wygląd tego programu, gdyż prezentuje on schemat wszystkich programów w Pascalu. Kiedy znudzi Ci się zmienianie powyższego programu i eksperymentowanie z nim, możesz wyjść ze środowiska graficznego wciskając kombinację klawiszy Alt+X (lub wchodząc do menu File i wybierając opcję Exit). Po wyjściu ze środowiska możesz spróbować znaleźć wszystkie swoje kody źródłowe - są one zapisane w folderze c:\pp\bin\win32 jako pliki *.pas. Dla wszystkich kodów, które skompilowałeś, w tym katalogu znajdzie się plik exe o tej samej nazwie. Uruchomienie tego pliku powoduje dokładnie to samo, co wykonanie Ctrl+F9 w środowisku Pascala.

Warto, abyś zapamiętał położenie tego folderu, gdyż przyda się to do wysyłania rozwiązań zadań do kolejnych lekcji.


Oczywiście istnieje wiele więcej możliwości, które można uzyskać programując w Pascalu, niż wypisywanie komunikatu na ekran. Będziesz je kolejno poznawał w ramach niniejszego kursu.


Wypisywanie i odczytywanie Jakub Radoszewski Na początek - ważne szczegóły techniczne


Język Pascal nie jest do końca tak rygorystyczny, jak opisywaliśmy w poprzedniej lekcji. Dlatego też kompilator:


* Nie odróżnia małych liter od dużych (poza tekstami otoczonymi przez znaki apostrofu '). Dlatego wolno zapisać np. słowo kluczowe program na przykład tak: pRoGRaM i kompilator zrozumie nasze zamiary. Tym niemniej polecamy nie stosować tego typu zapisu w programie. Również dobrym pomysłem jest konsekwentne stosowanie takiego samego schematu dla słów tego samego typu w programie.

* Pomija wszelkie spacje i puste linie w programie. Dlatego też - dla zwiększenia czytelności - osobne bloki programu warto oddzielać pustymi liniami, oraz stosować "wcięcia" w kodzie, czyli wstawiać spacje przed instrukcjami znajdującymi się np. wewnątrz bloku begin-end.


Jeszcze jedną możliwość zwiększania czytelności kodu - co pomaga nam chronić się przed błędami - dają w Pascalu komentarze. Dowolny tekst wewnątrz programu znajdujący się między nawiasem { a } będzie pomijany w procesie kompilacji. Warto w (zwłaszcza większych) programach, umieszczać komentarze, dzięki którym będziemy wiedzieli później, jakie były zadania poszczególnych części programu.


Instrukcja wypisywania Writeln


W jedynym dotychczas napisanym programie, jedyną naprawdę użyteczną instrukcją była instrukcja wypisywania (Write), pozwalająca wypisać na ekran żądany komunikat. W tej lekcji poznasz więcej możliwości i odmian instrukcji wypisywania oraz instrukcje wczytywania.


Jak wiesz, instrukcja wypisywania pozwala wypisać na ekran pewien tekst, o ile tylko jest on otoczony znakami apostrofu '. Załóżmy, że chciałbyś wypisać na ekran komunikat:

Stolica Polski to Warszawa. Stolica Niemiec to Berlin. Stolica Czech to Praga.


Wówczas w programie mógłbyś próbować to zrobić poprzez wielokrotne zastosowanie instrukcji wypisywania wewnątrz bloku begin-end:


1 program stolice;

2

3 begin

4 Write('Stolica Polski to Warszawa.');

5 Write('Stolica Niemiec to Berlin.');

6 Write('Stolica Czech to Praga.');

7 {ten program nie do konca zadziala}

8 end.


Zauważ, że na końcu każdej z instrukcji wypisywania umieścić trzeba średnik.


Niestety nie spowodowałoby to żądanego skutku. W zamian na ekranie pojawiłby się komunikat:

Stolica Polski to Warszawa.Stolica Niemiec to Berlin.Stolica Czech to Praga.


W tej sytuacji z pomocą przychodzi kolejna instrukcja wypisywania Writeln, która powoduje, że podany w niej tekst (również otoczony apostrofami) zostaje wypisany i po tym fakcie kursor przechodzi do następnej linii (czyli, że dalsze wypisywanie będzie się odbywać w następnej linii). Poprawny program stolice powinien więc wyglądać następująco:


1 program stolice;

2

3 begin

4 Writeln('Stolica Polski to Warszawa.');

5 Writeln('Stolica Niemiec to Berlin.');

6 Writeln('Stolica Czech to Praga.');

7 {teraz juz jest dobrze}

8 end.


Writeln to połączenie dwóch angielskich słów: write+line.


Inną możliwością oferowaną przez instrukcję Writeln jest wypisanie pustej linii (co czasami bywa potrzebne). W celu wypisania pustej linii, stosuje się instrukcję Writeln bez podawania jej w nawiasach jakiejkolwiek treści komunikatu. I tak na przykład program:


1 program stolice;

2

3 begin

4 Writeln('Stolica Polski to Warszawa.');

5 Writeln;

6 Writeln('Stolica Niemiec to Berlin.');

7 Writeln;

8 Writeln('Stolica Czech to Praga.');

9 end.


spowoduje wyświetlenie na ekranie komunikatu:

Stolica Polski to Warszawa. Stolica Niemiec to Berlin. Stolica Czech to Praga. Instrukcje wczytywania


Samo wypisywanie przez program komunikatów nie jest jeszcze bardzo ciekawe. O wiele bardziej interesujące jest to, że program pisany w Pascalu może być interaktywny - może on mianowicie pobierać od użytkownika pewne dane, wykonać na nich żądane operacje i wypisać wyniki na ekran. Podawanie programowi danych następuje przez wpisanie odpowiednich informacji na klawiaturze w trakcie działania programu. Do wczytywania służy instrukcja Read. Jej wywołanie ma postać:


1 Read(ZMIENNA);


Zmienną w Pascalu nazywamy pewną daną, która może przyjmować różne wartości. Każda zmienna w Pascalu ma przypisaną sobie nazwę (tzw. identyfikator), za pomocą którego możemy się do niej odwoływać. W rzeczywistości, zmienna jest tylko wskazaniem (adresem) pewnego miejsca w pamięci operacyjnej (RAM) komputera. Do programisty należy nadanie zmiennej nazwy.


Żeby móc korzystać ze zmiennej, musimy ją w programie zadeklarować (czyli poinformować kompilator o chęci jej użycia). Deklaracja zmiennej ma postać:


1 var ZMIENNA: TYP;


(gdzie var jest słowem kluczowym - czyli nie wolno go zmieniać) i musi zostać umieszczona w programie pomiędzy instrukcją


1 program nazwaprogramu;


a słowem kluczowym begin, oznaczającym początek części głównej programu. I wreszcie, w deklaracji TYP oznacza typ zmiennej, czyli jakiego typu wartości ta zmienna będzie przechowywać. Podstawowe typy zmiennych to:


* Integer - zapowiadający kompilatorowi, że zmienna jest liczbą całkowitą,

* Real - zapowiadający zmienną będącą liczbą rzeczywistą,

* String - zapowiadający zmienną będącą tekstem (w Pascalu nazywa się to także łańcuchem),

* Char - zapowiadający zmienną będącą pojedynczym znakiem kodu ASCII,


przy czym w tej lekcji będziemy się zajmowali wyłącznie typem całkowitym.


Słowo var jest skrótem od angielskiej nazwy zmiennej - variable.


Przykładowy program wczytujący pojedynczą liczbę całkowitą ma więc postać (w tym przykładzie zmiennej całkowitej nadaliśmy nazwę "liczba"):


1 program odczyt;

2

3 var liczba: Integer;

4

5 begin

6 Read(liczba);

7 end.


Identyfikatory, takie jak nazwy zmiennych i programu, nie mogą być kompletnie dowolne - mogą się składać z liter i cyfr, przy czym muszą się zaczynać od litery.


Jeżeli skompilujesz i wykonasz ten program, to "zatrzyma się" on, pokazując czarny ekran. Wówczas program będzie oczekiwał, aż podasz mu (wpiszesz z klawiatury) pewną liczbę całkowitą, np. 123. Kiedy wpiszesz liczbę i naciśniesz Enter, program zakończy działanie, usatysfakcjonowany z uzyskanej liczby. Jeżeli podasz mu coś innego niż liczbę, np. tekst "ala", to program również zakończy działanie, tyle że wypisze błąd wskazujący na to, że oczekiwał otrzymania wartości liczbowej, a uzyskał coś innego. Program oczekuje także, że liczba przez Ciebie podana będzie się zawierała w granicach -32768..32767 - przyczynę takiego ograniczenia poznasz wkrótce.

Wczytywanie i wypisywanie


Samo odczytanie zmiennej nic jednak nie daje. Zaletą zmiennych jest to, że można je również wypisywać, podając w instrukcji Write bądź Writeln nazwę zmiennej, którą chcemy wypisać. Żeby jednak wypisać wartość zmiennej, warto ją wcześniej wczytać (wczytanie powoduje, że pod jej nazwą pamiętana jest przez program podana przez użytkownika wartość) - jeżeli tego nie uczynimy, to zmienna w momencie wypisywania będzie miała pewną losową wartość, przypisaną jej przy uruchomieniu programu, i taka właśnie wartość zostanie wypisana na ekran. I tak, poniższy program odczytuje liczbę całkowitą i wypisuje ją dwukrotnie:


1 program liczby;

2

3 var liczba: Integer;

4

5 begin

6 Read(liczba);

7 Writeln(liczba);

8 Writeln(liczba);

9 end.


Jeżeli skompilujesz ten program i wykonasz, to znów zostaniesz poproszony o wpisanie pewnej liczby. W tym samym okienku, w którym mogłeś zobaczyć poprzednio komunikat "Hello world!", możesz teraz zobaczyć swoją liczbę wypisaną dwukrotnie.


Można również wielokrotnie coś wczytywać w programie. I tak na przykład, możemy napisać program, który wczytuje trzy liczby i wypisuje je w odwrotnej kolejności. W tym celu trzeba jednak w programie zadeklarować trzy zmienne całkowite. Żeby tego dokonać, nie należy jednak trzykrotnie pisać słowa kluczowego var - wystarczy napisać je raz, jak poniżej:


1 program trzyliczby;

2

3 var l1: Integer;

4 l2: Integer;

5 l3: Integer;

6

7 begin

8 Read(l1);

9 Read(l2);

10 Read(l3);

11 Writeln(l3);

12 Writeln(l2);

13 Writeln(l1);

14 end.


Żeby podać programowi trzy żądane liczby, musisz je oddzielić spacjami albo enterami (tak zwanymi znakami końca linii), żeby program wiedział, gdzie są granice między liczbami.


Jak łatwo zauważysz, ten kod jest dość nieprzyjemny - wielokrotnie trzeba powtarzać właściwie tę samą instrukcję, co przy pisaniu np. programu wczytującego 10 liczb i wypisującego je w odwrotnej kolejności byłoby dość uciążliwe. Dlatego też (co zauważysz w dalszym ciągu wielokrotnie), język Pascal zawiera wiele uproszczeń tego typu zapisów. I tak np. funkcji Read można podać od razu wszystkie 3 zmienne (jak i ogólnie więcej zmiennych), stosując zapis:


1 Read(l1, l2, l3);


i analogicznie można więcej zmiennych umieścić w instrukcji Writeln. Nasz program przybiera wówczas o wiele bardziej skondensowaną postać:


1 program lepszy;

2

3 var l1: Integer;

4 l2: Integer;

5 l3: Integer;

6

7 begin

8 Read(l1, l2, l3);

9 Writeln(l3, l2, l1);

10 {nie do konca o to nam chodzilo}

11 end.


Niestety, po skompilowaniu i wykonaniu nasz program nie zadziała jak powinien. Wypisze on liczby l3, l2 i l1, ale nie oddzieli ich żadnymi znakami (przez co "zleją" się w jedną liczbę). Żeby się z tym problemem uporać, możemy np. zechcieć umieścić między wypisywanymi liczbami odstępy (spacje) - je również możemy dołączyć po przecinkach w instrukcji Writeln tak:


1 Writeln(l3, ' ', l2, ' ', l1);


Możemy w powyższy sposób wypisać również inne teksty, oczywiście otoczone apostrofami, jak np. możemy opatrzyć wypisywane przez nas liczby komentarzem:


1 Writeln('Te liczby w odwrotnej kolejnosci to: ', l3, ' ', l2, ' ', l1);


Odmiana instrukcji odczytującej - Readln


Oprócz zwykłej instrukcji odczytującej Read, mamy jeszcze instrukcję Readln (bardzo podobną zresztą do funkcji Writeln), która powoduje odczytanie zmiennych w niej występujących (może ich być więcej niż jedna) i przejście z odczytem do nowej linii (co wymusza na użytkowniku naciśnięcie klawisza Enter).

Funkcja Readln będzie bardzo pożyteczna przy odczytywaniu zmiennych reprezentujących teksty (typu String). Ale o tym w następnej lekcji. O błędach kompilacji


Od tej lekcji zaczniesz pisać już samodzielnie programy w Pascalu, rozwiązując dołączone do lekcji zadania. Jeżeli napiszesz program i go skompilujesz (Alt+F9), to proces kompilacji może się udać albo nie. Jeżeli chociaż w bardzo małym stopniu Twój program odbiega od reguł języka Pascal (np. zapomniałeś jakiegoś średnika), to kompilacja zakończy się niepowodzeniem, a kompilator zgłosi błąd, na jaki się natknął (a także jego położenie w kodzie źródłowym programu). Błędy zgłaszane przez kompilator są z reguły dość dobrze przez niego opisane, więc na ich podstawie będziesz mógł nietrudno poprawić wszystkie mankamenty.


Typy w Pascalu Typy


Jak na razie nauczyłeś się korzystać wyłącznie ze zmiennych typu całkowitego (Integer). Wspomniane zostało, że typ ten ma ograniczoną "pojemność" (czyli że zmienne tego typu mogą przechowywać wyłącznie liczby z zakresu -32768..32767). Poniżej przedstawiamy listę ważniejszych typów całkowitych, jakie programista w Pascalu ma do dyspozycji, z zakresami, w jakich muszą się mieścić zmienne tych typów:


* Typ Byte (0..255) - jak sama nazwa wskazuje, zmienna tego typu zajmuje w pamięci komputera 1 bajt. Bajt składa się z 8 bitów, a każdy z nich jest zerem albo jedynką. Dlatego też można za jego pomocą reprezentować dowolną liczbę dziesiętną, której reprezentacja w układzie dwójkowym (binarnym) ma co najwyżej 8 cyfr (najmniejszą taką liczbą jest 0, które ma w układzie dwójkowym reprezentację "00000000", a największą 255, które ma reprezentację "11111111").

* Typ Shortint (-128..127) - również zmienna tego typu zajmuje w pamięci komputera 1 bajt, tylko jej zakres jest przesunięty. Ten typ jest reprezentowany w tzw. układzie uzupełnieniowym do dwójki.

* Typ Word (0..65535) - zmienna tego typu zajmuje w pamięci 2 bajty. Dlatego też zmienne tego typu są wyrażone przez liczby mające reprezentację dwójkową o co najwyżej 16 cyfrach (liczba 65535 ma reprezentację dwójkową "1111111111111111" - 16 jedynek).

* Typ Integer (-32768..32767) - podobny do typu Shortint, tylko że zajmuje 2 bajty w pamięci komputera.

* Typ Longword (0..ok. 4.000.000.000) - analogiczny do typu Word, tylko zajmuje 4 bajty pamięci (około znaczy trochę więcej niż).

* Typ Longint (ok. -2.000.000.000..ok. 2.000.000.000) - analogiczny do typu Integer, tylko zajmuje 4 bajty pamięci. Dokładniej dolna granica tego typu jest trochę mniejsza od minus 2 miliardów, a górna trochę większa od dwóch miliardów.


Oczywiście nie musisz od razu zapamiętywać, jakie dokładnie zakresy mają które typy, warto jednak, byś się dość dobrze orientował w tej kwestii. Jest to o tyle ważne, że w przypadku, kiedy będziesz chciał (np. przy okazji wczytywania) pewnej zmiennej przyporządkować liczbę niemieszczącą się w zakresie, to przy wykonywaniu programu wystąpi błąd.


Może Ci się wydawać, że taka ilość typów jest niepotrzebna, jako że największe zakresy mają typy Longint i Longword i zakresy wszystkich innych typów są mniejsze - w tym momencie nie jest to jeszcze dla Ciebie istotne, ale kiedy w przyszłości będziemy w programach wykorzystywać o wiele więcej zmiennych, to wtedy zajdzie potrzeba oszczędzania pamięci - czyli wykorzystywania zmiennych o zakresach dostosowanych do potrzeb.


Jak już wiesz, przy wczytywaniu liczby całkowitej wystarczy wpisać ją z klawiatury i zatwierdzić klawiszem Enter, natomiast wypisanie następuje w takiej samej formie, w jakiej została podana. Istnieje jeszcze jedna opcja wypisywania, której zapis ma postać:


1 Write(LICZBA:DLUGOSC);


gdzie LICZBA to zwykła liczba całkowita albo nazwa zmiennej typu całkowitego, natomiast DŁUGOŚĆ to pewna liczba całkowita. Ta forma wypisywania liczby całkowitej "wymusza" na programie, żeby wypisana liczba miała długość równą co najmniej tyle, ile zostało podane w parametrze DŁUGOŚĆ; tak więc, jeżeli sama liczba ma długość większą lub równą wartości DŁUGOŚĆ, to zostanie ona po prostu wypisana, natomiast jeżeli jej długość jest mniejsza, to zostanie ona w zapisie poprzedzona tyloma znakami spacji (odstępu), żeby łącznie cały jej zapis miał długość równą DŁUGOŚĆ. I tak na przykład program:


1 program calkowite;

2

3 var l: Integer;

4

5 begin

6 Read(l);

7 Writeln(l:3);

8 Writeln(l:6);

9 Writeln(-123:7);

10 end.


jeżeli podamy mu liczbę l równą 1234, to wypisze:

1234 1234 -123 Typy rzeczywiste


Nie tylko liczby całkowite są reprezentowane w zmiennych o zróżnicowanych typach. Istnieje również kilka typów rzeczywistych; należą do nich typ Real (zajmuje 6 bajtów), Single (o pojedynczej dokładności - zajmuje 4 bajty), Double (o podwójnej dokładności - zajmuje 8 bajtów) i Extended (zajmuje 10 bajtów) - czym większy rozmiar typu, tym większa jego dokładność. Napiszmy teraz bardzo podobny do już raz napisanego program, który odczytuje trzy liczby rzeczywiste i je wypisuje w odwrotnej kolejności:


1 program rzeczywiste;

2

3 var l1, l2, l3: Real;

4

5 begin

6 Read(l1, l2, l3);

7 Writeln(l3, ' ', l2, ' ', l1);

8 end.


Zauważ, że tym razem został zastosowany inny zapis przy deklaracji zmiennych. Przy deklaracji można bowiem w jednym miejscu, po przecinku wypisać kilka nazw zmiennych, o ile są one wszystkie tego samego typu. To również znacznie skraca i upraszcza zapisy, więc będzie stosowane w dalszym ciągu.


Liczby rzeczywiste podajemy programowi w postaci liczb z kropką (a nie przecinkiem!) dziesiętną, której użycie nie jest jednak konieczne (czyli prawidłowo wpisana liczba rzeczywista może mieć postać np. 22.11, 100.0, ale też i po prostu 100). Niemniej jednak, nasz program nie wypisze naszych trzech liczb w takiej formie, w jakiej mu je podaliśmy - tak np. liczbę 22.11 wypisze w postaci 2.21100000000E+01. Jest tak dlatego, że liczby rzeczywiste w komputerze są reprezentowane w postaci , gdzie to tzw. mantysa liczby, a to cecha, przy czym obie liczby są liczbami całkowitymi (komputer nie jest w stanie reprezentować liczb rzeczywistych z "nieskończoną dokładnością", dlatego też musi stosować przybliżenia zawierające liczby całkowite, które jest już w stanie reprezentować dokładnie). Dlatego otrzymany przez nas wynik oznacza, że nasza liczba to .


Z pewnością o wiele przyjaźniejszą formą zapisu byłby dla użytkownika zapis liczby rzeczywistej w formie standardowej. W tym celu, możemy w instrukcji wypisywania wprowadzić parametry podobnie jak przy wypisywaniu liczb całkowitych:


1 Write(LICZBA:DLUGOSC:PRECYZJA);


gdzie LICZBA jest pewną liczbą rzeczywistą albo zmienną typu rzeczywistego, a DŁUGOŚĆ i PRECYZJA to pewne liczby całkowite. Zapis ten oznacza, że wypisywana liczba ma mieć dokładnie tyle cyfr po przecinku ile wynosi parametr PRECYZJA i przy tym cały zapis o długości niemniejszej niż parametr DŁUGOŚĆ (podobnie jak powyżej, jeżeli liczba z tyloma cyframi po przecinku ile jest wyznaczone w parametrze PRECYZJA ma długość większą lub równą parametrowi DŁUGOŚĆ, to zostanie ona po prostu wypisana). Cyfry znajdujące się po przecinku, które nie zmieszczą się w ramach precyzji, zostaną pominięte, a ostatnia wypisana cyfra będzie zawierać ewentualne zaokrąglenie. Tak więc jeżeli na przykład poniższemu programowi:


1 program rzeczywiste;

2

3 var l: Real;

4

5 begin

6 Read(l);

7 Writeln(l:0:5);

8 Writeln(l:7:2);

9 end.


podać liczbę rzeczywistą 22.116, to otrzymamy następujący wynik:

22.11600 22.12 Polecamy popróbować, jakie rezultaty powstaną, kiedy do powyższego i podobnych programów powprowadzamy różne liczby rzeczywiste. Pomoże Ci to nabrać biegłości we współpracy z liczbami rzeczywistymi. Działania na liczbach


Samo wczytywanie liczb i ich wypisywanie, nawet w zmienionej kolejności, nie jest jeszcze bardzo ciekawe. Język Pascal umożliwia wykonywanie różnych działań na liczbach całkowitych i rzeczywistych. Podstawowe działania na liczbach całkowitych to: + (dodawanie), - (odejmowanie), * (mnożenie), div (dzielenie) i mod (reszta z dzielenia) oraz - w znaczeniu negacji (zmiany znaku) liczby. Tak na przykład możemy napisać program, który wczytuje długości boków prostokąta i wypisuje jego pole i obwód:


1 program prostokat;

2

3 var a, b: Longint;

4

5 begin

6 Read(a, b);

7 Writeln('Pole prostokata o bokach ', a, ' i ', b, ' jest rowne ', a * b);

8 Writeln('Obwod prostokata o bokach ', a, ' i ', b, ' jest rowny ', 2 * (a+b));

9 end.


Jak widzisz, możemy za pomocą instrukcji Writeln wypisywać nie tylko same wartości zmiennych, ale także i wartości wyrażeń matematycznych zawierających zmienne i liczby całkowite. Tak jak w matematyce, jest ustalona kolejność wykonywania działań i jest ona identyczna jak w matematyce (najpierw mnożenie i dzielenie, potem dodawanie i odejmowanie). Również mamy do dyspozycji nawiasy (tylko okrągłe), które wpływają na kolejność wykonywania działań.


Osobną kwestią jest wykonywanie dzielenia na liczbach całkowitych. W Pascalu wykonywane jest dzielenie z resztą, przy czym, jeżeli chcemy uzyskać iloraz dwóch liczb, to stosujemy działanie div, natomiast w celu uzyskania reszty, stosujemy działanie mod. Jako przykład zapiszmy program, który wykonuje dzielenie z resztą:


1 program dzielenie_z_reszta;

2

3 var a, b: Longint;

4

5 begin

6 Read(a, b);

7 Writeln(a, ' / ', b, ' = ', a div b, ' r. ', a mod b);

8 end.


Tak jak w matematyce nie wolno dzielić przez 0, tak samo jest to niemożliwe w Pascalu. Dlatego jeżeli wprowadzimy do tego programu b=0, to program zakończy się błędem. Zauważ, że w nazwie programu użyliśmy symbolu podkreślenia "_" w celu oddzielenia wyrazów. Jest to jedyny symbol oprócz liter i cyfr, który może występować w identyfikatorach, takich jak nazwy zmiennych czy programu.


Zupełnie analogiczne działania możemy stosować także w przypadku liczb rzeczywistych. Mamy do dyspozycji działania +, -, * oraz / (dzielenie), a także - w znaczeniu negacji liczby. Dla przykładu zamieszczony jest program wczytujący dwie liczby i wypisujący wyniki 4 podstawowych działań na nich:


1 program dzialania;

2

3 var a, b: Real;

4

5 begin

6 Read(a, b);

7 Writeln('Suma liczb jest rowna ', a + b:0:3);

8 Writeln('Roznica liczb jest rowna ', a - b:0:3);

9 Writeln('Iloczyn liczb jest rowny ', a * b:0:3);

10 Writeln('Iloraz liczb jest rowny ', a / b:0:3);

11 end.


Pamiętaj o parametrach przy wypisywaniu liczb rzeczywistych! Najczęściej, jeżeli trzeba po prostu wypisać daną liczbę z pewną liczbą cyfr po przecinku, to parametr DŁUGOŚĆ ustawia się na 0, gdyż jest on wtedy bez znaczenia. Typ łańcuchowy i typ znakowy


Jak już wspominaliśmy, w Pascalu istnieją typy nie tylko reprezentujące liczby całkowite czy rzeczywiste. Dla przykładu, istnieje typ reprezentujący fragmenty tekstu - jest to typ String (tzw. typ łańcuchowy). Zmienne tego typu są więc ciągami znaków kodu ASCII. Ich długość musi być niewiększa niż 255 znaków. Jeżeli zapragniemy wczytać zmienną typu String, to przyporządkowana jej zostanie cała linia wpisana przez użytkownika. Jeżeli wówczas będziemy chcieli wczytać dalej jeszcze jakieś inne zmienne typu String, to w tym celu konieczne będzie zastosowanie instrukcji Readln przy tym pierwszym odczycie (jeżelibyśmy stosowali tylko instrukcję Read, to po wczytaniu pierwszej zmiennej nie zmienilibyśmy linii, z której następuje odczyt, a to by spowodowało, że następne zmienne nie byłyby wczytywane z następnych linii, tylko wciąż z tej samej linii i dlatego pozostawałyby tekstami pustymi). Dla przykładu program wczytujący imię, nazwisko i miejscowość zamieszkania i wypisujący te dane w odwrotnej kolejności mógłby wyglądać następująco (zakładamy, że wyżej wymienione dane znajdują się w osobnych liniach):


1 program dane_osobowe;

2

3 var imie, nazw, miasto: String;

4

5 begin

6 Write('Podaj swoje imie: ');

7 Readln(imie);

8 Write('Podaj swoje nazwisko: ');

9 Readln(nazw);

10 Write('Podaj swoje miasto zamieszkania: ');

11 Readln(miasto);

12 Writeln('Twoje dane osobowe to: ', miasto, ' - ', nazw, ' ', imie);

13 end.


Zmienne typu String również można wypisywać w instrukcji:


1 Write(NAZWA_ZMIENNEJ:DLUGOSC);


gdzie DŁUGOŚĆ to parametr będący liczbą całkowitą nieujemną. W przypadku, gdy długość zmiennej typu String jest mniejsza od parametru DŁUGOŚĆ, to zmienna zostaje wypisana z taką liczbą spacji z przodu zapisu, żeby łączna długość zapisu była równa DŁUGOŚĆ. W przeciwnym wypadku, zmienna jest po prostu zwyczajnie wypisana.

Istnieje również możliwość deklaracji zmiennych będących pojedynczymi znakami kodu ASCII - typem przechowującym pojedyncze znaki jest Char (z ang. character - znak). Kod ASCII wszystkim najpopularniejszym znakom takim jak litery, cyfry, znaki interpunkcyjne czy symbole działań matematycznych przyporządkowuje liczby z zakresu od 0 do 255. Więcej o kodzie ASCII dowiesz się w następnych lekcjach. O błędach wykonania


Po udanym skompilowaniu programu, w wyniku jego wykonania może się zdarzyć wiele rzeczy. W najlepszym przypadku program dla podawanych mu danych działa prawidłowo. Niestety zdarza się, że program dla podanych mu wartości wypisuje nie to, co powinien - wtedy należy przejrzeć jego kod źródłowy i poszukać błędu, ewentualnie w przypadku większych kodów wypisywać po drodze wartości różnych zmiennych aby sprawdzić, z którą jest coś nie tak. W innych przypadkach program może się zakończyć tzw. błędem wykonania (ang. Runtime Error), który to błąd zostanie zgłoszony w trakcie jego działania i spowoduje jego przerwanie. Błędy wykonania najczęściej następują w przypadku próby wykonania czynności, które naruszają pewne ograniczenia, na przykład powodują przekroczenie zakresu typu całkowitego (więcej takich sytuacji poznasz w kolejnych lekcjach). Przy błędzie wykonania jest także zawsze podana linia kodu, w której on wystąpił, co ułatwia jego wyszukanie i wyeliminowanie.



Instrukcje warunkowe Wstęp


Żeby zaprezentować siłę i potrzebę użycia instrukcji warunkowych, rozwiążmy następujące zadanie:


"Napisz program, który wczyta 5 liczb całkowitych a, b, c, d, e i w przypadku, gdy wypisze , a w przypadku, gdy wypisze . Możesz założyć, że liczby i ich sumy zmieszczą się w typie Longint i że zawsze lub ."


Zadanie z pozoru wygląda na nierozwiązywalne przy znanych nam metodach; tym niemniej, polecamy zastanowić się nad nim przed przeczytaniem rozwiązania.


Otóż można zauważyć, że wartość poniższego wyrażenia jest dla e=-2 równa c+d, a dla e=2 równa a+b:


(sprawdzenie tego nie powinno sprawić Ci trudności).


Napisanie odpowiedniego programu pozostawiamy Ci jako (wartościowe) ćwiczenie.


Jak widać, udało się nam rozwiązać to zadanie, ale brzmi ono na tyle "prosto", że aż dziwnym wydaje się być, żeby napisanie tego programu wymagało tak trudnego pomysłu. Za chwilę poznasz instrukcję, która ułatwi rozwiązywanie tego i innych problemów, w których trzeba dokonać pewnej decyzji w programie (czyli w zależności od tego, jakie program dostanie dane, będzie wypisywał różne rodzaje informacji).

Instrukcja warunkowa if


Schemat instrukcji warunkowej if jest następujący:


1 if WARUNEK then INSTRUKCJA1 (else INSTRUKCJA2);


Jeżeli pod WARUNEK wpiszemy pewne wyrażenie (o jego budowie wkrótce dowiesz się więcej), które może przyjmować wartość prawda lub fałsz, i to wyrażenie okaże się być prawdziwe, to zostanie wykonana INSTRUKCJA1; jeżeli zaś będzie ono fałszywe, to o ile podaliśmy fragment w nawiasie (nie jest on konieczny), to zostanie wykonana INSTRUKCJA2. W powyższym zapisie, słowa if, then i else są słowami kluczowymi. Pamiętać też należy o umieszczeniu średnika po instrukcji warunkowej if i o nieumieszczaniu go po INSTRUKCJI1, o ile występuje część zawierająca słowo else - w przeciwnym przypadku średnik w tym miejscu jest konieczny (często popełniany błąd).


Najprostsze wyrażenia, które mogą być prawdziwe lub fałszywe otrzymamy, wykorzystując znane w matematyce relacje równości, mniejszości-większości, niemniejszości-niewiększości i różności (), liczby i działania na liczbach. Instrukcją z kolei może być na przykład wczytanie czy wypisanie czegoś.


Dla przykładu, możemy napisać program rozwiązujący wyżej opisane zadanie za pomocą instrukcji warunkowej:


1 program lepszy;

2

3 var a, b, c, d, e: Longint;

4

5 begin

6 Read(a, b, c, d, e);

7 if e = 2 then

8 Write(a + b)

9 else

10 Write(c + d);

11 end.


Nasz program wczyta 5 liczb i gdy e=2, to wypisze a+b, a w przeciwnym wypadku wypisze c+d (gdyż wtedy wiadomo, że będzie e=-2).


Możemy napisać teraz program, który wczytuje liczbę całkowitą i wypisuje jej znak (jeżeli jest nieujemna, to +, jeżeli ujemna to -). Posłużymy się teraz relacją "być niewiększym niż", która w Pascalu, ze względu na brak klawisza na klawiaturze, zapisuje się jako "<=" (tak samo, relacja niemniejszości to w Pascalu ">=", a różności to "<>").


1 program znak;

2

3 var x: Longint;

4

5 begin

6 Read(x);

7 if x >= 0 then

8 Write('+')

9 else

10 Write('-');

11 end.


Teraz napiszemy program, który w trochę bardziej zaawansowany sposób sprawdzi znak naszej liczby: wyróżni jeszcze przypadek, gdy x=0 i wtedy wypisze 0. W tym celu możemy w części po słowie else jako INSTRUKCJA2 użyć również instrukcji warunkowej:


1 program znak2;

2

3 var x: Longint;

4

5 begin

6 Read(x);

7 if x > 0 then

8 Write('+')

9 else

10 if x = 0 then

11 Write(0)

12 else

13 Write('-');

14 end.


Jak widzisz, wykorzystanie wcięć w kodzie jest w tym przypadku bardzo korzystne, gdyż pozwala "połapać się", które instrukcje wypisywania dotyczą których instrukcji warunkowych. Operatory logiczne


Spróbujmy teraz napisać program, który wczytuje jedną literę (małą), a wypisuje, czy ten jest ona samogłoską, czy spółgłoską. Literę wczytamy do zmiennej typu Char (reprezentującej jeden znak). Tak jak dowolny tekst, w Pascalu także pojedyncze znaki otacza się apostrofami. Możemy oczywiście zapisać nasz program korzystając z metod wyżej używanych na przykład tak:


1 program litera;

2

3 var lit: Char;

4

5 begin

6 Read(lit);

7 if lit = 'a' then

8 Write('Samogloska')

9 else

10 if lit = 'e' then

11 Write('Samogloska')

12 else

13 if lit = 'i' then

14 Write('Samogloska')

15 else

16 if lit = 'o' then

17 Write('Samogloska')

18 else

19 if lit = 'u' then

20 Write('Samogloska')

21 else

22 if lit = 'y' then

23 Write('Samogloska')

24 else

25 Write('Spolgloska');

26 end.


Jak łatwo zauważysz, program ten jest dość długi i nieprzyjemny. Jeżeli zapisać nasz warunek w mowie potocznej, to dokonujemy sprawdzenia: "jeżeli litera to a, to jest samogłoską, dalej, jeżeli litera to e, to jest samogłoską, dalej ... jeżeli litera to y to jest samogłoską, no a w przeciwnym razie jest spółgłoską". W istocie to, co chcieliśmy w programie napisać, to "jeżeli litera jest jedną z liter a, e, i, o, u, y, to jest samogłoską, a w przeciwnym przypadku spółgłoską". W celu skrócenia zapisu, w Pascalu mamy możliwość skorzystania przy budowie wyrażeń, które są prawdą albo fałszem, z tak zwanych operatorów logicznych: and (i, oraz), or (lub) oraz not (nie). Za pomocą spójnika and możemy połączyć dwa wyrażenia w jedno, będące prawdziwym wtedy i tylko wtedy, gdy oba wyrażenia składowe są prawdziwe. Analogicznie, spójnik or łączy dwa wyrażenia w jedno, prawdziwe, jeżeli którekolwiek z podwyrażeń jest prawdziwe. Wreszcie not, postawione przed pewnym wyrażeniem, powoduje jego zaprzeczenie (czyli coś, co jest prawdziwe gdy podwyrażenie jest fałszywe i odwrotnie). Za pomocą spójników and oraz or możemy łączyć także więcej niż dwa wyrażenia. Kiedy jakieś wyrażenia łączymy operatorami logicznymi, to musimy je umieszczać w nawiasach.

Warto także wiedzieć, że operatory and oraz or nie są równorzędne pod względem pierwszeństwa - operator and ma wyższe pierwszeństwo, niż or (odpowiada to sytuacji jak przy mnożeniu i dodawaniu). Gdybyś kiedykolwiek zapomniał, który operator ma wyższy priorytet, to staraj się stosować wszędzie nawiasy, aby się nie pomylić.


Dla przykładu, wykorzystując spójnik or, możemy znacznie skrócić powyższy program:


1 program litera2;

2

3 var lit: Char;

4

5 begin

6 Read(lit);

7 if (lit = 'a') or (lit = 'e') or (lit = 'i') or (lit = 'o') or

8 (lit = 'u') or (lit = 'y') then

9 Write('Samogloska')

10 else

11 Write('Spolgloska');

12 end.


Innym przykładem zastosowania nowopoznanych operatorów jest napisanie programu, który wczytuje boki trójkąta i sprawdza, czy trójkąt ten jest równoboczny, równoramienny czy też różnoboczny:


1 program trojkat;

2

3 var a, b, c: Longint;

4

5 begin

6 Read(a, b, c);

7 if (a = b) and (b = c) then {jezeli wszystkie boki sa rowne (oczywiscie,

8 gdy a=b i b=c, to musi byc c=a)}

9 Write('Rownoboczny')

10 else

11 if (a = b) or (a = c) or (b = c) then {jezeli jakiekolwiek dwa boki sa rowne}

12 Write('Rownoramienny')

13 else {juz zadne dwa boki nie sa rowne}

14 Write('Roznoboczny');

15 end.


Instrukcje złożone


Dotychczas poznana postać instrukcji warunkowej if charakteryzuje się tym, że w ramach INSTRUKCJI1 i INSTRUKCJI2 może wystąpić tylko jedna instrukcja. Czasem zachodzi jednak potrzeba, żeby w przypadku zachodzenia pewnego warunku wykonać więcej niż jedną instrukcję. Wówczas możemy te instrukcje, które chcemy w danym miejscu wykonać, umieścić w ramach INSTRUKCJI1 czy INSTRUKCJI2, otaczając je słowami kluczowymi begin oraz end; po każdej z tych instrukcji należy wtedy - jak w przypadku pojedynczych instrukcji - umieścić średnik.


Dla przykładu wykorzystamy tę możliwość w programie, który odczytuje pewien kąt (w stopniach, między 0 a 360 stopni) i sprawdza, czy jest on wypukły, półpełny czy wklęsły; jeżeli jest wypukły, to sprawdza, czy jest ostry, prosty czy rozwarty, jeżeli jest ostry, to sprawdza czy jest zerowy, i wreszcie, jeżeli jest wklęsły, to sprawdza, czy jest pełny.


1 program katy;

2

3 var kat: Real;

4

5 begin

6 Read(kat);

7 if kat < 180 then {Czy wypukly?}

8 begin

9 Writeln('Wypukly');

10 if kat < 90 then {Jesli wypukly, to czy ostry}

11 begin

12 Writeln('Ostry');

13 if kat = 0 then {Jesli wypukly i ostry, to czy zerowy?}

14 Writeln('Zerowy');

15 end

16 else

17 if kat = 90 then {Jesli wypukly, to czy prosty?}

18 Writeln('Prosty')

19 else {Jesli wypukly i nie ostry i nie prosty, to juz na pewno rozwarty}

20 Writeln('Rozwarty');

21 end

22 else {Czy polpelny?}

23 if kat = 180 then

24 Writeln('Polpelny')

25 else {Jesli nie wypukly i nie polpelny, to juz na pewno wklesly}

26 begin

27 Writeln('Wklesly');

28 if kat = 360 then {Jesli wklesly, to czy pelny?}

29 Writeln('Pelny');

30 end;

31 end.


Kod ten jest niestety skomplikowany, gdyż i sama klasyfikacja kątów jest dość skomplikowana.


Dzięki wykorzystaniu instrukcji złożonych, w przypadku wprowadzenia kąta o mierze 45 stopni uzyskamy informację, że jest zarówno wypukły, jak i ostry; napisanie takiego programu bez instrukcji złożonych stanowi nie lada wyzwanie.

Instrukcje złożone będą się pojawiać nie tylko w przypadku instrukcji warunkowych. Warto więc zapamiętać ich postać, gdyż mimo zmiany ich miejsca występowania, sama postać pozostanie niezmienna. Pouczający przykład


Pouczający przykład, kiedy instrukcje złożone są niezbędne, wygląda z pozoru na łatwy do rozwiązania bez nich. Mamy w nim napisać program, który wczytuje liczbę całkowitą i jeżeli jest dodatnia, to jeżeli jest równa 5 to wypisuje 5, a jeżeli nie jest dodatnia, to wypisuje -.


Narzucająca się próba rozwiązania:


1 program proba; {nieudana}

2 var x: Integer;

3

4 begin

5 Read(x);

6 if x > 0 then

7 if x = 5 then

8 Writeln(5)

9 else

10 Writeln('-');

11 end.


okazuje się niespodziewanie być nieudana! Powyższy program nie zawsze działa tak, jak powinien - właściwie działa poprawnie tylko dla ! Jeżeli i nie jest równe 5, to program wypisuje -, a dla liczb niedodatnich nic nie wypisuje. Dzieje się tak dlatego, że instrukcja else jest przez kompilator wiązana z najbliższą dopuszczalną dla niej instrukcją if, czyli w przypadku naszego programu z "if x = 5". Żeby wymusić na kompilatorze poprawne związanie instrukcji else z "if x > 0", należy użyć instrukcji złożonych:


1 program sukces; {udana proba}

2 var x: Integer;

3

4 begin

5 Read(x);

6 if x > 0 then

7 begin

8 if x = 5 then

9 Writeln(5);

10 end

11 else

12 Writeln('-');

13 end.


Opisany przykład jest w praktyce częstym źródłem trudnych do znalezienia błędów. Wynikają one z tego, że często tak bardzo przywiązujemy się do wcięć, że zapominamy, że kompilator ich absolutnie nie dostrzega.



Instrukcja przypisania i instrukcja warunkowa case Instrukcja przypisania


Jak dotychczas, zmienne wykorzystywaliśmy tylko i wyłącznie do wczytywania jakichś danych oraz do wypisywania ich (bądź ich połączonych pewnymi działaniami) na ekran. Zmienna jest jednak - jak sama nazwa wskazuje - obiektem zmiennym, więc możemy w trakcie wykonywania programu wielokrotnie zmieniać jej wartość. Zmiana wartości zmiennej w programie (poza wczytywaniem) jest wykonywania w tzw. instrukcji przypisania - w niej po prostu przypisujemy zmiennej nową wartość. Schemat instrukcji przypisania jest bardzo prosty:


1 ZMIENNA := WARTOSC;


Musimy pamiętać przy tym, żeby wartość mieściła się w typie danej zmiennej, czyli np. zmiennej typu całkowitego nie możemy przypisać znaku czy tekstu, a także zmienej typu Integer przypisać wartości .


Zanim podamy przykład zastosowania instrukcji przypisania, opowiemy o jednej bardzo ważnej instrukcji przypisania. Jeżeli zmienna x jest typu całkowitego, to instrukcja:


1 x := x + 1;


powoduje zwiększenie wartości zmiennej x o 1. Dzieje się tak, gdyż instrukcja przypisania najpierw wylicza wartość wyrażenia po swojej prawej stronie (którą w tym przypadku jest suma dotychczasowej wartości zmiennej i 1) i dokonuje przypisania jej do zmiennej po lewej stronie (czyli x) - ostatecznie x-owi zostaje przyporządkowana wartość x+1.


Dla przykładu napiszmy program, który wczytuje 3 litery i wypisuje, ile z nich stanowią samogłoski:


1 program samogloski;

2

3 var lit: Char;

4 ile: Integer;

5

6 begin

7 ile := 0; {na poczatku liczba samoglosek jest zerowa}

8 Read(lit);

9 if (lit = 'a') or (lit = 'e') or (lit = 'i') or (lit = 'o') or (lit = 'u')

10 or (lit = 'y') then

11 ile := ile + 1; {nie potrzebujemy czesci instrukcji warunkowej else, gdyz

12 jezeli litera nie jest samogloska, to nie interesuje nas,

13 czym ona faktycznie jest}

14 Read(lit); {zauwazmy, ze mozemy do tej samej zmiennej cos wczytywac

15 wielokrotnie, gdyz juz w tym momencie nie interesuje nas,

16 jaka byla poprzednia litera}

17 if (lit = 'a') or (lit = 'e') or (lit = 'i') or (lit = 'o') or (lit = 'u')

18 or (lit = 'y') then

19 ile := ile + 1;

20 Read(lit);

21 if (lit = 'a') or (lit = 'e') or (lit = 'i') or (lit = 'o') or (lit = 'u')

22 or (lit = 'y') then

23 ile := ile + 1;

24 Writeln(ile);

25 end.


Inny przykład wykorzystania instrukcji przypisania otrzymamy, próbując napisać program, który wczytuje dwie liczby i wypisuje je w kolejności mniejsza-większa (do tego celu wykorzystamy pomocnicze zmienne min i max, którym przypiszemy, odpowiednio, mniejszą i większą z liczb a i b):


1 program kolejnosc;

2

3 var a, b, min, max: Longint;

4

5 begin

6 Read(a, b);

7 if a < b then

8 begin

9 min := a;

10 max := b;

11 end

12 else

13 begin

14 min := b;

15 max := a;

16 end;

17 Write(min, ' ', max);

18 end.


Można ten program uprościć, obierając inną strategię - jeżeli , to pozostawimy zmienne a i b bez zmian; w przeciwnym przypadku, dokonamy zamiany zawartości tych zmiennych - w ten sposób, na końcu i tak i tak uzyskamy układ, w którym wartość zmiennej a jest mniejsza od wartości zmiennej b.


Problematyczna okazuje się jednak zamiana wartości zmiennych; nie możemy tego wykonać tak:


1 a := b;

2 b := a;


gdyż po pierwszym przypisaniu wartości zmiennych a i b będą takie same, więc drugie przypisanie nic nie zmieni i na końcu uzyskamy po prostu . Do wykonania zamiany potrzebna jest jedna dodatkowa zmienna, której przypiszemy "na chwilę" zawartość zmiennej a, następnie zmiennej a przypiszemy zawartość zmiennej b, a na końcu przypiszemy zmiennej b zawartość zmiennej dodatkowej, która jest taka sama jak stara zawartość zmiennej a.


Po tym wstępie, możemy już napisać poprzedni program w wersji ulepszonej, czyli krótszej:


1 program lepszy;

2

3 var a, b, tymcz: Longint;

4

5 begin

6 Read(a, b);

7 if a > b then

8 begin

9 tymcz := a;

10 a := b;

11 b := tymcz;

12 end;

13 Write(a, ' ', b);

14 end.


Jak łatwo zauważysz, program nasz dość łatwo zapisać w ogóle bez używania instrukcji przypisania, tak:


1 program najlepszy;

2

3 var a, b: Longint;

4

5 begin

6 Read(a, b);

7 if a < b then

8 Write(a, ' ', b)

9 else

10 Write(b, ' ', a);

11 end.


Tym niemniej, o wiele łatwiej zapisać program analogiczny do tego, tylko porządkujący 3 liczby, z pomocą przypisań niż bez. Zastosujemy w tym celu metodę: będziemy się starali uzyskać na samym końcu najmniejszą liczbę w zmiennej a, średnią w zmiennej b, a największą w zmiennej c. Tak więc, najpierw uporządkujemy liczby a i b niemalejąco (z wykonaniem co najwyżej jednej zamiany). Następnie zastanowimy się, jak w stosunku do liczb a i b wypada liczba c: jeżeli jest większa od b, to jest największą z nich i nie ma już żadnych zamian do wykonania; jeżeli jest mniejsza od b ale większa od a, to trzeba będzie zamienić wartości zmiennych b i c (gdyż b jest największa, a c jest średnia); wreszcie, jeżeli c jest mniejsza od a, to jest najmniejszą z liczb, więc trzeba będzie dokonać serii zamian, jako c biorąc b, jako b biorąc a, a jako a biorąc c (tu też potrzebna będzie zmienna pomocnicza):


1 program porzadek;

2

3 var a, b, c, tymcz: Longint;

4

5 begin

6 Read(a, b, c);

7 {Porzadkujemy zmienne a i b.}

8 if a > b then

9 begin

10 tymcz := a;

11 a := b;

12 b := tymcz;

13 end;

14 {Lokalizujemy polozenie wartosci zmiennej c.}

15 if c < b then {W przeciwnym przypadku nic nie trzeba robic.}

16 if c < a then {c jest najmniejsza}

17 begin

18 tymcz := c;

19 c := b;

20 b := a;

21 a := tymcz;

22 end

23 else {c jest miedzy a i b}

24 begin

25 tymcz := b;

26 b := c;

27 c := tymcz;

28 end;

29 Write(a, ' ', b, ' ', c);

30 end.


Jeżeli uważasz ten program za skomplikowany, to polecamy spróbować napisać go bez użycia instrukcji przypisania. Instrukcja warunkowa case


Wyobraźmy sobie, że mamy do napisania program, który wczytuje numer miesiąca i wypisuje trzyliterowy skrót jego nazwy. Gdybyśmy chcieli go napisać wyłącznie z wykorzystaniem instrukcji warunkowych if, to byłby on, co łatwo zauważysz, strasznie długi i nieciekawy. Z pomocą przychodzi nam druga instrukcja warunkowa case, której schemat jest następujący:


1 case ZMIENNA of

2 WYRAZENIE1: INSTRUKCJA1;

3 WYRAZENIE2: INSTRUKCJA2;

4 ...

5 WYRAZENIE(k): INSTRUKCJA(k);

6 end;


Instrukcja ta powoduje, że w zależności od wartości ZMIENNEJ, zostaje wykonana dana instrukcja. ZMIENNA musi tutaj być tzw. typu porządkowego (czyli najczęściej całkowitego lub znakowego, ale NIE rzeczywistego ani tekstowego - String). WYRAŻENIA mogą być tutaj różnej postaci; dla potrzeb tego przykładu wystarczy, że wyrażenia mogą być po prostu wartościami typu ZMIENNEJ.


Nasz program możemy więc zapisać tak:


1 program miesiace;

2

3 var m: Byte;

4

5 begin

6 Read(m);

7 case m of

8 1: Write('sty');

9 2: Write('lut');

10 3: Write('mar');

11 4: Write('kwi');

12 5: Write('maj');

13 6: Write('cze');

14 7: Write('lip');

15 8: Write('sie');

16 9: Write('wrz');

17 10: Write('paz');

18 11: Write('lis');

19 12: Write('gru');

20 end;

21 end.


WYRAŻENIA mogą także być kilkoma wartościami wymienionymi po przecinku (to pozwala czasem istotnie skracać zapisy). Przykład zastosowania tego ulepszenia to program, który wczytuje numer miesiąca i wypisuje liczbę dni tego miesiąca (wiele miesięcy ma tę wartość wspólną):


1 program dni_miesiecy;

2

3 var m: Byte;

4

5 begin

6 Read(m);

7 case m of

8 2: Write(28); {luty - zakladamy, ze rok nie jest przestepny}

9 4, 6, 9, 11: Write(30); {kwiecien, czerwiec, wrzesien i listopad}

10 1, 3, 5, 7, 8, 10, 12: Write(31); {reszta}

11 end;

12 end.


Zaprezentujemy jeszcze jedno udoskonalenie instrukcji warunkowej case:


1 case ZMIENNA of

2 WYRAZENIE1: INSTRUKCJA1;

3 WYRAZENIE2: INSTRUKCJA2;

4 ...

5 WYRAZENIE(k): INSTRUKCJA(k);

6 else INSTRUKCJA; {udoskonalenie}

7 end;


w którym po słowie kluczowym else umieszczamy instrukcję, która ma zostać wykonana, kiedy wartość ZMIENNEJ nie będzie pasować do żadnego z WYRAŻEŃ. I tak na przykład, możemy uprościć nasz program, dodając też na początku sprawdzenie, czy liczba podana jako m rzeczywiście reprezentuje numer miesiąca:


1 program dni;

2

3 var m: Byte;

4

5 begin

6 Read(m);

7 if (m > 12) or (m < 1) then

8 Write('To nie jest numer miesiaca.')

9 else

10 case m of

11 2: Write(28);

12 4, 6, 9, 11: Write(30);

13 else Write(31);

14 end;

15 end.


Większe wyzwanie


Na koniec tej lekcji zaplanowaliśmy pewien trudniejszy przykład, zawierający w sobie zastosowanie instrukcji warunkowej if i przypisań, ale także i nowych elementów języka. Będziemy chcieli napisać program, który wczyta imię (złożone dla uproszczenia tylko z małych liter alfabetu angielskiego) i wypisze liczbę jego samogłosek i spółgłosek, a także pierwsze i ostatnie wystąpienie samogłoski w imieniu. Dla uproszczenia założymy, że imię ma co najmniej 1 literę i co najwyżej 5 liter.


Żeby rozwiązać to zadanie, musimy trochę bliżej poznać własności zmiennych typu String. Po pierwsze, mamy funkcję Length, która zwraca długość (czyli liczbę znaków) danego tekstu. Drugą potrzebną rzeczą będzie umiejętność dostępu do poszczególnych liter tekstu. Uzyskujemy ją za pomocą nawiasów kwadratowych: jeżeli zmienna s jest typu String i INDEKS jest niemniejszy od 1 i niewiększy od długości s, to pod postacią s[INDEKS] reprezentowany jest znak tekstu s o indeksie INDEKS; należy uważać na te ograniczenia, gdyż wykorzystanie takiego wyrażenia w przeciwnym przypadku spowoduje błąd wykonania!


Teraz już możemy napisać nasz program. Gorąco zachęcamy Ciebie do pomyślenia nad szczegółami zapisu tego programu przed obejrzeniem poniższego kodu - pomoże Ci to dostrzec istotne trudności w jego pisaniu. Będziemy w nim stosowali komentarze, które pomogą nie pogubić się w nim.


1 program trudniejszy;

2

3 var s: String;

4 ile_sam, pierwsza, ostatnia: Integer;

5

6 begin

7 {inicjowanie zmiennych}

8 Readln(s);

9 ile_sam := 0;

10 pierwsza := 0;

11 ostatnia := 0;

12

13 {sprawdz pierwsza litere}

14 if (s[1] = 'a') or (s[1] = 'e') or (s[1] = 'i') or (s[1] = 'o')

15 or (s[1] = 'u') or (s[1] = 'y') then

16 begin

17 ile_sam := ile_sam + 1;

18 if pierwsza = 0 then

19 pierwsza := 1;

20 ostatnia := 1;

21 end;

22

23 {jezeli imie ma co najmniej 2 znaki, sprawdz druga litere}

24 if (Length(s) >= 2) and

25 ((s[2] = 'a') or (s[2] = 'e') or (s[2] = 'i') or (s[2] = 'o')

26 or (s[2] = 'u') or (s[2] = 'y')) then

27 begin

28 ile_sam := ile_sam + 1;

29 if pierwsza = 0 then

30 pierwsza := 2;

31 ostatnia := 2;

32 end;

33

34 {jezeli imie ma co najmniej 3 znaki, sprawdz trzecia litere}

35 if (Length(s) >= 3) and

36 ((s[3] = 'a') or (s[3] = 'e') or (s[3] = 'i') or (s[3] = 'o')

37 or (s[3] = 'u') or (s[3] = 'y')) then

38 begin

39 ile_sam := ile_sam + 1;

40 if pierwsza = 0 then

41 pierwsza := 3;

42 ostatnia := 3;

43 end;

44

45 {jezeli imie ma co najmniej 4 znaki, sprawdz czwarta litere}

46 if (Length(s) >= 4) and

47 ((s[4] = 'a') or (s[4] = 'e') or (s[4] = 'i') or (s[4] = 'o')

48 or (s[4] = 'u') or (s[4] = 'y')) then

49 begin

50 ile_sam := ile_sam + 1;

51 if pierwsza = 0 then

52 pierwsza := 4;

53 ostatnia := 4;

54 end;

55

56 {jezeli imie ma co najmniej 5 znakow, sprawdz piata litere}

57 if (Length(s) >= 5) and

58 ((s[5] = 'a') or (s[5] = 'e') or (s[5] = 'i') or (s[5] = 'o')

59 or (s[5] = 'u') or (s[5] = 'y')) then

60 begin

61 ile_sam := ile_sam + 1;

62 if pierwsza = 0 then

63 pierwsza := 5;

64 ostatnia := 5;

65 end;

66

67 {wypisz wynik}

68 Writeln('Liczba samoglosek to : ', ile_sam, ' a liczba spolglosek to: ',

69 Length(s) - ile_sam);

70 if ile_sam > 0 then

71 begin

72 Writeln('Pierwsza z nich jest na pozycji ', pierwsza);

73 Writeln('Ostatnia z nich jest na pozycji ', ostatnia);

74 end;

75 end.


Uwagi do programu


W programie dla każdej litery imienia sprawdzamy, czy jest samogłoską (najpierw sprawdzając, czy ta litera w ogóle istnieje, czyli czy imię ma wystarczającą liczbę liter). Jeżeli jest, to zwiększamy wartość zmiennej reprezentującej liczbę samogłosek i odpowiednio poprawiamy wartości zmiennych ostatnia i pierwsza (zawsze wtedy zmieniamy wartość zmiennej ostatnia, dzięki temu będzie ona przechowywać indeks ostatniej samogłoski w słowie; z drugiej strony, wartość zmiennej pierwsza może będzie zmieniona co najwyżej raz - czyli wtedy, kiedy jej wartość będzie równa 0 - przy pierwszej samogłosce). Trzeba przy tym koniecznie pamiętać o zainicjowaniu (czyli przypisaniu początkowych wartości) zmiennych zliczających, gdyż bez tego mają one na początku pewne losowe wartości (nie zainicjowanie zmiennych jest bardzo częstym błędem, który bardzo trudno wykryć). Na końcu następuje wypisanie statystyk, przy czym wypisujemy informację o pierwszej i ostatniej samogłosce, jeżeli imię zawiera jakiekolwiek samogłoski.

Jak łatwo zauważysz, zmienna ostatnia nie musiała być zainicjowana. Tym niemniej, lepiej zainicjować zmienną, której nie musimy inicjować, niż nie zainicjować zmiennej, którą powinniśmy. Dlatego polecamy praktycznie zawsze (przynajmniej na początku nauki) inicjować zmienne.


Pętla for


Pętla for, jak również i inne pętle, pozwala na wielokrotne wykonywanie tej samej czynności. Stanowi to po pierwsze pewne ułatwienie, pozwalające na uniknięcie wielokrotnego wpisywania tego samego fragmentu kodu; z drugiej strony, umożliwia to uzyskanie nowych możliwości w pisaniu programów.


Załóżmy, że chcemy wczytać 20 liczb rzeczywistych i obliczyć ich średnią arytmetyczną.

Średnią arytmetyczną z n liczb rzeczywistych obliczamy dzieląc ich sumę przez ich liczbę:


Oczywiście można by ten cel uzyskać z wykorzystaniem jedynie poznanych dotychczas metod:


1 program srednia1;

2 var a, suma: Real;

3

4 begin

5 suma := 0.0;

6 Read(a); suma := suma + a;

7 Read(a); suma := suma + a;

8 {... to samo jeszcze 17 razy}

9 Read(a); suma := suma + a;

10 Writeln((suma / 20.0):0:3);

11 end.


tym niemniej jest to dosyć kłopotliwe (wielokrotnie powtarzamy ten sam kod) i mało elastyczne (w celu obliczenia średniej arytmetycznej 100 liczb, musielibyśmy dopisać jeszcze 80 identycznych linii).


Dlatego też, w celu wykonania naszego zadania wykorzystamy pętlę for, której schemat jest następujący:


1 for ZMIENNA := WYRAZENIE1 to WYRAZENIE2 do INSTRUKCJA;


W trakcie wykonywania się pętli for, ZMIENNA przyjmuje kolejno każdą z wartości pomiędzy wartością WYRAŻENIA1 a wartością WYRAŻENIA2 (włącznie z wartością WYRAŻENIA1 i wartością WYRAŻENIA2) dokładnie raz i dla każdej z tych wartości zostaje wykonana INSTRUKCJA (wewnątrz której ZMIENNA przyjmuje właśnie daną wartość i może być tam wykorzystywana). ZMIENNA i obie wartości muszą być typu porządkowego (najczęściej całkowitego lub znakowego (Char)).

ZMIENNĄ nazywa się czasem zmienną sterującą pętli.


UWAGA: w tym przypadku zakładamy, że wartość WYRAŻENIA1 jest niewiększa od wartości WYRAŻENIA2, to znaczy jeżeli WYRAŻENIE1>WYRAŻENIE2, to pętla się w ogóle nie wykona. Dla odwrotnego przypadku, kiedy WYRAŻENIE1>WYRAŻENIE2 i chcielibyśmy wykonywać pętlę "w dół", istnieje odmiana pętli for:


1 for ZMIENNA := WYRAZENIE1 downto WYRAZENIE2 do INSTRUKCJA;


w której słowo kluczowe to zostało zastąpione słowem kluczowym downto.


W naszym przypadku zastosowanie pętli for może wyglądać następująco:


1 program srednia2;

2 var a, suma: Real;

3 i: Integer;

4

5 begin

6 suma := 0.0;

7 for i := 1 to 20 do

8 begin

9 Read(a);

10 suma := suma + a;

11 end;

12 Writeln((suma / 20.0):0:3);

13 end.


Przypomnijmy, że stosowanie instrukcji złożonej wymaga umieszczenia jej między słowami kluczowymi begin oraz end - jak było również w przypadku instrukcji warunkowych.


W ten sposób treść wielokrotnie wykonywanej instrukcji musieliśmy napisać tylko raz! Pętla for umożliwia nam również jeszcze bardziej elastyczny zapis programu. Możemy np. na początku wczytać liczbę liczb, których średnią chcemy obliczyć:


1 program srednia3;

2 var a, suma: Real;

3 i, ile: Integer;

4

5 begin

6 suma := 0.0;

7 Read(ile);

8 for i := 1 to ile do

9 begin

10 Read(a);

11 suma := suma + a;

12 end;

13 Writeln((suma / ile):0:3);

14 end.


Zauważmy, że bez pętli for (czy też innych pętli, które jeszcze poznamy), nie bylibyśmy w stanie zmieniać liczby wczytywanych liczb.


Innym ciekawym problemem - który pokazuje siłę pętli for - jest badanie, czy dana liczba naturalna jest liczbą pierwszą.

Liczba naturalna jest liczbą pierwszą, jeżeli nie jest ona podzielna przez żadną liczbę oprócz 1 oraz samej siebie. Liczby, które nie są pierwsze, nazywamy liczbami złożonymi. Zakładamy przy tym, że 0 i 1 nie są ani pierwsze, ani złożone.


Tak na przykład początkowymi liczbami pierwszymi są 2, 3, 5, 7, 11, 13, ..., a złożonymi: 4, 6, 8, 9, 10, 12, ...


Aby sprawdzić, czy liczba naturalna n jest pierwsza, z definicji wystarczy zbadać, czy którakolwiek z liczb od 2 do jest jej dzielnikiem. W celu zapamiętania, czy którakolwiek liczba z wyżej wymienionych była dzielnikiem n, wykorzystamy zmienne nowego typu - typu logicznego (Boolean). Zmienna typu logicznego może przyjmować tylko 2 wartości: prawda (true) lub fałsz (false). Możemy w instrukcji warunkowej if jako warunku użyć także zmiennej typu logicznego, gdyż podobnie jak dotychczasowe układane przez nas warunki, może ona być prawdziwa lub fałszywa.


1 program pierwszosc;

2 var i, n: Longint;

3 czy_pierwsza: Boolean;

4

5 begin

6 Read(n);

7 czy_pierwsza := true; {na poczatek zalozmy, ze liczba jest pierwsza}

8

9 {teraz, dla kazdego i=2,...,n-1 sprawdzmy, czy jest ono moze dzielnikiem n;

10 jezeli tak, to n na pewno nie jest pierwsza}

11 for i := 2 to n - 1 do

12 if n mod i = 0 then {wykorzystanie zmiennej i we wnetrzu petli!}

13 czy_pierwsza := false;

14

15 {jezeli zadna z liczb 2,...,n-1 nie okazala sie dzielnikiem n, czyli

16 zmienna czy_pierwsza ma wartosc true, to liczba n jest na pewno pierwsza}

17 if n <= 1 then

18 Writeln(n, ' nie jest ani pierwsza, ani zlozona.')

19 else

20 if czy_pierwsza then

21 Writeln(n, ' jest pierwsza.')

22 else

23 Writeln(n, ' jest zlozona.');

24 end.


Zauważmy, że w celu sprawdzenia, czy liczba n jest pierwsza, wystarczy przeanalizować jako potencjalne jej dzielniki wszystkie liczby z zakresu od 2 do pierwiastka z niej. Faktycznie, jeżeli n nie jest pierwsza, to można ją przedstawić w postaci , gdzie obie liczby a i b są naturalne i żadna nie jest równa 1. Gdyby zachodziło , to byłoby , co prowadzi do sprzeczności i dowodzi, że choć jedna z liczb a, b mieścić się musi w wyżej opisanym zakresie. Stąd w naszym programie można zmienić pętlę for z:


1 for i := 2 to n - 1


na:


1 for i := 2 to Trunc(Sqrt(n))


gdzie Sqrt oznacza operację wyciągnięcia pierwiastka kwadratowego z liczby (wynikiem jest liczba typu rzeczywistego), a Trunc to całość z liczby, czyli największa liczba naturalna niewiększa od tej liczby (często stosowane są także określenia cecha, część całkowita, entier i podłoga).


Nasza modyfikacja, z pozoru nieważna, powoduje znaczne zmniejszenie liczby wykonywanych operacji (jest to bardzo ważne przy badaniu pierwszości dużych liczb). Polecamy porównać szybkości programów z zastosowaniem zwykłej i ulepszonej wersji powyższej pętli dla dość dużej liczby pierwszej, na przykład 999.999.937 - na przeciętnym aktualnie komputerze PC pierwszy działa kilkadziesiąt sekund, a drugi - niezauważalnie szybko!


Warto jeszcze zauważyć, że jeżeli w pętli for wartość WYRAŻENIA1 jest większa od wartości WYRAŻENIA2 (co dzieje się np. w naszym programie dla ), to pętla ta w ogóle nie jest wykonywana.

Drobna uwaga


W pętli for wartość WYRAŻENIA1 i WYRAŻENIA2 wyliczają się przed rozpoczęciem wykonywania kolejnych kroków pętli. Stąd w poniższym programie:


1 program petla;

2 var i, n: Integer;

3

4 begin

5 n := 1000;

6 for i := 1 to n do

7 n := n - 1;

8 end.


pętla wykona się 1000 razy. Z kolei zmiana wartości zmiennej sterującej wewnątrz pętli powoduje stosowne zmiany w jej wykonywaniu.


Niezależnie od powyższych uwag, i tak nie polecamy wewnątrz pętli zmieniać wartości ZMIENNEJ, WYRAŻENIA1 czy WYRAŻENIA2, gdyż może to powodować duży bałagan w kodzie.



Tablice jako zastosowanie pętli for Tablice


Bardzo często przy okazji programowania pojawia się konieczność pamiętania przez program dużych ilości danych. Wówczas oczywiście używanie do wszystkiego osobnych zmiennych nie daje oczekiwanych rezultatów (jest kłopotliwe i niewygodne w użyciu). Dlatego też w Pascalu możemy wykorzystywać tzw. tablice, reprezentujące ciągi elementów tego samego typu, w pewien sposób ponumerowane.


Tablicę deklarujemy w następujący sposób:


1 var tablica: array[POCZATEK..KONIEC] of TYP;


W powyższym zapisie słowo array jest słowem kluczowym, POCZĄTEK i KONIEC reprezentują zakres numeracji pól tablicy (tablica jest w tym przypadku numerowana: POCZĄTEK, POCZĄTEK+1, ... ,KONIEC-1, KONIEC, gdzie te kolejne liczby są nazywane indeksami pól tablicy), natomiast TYP to typ danych zapisanych w tablicy. Dla przykładu, deklaracja tablicy zawierającej liczby całkowite typu Integer ponumerowane od 2 do 7 ma postać:


1 var tab: array[2..7] of Integer;


O przydatności tablicy decyduje to, że do każdego pola tablicy (z zakresu od POCZĄTEK do KONIEC) możemy się odwołać za pomocą nawiasów kwadratowych (podobnie jak w przypadku tekstów - typ String). W ten sposób, żeby odczytać, jaka wartość znajduje się w powyższej tablicy na 5-tym polu, wystarczy napisać tab[5]. Analogicznie, możemy zmienić wartość na tym polu na przykład tak:


1 tab[5] := 17;


Podobnie, z elementami tablicy można robić to wszystko, co można było robić ze zmiennymi (wczytywać, wypisywać i inne).

Pamiętaj, że nie wolno odwoływać się do pól tablicy poza zakresem. Niestosowanie się do tego spowoduje błąd wykonania! (podobnie jak w przypadku sięgania do znaku poza zakresem zmiennej łańcuchowej)


Tablicę możemy indeksować wartościami całkowitymi i znakowymi (czyli na przykład można zrobić tablicę o indeksach od 'a' do 'e'). Ponieważ rozmiary tablic mogą być dość duże (i takie też zazwyczaj są), to bardzo często do obsługi tablic używa się pętli for.


Dla przykładu, napiszmy program, który w tablicy zapamiętuje potęgi dwójki od zerowej do dwudziestej, po czym je wypisuje:


1 program tablica;

2

3 var tab: array[0..20] of Longint;

4 i: Integer;

5

6 begin

7 tab[0] := 1;

8 for i := 1 to 20 do

9 {Nastepna potega dwojki jest po prostu 2 razy wieksza niz poprzednia.}

10 tab[i] := tab[i-1] * 2;

11 for i := 0 to 20 do

12 Writeln(i, ' : ', tab[i]);

13 end.


Ciekawsze przykłady


Przyszła teraz pora na przeanalizowanie kilku ciekawych i przydatnych zastosowań tablic. Dla przykładu, napiszmy program, ktory wczytuje ciąg liczb (jako najpierw ilość liczb, a potem te liczby - zakładamy, że ilość liczb nie przekroczy 1000) i wypisuje go w odwrotnej kolejności (od ostatniej do pierwszej):


1 program ciag;

2

3 var tab: array[1..1000] of Integer;

4 i, n: Integer;

5

6 begin

7 Read(n);

8 for i := 1 to n do

9 Read(tab[i]);

10 for i := n downto 1 do

11 {Pamietaj, ze przy petli for wypisywanie od konca do poczatku wymaga

12 zastapienia slowa kluczowego to slowem kluczowym downto.}

13 Writeln(tab[i]);

14 end.


Spróbujmy teraz rozwiązać trochę inny problem, polegający na faktycznym odwróceniu naszej tablicy, czyli zamianie elementów w tej tablicy porządkiem tak, żeby odpowiadały odwróconemu początkowemu ciągowi. W celu jego rozwiązania wykorzystamy metodę zamiany zawartości zmiennych i dla każdej pozycji tablicy od pierwszej do -iej zamienimy jej zawartość z pozycją będącą jej odbiciem lustrzanym względem środka tablicy. Tak więc, zamienimy zawartości pól 1 i n, 2 i n-1 itd.


1 program odwrocenie;

2

3 var tab: array[1..1000] of Integer;

4 i, n, pom: Integer;

5

6 begin

7 Read(n);

8 for i := 1 to n do

9 Read(tab[i]);

10 for i := 1 to n div 2 do

11 begin

12 {Zamien zawartosc i-tego pola tablicy z zawartoscia (n-i+1)-ego.}

13 pom := tab[i];

14 tab[i] := tab[n-i+1];

15 tab[n-i+1] := pom;

16 end;

17 for i := 1 to n do

18 Writeln(tab[i]);

19 end.


Przykład zastosowania tablicy indeksowanej znakami kodu ASCII może dać napisanie programu, który znajduje statystyki słowa (czyli liczbę poszczególnych liter w nim występujących). W tym programie wykorzystuje się także jeszcze nieużywane przez nas możliwości pętli for (to, że zmienna może przebiegać nie tylko wartości całkowite, ale i pewne wartości znakowe). Dla ułatwienia załóżmy, że wszystkie litery słowa są małe:


1 program statystyki;

2

3 var stat: array['a'..'z'] of Integer;

4 s: String;

5 znak: Char;

6 i: Integer;

7

8 begin

9 Readln(s);

10 {Petla indeksowana zmienna znakowa (tablica zreszta tez)!}

11 for znak := 'a' to 'z' do

12 stat[znak]:=0;

13 {Przegladamy poszczegolne litery słowa s i powiekszamy wartosci odpowiednich

14 pol w tablicy stat}

15 for i := 1 to Length(s) do

16 stat[s[i]] := stat[s[i]] + 1;

17 {Wypisujac wynik, przegladamy cala tablice stat}

18 for znak := 'a' to 'z' do

19 Writeln(znak, ' : ', stat[znak]);

20 end.


Trudniejszy problem


Zajmijmy się teraz trudniejszym problemem: mając dany pewien ciąg liczb należy sprawdzić, ile par różnych jego elementów jest liczbami względnie pierwszymi.

Dwie liczby nazywamy względnie pierwszymi, jeżeli nie posiadają wspólnego dzielnika większego od 1.


W celu przeanalizowania wszystkich par elementów z tablicy, potrzebne będzie zastosowanie dwóch pętli for, jednej zagnieżdżonej (czyli wewnątrz) drugiej. Sprawdzenie, czy dwie liczby nie posiadają większego od 1 wspólnego dzielnika, zostanie wykonane poprzez przejrzenie wszystkich potencjalnych dzielników w zakresie od 2 do mniejszej z tych liczb:


1 program pary;

2

3 var tab: array[1..1000] of Longint;

4 i, j, k: Longint; {zmienne do wykonywania petli}

5 n, ile, wynik: Longint;

6 czy: Boolean;

7

8 begin

9 Read(n);

10 for i := 1 to n do

11 Read(tab[i]);

12

13 wynik := 0;

14 {Przyszla pora na dwie petle, jedna wewnatrz drugiej.}

15 for i := 1 to n do

16 for j := 1 to n do

17 if i <> j then {pamietajmy, ze rozwazamy tylko rozne elementy ciagu}

18 begin

19 {Bedziemy analizowac liczby od 2 do mniejszej z liczb (tab[i],tab[j]).}

20 if tab[i] > tab[j] then

21 ile := tab[j]

22 else

23 ile := tab[i];

24

25 czy := true;

26 {Sprawdzamy, czy ktorakolwiek z tych liczb nie dzieli tab[i] i tab[j]

27 naraz - jezeli tak jest, to ta para liczb nie jest wzglednie pierwsza.}

28 for k := 2 to ile do

29 if (tab[i] mod k = 0) and (tab[j] mod k = 0) then

30 czy := false;

31

32 {Jezeli wszystkie sprawdzane liczby nie dzielily tab[i] i tab[j] naraz,

33 to te liczby sa wzglednie pierwsze.}

34 if czy then

35 wynik := wynik + 1;

36 end;

37 Writeln(wynik);

38 end.


Powyższy kod z pewnością wymaga komentarza. W przypadku, gdy wykonywane są dwie pętle for, z czego jedna zawiera się wewnątrz drugiej, to zarówno zmienna w pierwszej pętli jak i ta w drugiej pętli przebiega cały swój zakres, czyli przy każdej wartości pierwszej zmiennej, druga zmienna przebiega cały swój zakres. W ten sposób otrzymujemy par wartości zmiennych i oraz j.


Tak więc w powyższym programie, dla każdej pary wartości i oraz j jest wykonywany (pod warunkiem, że i oraz j są różne, co jest sprawdzane tuż za wywoływanymi pętlami) blok instrukcji między słowami kluczowymi begin oraz end. W ramach tego bloku, znajdowana jest mniejsza z liczb tab[i] i tab[j] (i zapamiętywana pod postacią zmiennej ile), a następnie wywoływana jest trzecia pętla przeglądająca wszystkie liczby od 2 do ile. Jeżeli którakolwiek z nich jest dzielnikiem zarówno tab[i] jak i tab[j], to zmienna logiczna czy zostaje zmieniona na false (czyli wtedy te liczby na pewno nie są względnie pierwsze). Na końcu sprawdzana jest wartość zmiennej czy i jeżeli jest ona prawdą, to znaleźliśmy nową parę liczb względnie pierwszych w tablicy i powiększana jest wartość zmiennej wynik.

Typ wyliczeniowy, definicje stałych


Lekcję kończymy dwiema dość ważnymi uwagami:


* Zapis POCZĄTEK..KONIEC w deklaracji tablicy jest w rzeczywistości zapisem pewnego typu, tzw. typu wyliczeniowego. Oznacza to, że jeżeli zadeklarowana zostanie zmienna pokroju:


1 var i: 12..18;


to będzie to zmienna całkowita, która będzie mogła przyjmować wartości wyłącznie z przedziału od 12 do 18. Czasami takie deklaracje zmiennych mogą być pomocne w zapamiętywaniu, w jakim zakresie powinno się utrzymywać wartości konkretnej zmiennej. Tym niemniej, jest to (poza deklaracjami tablic) używane dość rzadko.

* W miarę coraz to dalszej nauki programowania, długość pisanych przez Ciebie programów staje się coraz większa (co wkrótce będzie wiązało się z koniecznością używania wielu tablic o różnych, często bardzo dużych zakresach). Dlatego też w takich przypadkach zalecane jest definiowanie w programie stałych, które stanowią po prostu oznaczenia na pewne stałe liczby, które dość często pojawiają się w programie. Stałą definiuje się jako:


1 const STALA = WARTOSC;


gdzie STAŁA to po prostu nazwa nadawana stałej (identyfikator, jak w przypadku nadawania nazw zmiennym), a WARTOŚĆ to stała wartość jej nadawana (najczęściej pewna liczba czy ciąg znaków). Przykładem definicji stałej jest:


1 const MILION = 1000000;


Definicje stałych muszą być poprzedzone słowem kluczowym const, które pełni w ich przypadku taką rolę jak słowo kluczone var w przypadku deklaracji zmiennych. Ważnym jest, by definicje stałych występowały przed definicjami zmiennych.


Zaletą stosowania stałych jest to, że po pierwsze z treści programu znikają dziwne duże liczby, co do których trudno jest w pewien czas po napisaniu programu powiedzieć, jakie miało być ich znaczenie, po drugie łatwiej jest uniknąć trudnych do znalezienia błędów (gdyż łatwo się pomylić i np. wpisać o jedno zero więcej przy wpisywaniu któryś raz z rzędu liczby 1000000), a po trzecie powiększa się elastyczność kodu (jeżeli będziesz chciał kiedyś zmienić stałą 1000000 na 999999, to nie będziesz musiał tego czynić w kilkudziesięciu miejscach w kodzie, tylko w jednym). Dla odróżnienia ich od zmiennych, będziemy zapisywali identyfikatory stałych wielkimi literami (tym niemniej, nie wolno jest powtarzać identyfikatorów stałych i zmiennych gdyż, przypomnijmy, Pascal nie odróżnia małych i wielkich liter).



Procedury i funkcje


Procedury i funkcje to elementy języka Pascal, które umożliwiają grupowanie fragmentów kodu źródłowego programu. Taki fragment kodu jest zapisywany w programie poza podstawowym blokiem begin-end. i może być potem wielokrotnie wykorzystywany.

Procedury


Definicja procedury o nazwie NAZWA w języku Pascal ma postać:


1 procedure NAZWA(PAR1: TYP1; PAR2: TYP2; ...);

2 var ZMIENNAA: TYPA;

3 ZMIENNAB: TYPB;

4 ...

5 begin

6 KOD_PROCEDURY

7 end;


Słowo procedure jest kolejnym słowem kluczowym języka Pascal. Po nazwie procedury następuje lista jej parametrów, czyli wartości przekazywanych do wnętrza procedury - kolejne parametry są podane w postaci PARAMETR: TYP. Następnie mogą nastąpić deklaracje zmiennych, które mają postać dokładnie taką samą, jak w programie głównym (najpierw słówko kluczowe var, a potem ciąg deklaracji postaci ZMIENNA: TYP). Te zmienne to tak zwane zmienne lokalne, w przeciwieństwie do zmiennych deklarowanych w programie głównym, zwanych zmiennymi globalnymi. Dalej następuje KOD_PROCEDURY, który jest zapisany za pomocą instrukcji takich samych jak główna treść programu w bloku begin-end., więc można w nim wykorzystywać na przykład instrukcje warunkowe (if, case) czy pętlę for. W KODZIE_PROCEDURY można wykorzystywać wszystkie zmienne zadeklarowane w programie głównym, zmienne zadeklarowane na początku procedury, a także przekazane jej parametry, których wartości jednak nie da się zmieniać (czyli nie możemy na przykład nic parametrom przypisać).

Istnieje też możliwość przekazywania do procedury parametrów, których wartości można zmieniać - poznasz ją w dalszej części lekcji.


Procedury zaspisuje się w programie za deklaracjami zmiennych, a przed głównym blokiem programu.


Żeby użyć procedury w programie, trzeba ją wywołać albo w programie głównym, albo na przykład wewnątrz innej procedury. Takie wywołanie ma postać:


1 NAZWA(PAR1, PAR2, ...);


Jako parametry PAR1, PAR2, ... podajemy przy wywołaniu wartości (czyli albo konkretne liczby czy znaki, albo identyfikatory zmiennych), które chcemy jej przekazać.


Procedury są przydatne głównie z dwóch powodów:


1. W przypadku długich programów, zapisywanie całego kodu źródłowego w jednym bloku bardzo utrudnia pracę (na przykład poprawienie jakichś fragmentów czy szukanie błędów). Wykorzystując procedury można kod źródłowy programu podzielić na mniejsze bloki (najczęściej wykonujące pojedyncze tematycznie czynności).

2. Jeżeli dany fragment kodu będzie wykorzystywany w wielu miejscach w programie, to złym pomysłem jest wielokrotne przepisywanie go (wydłuża kod i ułatwia popełnianie błędów). W takim wypadku można po prostu dany fragment kodu ująć w osobnej procedurze i wywołać ją w stosownych miejscach.


Zapiszmy procedurę, która dla podanej liczby całkowitej wypisze w kolejnych liniach wszystkie jej dzielniki. Procedura będzie miała jeden parametr - podaną liczbę całkowitą.


1 procedure dzielniki(n: Longint);

2 var i: Longint;

3 begin

4 for i := 1 to n do

5 if n mod i = 0 then

6 Writeln(i);

7 end;


W powyższej procedurze użyliśmy jednej zmiennej lokalnej - i, która była nam potrzebna w pętli for. Możemy użyć tak zapisanej procedury w programie wypisującym wszystkie dzielniki liczb: 768, 1024 i 123:


1 program dzielniki_liczb;

2 {nie potrzebujemy zadnych zmiennych globalnych}

3 procedure dzielniki(n: Longint);

4 var i: Longint;

5 begin

6 for i := 1 to n do

7 if n mod i = 0 then

8 Writeln(i);

9 end;

10

11 begin

12 Writeln('Dzielniki liczby 768:');

13 dzielniki(768);

14 Writeln('Dzielniki liczby 1024:');

15 dzielniki(1024);

16 Writeln('Dzielniki liczby 123:');

17 dzielniki(123);

18 end.


W powyższym programie idea używania procedur staje się widoczna - gdybyśmy nie napisali procedury, to musielibyśmy ten sam 3-linijkowy kod napisać 3 razy.


Możemy teraz napisać program, który - z wykorzystaniem tej samej procedury - wypisze dzielniki wszystkich liczb od 1 do 100:


1 program dzielniki_liczb;

2 var j: Longint;

3

4 procedure dzielniki(n: Longint);

5 var i: Longint;

6 begin

7 for i := 1 to n do

8 if n mod i = 0 then

9 Writeln(i);

10 end;

11

12 begin

13 for j := 1 to 100 do

14 begin

15 Writeln('Dzielniki liczby ', j);

16 dzielniki(j);

17 end;

18 end.


W powyższym programie mamy przykład podania zmiennej jako parametru procedury. Jaki skutek odniosła by zmiana nazwy zmiennej globalnej j na i? Okazuje się, że ... program nadal działałby poprawnie! Jest tak dlatego, że zmienne lokalne danej procedury nie są widoczne "na zewnątrz" niej, czyli w innych procedurach czy kodzie programu głównego (dlatego w powyższym programie NIE MOŻNA BY w głównym bloku programu używać po prostu zmiennej i, zadeklarowanej w kodzie procedury). Stąd jeżeli w procedurze zadeklarujemy zmienną o takiej samej nazwie jak zmienna w programie głównym, to będzie ona przez kompilator traktowana jako ZUPEŁNIE NOWA zmienna i żadne zmiany jej wartości nie będą miały wpływu na wartość zmiennej z programu głównego. Z kolei jeżeli dana zmienna nie jest zadeklarowana wewnątrz procedury, ale w kodzie głównym programu, to wszelkie zmiany jej wartości w procedurze będą miały wpływ na jej wartość w programie głównym.

Powyższy wywód wydaje się być mało istotny. Tym niemniej BARDZO CZĘSTYM źródłem błędów jest na przykład zmiana wartości zmiennej globalnej w procedurze w efekcie przeoczenia, że nie jest ona tylko zmienną lokalną. Należy więc uważać przy używaniu zmiennych lokalnych i globalnych!!! Funkcje


Funkcje różnią się od procedur tylko tym, że w wyniku swojego działania zwracają jakąś wartość (co odpowiada przekazaniu do miejsca wywołania "wyniku pracy" funkcji). Deklaracja funkcji ma postać bardzo podobną do deklaracji procedury:


1 function NAZWA(PAR1: TYP1; PAR2: TYP2; ...): TYP;

2 var ZMIENNAA: TYPA;

3 ZMIENNAB: TYPB;

4 ...

5 begin

6 KOD_PROCEDURY

7 end;


Jedynymi różnicami w stosunku do deklaracji procedury są:


* Zamiast słowa kluczowego procedure jest słowo kluczowe function.

* Za nazwą i parametrami funkcji zostaje podany typ wyniku, jaki funkcja zwraca.


Zwrócenie wyniku w funkcji odbywa się przez przypisanie pewnej wartości do jej nazwy:


1 NAZWA := WARTOSC;


które odbywa się w dowolnym miejscu w kodzie funkcji (najczęściej na końcu całej funkcji lub na końcu większego jej fragmentu).


Dla przykładu napiszemy teraz dwie bardzo proste funkcje, które zwracają odpowiednio mniejszą i większą z dwóch przekazanych im wartości. Te dwie funkcje bardzo często pojawiają się w programach, gdyż są często wykorzystywane w wielu miejscach kodu.


1 program min_i_max;

2 var x, y: Longint;

3

4 function min(x, y: Longint): Longint;

5 begin

6 if x > y then

7 min := y

8 else

9 min := x;

10 end;

11

12 function max(x, y: Longint): Longint;

13 begin

14 if x > y then

15 max := x

16 else

17 max := y;

18 end;

19

20 begin

21 Readln(x, y);

22 Writeln('Minimum: ', min(x, y));

23 Writeln('Maksimum: ', max(x, y));

24 end.


W powyższym kodzie zostało przy okazji zaprezentowane wykorzystanie wyniku funkcji w programie. Zauważmy, że wszędzie w powyższym programie pojawiają się identyfikatory x i y, a mimo to wszystko działa poprawnie. Jest tak dlatego, że parametry przekazane funkcji "działają" podobnie jak zmienne lokalne, to znaczy jeżeli w kodzie funkcji (a także oczywiście procedury) wykorzystywany jest identyfikator, będący zarówno identyfikatorem zmiennej globalnej, jak i parametru, to w ramach tej procedury/funkcji będzie on reprezentował wyłącznie ten parametr.


Wyjaśnić to trochę może inny, trochę dziwniejszy ale jednocześnie poprawny zapis powyższego programu:


1 program min_i_max;

2 var x, y: Longint;

3

4 function min(x, y: Longint): Longint;

5 begin

6 if x > y then

7 min := y

8 else

9 min := x;

10 end;

11

12 function max(x, y: Longint): Longint;

13 begin

14 if x > y then

15 max := x

16 else

17 max := y;

18 end;

19

20 begin

21 Readln(y, x);

22 Writeln('Minimum: ', min(y, x));

23 Writeln('Maksimum: ', max(y, x));

24 end.


Zauważ jeszcze, że przy użyciu wielu parametrów tego samego typu możemy je zadeklarować, wymieniając je po przecinku (ważna jest jednak kolejność, gdyż od niej zależy, która wartość zostanie przypisana któremu z parametrów przy wywołaniu). Na przykład przy wywołaniu funkcji:


1 program przyklad;

2 {...}

3 function licz(a, b: Integer; c, e, d: Char; f: Longint; h, g: Integer): Longint;

4 begin

5 {...}

6 end;

7

8 begin

9 {...}

10 licz(1, 2, '3', '4', '5', 6, 7, 8);

11 {...}

12 end.


poszczególne parametry będą miały wartości: a=1, b=2, c='3', d='5', e='4', f=6, g=8 oraz h=7.

Dalsze przykłady i nowe własności


Jako kolejny przykład zapiszemy (z użyciem funkcji) program, który wypisuje wszystkie liczby pierwsze od 1 do 1000:


1 program pierwsze;

2 var i: Integer;

3

4 function czypierwsza(n: Integer): Boolean;

5 {ta funkcja rowniez czesto sie przydaje}

6 var czy: Boolean;

7 i: Integer;

8 begin

9 czy := true;

10 if n <= 1 then

11 czy := false;

12 {w lekcji 8 pojawilo sie sprawdzanie pierwszosci - tam znajdziesz

13 wyjasnienie, po co to Trunc(Sqrt(n)) zamiast po prostu n-1}

14 for i := 2 to Trunc(Sqrt(n)) do

15 if n mod i = 0 then

16 czy := false;

17 czypierwsza := czy;

18 end;

19

20 begin

21 for i := 1 to 1000 do

22 if czypierwsza(i) then

23 Writeln(i);

24 end.


Znów specjalnie użyliśmy tej samej nazwy zmiennej lokalnej i globalnej - i.


Na początku lekcji obiecaliśmy, że opowiemy o parametrach "zmiennych", czyli takich, których wartości mogą być zmieniane wewnątrz procedur czy funkcji. Parametry takie również umieszczamy w nawiasie po nazwie procedury, tylko poprzedzamy je słowem kluczowym var. Wówczas taki parametr może być dowolnie zmieniany wewnątrz procedury czy funkcji, a końcowy efekt zmian JEST widoczny na zewnątrz procedury/funkcji.


Najlepiej zobaczyć to na przykładzie - napiszemy program, który wczytuje dwie liczby i wypisuje je w odwrotnej kolejności (coś takiego już pokazywaliśmy w lekcji 4, ale tym razem zrobimy to w bardziej skomplikowany sposób, żeby uzyskać uniwersalną procedurę zamieniającą wartości podanych jej parametrów):


1 program odwroc;

2 var a, b: Longint;

3

4 procedure swap(var a, b: Longint); {swap to z ang. zamiana}

5 var tymcz: Longint;

6 begin

7 {o zamianie wartosci zmiennych pisalismy juz w lekcji 7 - tam jest

8 wyjasnione, po co jest zmienna tymczasowa tymcz}

9 tymcz := a;

10 a := b;

11 b := tymcz;

12 end;

13

14 begin

15 Readln(a, b);

16 swap(a, b);

17 Writeln(a, ' ', b);

18 end.


Zauważ, że jeżeli słowo kluczowe var znajduje się przed deklaracją kilku parametrów po przecinku, to odnosi się ono do WSZYSTKICH tych parametrów. Jeżeli chciałbyś mieć tylko jeden parametr zmienny, a jeden zwykły, to musiałbyś zacząć definicję powyższej procedury np. tak:


1 procedure swap(var a: Longint; b: Longint);


Zmienne parametry mogą być przydatne także do wygodnego zwrócenia wyniku działania funkcji w przypadku, gdy składa się on z więcej niż jednej wartości.


Na koniec przedstawimy przykład, kiedy jedna funkcja jest wywoływana wewnątrz drugiej. Napiszemy funkcję sprawdzającą, czy podany tekst składa się wyłącznie z małych liter, a jeżeli tak, to czy co najmniej połowę znaków danego tekstu stanowią samogłoski:


1 program czydobry;

2 var tekst: String;

3

4 function samogloska(znak: Char): Boolean;

5 begin

6 samogloska := (znak = 'a') or (znak = 'e') or (znak = 'i') or (znak = 'o') or

7 (znak = 'u') or (znak = 'y');

8 end;

9

10 {Tu mamy do czynienia z czyms nowym - mozemy porownywac ze soba znaki.

11 Porownanie zostaje dokonane po kodach ASCII znakow. Z kolei korzystamy z tego,

12 ze kody ASCII miedzy kodem 'a' a kodem 'z' maja tylko male litery.}

13 function czymala(znak: Char): Boolean;

14 begin

15 czymala := (znak >= 'a') and (znak <= 'z');

16 end;

17

18 function dobry(t: String): Boolean;

19 var i, licznik: Longint;

20 male: Boolean;

21 begin

22 male := true;

23 for i := 1 to Length(t) do

24 if not czymala(t[i]) then

25 male := false;

26 if not male then

27 dobry := false

28 else

29 begin

30 for i := 1 to Length(t) do

31 if samogloska(t[i]) then

32 licznik := licznik + 1;

33 if 2 * licznik >= Length(t) then {*}

34 dobry := true

35 else

36 dobry := false;

37 end;

38 end;

39

40 begin

41 Readln(tekst);

42 if dobry(tekst) then

43 Writeln('Dobry')

44 else

45 Writeln('Niedobry');

46 end.


Ponieważ identyfikatory procedur i funkcji są równie dobrymi identyfikatorami jak wszystkie inne, to również one nie mogą się powtarzać (czyli np. nie moglibyśmy nazwać poprzedniego programu "dobry", gdyż byłaby to taka sama nazwa, jak nazwa najważniejszej funkcji).


Powyższy kod zasługuje na pewien komentarz. Jak widzisz, tym razem dwie pomocnicze funkcje są wywoływane wewnątrz głównej funkcji; jeżeli taka sytuacja zachodzi, to definicja (czyli treść) procedury czy funkcji wywoływanej musi być w kodzie programu PRZED treścią procedury/funkcji, która ją wywołuje.

Zastanów się, czemu warunek * w powyższym programie nie może wyglądać tak: licznik>=Length(t) div 2. Uwaga


W tej lekcji poznałeś czym są procedury i funkcje w języku Pascal. Możemy więc teraz powiedzieć, że z funkcjami i procedurami miałeś do czynienia już wcześniej, tylko mogłeś o tym nie wiedzieć. Mianowicie już odczytywanie i wypisywanie odbywa się za pomocą procedur Read i Write, których nie musiałeś sam definiować, gdyż są one gotowe w języku Pascal. Z kolei przykładem już zdefiniowanej funkcji jest funkcja Length, zwracająca długość podanego jej łańcucha.


Więcej gotowych procedur i funkcji poznasz w kolejnych lekcjach.


Kolejne pętle Pętla while


Dotychczas poznana pętla for ma pewną wadę - mianowicie przed rozpoczęciem jej wykonywania musi być znana liczba kroków, które zostaną wykonane. Czasami potrzebne są pętle, które wykonują się dopóki jakiś warunek jest spełniony (lub też do momentu, kiedy jakiś warunek stanie się spełniony). W pierwszym przypadku stosowną pętlą w Pascalu jest pętla while, a w drugim pętla repeat.


Pętla while w Pascalu ma postać:


1 while WARUNEK do INSTRUKCJA;


W powyższym zapisie WARUNEK jest pewnym wyrażeniem, które może przyjmować wartość prawda lub fałsz (dokładnie tak samo wygląda warunek w instrukcji warunkowej if), natomiast INSTRUKCJA to pojedyncza instrukcja bądź grupa instrukcji (w drugim przypadku są one otoczone jak zwykle przez słowa begin i end). Pętla while wykonuje się do momentu, kiedy WARUNEK przestaje być prawdziwy; dokładniej w każdym przebiegu pętli jest sprawdzana prawdziwość warunku i w zależności od niej jest lub nie jest wykonywana INSTRUKCJA. Oznacza to w szczególności, że jeżeli na samym początku warunek jest fałszywy, to INSTRUKCJA nie zostanie nigdy wykonana.


Przykładem zastosowania pętli while jest program, który dla danej liczby całkowitej dodatniej podaje liczbę cyfr, z których ona się składa:


1 program cyfry;

2 var n: Longint;

3

4 function liczba_cyfr(n: Longint): Byte;

5 var k: Longint;

6 wynik: Byte;

7 begin

8 k := 1;

9 wynik := 1;

10 while n >= k do

11 begin

12 k := k * 10;

13 wynik := wynik + 1;

14 end;

15 liczba_cyfr := wynik - 1;

16 end;

17

18 begin

19 Readln(n);

20 Writeln(liczba_cyfr(n));

21 end.


W powyższym przykładzie, kiedy przestanie zachodzić warunek n>=k pętli while, to będzie to oznaczało, że k jest najmniejszą potęgą 10-tki większą od n. Ponieważ zawsze zmienna wynik przechowuje liczbę cyfr liczby k, to w chwili zakończenia pętli będzie o jeden większa od liczby cyfr n (jako że k będzie o jedną cyfrę dłuższe od n); dlatego zwracana liczba to właśnie wynik-1.

Zastanów się, coby się stało, gdyby powyższą funkcję wywołać z parametrem n=0.


Powyższe rozwiązanie naszego zadania ma jednak pewną ukrytą wadę. Jest ona związana z tym, że cały czas nie podawaliśmy ograniczenia na wielkość danych wejściowych, czyli w tym przypadku n - przyjęliśmy tylko milcząco, że zmieści się ona w typie Longint. Zastanówmy się, coby się stało, gdyby powyższą funkcję wywołać z parametrem n=2.000.000.000 (dwa miliardy).


Wówczas powyższa funkcja wykonywałaby się aż do chwili kiedy k>n, ale takie k powinno być równe 10.000.000.000. A ponieważ zakres typu Longint jest od około minus dwóch miliardów do około dwóch miliardów (już w lekcji 5 polecaliśmy zapamiętać zakresy używanych typów całkowitych!), to szukane k nie zmieściłoby się w typie, co spowodowałoby nieprzewidziane zachowanie programu.


Istnieje rozwiązanie tego zadania, które nie ma wyżej opisanego mankamentu. Zamiast stosować metodę wstępującą ("od dołu do góry", czyli dla coraz większych wartości k) możemy zastosować metodę zstępującą; będzie ona polegała na "odcinaniu" kolejnych cyfr liczby n (poczynając od ostatniej), aż nie osiągnie ona zera. Odcinanie ostatniej cyfry odpowiada podzieleniu liczby przez 10.


1 program cyfry;

2 var n: Longint;

3

4 function liczba_cyfr(n: Longint): Byte;

5 var wynik: Byte;

6 begin

7 wynik := 0;

8 while n > 0 do

9 begin

10 n := n div 10; {odetnij ostatnia cyfre liczby n}

11 wynik := wynik + 1;

12 end;

13 liczba_cyfr := wynik; {ile cyfr odcielismy}

14 end;

15

16 begin

17 Readln(n);

18 Writeln(liczba_cyfr(n));

19 end.


Na pierwszy rzut oka coś dziwnego dzieje się w powyższej funkcji - otóż próbujemy zmieniać parametr, który z definicji miał być czymś, czego w procedurze czy funkcji nie można zmienić! Otóż prawdą jest, że parametru zmienić się nie da, to znaczy mimo prób przypisania n:=n div 10 w powyższej funkcji liczba n po jej zakończeniu będzie wciąż taka sama, jak na początku (czyli wypisana zostanie ta sama liczba, którą wczytaliśmy), tym niemniej w obrębie samej funkcji wartość parametru n może się zmieniać. Oznacza to, że faktycznie możemy operować parametrem tak samo jak zmienną wewnątrz funkcji, ale wyniki naszych operacji po prostu nie będą widoczne na zewnątrz.

Naprawdę warto uważać przy korzystaniu ze zmiennych lokalnych i parametrów zmiennych i stałych, gdyż wobec ich wielu właściwości i subtelnych różnic nietrudno się pomylić. Pętla repeat


Pętla repeat jest wykonywana aż do momentu, kiedy WARUNEK jest spełniony. Jej składnia to:


1 repeat INSTRUKCJA until WARUNEK;


W powyższym zapisie INSTRUKCJA to znów pojedyncza instrukcja bądź grupa instrukcji, ale WYJĄTKOWO w przypadku pętli repeat grupy instrukcji nie otaczamy nigdy słowami begin oraz end!


Różnica między tą pętlą a pętlą while wydaje się nieznaczna. Jest tak po części faktycznie, gdyż jest to różnica "kosmetyczna" - schemat wykonywania pętli repeat jest taki, że najpierw jest wykonywana INSTRUKCJA, a dopiero potem jest sprawdzany WARUNEK i, w przypadku jego prawdziwości, następuje zakończenie pętli. W związku z odwrotną w stosunku do pętli while kolejnością tych czynności, na przykład w przypadku fałszywości WARUNKU na samym początku pętli repeat INSTRUKCJA i tak zostanie co najmniej raz wykonana. Ogólnie praktycznie wszystko, co da się zapisać za pomocą pętli repeat, da się też zapisać za pomocą samej pętli while (czasem z dodatkowymi instrukcjami warunkowymi) i odwrotnie, więc po prostu zawsze należy dobierać pętlę do charakteru zapotrzebowań.


Dla przykładu zapiszmy program, który wczytuje liczby i sumuje je aż do momentu, kiedy któraś nie będzie ujemna:


1 program sumuj;

2 var suma, n: Longint;

3

4 begin

5 suma := 0;

6 repeat

7 Readln(n);

8 if n > 0 then

9 suma := suma + n;

10 until n < 0;

11 Writeln(suma);

12 end.


W tym przypadku zamiana pętli repeat na pętlę while nie mogłaby się odbyć po prostu tak:


1 program sumuj1;

2 var suma, n: Longint;

3

4 begin

5 suma := 0;

6 while n > 0 do

7 begin

8 Readln(n);

9 if n > 0 then

10 suma := suma + n;

11 end;

12 Writeln(suma);

13 end.


gdyż wartość n przed pierwszym przebiegiem pętli (kiedy jeszcze nic nie wczytaliśmy) jest nieokreślona, czyli nie wiadomo, czy ta pętla by się w ogóle zaczęła. Dlatego też tuż przed pętlą trzeba by dodać przypisanie liczbie n dowolnej dodatniej wartości, aby się upewnić, że pętla się rozpocznie.

Dalsze przykłady


Chcemy teraz napisać program, który sumuje całą linię wczytanych liczb (czyli sumuje liczby aż do napotkania końca linii). Do tego celu potrzebny będzie dodatkowy element języka Pascal, mianowicie funkcja Eoln, która zwraca prawdę, jeżeli doszliśmy do końca wczytywanej linii:


1 program sumuj_do_skutku;

2 var suma, n: Longint;

3

4 begin

5 suma := 0;

6 while not eoln do

7 begin

8 Read(n);

9 suma := suma + n;

10 end;

11 Writeln(suma);

12 end.


Kolejnym ciekawym przykładem jest zmiana układu pozycyjnego, w którym dana liczba jest przedstawiona. Najczęściej przedstawiamy liczby w układzie dziesiątkowym, natomiast w zastosowaniach komputerowych ważne są przede wszystkim inne układy pozycyjne, na przykład dwójkowy czy szesnastkowy.


W układzie dwójkowym (binarnym) występują tylko dwie cyfry - 0 i 1. Reprezentacja liczby w tym układzie ma postać: , gdzie są właśnie zerami lub jedynkami. Podobnie jak w układzie dziesiątkowym, każda liczba ma w układzie dwójkowym dokładnie jedną reprezentację.


Aby zamienić liczbę z układu dziesiątkowego na dwójkowy, możemy zastosować metodę zstępującą, podobną do tej zastosowanej w liczeniu liczby cyfr danej liczby. Możemy zauważyć, że ostatnią cyfrę w reprezentacji (odpowiadającą cyfrze jedności w układzie dziesiątkowym) możemy policzyć jako resztę z dzielenia danej liczby przez 2 (gdyż wszystkie pozostałe wyrażenia w reprezentacji są podzielne przez 2). Aby uzyskać drugą od końca cyfrę, możemy najpierw podzielić naszą liczbę przez 2:




a następnie zastosować tę samą technikę co przed chwilą - wziąć resztę z dzielenia wyniku przez 2. To postępowanie (dzielenie przez 2 i branie reszty z dzielenia wyniku przez 2) możemy kontynuować dopóki wynik częściowy (powstały z kolejnego dzielenia liczby przez 2) jest dodatni. Możemy zapisać stosowny program, który wypisuje reprezentację w układzie dwójkowym wczytanej liczby całkowitej dodatniej:


1 program zamiana;

2 const MAX = 31;

3

4 var n : Longint;

5

6 procedure binarny(n: Longint);

7 var dlug, k: Integer;

8 repr: array[1..MAX] of Integer;

9 begin

10 dlug := 0; {aktualna dlugosc reprezentacji}

11 while n > 0 do

12 begin

13 dlug := dlug + 1;

14 repr[dlug] := n mod 2;

15 n := n div 2;

16 end;

17 for k := dlug downto 1 do

18 Write(repr[k]);

19 end;

20

21 begin

22 Readln(n);

23 binarny(n);

24 end.


Procedura binarny wylicza najpierw cyfry reprezentacji dwójkowej od końca (zgodnie z wyżej opisaną metodą), a następnie wypisuje je w odpowiedniej kolejności. Ponieważ chcemy, żeby procedura działała poprawnie dla wszystkich liczb dodatnich, mieszczących się w typie Longint, a takie liczby są mniejsze od (czyli mają co najwyżej 31 cyfr w reprezentacji binarnej), to rozmiar tablicy został ustalony na 31. Gdybyśmy chcieli zapisać poprawną procedurę tylko dla liczb dodatnich typu Integer, które mają co najwyżej 15 cyfr w reprezentacji binarnej, to moglibyśmy zmienić stałą MAX na 15.

Dość często zdarza się, że w podobnych jak powyżej rozumowaniach szacuje się potrzebną wielkość tablicy. Niestety zdarza się, że w takim szacowaniu się pomylimy o niewielką liczbę (np. ustalimy limit na 31, a będzie potrzebne 32). Dlatego czasem warto deklarować tablice o kilka (np. pięć) większe niż potrzeba, żeby w przypadku takiego drobnego niedoszacowania nie natknąć się na błędy wykonania wynikłe z przekroczenia zakresu tablicy.


Podobnie moglibyśmy zapisać procedurę, która przedstawia liczbę w dowolnym układzie pozycyjnym, zamieniając wszystkie pojawiające się w powyższej procedurze dwójki na podstawę tego układu. W przypadku układów, w których ta podstawa jest większa od 10, pojawia się jeszcze jeden problem - mianowicie nie starcza cyfr układu dziesiątkowego do oznaczenia wszystkich cyfr danego układu. Dla przykładu, w układzie szesnastkowym występuje szesnaście cyfr: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 i 15. Dla wygody zwykło się cyfry większe od 9 oznaczać kolejnymi literami; tak więc cyfry układu szesnastkowego to: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E i F.


Zapiszmy więc procedurę, która zamienia daną liczbę na układ szesnastkowy, uwzględniając powyższą uwagę:


1 const MAX = 8; {tyle cyfr wystarczy}

2

3 procedure szesnastkowy(n: Longint);

4 var dlug, k: Integer;

5 repr: array[1..MAX] of Integer;

6 begin

7 dlug := 0; {aktualna dlugosc reprezentacji}

8 while n > 0 do

9 begin

10 dlug := dlug + 1;

11 repr[dlug] := n mod 16;

12 n := n div 16;

13 end;

14 for k := dlug downto 1 do

15 case repr[k] of

16 10: Write('A');

17 11: Write('B');

18 12: Write('C');

19 13: Write('D');

20 14: Write('E');

21 15: Write('F');

22 else Write(repr[k]);

23 end;

24 end;



Złożone typy danych Typ tablicowy


Dotychczas programując w języku Pascal wykorzystywaliśmy przede wszystkim gotowe typy danych (typy całkowite: Integer, Longint, ...; typy rzeczywiste: Real, Double, ...; typ znakowy Char, typ łańcuchowy String czy typ logiczny Boolean). Nauczyliśmy się także definiować zmienne, będące tablicami wykorzystującymi wyżej wymienione typy. Często jednak dla wygody (aby nie trzeba było przy każdej nowej zmiennej tablicowej pisać całej deklaracji tablicy) chcielibyśmy umieścić tylko w jednym miejscu programu definicję danego typu tablicowego. W języku Pascal można to zrobić w następujący sposób:


1 type NAZWA_TYPU = array[POCZATEK..KONIEC] of TYP;


Po zapisaniu takiej definicji (najczęściej deklaracje własnych typów danych umieszcza się między deklaracjami stałych a deklaracjami zmiennych globalnych) można deklarować zmienne typu NAZWA_TYPU w dalszej części programu. Taki sposób deklaracji typów działa także w przypadku wszystkich dalej omówionych typów danych.


Innym usprawnieniem typu tablicowego jest umożliwienie definiowania tablic wielowymiarowych (czyli tablic, których elementami są tablice). Takie tablice deklaruje się, wymieniając po przecinku początki i końce kolejnych wymiarów tablicy:


1 type NAZWA_TYPU = array[POCZ1..KON1, POCZ2..KON2, ...] of TYP;


Oczywiście nie trzeba deklarować własnego typu reprezentującego taką tablicę (ale można) - wystarczy zadeklarować zmienną jako tablicę wielowymiarową. Aby się odwołać do konkretnego pola takiej tablicy, należy między nawiasami kwadratowymi umieścić po przecinkach numery reprezentujące to polę. Na przykład, poprawnymi odwołaniami do tablicy:


1 var t: array[1..5, 1..6, 1..7] of Longint;


są: t[1, 1, 1], t[5, 6, 7] czy t[4, 3, 2].

Nie powinno się odwoływać do pola tablicy, znajdującego się poza jej zakresem w którymkolwiek wymiarze - nieprzestrzeganie tego może spowodować błąd wykonania.


Jako przykład zastosowania tego typu tablic, rozwiążmy następujący problem: należy napisać program, który wczyta kwadratową tablicę liczb całkowitych i wypisze jej kolejne kolumny jako wiersze. Przykładowo dla poniższej tablicy:


1 2 3

4 5 6

7 8 9


program powinien wypisać tablicę:


1 4 7

2 5 8

3 6 9


Kod źródłowy takiego programu może wyglądać następująco:


1 program zamiana;

2 const MAX = 1000; {maksymalny rozmiar tablicy}

3

4 type Tablica = array[1..MAX, 1..MAX] of Longint; {wlasny typ tablicowy}

5

6 var t: Tablica;

7 i, j, n: Longint;

8

9 begin

10 Readln(n);

11 for i := 1 to n do

12 for j := 1 to n do

13 Read(t[i, j]);

14 for j := 1 to n do

15 begin

16 for i := 1 to n do

17 Write(t[i, j], ' ');

18 Writeln;

19 end;

20 end.


Typ rekordowy


Typ rekordowy jest szczególnie wygodny jeżeli chodzi o przechowywanie rozmaitych danych (nawet o różnych typach) w jednej zmiennej. Może się to przydać na przykład przy gromadzeniu danych o osobach: imię, nazwisko, data i miejsce urodzenia, adres, telefon itp. Typ rekordowy w języku Pascal deklaruje się następująco:


1 type NAZWA_TYPU = record

2 POLE1: TYP1;

3 POLE2: TYP2;

4 ...

5 end;


Rekord o nazwie NAZWA_TYPU może więc zawierać w sobie wiele "zmiennych" (o identyfikatorach POLE1, POLE2, ...) i o różnych typach. Przykładowa definicja rekordu zawierającego dane osobowe to:


1 type Dane_osobowe = record

2 imie, nazwisko: String;

3 rok_ur: Integer;

4 miejsce_ur, adres: String;

5 telefon, pesel: Longint;

6 {...}

7 end;


Jak zapewne zauważyłeś, możemy wiele pól o tym samym typie skupiać w jednej deklaracji. Tym niemniej nazwa żadnego identyfikatora pola nie może się powtórzyć w ramach jednego rekordu.


Odwoływanie się do pól danego rekordu uzyskujemy za pomocą kropki. Na przykład, jeżeli dane jest zmienną typu Dane_osobowe, to poprawne są instrukcje:


1 dane.imie := 'Jan';

2 dane.nazwisko := 'Kowalski';

3 dane.rok_ur := 1989;

4 Writeln(dane.imie, ' ', dane.nazwisko, ' ', dane.rok_ur);


Innymi przykładami, w których często warto rozważyć użycie typu rekordowego są struktury geometryczne, np. punkt (rekord z polami x, y lub x, y, z, oznaczającymi współrzędne) czy odcinek (rekord z polami x1, y1, x2, y2 - współrzędne początku i końca odcinka - czy nawet rekord o polach p1, p2, będących rekordami reprezentującymi wierzchołki odcinka).

Instrukcja with


Do wypełniania pól rekordów (i ogólnie łatwiejszego dostępu do nich) bardzo pomocna jest instrukcja with. Ma ona postać:


1 with REKORD do INSTRUKCJA


REKORD jest nazwą zmiennej rekordowej, do której chcielibyśmy mieć dostęp w ramach następującej dalej INSTRUKCJI. W ramach samej instrukcji możemy się posługiwać samymi identyfikatorami pól, bez poprzedzania ich nazwą zmiennej rekordowej i kropką. Na przykład:


1 with dane do

2 begin

3 imie := 'Jan';

4 nazwisko := 'Kowalski';

5 rok_ur := 1989;

6 Writeln(imie, ' ', nazwisko, ' ', rok_ur);

7 end;


wykonuje dokładnie to samo, co wyżej opisane instrukcje (w sekcji o typie rekordowym).

Typ wyliczeniowy


Czasem zachodzi potrzeba przechowania danych, które mogą mieć tylko kilka z góry ustalonych wartości. Dobrymi przykładami mogą być dni tygodnia, miesiące czy nazwy kart większych od 10-tki (as, król, dama, walet). Do wygodnego przechowywania tego typu danych wprowadzono możliwość definiowania tak zwanego typu wyliczeniowego, którego definicja polega na wyliczeniu możliwych jego wartości (muszą one być identyfikatorami). Definicja typu wyliczeniowego ma postać:


1 type NAZWA_TYPU = (ID1, ID2, ...);


Przykładowe przydatne w praktyce definicje tego typu to:


1 type Dni_tygodnia = (poniedzialek, wtorek, sroda, czwartek, piatek, sobota,

niedziela);

2 type Miesiace = (styczen, luty, marzec, kwiecien, maj, czerwiec, lipiec,

3 sierpien, wrzesien, pazdziernik, listopad, grudzien);

4 type Karty = (as, krol, dama, walet);


W programie można się dalej swobodnie posługiwać identyfikatorami, podobnie jak liczbami czy znakami (nie można ich jedynie wczytywać ani wypisywać), na przykład:


1 program tydzien;

2 var d: Dni_tygodnia;

3

4 begin

5 d := wtorek;

6 if d = sroda then

7 Writeln('Sroda')

8 else

9 Writeln('Nie sroda');

10 end.


Typ plikowy


Język Pascal umożliwia pracę z dwoma typami plików: plikami z danymi i plikami tekstowymi. Pliki z danymi umożliwiają przechowywanie wartości pewnych typów nawet po zakończeniu programu - ich deklaracja ma postać:


1 type NAZWA_PLIKU = file of NAZWA_TYPU;


gdzie NAZWA_TYPU określa, jakiego typu dane plik będzie przechowywał. Ze względu na to, że tego typu pliki najczęściej nie są przydatne poza większymi projektami, wykonywanymi w języku Pascal, nie przytaczamy w tym miejscu ich szczegółowego opisu.


Pliki tekstowe to pliki w standardowym formacie (zawierają dane w postaci znaków kodu ASCII, podzielonych na wiersze). Obsługa takich plików (odczyt, zapis) jest dzięki temu bardzo podobna do standardowego odczytywania i wypisywania.


Pracę z plikiem tekstowym można podzielić na kilka etapów:


1. Deklaracja pliku (jako zmiennej plikowej).

2. Skojarzenie pliku z fizycznym plikiem na dysku.

3. Otwarcie pliku.

4. Praca z plikiem (odczyt lub zapis).

5. Zamknięcie pliku.


Krok 1 odbywa się po prostu przez deklarację zmiennej typu Text.


Krok 2 można wykonać za pomocą procedury Assign. Jej wywołanie ma postać:


1 Assign(NAZWA_PLIKU, SCIEZKA)


NAZWA_PLIKU to nazwa zmiennej plikowej (typu Text), natomiast ŚCIEŻKA to ścieżka dostępu (adres na dysku) do pliku, z którym chcemy tę zmienną skojarzyć. ŚCIEŻKA powinna być podana jako łańcuch, czyli na przykład jako adres pliku podany w apostrofach. Możemy podawać całą ścieżkę - np. c:/pliki/moje_pliki/plik.txt - lub też ścieżkę od miejsca wywoływania danego programu (czyli gdzie jest wywoływany plik exe) - np. ../dane/plik.txt. W szczególności niepodanie jakiejkolwiek ścieżki, tylko samej nazwy pliku, spowoduje skojarzenie zmiennej z plikiem w bieżącym folderze.

".." w powyższym adresie pliku oznacza przejście do folderu o jeden wyżej w hierarchii folderów.


Krok 3 przeprowadzamy na różne sposoby w zależności od tego, co potem zamierzamy robić z plikiem. Jeżeli zamierzamy jedynie odczytywać z pliku, to do otwarcia pliku wykorzystujemy procedurę Reset. Jeżeli zamierzamy pisać do pliku, lekceważąc jego ewentualną poprzednią zawartość, to wykorzystujemy procedurę Rewrite. Wreszcie jeżeli chcemy pisać do pliku z pozostawieniem jego poprzedniej zawartości (czyli dopisywać informację w pliku za poprzednią zawartością), to wykorzystujemy procedurę Append. Wszystkie te procedury pobierają jako parametr jedynie nazwę zmiennej plikowej.

Jeżeli plik określony ścieżką nie istnieje, to zarówno Reset, jak i Append kończą się błędem wykonania, natomiast Rewrite po prostu tworzy nowy plik o podanej nazwie.


Krok 4 opiera się na stosowaniu zmodyfikowanego odczytu lub zapisu do pliku - jedyną różnicą jest to, że jako pierwszy parametr procedury Read, Readln, Write czy Writeln podajemy identyfikator zmiennej plikowej. Dodatkowymi funkcjami dostępnymi w trybie odczytu z pliku są Eoln(PLIK), która zwraca prawdę jeżeli znajdujemy się na końcu wiersza w pliku oraz Eof(PLIK), która zwraca prawdę jeżeli znajdujemy się na końcu pliku.


Krok 5 odbywa się po zakończeniu interakcji z plikiem i polega na wywołaniu procedury Close z parametrem będącym nazwą zmiennej plikowej. Należy o tym kroku pamiętać zwłaszcza w przypadku zapisu do pliku, gdyż w przypadku niewywołania tej procedury część danych może zostać utracona (nie ulec zapisaniu do pliku).


Napiszmy prosty program, który odczytuje z pliku dane.in dwie liczby całkowite i wypisuje do pliku dane.out ich sumę:


1 program pliki;

2 var plik1, plik2: Text;

3 a, b: Longint;

4

5 begin

6 Assign(plik1, 'dane.in');

7 Reset(plik1);

8 Assign(plik2, 'dane.out');

9 Rewrite(plik2);

10 Readln(plik1, a, b);

11 Writeln(plik2, a + b);

12 Close(plik1);

13 Close(plik2);

14 end.







Wyszukiwarka