Podstawy Programowania Wykład drugi: Inicjalizacja zmiennych, operatory. 1.Inicjalizacja zmiennych Zanim użyjemy zmiennej w programie, powinniśmy nadać jej jakąś wartość po- czątkową (zainicjalizować zmienną). Brak inicjalizacji jest jednym z najczęstszych błędów, jakie popełniają programiści. Inicjalizację zmiennej możemy wykonać na kilka sposobów. Domyślnie wszystkie zmienne globalne (zadeklarowane w sekcji var programu głównego) mają zerową wartość począt- kową (tzn. w przypadku zmiennej typu byte jest to 0, w przypadku real wartość 0.0, w przypadku char znak o kodzie ASCII równym 0). Jeśli chcemy, aby zmienna miała wartość zerową wystarczy zadeklarować ją jako zmienną global- ną. Jeśli chcemy nadać jej jakąś wartość różną od zera możemy postąpić na dwa sposoby: użyć instrukcji przypisania (:=) lub zadeklarować ją jako zmienną zainicjalizowaną. Oto przykład programu, który stosuje oba sposoby inicjaliza- cji zmiennej. Instrukcja przypisania powoduje program inicjalizacja; nadanie zmiennej znajdującej się po const jej lewej stronie wartości znajdującej y:integer = -1; się po jej prawej stronie. Ta wartość var może być stałą, wartością wyrażenia, x:integer; lub wartością innej zmiennej. begin Zmienną inicjalizowaną deklarujemy x:=5; w sekcji programu rozpoczynającej writeln('Wartość x ',x); się słowem kluczowym const (w tym writeln('Wartość y ',y); wypadku może ona również wy- y:=-4; stępować po części var). Deklaracja writeln('Nowa wartość y ',y); tej zmiennej jest podobna do dekla- readln; racji stałej, z tym, że podajemy typ. end. Uruchamiając program można prze- konać się, że jest to rzeczywiście zmienna, a nie stała. W czwartym od końca wierszu programu następuje zmiana jej wartości, co w przypadku stałej nie jest możliwe. Zamiast inicjalizo- wać zmienną bezpośrednio w programie, możemy poprosić użytkownika o po- danie wartości, jaką chce nadać zmiennej. Najprościej zrobić to przy pomocy procedury readln. Jeśli chcemy, aby użytkownik nadał zmiennej o nazwie a jakąś wartość, w treści programu umieszczamy zapis: readln(a); Procedura readln zatrzyma program do czasu, aż użytkownik wprowadzi odpowiednią war- tość i naciśnie klawisz Enter. Po uzyskaniu w ten sposób wartości, procedura przypisuje ją zmiennej a. Jeśli wywołamy tę procedurę bez żadnej zmiennej, to spowoduje ona tylko zatrzymanie programu do czasu naciśnięcia przez użyt- kownika wspomnianego wcześniej klawisza. Istnieje również możliwość równo- czesnego zainicjalizowania przez tę procedurę dwóch zmiennych np.: a i b. Wó- 2 wczas należy użyć tej procedury następująco: readln(a,b); Użytkownik programu powinien podać dwie wartości rozdzielone spacją i nacisnąć Enter. Zalecam jednak używanie procedury readln z pojedynczymi zmiennymi. Aby wypisać wartość zmiennej na ekran możemy użyć procedury write lub writeln. Druga różni się od pierwszej tym, że po wypisaniu komunikatu na ekran umieszcza kursor w następnym wierszu ekranu. Jeśli chcemy wypisać na ekran wartość zmiennej a możemy zrobić to następująco: writeln(a);. Jeśli chce- my dodać komunikat, to umieszczamy go w apostrofach i po przecinku wy- mieniamy nazwę zmiennej, której wartość chcemy wypisać: writeln('Wartość zmiennej a to ',a);. W przypadku, kiedy wartość zmiennej jest liczbą całkowitą lub naturalną, możemy określić również na ilu miejscach na ekranie ma zostać wypisana, np.: writeln('Wartość zmiennej a to ',a:3); W tym przypadku zostaną zarezerwowane trzy miejsca na ekranie na wypisanie wartości zmiennej. Dla wartości zmiennoprzecinkowej możemy podać również, ile miejsc chcemy zare- zerwować na część ułamkową: writeln('Wartość zmiennej a ',a:3:2); - w tym przypadku zostaną zarezerwowane trzy miejsca na wypisanie wartości liczby, w tym dwa na część ułamkową. Oto program pokazujący kilka sposobów inicjalizacji zmiennych różnego typu: program inicjalizacja_2; uses crt; var logiczna:boolean; znak:char; liczba_naturalna:byte; liczba_calkowita:shortint; liczba_rzeczywista, lrz:real; lancuch:string; begin clrscr; logiczna:=TRUE; lrz:=3e-10; {Liczba e oznacza podstawę potęgi 10, czyli jest to trzy razy dziesięć do potęgi minus dziesiątej} liczba_rzeczywista:=0.03; znak:=#65; {65 to kod ASCII dużej litery A, poprzedzamy go znakiem #} writeln('Wartość lrz bez formatowania ',lrz); writeln('Wartość liczba_rzeczywista z formatowaniem :3:2 ', liczba_rzeczywista:3:2); writeln('Znak: ',znak); lancuch:='Napis'; 3 writeln(lancuch); liczba_naturalna:=4; liczba_calkowita:=liczba_naturalna; writeln(liczba_naturalna:4); writeln(liczba_calkowita:10); znak:='a'; {W ten sposób też możemy inicjalizować zmienną typu char podając znak w apostrofach} writeln(znak); readln; end. Oprócz tych sposobów inicjalizacji zmiennych, które zostały uwzględnione w programie możemy również nadać zmiennej wartość pseudolosową1. W tym celu najpierw musimy wywołać procedurę randomize, poza wszelkimi instruk- cjami iteracyjnymi2, a następnie dla uzyskania konkretnej wartości musimy wywołać funkcję random z parametrem, będącym wartością mieszczącą się w typie word, np.: a:=random(10); gdzie a jest zmienną typu całkowitego. Taki zapis oznacza, że tej zmiennej zostanie przypisana pseudolosowa wartość całkowita z przedziału od 0 do 9. Jeśli chcemy wylosować tylko wartość ułam- kową, to musimy wywołać random, bez żadnego parametru, wówczas losowane będą wartości z przedziału od [0,1). Jeśli inicjalizując zmienna pewną liczbą, wygodniej będzie ją nam zapisać w kodzie szesnastkowym, to możemy to uczy- nić poprzedzając jej wartość znakiem $, np.: x:=$5a; Jeśli zmiennej nadajemy w programie (obojętnie w jaki sposób) kilkukrotnie wartość (a tak najczęściej robimy), to tylko pierwsze przypisanie jest nazywane inicjalizacją. 2.Operatory i wyrażenia Język Pascal oferuje szereg operatorów, które umożliwiają budowanie wyrażeń i przeprowadzanie operacji na wartościach zmiennych. Poniższa tabela przed- stawia najważniejsze z nich, wraz z ich priorytetami. Operator Priorytet Kategoria +,-,not 1 (najwyższy) jednoargumentowe *,/,div,mod,and,shl,shr 2 multiplikatywne +,-,or,xor 3 addytywne =,<>,<,>,<=,>= 4 (najniższy) relacyjne Priorytet określa kolejność wykonywania operatorów w wyrażeniu (kolejność 1 Ponieważ sposób generowania takich wartości nie gwarantuje ich pełnej losowości, to nie mówimy o nich, że są losowe, tylko pseudolosowe. 2 O tych instrukcjach dowiemy się na następnym wykładzie. 4 działań). Najwyższy mają operatory jednoargumentowe. Operator powoduje zmianę znaku liczby na przeciwny, + nie zmienia wartości liczby (został wprowadzony jako dopełnienie poprzedniego operatora), natomiast operator not jest operatorem negacji zamienia wartość każdego bitu liczby na przeciwną (0 na 1 i odwrotnie). Niższy priorytet mają operatory multiplikatywne: * oznacza mnożenie, / dzielenie, div dzielenie całkowite (bez reszty), mod operacja modulo (reszta z dzielenia), and operator mnożenia logicznego, shl mnożenie liczby przez potęgę dwójki (przesunięcie jej reprezentacji binarnej o zadaną licz- bę miejsc w lewo), shr dzielenie liczby przez potęgę dwójki (przesunięcie jej reprezentacji binarnej o zadaną liczbę miejsc w prawo). Kolejne są operatory addytywne. Operator + oznacza dodawanie lub, jeśli jego argumentami są zmienne typu string lub char, łączenie zawartości tych zmiennych (konkate- nację). Operator or oznacza sumę logiczną, operator xor różnicę symetryczną. Najniższe priorytety mają operatory relacyjne: = to operator porównania (nie mylić z instrukcją przypisania !!), <> operator różne , < operator mniejsze , > operator większe , <= mniejsze lub równe, >= większe lub równe. Operatory te zwracają wartość logiczną i mogą porównywać nie tylko zmienne liczbowe, ale również zmienne typu string, char i boolean. Należy pamiętać, że operacje arytmetyczne są wykonywane modulo zakres typu zmiennej. Przykład: program operatory_1; uses crt; var a:byte; begin clrscr; a:=255; a:=a+1; writeln(a); readln; end. W wyniku wykonania tego programu na ekranie zobaczymy nie liczbę 256, lecz zero. Dlaczego ? - liczba 256 nie mieści się w zakresie typu byte i dlatego zosta- nie obcięta do wartości zero. Jeśli zamiast o jeden zwiększylibyśmy jej wartość o 2, to otrzymalibyśmy w wyniku liczbę 1. Jeśli jednak umieścilibyśmy wyraże- nie a+1 bezpośrednio w instrukcji writeln: writeln(a+1); to otrzymalibyśmy po- prawny wynik. Dzieje się tak dlatego, że procedura write (lub writeln) zakłada dla wyrażeń w których występują zmienne typu byte i word, że wynik jest typu word. Oznacza to, że problemy wystąpią dopiero przy wartościach wyrażeń 5 większych niż 65535. Podobne zjawisko zachodzi również dla innych typów zmiennych: program operatory_2; uses crt; var x:shortint; begin clrscr; x:=-128; x:=x-1; writeln(x); readln; end. Wyjaśnienia wymaga sposób działania operatorów and, or, xor oraz shl i shr. Działanie operatora and dotyczy wszystkich bitów jego argumentów. Jeśli bity argumentów, znajdujące się na tej samej pozycji są równe jeden, to odpowiada- jący im bit wyniku będzie miał wartość 1, w innych przypadkach wartość 0. Np.: 5 and 3 da wartość 1, bo: 00000101 and 00000011 00000001 Jeśli argumentami tego operatora są wartości logiczne, to zwraca on wartość TRUE, wtedy i tylko wtedy, kiedy oba argumenty też są równe TRUE. Operator or również działa na poziomie bitów, ale według innej reguły. Bit znaj- dujący się na określonej pozycji w wyniku ma wartość zero, wtedy i tylko wte- dy, gdy oba odpowiadające mu bity argumentów mają też wartości zero. W przeciwnym wypadku ma wartość 1. Np.: 5 or 3 daje wartość 7, bo: 00000101 or 00000011 00000111 Jeśli argumentami tego operatora są wartości logiczne, to zwraca on wartość FALSE, wtedy i tylko wtedy, kiedy oba argumenty też są równe FALSE. Operator xor ustawia wartość odpowiedniego bitu wyniku na wartość 1, wtedy i tylko wtedy, gdy odpowiadające mu bity argumentów są różne, w przeciwnym 6 wypadku ten bit ma wartość 0. Np. 5 xor 3 daje wartość 6, bo: 00000101 xor 00000011 00000110 Jeśli argumentami tego operatora są wartości logiczne, to zwraca on wartość TRUE, wtedy i tylko wtedy, kiedy wartości argumentów się różnią, a FALSE kiedy są takie same. Operator shr jest operatorem przesunięcia w prawo, czyli dzielenia całkowitego przez potęgę dwójki. Wyrażenie 5 shr 2 daje wartość 1, bo 00000101 shr 2 = 00000001 (odpowiada wyrażeniu 5 div 4) Jak można zaobserwować wartości najbardziej skrajnych bitów po prawej stronie są tracone, natomiast bity po lewej stronie przyjmują wartość zero. Po- dobnie działa operator shl, ale on dokonuje przesunięcia w lewo, czyli mnożenia przez potęgę dwójki. Oto przykład 5 shl 2 = 20, bo 00000101 shl 2 = 00010100 (odpowiada wyrażeniu 5*4) Język Trubo Pascal, pozwala również na budowanie wyrażeń logicznych, w których używane są operatory relacyjne. Oto przykład programu, w którym umieszczono dwa takie wyrażenia: program operatory_3; uses crt; var a,b,c:boolean; begin clrscr; a:=false; b:=false; c:=true; writeln(a=b or c); {false} writeln((a=b) or c); {true} readln; end. 7 3.Inne działania Oprócz operatorów Pascal dostarcza programistom funkcji i procedur3, które pozwalają na wykonanie innych operacji na zawartościach zmiennych. Do nich między innymi należą: succ - funkcja zwracająca następnik, działa tylko dla typów porządkowych, np.: b := succ(a); pred - funkcja zwracająca poprzednik, działa tylko dla typów porządkowych, np.: b := pred(a); ord - funkcja zamienia wartość typu porządkowego na wartość całkowitą, naj- częściej służy do uzyskiwania kodu ASCII znaku, np.: x:=ord('a'); chr - funkcja zmienia wartość całkowitą na znak writeln(chr(97)); abs - funkcja zwraca wartość bezwzględną liczby writeln(abs(-5));, int - funkcja, zwraca część całkowitą liczby zmiennoprzecinkowej, wartość zwracana jest typu real; frac - jak wyżej, ale zwraca część ułamkową, trunc - podobnie jak int zwraca część całkowitą liczby rzeczywistej, ale jako wartość typu integer, high - funkcja ta zwraca maksymalną wartość, jaką da się zapisać w zmiennej danego typu porządkowego, low - jak wyżej, ale zwraca wartość minimalną, 3 Te pojęcia będą oczywiście wyjaśnione na innych wykładach, ale żeby zrozumieć zawartość tego rozdziału wprowadzmy kilka nieformalnych definicji. Wywołanie procedury ma postać: NazwaProcedury(parametry); parametrami mogą być zarówno wartości, jak i zmienne, z tym że nie zawsze możliwa jest dowolność, tzn. w niektórych przypadkach parametry są parametrami wyjściowymi i muszą być zmiennymi. Wywołanie funkcji ma zaś postać: Zmienna := NazwaFunkcji(parametry); Funkcja zwraca zawsze jakąś wartość, która jest przypisywana zmiennej, lub ignorowana (wó- wczas nie występuje instrukcja przypisania i jej lewa strona). Zarówno w przypadku procedur, jak i funkcji nie zawsze musi występować lista parametrów, niektóre z nich po prostu nie wy- magają żadnych parametrów. 8 round - funkcja wykonuje zaokrąglenie części ułamkowej liczby zmiennoprzecinkowej, wartość wynikowa jest typu longint, str procedura ta zamienia wartość liczbową na łańcuch znaków, np.: str(11.58:4:2, nap); gdzie nap jest zmienną typu string, val dokonuje konwersji w drugą stronę, tzn. zamienia łańcuch znaków na liczbę, np.: val('25.5',liczba,blad), gdzie liczba jest zmienną typu real (może być też zmienną typu integer), blad zmienną typu integer. W razie niepowodzenia konwersji w zmiennej blad będzie zapisany numer pozycji znaku, który spowodował błąd. sin funkcja zwracająca wartość sinusa dla określonej wartości kąta, parametr i wartość zwracana są typu real. Przyjmuje, że kąt jest wyrażony w radianach! cos jak wyżej, ale zwracana wartość cosinusa. exp funkcja zwracająca wartość funkcji ex (eksponenty). ln funkcja zwracająca wartość logarytmu naturalnego4. sizeof funkcja, która podaje rozmiar zmiennej (liczbę bajtów którą zajmuje w pamięci). sqr funkcja zwracająca kwadrat wartości podanej jej przez parametr. sqrt funkcja zwracająca pierwiastek kwadratowy wartości podanej jej przez parametr. odd funkcja zwraca wartość TRUE, jeśli podana jej liczba całkowita, miesz- cząca się w zakresie typu longint jest wartością nieparzystą. inc procedura zwiększająca wartość zmiennej podanej jej jako pierwszy para- metr, o wartość, która jest jej podana jako drugi parametr. Jeśli drugi parametr nie występuje, to wartość zmiennej jest zwiększana o jeden. dec jak wyżej, ale wartość zmiennej jest zmniejszana, hi podaje wartość starszego bajta zmiennych typu word i integer (2 baj- 4 Jeśli chcemy obliczyć wartość funkcji ax, gdzie a i x są liczbami zmiennoprzecinkowymi i a>0, to możemy skorzystać z wyrażenia exp(x*ln(a)) 9 towych). lo jak wyżej, ale podaje wartość młodszego bajta. swap zamienia miejscami wartości bajtów zmiennych typu word i integer. W przypadku kompilatora FreePascal funkcje lo i hi oraz procedura swap mogą działać dla typów danych, które są 32-bitowe (mają wielkość 4 bajtów). W takich przypadkach zamieniane są miejscami słowa (2-bajty), tzn. starsze słowo jest zamieniane z młodszym słowem. 4.Zgodność i konwersje typów zmiennych Załóżmy, że mamy dane dwie zmienne. Pierwsza nazywa się x i jest typu T1, a druga y i jest typu T2. Przypisanie x:=y; jest poprawne jeśli spełniony jest któryś z warunków: typy T1 i T2 są identyczne, typ T1 i T2 są zgodnymi typami porządkowymi, a wartość zmiennej y jest jedną z możliwych wartości w typie T1, typy T1 i T2 są typami zmiennoprzecinkowymi i wartość zmiennej y po zaokrągleniu jest jedną z możliwych wartości w typie T1, typ T1 jest typem zmiennoprzecinkowym, a typ T2 jest typem całkowitym, T1 i T2 są typami całkowitymi, T1 jest typem łańcuchowym (string), a T2 jest typem znakowym. W innych przypadkach musimy zastosować konwersję typów wartości. Dla zmiennych porządkowych możliwa jest konwersja według schematu: x := NazwaTypu(y); przykładowo x:=Integer('A'); Inne konwersje wykonujemy korzy- stając, z funkcji i procedur, które zostały opisane w poprzednim rozdziale. Jeśli nie dokonamy takiej konwersji, to kompilator zgłosi błąd. 10