Turbo Pascal. Programowanie

Turbo Pascal.
Programowanie

Autor: Tomasz M. Sadowski
Format: B5, stron: 136

Copyright © 1996 by Wydawnictwo Helion
wersja spakowana

helion.pl

Wyrażenia

Temat wyrażenia został "przemycony" do książki już dość dawno temu i wypadałoby w końcu tę kwestię wyjaśnić. W niniejszym rozdziale spróbujemy wyjaśnić, czym są wyrażenia, do czego służą oraz jakie są ich składniki i reguły tworzenia.

Wyrażenia pozwalają na przekształcanie informacji w celu uzyskania odpowiednich wyników i stanowią jeden z podstawowych składników programów. Każde wyrażenie stanowi symboliczny zapis pewnej operacji na danych reprezentowanych przez zmienne (opisane identyfikatorami) i stałe (zapisane jawnie). Sama operacja realizowana jest za pomocą operatorów oraz funkcji. Pojęcie wyrażenia najłatwiej będzie nam zilustrować na przykładzie matematycznym: jak wiadomo, długość przeciwprostokątnej c trójkąta prostokątnego wyraża się wzorem

Przekładając to na Pascal otrzymamy

c := sqrt(a*a + b*b)

Zapis znajdujący się po prawej stronie znaku := (tak zwanego operatora przypisania) jest właśnie wyrażeniem. W jego skład wchodzą cztery identyfikatory (a i b) symbolizujące zmienne przechowujące długości przyprostokątnych, trzy operatory (* i +) symbolizujące operacje mnożenia i dodawania oraz identyfikator sqrt reprezentujący funkcję - pierwiastek kwadratowy. Ponieważ o zmiennych i stałych już mówiliśmy, zajmiemy się obecnie operatorami.

Operator jest zastrzeżonym słowem języka, stanowiącym symboliczną reprezentację pewnego działania na danych reprezentowanych przez argumenty operatora. W większości przypadków operatory posiadają dwa argumenty, istnieją jednak również operatory jednoargumentowe. W Pascalu zdefiniowano kilka grup operatorów, z których najczęściej wykorzystywanymi są zapewne operatory arytmetyczne:

Tablica 2. Operatory arytmetyczne

Operator
Znaczenie
Przykład
* mnożenie 2*2 = 4
/ dzielenie 2/3 = 0.66666...
div dzielenie całkowite 2 div 3 = 0
mod reszta z dzielenia 3 mod 2 = 1
+ dodawanie 2 + 3 = 5
- odejmowanie 2 - 3 = -1
- (jednoargumentowy) zmiana znaku -1 = -1

Z wyjątkiem operatorów div i mod, przeznaczonych wyłącznie do działań na liczbach całkowitych, wszystkie pozostałe operatory "współpracują" zarówno z liczbami całkowitymi, jak i rzeczywistymi. W przypadku wykonywania kilku działań w obrębie jednego wyrażenia istotny jest tzw. priorytet operatorów, określający pierwszeństwo pewnych działań przed innymi. Dla operatorów arytmetycznych priorytety wyglądają mniej więcej tak, jak w "zwykłej" matematyce: pierwszeństwo mają operatory mnożenia i dzielenia (*, /, div i mod), wykonywane zawsze przed dodawaniem i odejmowaniem. Działania reprezentowane przez operatory o tym samym priorytecie wykonywane są w kolejności od lewej do prawej.

Pamiętanie o priorytetach operatorów jest sprawą bardzo istotną, gdyż niewłaściwa kolejność wykonywania działań prowadzi często do uzyskania zupełnie innego wyniku, niż się spodziewaliśmy. Przykładowo, iloraz

nie zostanie poprawnie obliczony, jeśli zapiszesz go w postaci

1 + 2 / 3

bowiem zamiast wartości 1 otrzymasz 1 plus 2/3, czyli 1.6666... . Właściwą kolejność wykonywania działań możesz jednak wymusić za pomocą nawiasów, zapisując nasz iloraz jako

(1 + 2) / 3

Fragmenty wyrażenia ujęte w nawiasy są zawsze obliczane przed wykonaniem wszystkich pozostałych działań. Nawiasów warto (a nawet należy) używać również wówczas, gdy nie jest się pewnym co do kolejności wykonywania działań. Pamiętaj, że nawiasy nie powodują generowania dodatkowego kodu wynikowego, a jedynie zmieniają kolejność operacji, a więc "nic nie kosztują" (oprócz konieczności wpisania kilku dodatkowych znaków, co jednak jest lepsze od narażania się na trudno wykrywalne błędy).

Operatory arytmetyczne nie wyczerpują oczywiście arsenału dostępnego w Turbo Pascalu. Kolejną grupę tworzą operatory bitowe i logiczne, nazwane tak dlatego, iż przeznaczone są do wykonywania działań na bitach liczb całkowitych lub do przekształcania wartości logicznych. A oto one:

Tablica 3. Operatory bitowe i logiczne

Operator
Znaczenie
Przykład
logiczny
bitowy
not negacja not true = false not 15 = 240
and iloczyn logiczny true and false = false 7 and 15 = 7
shl przesunięcie bitów w lewo   7 shl 2 = 28
shr przesunięcie bitów w prawo   128 shr 4 = 8
or suma logiczna true or false = true 7 or 128 = 135
xor suma modulo 2 true xor true = false 7 xor 15 = 8

Zrozumienie działania operatorów logicznych nie powinno nastręczać trudności. Jeśli chodzi o operatory bitowe, to ich działanie sprowadza się do manipulowania poszczególnymi bitami (mogącymi przyjmować wartości 0 lub 1) w komórkach pamięci zajmowanych przez liczbę. Jednoargumentowy operator not neguje poszczególne bity (zmienia ich wartości na przeciwne), operator and ustawia dany bit wyniku na 1 tylko wtedy, gdy odpowiadające sobie bity obu argumentów mają wartość 1, or - gdy co najmniej jeden z nich ma wartość 1, zaś xor ustawia bit na 1 tylko wtedy, gdy jeden z bitów ma wartość 1 a drugi 0. Operatory shl i shr mają charakter wyłącznie bitowy; dla liczb całkowitych (w pewnym uproszczeniu) przesunięcie bitów o n pozycji w lewo lub w prawo odpowiada pomnożeniu lub podzieleniu przez 2n. W ramach ćwiczeń proponuję Ci sprawdzić poprawność podanych wyżej przykładów (potrzebna Ci będzie znajomość systemu dwójkowego, w którym np. całkowita liczba 7 zapisywana jest jako 00000111, czyli sekwencja ośmiu bitów, z których trzy najmniej znaczące mają wartość 1).

Ostatnią ważną grupę operatorów stanowią operatory relacyjne, służące do porównywania obiektów (nie tylko liczb, ale również znaków czy łańcuchów). Wszystkie operatory relacyjne są dwuargumentowe (oczywiście typy obu argumentów muszą być zgodne, tj. nie można porównywać łańcucha z liczbą) i dają w wyniku wartość logiczną.

Tablica 4. Operatory relacyjne

Operator
Znaczenie
Przykład
= równy... 3 = 3.14 (false)
<> różny od... 3 <> 3.14 (true)
< mniejszy od... 3 < 3.14 (true)
<= mniejszy lub równy... 3 <= 3.14 (false)
> większy od... 3 > 3.14 (false)
>= większy lub równy 3 >= 3 (true)

Musimy jeszcze ustosunkować się do wyrażeń "mieszanych", zawierających operatory należące do kilku grup. Przykładowo, obliczając wysokość stypendium studenckiego należy określić, czy średnia ocen delikwenta jest wystarczająco wysoka, a dochód na jednego członka rodziny wystarczająco niski. Aby zatem ustalić, czy Kowalskiemu należy się stypendium, użyjemy przykładowego wyrażenia

(Dochod/LiczbaOsob < 150) and (Srednia > 3.75)

Wyrażenie to zawiera doskonałą mieszankę operatorów arytmetycznych, logicznych i relacyjnych. W takich sytuacjach kolejność wykonywania działań jest następująca:

Tablica 5. Priorytety operatorów

Operatory
Priorytet
not 1 (najwyższy)
* / div mod and shl shr 2 (niższy)
+ - or xor 3 (jeszcze niższy)
= <> < <= > >= 4 (najniższy)

Oczywiście, jeśli wyrażenie zawiera nawiasy, ich zawartość zostanie wyliczona przed wykonaniem pozostałych działań. Pokazany wyżej przykład ilustruje jednocześnie dość typową sytuację, w której musimy połączyć ze sobą kilka warunków (wyrażonych nierównościami, czyli operatorami relacyjnymi). Ponieważ do łączenia warunków służą operatory logiczne (zwykle or lub and), mające wyższy priorytet, niezbędne jest użycie nawiasów (ich pominięcie jest bardzo pospolitym błędem; o reakcji kompilatora najlepiej przekonaj się sam).

Jak już powiedzieliśmy, wyrażenia mogą zawierać elementy najróżniejszych typów (liczby, znaki, łańcuchy itp.), jednak najczęściej będziesz spotykał się z wyrażeniami reprezentującymi operacje arytmetyczne. Aby rozdział ten nie ograniczał się do pustego teroretyzowania, spróbujemy zastosować wyrażenia w praktyce, tworząc program rozwiązujący równanie kwadratowe znaną Ci zapewne metodą wyznaczników.

Zapisując nasze równanie w postaci

możemy obliczyć pierwiastki (tj. wartości x, dla których równanie przyjmuje wartość zero) jako

gdzie * jest tzw. wyznacznikiem równania, obliczanym jako

Znak w poprzednim wzorze oznacza, że równanie ma w ogólności dwa pierwiastki, z których jeden oblicza się przez dodanie wartości w liczniku ułamka, zaś drugi - przez ich odjęcie.

Sam program powinien wyglądać następująco:

  początek
    odczytaj wartości współczynników a, b i c,
    oblicz wartość wyznacznika
    oblicz pierwiastki i wypisz je na ekranie
  koniec

W postaci pascalowej będzie to nieco bardziej skomplikowane:

  program Rownanie_Kwadratowe;
  { Program rozwiązuje równanie kwadratowe metodą wyznaczników }
    
  var
    a, b, c : real; { współczynniki }
    delta : real;  { wyznacznik } 
    x1, x2 : real;  { pierwiastki } 
     
  begin
    writeln('Program rozwiazuje rownanie kwadratowe') 
    writeln('a*x^2 + b*x + c');  
    write('Podaj wspolczynnik a: '); { wprowadź współczynniki }
    readln(a);  
    write('Podaj wspolczynnik b: ');  
    readln(b);  
    write('Podaj wspolczynnik c: ');  
    readln(c);  
    delta := sqr(b) - 4*a*c; { oblicz wyznacznik }
    x1 := (-b + sqrt(delta))/(2*a); { oblicz pierwiastki }
    x2 := (-b - sqrt(delta))/(2*a); { znaczek := to tzw. }
      { przypisanie }
    writeln('Pierwiastki:'); { wyświetl pierwiastki }
    writeln('x1 = ', x1:12:4);  
    writeln('x2 = ', x2:12:4);  
    readln;  
  end.

Wyrażenia wykorzystywane do obliczania poszczególnych wartości są tu nieco bardziej skomplikowane, ale nadal nie powinieneś mieć problemów z ich analizą. Dwoma nowymi (no, nie do końca) elementami są operator przypisania oraz funkcje.

Operator przypisania jest jednym z najpospoliciej wykorzystywanych operatorów pascalowych. Znaczek := (dwukropek i znak równości) czyta się "staje się" lub "przypisz", zaś jego efekt sprowadza się do umieszczenia w obiekcie znajdującym się po lewej stronie wartości znajdującej się po prawej stronie. "Obiekt" umieszczony po lewej stronie operatora przypisania określany jest mianem l-wartości (ang. lvalue). Jest to na ogół zmienna, możliwe jest jednak przypisanie wartości do identyfikatora funkcji (o tym będziemy jeszcze mówić). Ponieważ przypisanie polega na umieszczeniu wartości gdzieś w pamięci komputera (w miejscu określonym przez nazwę obiektu), nie możesz po lewej stronie operatora przypisania umieścić stałej czy wyrażenia. Po prawej stronie operatora przypisania panuje znacznie większa demokracja: dopuszczalne są tam praktycznie dowolne wyrażenia (a więc w szczególności również pojedyncze stałe i zmienne), byleby typ wartości uzyskanej w wyniku obliczenia wyrażenia był zgodny z typem obiektu znajdującego się po drugiej stronie operatora :=.

Zagadnienie zgodności typów w sensie przypisania (ang. assignment compatibility) jest tyleż istotne, co złożone. Z dość dobrym przybliżeniem można powiedzieć, że typ wyrażenia znajdującego się po prawej stronie operatora := powinien być identyczny z typem obiektu znajdującego się po lewej stronie, z kilkoma wyjątkami. Do najważniejszych wyjątków należą przypisania:

Przy tej okazji warto również wspomnieć, że niektóre przypisania pomiędzy typami rzeczywistymi mogą spowodować utratę dokładności lub przekroczenie zakresu (np. próba "wciśnięcia" wartości typu extended do zmiennej typu single).

Jeśli idzie o drugą nowość, czyli funkcje, to w naszym programie pojawiły się wywołania dwóch tzw. bibliotecznych funkcji arytmetycznych: sqr i sqrt. Temat funkcji będzie dyskutowany nieco później; na razie wystarczy Ci wiedzieć, że funkcja (i pokrewna jej procedura) jest pewnym narzędziem pozwalającym na przekształcenie podanych jej wartości (tzw. argumentów) do żądanej postaci. Praktycznie każdy język programowania dysponuje mniejszym lub większym zestawem (biblioteką) funkcji realizujących operacje arytmetyczne, znakowe, graficzne i inne. Biblioteka funkcji i procedur dostępnych w Turbo Pascalu jest ogromna i omawianie jej mijałoby się z celem; wśród często wykorzystywanych funkcji arytmetycznych, oprócz nierzadko mylonych ze sobą sqr (podniesienie do kwadratu) i sqrt (pierwiastek kwadratowy), znajdują się również funkcje trygonometryczne (sin, cos, arctan), logarytmiczne (ln, exp) i wiele innych.

W ten sposób omówiliśmy zasadnicze tematy związane z wyrażeniami i realizacją obliczeń arytmetycznych. Wykorzystując poznane wiadomości możesz pokusić się o napisanie programu obliczającego wartości bardziej skomplikowanych wzorów (jakich? to już pewnie sam wiesz najlepiej). Niestety, o czym być może już się przekonałeś, Turbo Pascal nie jest na tyle inteligentny, by obronić się przed próbą wykonania operacji nielegalnej z matematycznego punktu widzenia (np. dzielenia przez zero). Jeśli jeszcze nie miałeś okazji potknąć się o ten problem, spróbuj za pomocą naszego programiku znaleźć miejsca zerowe funkcji

x2 + 2x + 3

Nasze równanie nie ma pierwiastków rzeczywistych: wartość wyznacznika wynosi -8, a zatem jego pierwiastek kwadratowy nie daje się obliczyć. Ponieważ jednak Turbo Pascal o tym nie wie, efektem podjętej próby jest błąd wykonania (Invalid floating point operation - nielegalna operacja zmiennoprzecinkowa) i przerwanie realizacji programu. Co prawda żadna to tragedia, bo funkcja i tak nie ma pierwiastków, ale program mógłby zachowywać się przyzwoiciej, tj. zamiast "padać" - wyświetlać odpowiednią informację.

Nie ma problemu... ale to już może w następnym rozdziale.

Zapamiętaj

Poprzedni | Spis treści | Następny | Wersja spakowana |