Od matematyki do programowania. Wszystko, co każdy programista wiedzieć powinien 9788324632107

Dlatego w tej książce nie znajdziesz typowego abecadła. Zapoznasz się za to z danym problemem, a następnie programem kom

730 187 2MB

Polish Pages [319] Year 2011

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Od matematyki do programowania. Wszystko, co każdy programista wiedzieć powinien
 9788324632107

Citation preview

Ebookpoint.pl kopia dla: Rodenko David [email protected]

SPIS TREŚCI Wstęp

6

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz…

9

Co wiemy o liczbach?

9

Systemy zapisu liczb

16

Od problemu do programu… — słownik początkującego programisty

21

Kilka zdań o językach programowania

27

Pierwszy program — klasyczne przykłady w popularnych językach

32

Edycja, kompilacja i uruchomienie programu

36

Rozdział 2. Proste obliczenia — pola i obwody figur geometrycznych

40

Programy o strukturze liniowej

40

Instrukcje warunkowe i sprawdzanie poprawności danych

49

Pętle, czyli powtarzanie sekwencji wykonywanych czynności

52

Porównania, operatory logiczne i budowanie warunków złożonych

59

Stosowanie wybranych funkcji matematycznych i definiowanie własnych funkcji

70

Rozdział 3. Podejmowanie decyzji, czyli nieco więcej o instrukcjach warunkowych

75

Różne przypadki w prostych równaniach

75

Algorytm rozwiązywania równania kwadratowego

79

Rozwiązywanie równań wyższych stopni

85

Wybór jednej z wielu opcji…

98

Dialog programu z użytkownikiem — dane tekstowe

Ebookpoint.pl kopia dla: Rodenko David [email protected]

106

Spis treści

Rozdział 4. Instrukcje iteracyjne bez tajemnic

112

Pętle o stałej liczbie powtórzeń — przykłady tablicowania funkcji

112

Pętle ze sprawdzaniem warunku na końcu

120

Pętla ze sprawdzaniem warunku na początku

123

Która pętla lepsza, czyli krótkie porównanie instrukcji

124

Przerywanie działania pętli

127

Rozdział 5. Budujemy własne funkcje i procedury

131

Zmienne globalne i lokalne

131

Przekazywanie danych do procedur i funkcji, zwracanie wyników

132

Obliczanie potęg o wykładniku całkowitym

142

Konwersja jednostek miary kątów

145

Funkcje trygonometryczne i funkcje do nich odwrotne

151

To się jeszcze może przydać, czyli jak stworzyć własny moduł lub bibliotekę

156

Rozdział 6. Funkcje i procedury rekurencyjne

163

Kilka funkcji znanych ze szkoły

163

Symbol Newtona i trójkąt Pascala

168

Algorytm Euklidesa — wersja rekurencyjna

170

Liczby Fibonacciego

172

Koniec świata i wieże Hanoi

173

Rekurencja zamiast iteracji…

174

Rozdział 7. Liczby w matematyce i komputerze

178

Liczby naturalne i całkowite

178

Ułamki zwykłe — cztery podstawowe działania

187

Ułamki łańcuchowe

196

Liczby zmiennoprzecinkowe

199

Ebookpoint.pl kopia dla: Rodenko David [email protected]

Spis treści

Rozdział 8. Strukturalne typy danych — tablice i rekordy

208

Działania na tekstach — łańcuchowy typ danych

208

Tablicowe typy danych — tablice jedno- i wielowymiarowe

217

Rekordy i struktury

223

Tablica struktur

232

Rozdział 9. Liczby niewymierne i ich przybliżenia dziesiętne

236

Pierwiastek drugiego stopnia z 2

236

Sposoby obliczania pierwiastków drugiego stopnia

245

Obliczanie pierwiastków trzeciego stopnia

247

Obliczanie pierwiastków wyższych stopni

251

Złoty podział odcinka, liczba φ i ciąg Fibonacciego

252

Rozwinięcie dziesiętne liczby pi

258

Podstawa logarytmu naturalnego — liczba e

265

Rozdział 10. Ciągi i szeregi liczbowe

268

Sumowanie wyrazów ciągu liczbowego

268

Rozwinięcie funkcji w szereg liczbowy — szeregi funkcyjne

276

Rozdział 11. Podstawowe operacje na plikach

287

Zapisywanie i odczytywanie plików tekstowych

287

Sformatowane dane liczbowe w plikach tekstowych

295

Zapisywanie danych liczbowych w plikach binarnych

298

Modyfikacja danych w pliku binarnym

305

Rozdział 12. Co dalej?

312

Bibliografia

314

Skorowidz

315

Ebookpoint.pl kopia dla: Rodenko David [email protected]

WSTĘP Niniejsza publikacja jest adresowana do wszystkich, którzy chcieliby rozpocząć przygodę z programowaniem komputerów. Nie jest to typowy podręcznik omawiający krok po kroku podstawy wybranego języka programowania — tu najpierw przedstawiamy problem, a następnie program komputerowy służący do jego rozwiązania. Wielu początkujących programistów zastanawia się, jaki język programowania wybrać, który język jest najlepszy. Nie udzielamy na to pytanie jednoznacznej odpowiedzi, bo... taka odpowiedź po prostu nie istnieje. Pretekstem do rozważań są różne zadania, mniej lub bardziej skomplikowane, znane Czytelnikowi z lekcji matematyki lub nieznacznie wykraczające poza program nauczania w szkole. Wybrane zadania, a właściwie ich rozwiązania zaprezentowane w popularnych językach programowania, w Pascalu, C i C++, mają na celu przedstawienie Czytelnikowi podstawowej wiedzy o programowaniu komputerów i umożliwienie podjęcia decyzji w sprawie dalszej nauki. Zachęcamy Czytelnika do analizowania przedstawionych problemów oraz samodzielnego układania i rozwiązywania podobnych zadań. Najważniejsze jest umiejętne stosowanie podstawowych algorytmów i konstruowanie własnych rozwiązań. Kodowanie w wybranym języku programowania to tak naprawdę kwestia treningu, czyli liczby samodzielnie wykonanych przykładów. Kody źródłowe wszystkich omawianych zadań zostały umieszczone na FTP ftp://ftp.helion.pl/ przyklady/maalpr.zip, tak aby Czytelnik mógł porównać swoje rozwiązanie z propozycją autora. Sięganie po gotowe rozwiązanie problemu to jednak ostateczność… Dołączone kody źródłowe (w trzech językach) zawarto w plikach o nazwach zgodnych z omawianym przykładem. Nazwa pliku odpowiadająca rozwiązaniu zadania z przykładu 5.7 (rozdział 5., przykład 7.) ma postać p5_7.xxx, gdzie xxx oznacza jedno z rozszerzeń: pas, c lub cpp. Jeśli w opisie pojawiają się sugestie innych możliwości rozwiązania problemu, to pliki p5_7a. xxx, p5_7b.xxx... odpowiadają tym wariantom. Może się zdarzyć, że pewne przykłady zapisane w danym języku programowania nie są realizowane w innym języku (z różnych powodów) — taka sytuacja występuje jednak rzadko. Czasem pojawiające się elementy języka programowania wyprzedzają ich opis zawarty w dalszej części. Nie wszystko jednak można uporządkować. Ze względu na pewne rozbieżności pomiędzy wersjami (kompilatorami) języka Pascal podamy dwie wersje plików — podstawowy zestaw plików zapisanych i skompilowanych w Free Pascalu (pliki o nazwach w postaci p5_7.pas) oraz dodatkowy zestaw plików zapisanych i skompilowanych w Turbo Pascalu (pliki o nazwach TP5_7.PAS). Różnice pomiędzy wersjami języka (w zakresie występującym w tej książce) zostały zebrane w Dodatku A, który znajduje się na FTP (ftp://ftp.helion.pl/przyklady/maalpr.zip).

66 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Wstęp W rozdziale 1. przypomniano podstawową wiedzę o liczbach znaną z lekcji matematyki w szkole. Różnorodne obliczenia realizowane w dalszej części książki stanowią podstawę do zdobywania informacji na temat programowania w wybranych językach. W rozdziale tym zawarto również podstawowe pojęcia dotyczące algorytmów i programowania oraz przykłady realizacji wybranego algorytmu w kilku językach. Pod koniec rozdziału Czytelnik pozna podstawowe słowa kluczowe Pascala, C i C++, strukturę programu (aplikacji konsolowej) i sposób tworzenia programu wykonywalnego. To wszystko na podstawie klasycznego przykładu Hello World (Witaj Świecie). Rozdział 2. poświęcony prostym obliczeniom wprowadza nas w świat zmiennych liczbowych, operatorów arytmetycznych i instrukcji podstawiania. Zaczniemy od prostych obliczeń wykonywanych na liczbach całkowitych, następnie wprowadzimy przykłady wymagające zastosowania ułamków dziesiętnych reprezentujących przybliżenia liczb rzeczywistych (tzw. liczby zmiennoprzecinkowe). Nauczymy się też wprowadzać dane liczbowe (z klawiatury) i wyświetlać wyniki na ekranie. Ten rozdział stanowi też zapowiedź tego, co będzie tematem kolejnych rozdziałów (3., 4. i 5.). Proste programy o strukturze liniowej na długo nam nie wystarczą. Kontrola poprawności danych zmusza nas do sięgnięcia po operatory relacji i instrukcję warunkową omówioną szczegółowo w rozdziale 3. Początkowo wykonujemy obliczenia warunkowo, gdy dane są poprawne, potem zmuszamy użytkownika do podawania poprawnych danych (prosząc o powtórne podanie wartości). Prowadzi to do poznania pętli (ze sprawdzaniem warunku na końcu), co będzie szczegółowo omawiane w rozdziale 4. W ostatnim przykładzie (obliczanie pola powierzchni na podstawie wzoru Herona) poznajemy operatory logiczne i sposób budowania złożonych warunków logicznych. Przykład jest też pretekstem do rozbicia problemu na mniejsze podproblemy i sięgnięcia po funkcje i procedury (omawiane dalej w rozdziale 5.). W rozdziale 3. rozwiązujemy wiele różnych typów równań z jedną niewiadomą (od pierwszego do czwartego stopnia), poznając przy tym dokładniej instrukcje warunkowe. Stosowane algorytmy wymagają sięgnięcia po różne funkcje matematyczne dostępne standardowo w bibliotekach języków programowania oraz konstruowane przez nas na podstawie wzorów. I znów wyprzedzamy materiał z rozdziału 5. Rozdział 3. kończą dwa przykłady (można by je nazwać technicznymi), w których użytkownik odpowiada na zadane pytanie: t — tak lub n — nie i na tej podstawie realizowany jest dalszy przebieg programu (zastosowanie instrukcji warunkowej lub pętli). Rozdział 4., jak sugeruje jego tytuł, wyjaśnia „tajemnice” instrukcji iteracyjnych. Klasyfikujemy instrukcje iteracyjne (pętle ze sprawdzaniem warunku na początku, ze sprawdzaniem warunku na końcu oraz pętle o znanej liczbie powtórzeń typu for). Poznajemy też możliwości warunkowego przerwania działania pętli. W rozdziale 5. porządkujemy naszą wiedzę na temat konstruowania procedur i funkcji. W poprzednich rozdziałach budowaliśmy proste funkcje na potrzeby omawianych przykładów. Teraz szczegółowo omawiamy sposób przekazywania parametrów do funkcji (procedury) oraz zwracanie wyników. Budujemy m.in. moduł funkcji trygonometrycznych, który może być przydatny do rozwiązywania różnych zadań. 77 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Wstęp Rozdział 6. jest kontynuacją rozdziału 5., ale budowane funkcje wykorzystują tu mechanizm rekurencji. W sposób rekurencyjny wykonujemy wiele codziennych czynności, niejednokrotnie nie zdając sobie z tego sprawy. Rekurencja oferuje prostotę zapisu pewnych algorytmów, ale w zamian czasem wymaga większej liczby działań lub większej ilości pamięci potrzebnej do realizacji zadania. Warto jednak dokładniej poznać ten sposób rozwiązywania problemów. Dopiero w rozdziale 7. poświęcamy trochę miejsca na reprezentację binarną liczb w pamięci komputera i uporządkowanie wiadomości o typach liczbowych. Dlaczego dopiero teraz? Odpowiedź jest prosta — będą to nie tylko rozważania teoretyczne, gdyż mamy już dostatecznie dużo umiejętności, aby skonstruować odpowiednie programy i obejrzeć, co „siedzi” w pamięci komputera. Po raz pierwszy pojawiło się pojęcie wskaźnika. W tym rozdziale poruszamy również takie zagadnienia jak działania na ułamkach zwykłych i zamiana ułamków zwykłych na łańcuchowe (temat nieomawiany w szkole). W rozdziale 8. poruszamy temat strukturalnych typów danych (tablic i rekordów). Zaczynamy od tablicy znaków, czyli łańcuchowego typu danych. Możemy w postaci tekstu (łańcucha znaków) zapisać ułamek łańcuchowy lub liczbę binarną (ciąg 0 i 1). Pozwala to na budowanie funkcji konwertujących tekst na wartości liczbowe przez ten tekst reprezentowane. Omówiono również przykłady tablic jedno- i wielowymiarowych zawierających dane liczbowe. Wspomniano przy okazji o związku wskaźników z tablicami. Prosta struktura fraction pozwala na przedstawienie działań na ułamkach w sposób przystępniejszy niż w poprzednim rozdziale, gdzie para ułamków była reprezentowana przez cztery niezależne zmienne. Zbudowanie tablicy, której elementami są obiekty tej struktury, pozwala na przechowywanie ułamków łańcuchowych w pamięci komputera. Rozdział 9. traktuje o przybliżeniach dziesiętnych liczb niewymiernych. Zgromadzone dotychczas funkcje i procedury znajdują zastosowanie w procesie obliczania przybliżeń liczb niewymiernych (zwykle pierwiastków arytmetycznych) oraz liczb przestępnych e i π. Omówiona jest również iteracyjna metoda rozwiązywania równań. Nieskończone ciągi i szeregi liczbowe pozwalają na przedstawienie (rozwinięcie) wielu liczb niewymiernych. Skończone sumy szeregów stanowią przybliżenia dziesiętne tych liczb. Zagadnienie to zostało omówione w rozdziale 10. Rozdział 11. ma charakter techniczny i traktuje o przechowywaniu danych w plikach tekstowych i binarnych. Przedstawione tu przykłady możemy zmodyfikować i dostosować do dowolnego programu z wcześniejszych rozdziałów. Zamiast wprowadzać dane z klawiatury, możemy przygotować odpowiedni plik tekstowy lub binarny. Natomiast wyniki obliczeń możemy zapisać w pliku. Mimo pozornego chaosu w niektórych miejscach materiał przedstawiony w publikacji składa się w logiczną całość. Czy temat został wyczerpany? Spróbujemy na to pytanie odpowiedzieć na końcu. Co dalej? To pytanie stawiamy na zakończenie jako tytuł podsumowania. Tam spróbujemy na nie przynajmniej częściowo odpowiedzieć.

88 Ebookpoint.pl kopia dla: Rodenko David [email protected]

ROZDZIAŁ 1.

PODSTAWOWE POJĘCIA, CZYLI MAŁY ELEMENTARZ…

Co wiemy o liczbach? Już wiele tysięcy lat przed naszą erą w świadomości ludzi powstało podstawowe pojęcie matematyki — liczba. Ludy pierwotne rozróżniały dwie liczby, które obecnie moglibyśmy wyrazić słowami: jeden i wiele. Pojęcie liczby kształtowało się i rozwijało wraz z rozwojem cywilizacji i kultury. Liczby naturalne {1, 2, 3, …} mają bezpośredni związek z praktyczną działalnością człowieka, z liczeniem. Zbiór liczb naturalnych oznaczamy symbolem N. Do końca XIX w. liczby naturalne uznawano za pojęcie pierwotne, niewymagające odrębnego określenia. W 1891 roku włoski matematyk Giuseppe Peano podał aksjomatyczną definicję zbioru liczb naturalnych. Do zbioru liczb naturalnych często wliczamy również 0. W zbiorze liczb naturalnych można wykonywać dodawanie i mnożenie. Odejmowanie i dzielenie nie zawsze jest możliwe do wykonania. Kolejnym etapem rozwoju liczby było wprowadzenie pojęcia ułamka, dzięki któremu możliwe stało się dzielenie liczb naturalnych. Początkowo była to połowa, trzecia część, czwarta część… W babilońskim systemie miar powszechnie były stosowane części sześćdziesiąte. Do dzisiaj stosujemy te ułamki (1/60) w jednostkach czasu i stopniowej mierze kątów. Ułamek (zwykły) w postaci

l

, gdzie l (licznik) oraz m (mianownik, różny od 0) są liczbami m naturalnymi, rozpowszechnili Grecy i Hindusi, ale po raz pierwszy pojawiły się wcześniej, w starożytnym Egipcie i Mezopotamii. Elementy Euklidesa (ok. 300 r. p.n.e) zawierały przykłady stosowania ułamków. Zasady działań arytmetycznych na ułamkach zwykłych opracowano w Indiach w VIII w. n.e. W XVII w. holenderski inżynier Simon Stewin wprowadził ułamki dziesiętne (ułamki o mianowniku będącym potęgą liczby 10). W Indiach (VI – XI w.) wprowadzono liczby ujemne i zero, co umożliwiło odejmowanie liczb naturalnych bez ograniczeń. Najwcześniejsza wzmianka o liczbach ujemnych pochodzi z Chin z I w. p.n.e. W III w. n.e. Grek Diofantos w swoich rozważaniach o równaniach użył liczb ujemnych. Zero jako cyfra pojawiło się już u Babilończyków w celu usunięcia wieloznaczności w zapisie. Natomiast Hindusi zaczęli je traktować jako pełnoprawną liczbę. Liczby całkowite stanowią sumę trzech zbiorów: zbioru liczb naturalnych {1, 2, 3, …}, zbioru liczb ujemnych odpowiadających liczbom naturalnym {–1, –2, –3, …} oraz jednoelementowego zbioru {0} — zero. Możemy zatem mówić o liczbach całkowitych {0, 1, –1, 2, –2, 3, –3, …}, 99 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… całkowitych dodatnich (tożsamych z liczbami naturalnymi), całkowitych ujemnych, całkowitych nieujemnych (całkowitych dodatnich wraz z zerem) lub całkowitych niedodatnich (całkowitych ujemnych wraz z zerem). W zbiorze liczb całkowitych (oznaczanym w podręcznikach szkolnych symbolem C, a w literaturze matematycznej Z — to drugie oznaczenie wydaje się lepsze ze względu na późniejsze wprowadzenie liczb zespolonych, ang. complex) możemy wykonywać dodawanie i odejmowanie oraz mnożenie. Dzielenie nie zawsze jest wykonalne (14:(–7) = –2, 13:5 = ? — wynik nie należy do zbioru liczb całkowitych), a dzielenie przez 0 jest nieokreślone (niewykonalne). W zbiorze liczb całkowitych możemy stosować dzielenie całkowite i ewentualne wyznaczanie reszty z dzielenia, 13:5 = 2 r. 3, bo 3+2·5 = 13. W przypadku gdy w działaniu wystąpi liczba ujemna, znak ilorazu całkowitego wyznaczamy na podstawie znaków dzielnej i dzielnika (iloraz liczb o jednakowych znakach jest dodatni, a iloraz liczb o różnych znakach jest ujemny), przy czym często przyjmuje się, że znak dzielnej decyduje o znaku reszty: 13:(–5) = –2 r. 3, bo 3+(–2)·(–5) = 13 (–13):5 = –2 r. –3, bo –3+(–2)·5 = –13 (–13):(–5) = 2 r. –3, bo –3+2·(–5) = –13 Można też przyjąć konwencję, że reszta ma być zawsze nieujemna. W tym przypadku powyższe dzielenia przyjmą postać: 13:(–5) = –2 r. 3, bo 3+(–2)·(–5) = 13 (bez zmian) (–13):5 = –3 r. 2, bo 2+(–3)·5 = –13 (–13):(–5) = 3 r. 2, bo 2+3·(–5) = –13 Rozszerzając zbiór liczb całkowitych o ułamki (dodatnie i ujemne), otrzymamy zbiór liczb wymiernych (oznaczany w podręcznikach szkolnych symbolem W, a w literaturze naukowej — Q). W zbiorze Q wykonalne są cztery podstawowe działania arytmetyczne (dodawanie, odejmowanie, mnożenie i dzielenie), z wyłączeniem dzielenia przez 0 (i ta sytuacja nie ulegnie zmianie pomimo dalszego rozszerzania pojęcia liczby). Ścisłą definicję liczb wymiernych podał niemiecki matematyk i fizyk H.G. Grossmann (1809 – 1877): „Parę dwóch liczb całkowitych (m, n) nazywa się dopuszczalną, jeżeli n 0 . Dwie pary dopuszczalne (m, n) i (m’, n’) nazywa się równoważnymi (m, n) ~ (m’, n’), jeżeli mn’ = nm’. Relacja ~ jest relacją równoważności, wobec czego rozbija zbiór wszystkich par dopuszczalnych m na podzbiory rozłączne; podzbiór zawierający parę (m, n) oznacza się symbolem i nazywa n liczbą wymierną […]”1. Inną definicję liczb wymiernych podał wybitny matematyk niemiecki David Hilbert (1862 – 1943). Według Hilberta w zbiorze liczb rzeczywistych (o tym już za chwilę) równanie nx = m, 1

Encyklopedia szkolna. Matematyka, pod red. W. Waliszewskiego, Wydawnictwa Szkolne i Pedagogiczne, Warszawa 1988, s. 126.

10 10 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Co wiemy o liczbach? gdzie m i n są liczbami całkowitymi i n 0, ma dokładnie jedno rozwiązanie, które oznacza się m i nazywa liczbą wymierną2. n Jak wynika z przytoczonych definicji i naszych doświadczeń szkolnych, tę samą liczbę wy2 4 6 mierną reprezentuje wiele ułamków, np.: …. W tym momencie należałoby sobie 3 6 9 przypomnieć pojęcia skracania i rozszerzania ułamków (liczb wymiernych), największego wspólnego dzielnika i najmniejszej wspólnej wielokrotności (dla pary lub większej ilości liczb całkowitych). Do tych problemów powrócimy w dalszej części książki. Przypomnijmy tylko wzory podstawowych działań na ułamkach zwykłych:

p q

p ps qr , qs q

r s

r s

p r ps qr , qs q s

p r pr , : qs q s

ps . qr

Działania na ułamkach zwykłych bywają kłopotliwe, szczególnie dla większych wartości licznika i mianownika. Wygodne są działania na ułamkach dziesiętnych zapisanych z wykorzystaniem przecinka dziesiętnego (w krajach anglosaskich i w językach programowania — kropki dziesiętnej). Ich realizacja jest stosunkowo łatwa — sposobem pisemnym. Również w obliczeniach wykonywanych na kalkulatorze i przy użyciu komputera preferuje się ten sposób zapisu. Przydatna będzie umiejętność zamiany ułamka zwykłego na dziesiętny i odwrotnie. W niektórych przypadkach sprawa jest prosta:

4

42

8

5 5 2 10 13 13 4 250

250 4

0,8 52 1000

0,052

Taka zamiana możliwa jest tylko wtedy, gdy w rozkładzie mianownika ułamka na czynniki pierwsze występują wyłącznie liczby 2 i 5, bo 2·5 = 10 (w drugim przykładzie mamy mianownik 250 = 2·5·5·5, a zatem po rozszerzeniu ułamka przez 2·2 = 4 otrzymamy ułamek o mianowniku 2·2·2·5·5·5 = 1000). Można zamienić ułamek zwykły na dziesiętny, wykonując następujące operacje: dzielenie z resztą licznika przez mianownik, zamianę uzyskanej reszty na jednostki kolejnego rzędu (mnożenie przez 10) i kontynuację dzielenia: a. 2:5 = 0 r. 2 (0 całości, reszta 2 oznacza 20 części dziesiątych), 20:5 = 4 r. 0 (4 dziesiąte, reszta 0 oznacza koniec procesu dzielenia), zatem b. 13:250 = 0 r. 13 (0 całości i 13·10 części dziesiątych), 130:250 = 0 r. 130 (0 części dziesiątych i 130·10 części setnych), 2

2 5

0,4.

Tamże, s. 126.

11 11 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… 1300:250 = 5 r. 50 (5 części setnych i 50·10 części tysięcznych), 500:250 = 2 r. 0 (2 części tysięczne, reszta 0 oznacza koniec procesu dzielenia), stąd 13 otrzymujemy 0,052. 250 c. 17:6 = 2 r. 5 (2 całości i 5·10 części dziesiątych), 50:6 = 8 r. 2 (8 części dziesiątych i 2·10 części setnych), 20:6 = 3 r. 2 (3 części setne i 2·10 części tysięcznych) — reszta powtarza się! 20:6 = 3 r. 2 (3 części tysięczne i 2·10 części dziesięciotysięcznych). Dalsze obliczenia będą przebiegały w sposób analogiczny i uzyskamy ułamek dziesiętny nie17 skończony (nie wystąpi reszta 0). Stąd otrzymamy 2,8333333333333… 6 8 d. 8:33 = 0 r. 8; 80:33 = 2 r. 14; 140:33 = 4 r. 8 — reszta powtarza się, 0,242424 … 33 Ułamki z przykładów (a) i (b) nazywamy ułamkami dziesiętnymi skończonymi, a ułamki z przykładów (c) i (d) ułamkami okresowymi — powtarzające się cyfry lub grupy cyfr nazywamy okresami tych ułamków. Aby uniknąć zapisywania powtarzających się cyfr, wprowadzono

17 8 2,8 3 , 0, 24 . 6 33 Opisane wyżej dzielenie można wykonać w sposób analogiczny (znany ze szkoły) do dzielenia pisemnego liczb naturalnych. Przypomnijmy ponadto, że podczas dzielenia liczby l (licznik) przez m (mianownik) można uzyskać nie więcej niż m różnych reszt: 0, 1, 2, …, m–1, więc w procesie dzielenia wystąpi reszta 0 (ułamek dziesiętny będzie skończony) lub reszta powtórzy się (nie dalej niż po m krokach — ułamek w tym przypadku będzie nieskończony i okresowy). Uzasadniliśmy w ten sposób twierdzenie: zapis okresu w nawiasie okrągłym:

Każdą liczbę wymierną można przedstawić w postaci ułamka dziesiętnego skończonego lub okresowego.

Zamiana ułamków dziesiętnych skończonych na ułamki zwykłe nie stanowi problemu:

0,13

13 (ułamek ten jest nieskracalny), 3,24 100

3

24 100

3

12 50

3

6 . 25

Zamianę ułamka okresowego na ułamek zwykły można przeprowadzić, wykonując obliczenia wg schematu: 47 99·0,(47) = (100–1)·0,(47) = 100·0,(47)–0,(47) = 47,(47)–0,(47) = 47, czyli 0, 47 . 99 Na tym można byłoby zakończyć rozważania o liczbach, gdyż komputery wykonują obliczenia na liczbach całkowitych lub przybliżeniach dziesiętnych liczb wymiernych (o ograniczonej 12 12 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Co wiemy o liczbach? liczbie cyfr). Dokładniej omówimy ten temat podczas prezentacji typów danych liczbowych w komputerze. Pewien niepokój może wzbudzić taki pomysł — skonstruujmy nieskończony ciąg ułamków dziesiętnych w postaci: 0,1; 0,12; 0,121; 0,1212; 0,12122; 0,121221; 0,1212212; 0,12122122; 0,121221222; 0,1212212221; … (ogólnie: zero, przecinek, jedynka, dwójka, jedynka, dwie dwójki, jedynka, trzy dwójki, jedynka, cztery dwójki, jedynka itd.). Tak zbudowana liczba jest ułamkiem dziesiętnym nieskończonym i nieokresowym, czyli nie jest to liczba wymierna. Taki ciąg natomiast jest rosnący i ograniczony (każda z tych liczb jest mniejsza od 0,2). Na podstawie twierdzenia o zbieżności ciągu liczbowego możemy powiedzieć, że ten ciąg jest zbieżny do pewnej liczby niewymiernej (i tak pojawiła nam się kolejna nieznana liczba). Wypada wspomnieć również o ułamkach łańcuchowych, czyli wyrażeniach postaci:

b1

a0

b2

a1 a2

b3 a3



Do ułamków łańcuchowych powrócimy przy okazji obliczeń przybliżeń liczb niewymiernych. Jeśli oznaczymy długość przekątnej kwadratu o boku 1 literą x, to na podstawie twierdzenia p 2 2. Nie istnieje taka liczba wymierna x Pitagorasa otrzymamy równanie x , która spełniaq łaby to równanie. Dowód tego faktu przeprowadzimy nieco później. Mamy zatem kolejną liczbę niewymierną oznaczoną symbolem √ 2 . Liczbami niewymiernymi są również niektóre pier3

4

wiastki arytmetyczne (stopnia 2. i wyższych) z innych liczb wymiernych, np. √3 , √5 , √ 2 , √ 31 itd. Są to tzw. liczby algebraiczne, czyli zgodnie z definicją tego pojęcia istnieją niezerowe wielomiany o współczynnikach całkowitych, dla których te liczby są pierwiastkami (przykła2 4 2 dy równań w odpowiedniej kolejności: x 2 3 0, x 5 0, x 2 0, x 31 0). Istnieją liczby niewymierne, które nie są liczbami algebraicznymi — nazywamy je liczbami przestępnymi. Wśród liczb przestępnych znajduje się liczba e 2,718281828 … — podstawa logaryt3,14159 … (wyrażająca stosunek długości okręgu do jego mów naturalnych, a także liczba średnicy) i wiele innych liczb — jest ich więcej niż liczb wymiernych i liczb algebraicznych. Suma liczb wymiernych i niewymiernych jest zbiorem liczb rzeczywistych. Zbiór liczb rzeczywistych oznaczany symbolem R możemy utożsamiać ze zbiorem wszystkich punktów na prostej, co przedstawiono na rysunku (rysunek 1.1).

13 13 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz…

Rysunek 1.1  Interpretacja zbioru liczb rzeczywistych na prostej

W arytmetyce teoretycznej głównym zagadnieniem jest definicja liczb rzeczywistych. Punktem wyjścia tzw. konstruktywnych definicji liczb rzeczywistych jest zbiór liczb wymiernych. Definicje liczb rzeczywistych (Dedekinda, Cantora lub Weierstrassa) ze względu na swoją nieelementarność nie występują w nauczaniu szkolnym3. Nie będą one również przedmiotem naszych dalszych rozważań. Przypomnijmy natomiast podstawowe własności czterech działań arytmetycznych w poznanych zbiorach liczbowych zawarte w tabeli 1.1. Tabela 1.1.  Podstawowe własności czterech działań arytmetycznych

Zbiór liczbowy

Własność działania

N

Z

Q

R

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

nie

tak

tak

tak

nie

tak

tak

tak

Mnożenie jest zawsze wykonalne

tak

tak

tak

tak

Mnożenie jest łączne a b c

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

tak

Dodawanie jest zawsze wykonalne Dodawanie jest łączne a b

c

a

b c

0 jest elementem neutralnym dla dodawania a 0 Dodawanie jest przemienne a b

0 a

b a

Dla każdej liczby istnieje liczba przeciwna a

a

Odejmowanie jest zawsze wykonalne (definicja: a b

3

a

a

c

c b

a bc

1 jest elementem neutralnym dla mnożenia a 1 Mnożenie jest przemienne a b

a

ba

7DPĪHV

14 14 Ebookpoint.pl kopia dla: Rodenko David [email protected]

1a

a

0

a)

Co wiemy o liczbach?

Dla każdej liczby różnej od 0 istnieje liczba odwrotna a a Dzielenie jest zawsze wykonalne (dla b

0) (definicja: a :b

Mnożenie jest rozdzielne względem dodawania a b c i  a b c

ac bc

1

a c

1

a

cb

ab ac

1

nie

nie

tak

tak

a)

nie

nie

tak

tak

tak

tak

tak

tak

W przytoczonych wzorach konsekwentnie pisaliśmy znak mnożenia ·, chociaż reguły pisania wyrażeń algebraicznych pozwoliłyby go w tych wzorach pominąć. Musimy jednak o pisaniu tego znaku pamiętać (wyróbmy sobie taki nawyk), gdyż w wyrażeniach zapisywanych w językach programowania musi on występować. Nie będzie to co prawda symbol ·, ale *. W podanym zestawieniu pominięto zbiór liczb niewymiernych, w którym podane reguły obowiązują, ale wynik działania nie zawsze do tego zbioru należy:

e

3 — suma liczb niewymiernych jest liczbą niewymierną, 2 2 3 e 3 — suma liczb niewymiernych jest liczbą wymierną,

√ 2 √8 √16

4 — iloczyn liczb niewymiernych jest liczbą wymierną,

√ 2 √ 3 √ 6 — iloczyn liczb niewymiernych jest liczbą niewymierną. Brak możliwości rozwiązania równania x2 = –1 w zbiorze liczb rzeczywistych doprowadził do kolejnego matematycznego odkrycia. Przyjęto, że istnieje liczba i taka, że i2 = –1. Liczbę tę nazwano jednostką urojoną, a liczby postaci bi, gdzie b jest dowolną liczbą rzeczywistą, nazwano liczbami urojonymi. W takiej sytuacji równanie to ma dwa pierwiastki urojone x = i i x = –i (bo i2 = –1 oraz (–i)2 = (–i)·(–i) = i·i = –1). Kontynuując rozważanie, wprowadzono liczby postaci a+bi (gdzie a i b są liczbami rzeczywistymi) i nazwano je liczbami zespolonymi. Na liczbach tej postaci działania wykonujemy według wzorów: (a+bi)+(c+di) = ((a+c)+(b+d)i) (a+bi)–(c+di) = ((a–c)+(b–d)i) (a+bi)·(c+di) = ((ac–bd)+(ad+bc)i)

a bi c di

ac bd c

2

d

2

bc ad c

2

d

2

i

O zbiorze liczb zespolonych powiemy nieco więcej w rozdziale 3., teraz jedynie wypada dodać, że liczby zespolone w postaci a+0i utożsamiamy z liczbami rzeczywistymi. Rozszerzeniem liczb zespolonych są kwaterniony, które zostały opisane przez irlandzkiego matematyka Williama Hamiltona w 1843 r. i służyły do opisu mechaniki w przestrzeni 15 15 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… trójwymiarowej. Obecnie kwaterniony są używane m.in. w grafice komputerowej do wykonywania obrotów w przestrzeni trójwymiarowej. W pakiecie DirectX zdefiniowana jest klasa obsługująca kwaterniony.

Systemy zapisu liczb Obecnie najbardziej rozpowszechnionym systemem zapisywania liczb jest dziesiątkowy system liczbowy (lub inaczej: dziesiętny). Jest to system pozycyjny o podstawie dziesięć. Do zapisu liczb używa się dziesięciu cyfr: 0, 1, 2, 3, 4, 5, 6, 7, 8 i 9. Ta sama cyfra ma różne znaczenie w zależności od miejsca (pozycji), na którym stoi. Pierwsza cyfra (licząc od lewej strony) przedstawia liczbę jednostek najwyższego rzędu mieszczących się w danej liczbie. Kolejna cyfra (idąc w prawo) jest liczbą jednostek rzędu o jeden niższego (waga pozycji jest dziesięć razy mniejsza) i tak dalej, aż do cyfry jedności, np. liczbę dziewięćdziesiąt dwa tysiące siedemset czterdzieści trzy zapiszemy w postaci: 92743 = 9·10000+2·1000+7·100+4·10+3·1 Przedstawiając rozwinięcie dziesiętne liczby, wygodnie jest posługiwać się wagami poszczególnych pozycji w postaci potęg liczby 10: 92743 = 9·104+2·103+7·102+4·101+3·100 Jeśli zapisywana liczba nie jest całkowita, to na prawo od cyfry jedności umieszczamy separator (znak) dziesiętny (w Polsce jest to przecinek, w krajach anglosaskich kropka — i takiego znaku będziemy używać, zapisując stałe liczbowe w językach programowania), a cyfry zapisane po separatorze będą miały kolejno wagi części dziesiątych, setnych, tysięcznych, dziesięciotysięcznych… (10–1, 10–2, 10–3, 10–4, …), np. liczbę trzy i siedemset pięćdziesiąt dwie tysięczne zapiszemy w następujący sposób: 3,752 = 3·100+7·10–1+5·10–2+2·10–3 (w kalkulatorze lub w programie komputerowym będzie to 3.752). Oprócz separatora dziesiętnego możemy stosować separator grup cyfr dla oddzielenia tysięcy, milionów itd. W krajach stosujących kropkę jako separator dziesiętny wykorzystuje się czasem przecinek w funkcji separatora grup cyfr, i na odwrót — w krajach stosujących przecinek jako separator dziesiętny często używana jest do tego kropka. W Polsce do oddzielania grup trzycyfrowych stosowana jest spacja nierozdzielająca (np. 92 743). Ułatwia to czytanie dużych liczb, jednak podczas wprowadzania danych do systemów komputerowych może prowadzić do błędnej interpretacji liczb przez maszynę. Przeanalizujmy sposób wprowadzania liczb naturalnych do kalkulatora. Po włączeniu zasilania lub użyciu przycisku kasującego CE/C na wyświetlaczu pojawia się 0. Naciskając kolejno przyciski z cyframi 9, 2, 7, 4, 3, wprowadzimy do rejestru (i na wyświetlacz) liczbę 92743. W trakcie tej operacji wartość wprowadzanej liczby będzie ulegała zmianie według schematu przedstawionego w tabeli 1.2.

16 16 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Systemy zapisu liczb Tabela 1.2.  Operacje wykonywane podczas wprowadzania liczb do kalkulatora

Cyfra (klawisz)

Operacja

Rejestr (wyświetlacz) 0

9

0·10+9 = 9

9

2

9·10+2 = 92

92

7

92·10+7 = 927

927

4

927·10+4 = 9274

9274

3

9274·10+3 = 92743

92743

Wykonajmy teraz operację odwrotną. Daną liczbę dzielmy przez 10 (dzielenie z resztą) i zapisujmy kolejne uzyskiwane reszty. Otrzymanie ilorazu (całkowitego) równego 0 zakończy proces obliczeń: 92743:10 = 9274 r. 3, 9274:10 = 927 r. 4, 927:10 = 92 r. 7, 92:10 = 9 r. 2, 9:10 = 0 r. 9 (koniec). Nietrudno zauważyć, że otrzymane reszty z dzielenia przez 10 odpowiadają cyfrom wyjściowej liczby, zapisanym w odwrotnej kolejności, tj. od rzędu najniższego (rzędu jedności) do rzędu najwyższego. Taki schemat postępowania możemy stosować dla dowolnej liczby naturalnej i uzyskamy w ten sposób jej cyfry. Ktoś może zastanawiać się, po co to robimy — przecież widzimy, jakimi cyframi liczba została zapisana. Tak, człowiek widzi, ale czy maszyna postrzega tę liczbę w taki sam sposób? Spróbujmy zastosować ten schemat dla liczby 97, zastępując liczbę 10 liczbą 2 — otrzymywane reszty z dzielenia mogą przyjmować jedną z dwóch wartości: 0 lub 1. 97:2 = 48 r. 1, 48:2 = 24 r. 0, 24:2 = 12 r. 0, 12:2 = 6 r. 0, 6:2 = 3 r. 0, 3:2 =1 r. 1, 1:2 = 0 r. 1 (iloraz równy 0, koniec). Zapisując uzyskane reszty w odwrotnej kolejności, otrzymamy ciąg cyfr 1100001. Jest to liczba 97 zapisana w układzie dwójkowym (zwanym też układem binarnym). Cyfry układu binarnego nazywamy bitami (ang. binary digit) — słowo bit ma też inne znaczenie, o czym powiemy w stosownym czasie. 17 17 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… Wagi poszczególnych cyfr (od najniższego rzędu) są równe kolejnym potęgom liczby 2 (podstawy układu): 20 = 1, 21 = 2, 22 = 4, 23 = 8, 24 = 16, 25 = 32, 26 = 64, 27 = 128 itd. Aby uniknąć niejasności co do tego, w jakim układzie liczba została zapisana, będziemy w nawiasie (jako indeks dolny) podawać podstawę w zapisie dziesiętnym. Dla sprawdzenia policzmy wartość uzyskanej przed chwilą liczby binarnej: 1100001(2) = 1·26+1·25+0·24+0·23+0·22+0·21+1·20 = = 1·26+1·25+1·20 = 1·64+1·32+1·1 = 97(10) O ile układ dziesiątkowy jest wygodny dla ludzi, to układ binarny (dwójkowy) stanowi podstawę działania komputerów. Do tego tematu jeszcze powrócimy. Zapisując liczby ujemne w systemie dziesiątkowym, stawiamy przed liczbą znak minus (–). W układzie binarnym (w przypadku obliczeń wykonywanych przez komputer) sprawa jest bardziej skomplikowana, ale w tej chwili nie będziemy tego rozważali. Wyjaśnijmy tylko, że liczby w komputerze przechowywane są w postaci słów binarnych o określonej długości (8, 16, 32, 64 bitów) i tzw. najstarszy bit może być interpretowany jako bit znaku. W podobny sposób możemy konwertować liczby dziesiętne na liczby o dowolnej podstawie. Problemem może być wyłącznie kwestia liczby cyfr i potrzebnych do ich zapisywania znaków. Dla informatyków znaczenie mają jeszcze układ ósemkowy (oktalny) i szesnastkowy (heksadecymalny). W pierwszym przypadku korzystamy z ośmiu cyfr: 0, 1, 2, 3, 4, 5, 6, 7. Z kolei w układzie szesnastkowym brakujące cyfry uzupełniamy początkowymi literami alfabetu łacińskiego. Zestaw 16 cyfr przyjmie postać: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, i F (można też używać małych liter). Dodatkowe cyfry odpowiadają następującym liczbom układu dziesiątkowego: A = 10, B = 11, C = 12, D = 13, E = 14 i F = 15. Zapiszmy liczbę 1000(10) w układzie szesnastkowym i ósemkowym: 1000:16 = 62 r. 8 (cyfra 8), 62:16 = 3 r. 14 (cyfra E), 3:16 = 0 r. 3 (cyfra 3, iloraz 0 — koniec). 1000(10) = 3E8(16) 1000:8 = 125 r. 0 (cyfra 0), 125:8 = 15 r. 5 (cyfra 5), 15:8 = 1 r. 7 (cyfra 7), 1:8 = 0 r. 1 (cyfra 1, iloraz równy 0 — koniec). 1000(10) = 1750(8) Sprawdźmy uzyskane rezultaty: 3E8(16) = 3·162+14·161+8·160 = 3·256+14·16+8·1 = 768+224+8 = 1000(10) 1750(8) = 1·83+7·82+5·81+0·80 = 1·512+7·64+5·8+0·1 = 512+448+40 = = 1000(10) 18 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Systemy zapisu liczb Stosowany przez nas zapis liczb w różnych systemach pozycyjnych jest wygodny w rozważaniach teoretycznych. W praktyce podczas wprowadzania do systemu komputerowego danych liczbowych zapisanych w systemie ósemkowym lub szesnastkowym stosuje się inne sposoby zapisu — bez podawania podstawy w nawiasie, np. 01750 (układ ósemkowy w C lub C++), #03E8, $3E8, 0x3E8, 3E8h (liczby szesnastkowe — zapis w różnych językach). Powróćmy do ułamków. Uprzednio pokazaliśmy, jak z liczby naturalnej „wydobyć” jej cyfry. W matematyce symbolem E(x) lub [x] oznacza się funkcję określoną następująco: E(x) = x, gdy x jest liczbą całkowitą; E(x) jest największą liczbą całkowitą mniejszą od x, gdy x nie jest liczbą całkowitą (fr. entier — całkowity). Tym razem nasze obliczenia przebiegną wg schematu: 0,3724 — dany ułamek (w postaci dziesiętnej), E(10·0,3724) = E(3,724) = 3 (cyfra na pozycji części dziesiątych), E(10·0,724) = E(7,24) = 7 (cyfra na pozycji części setnych), E(10·0,24) = E(2,4) = 2 (cyfra na pozycji części tysięcznych), E(10·0,4) = 4 (cyfra na pozycji części dziesięciotysięcznych) — koniec pracy, dalsze cyfry rozwinięcia ułamka są zerami.

13 Analogiczne rozumowanie możemy przeprowadzić dla ułamka zwykłego, np. : 5 50 E 10

13 50

E

130 50

E 2

E 10

30 50

E

300 50

E 6

30 50

2

6

dalsze cyfry rozwinięcia dziesiętnego są zerami: Powtórzmy jeszcze raz rachunek dla ułamka

E 10

E 10

13 60 10 60

E

E

130 60 100 60

E 2

10 60

2

E 1

40 60

1

13 50

0,260000 …

0,26

13 : 60

E 10

40 60

E

400 60

E 6

40 60

6

E 10

40 60

E

400 60

E 6

40 60

6 i cyfra 6 będzie się powtarzała.

Otrzymamy rozwinięcie dziesiętne nieskończone i okresowe:

13 60

0,2166666 …

0,21 6 . 19 19

Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… Czas na kolejne „odkrycia” — zamiast mnożenia przez 10 zastosujmy mnożenie przez 2. a) ułamek 0,4:

E 2 0,4

E 0,8

0

E 2 0,8

E 1,6

1

E 2 0,6

E 1,2

1

E 2 0,2 E 0,4 0 i teraz nastąpią powtórzenia (E 2 0,4 E 0,8 0…). Okazuje się, że liczba 0,4 w systemie binarnym jest ułamkiem nieskończonym i okresowym: 0,4(10) = 0,01100110011001100…(2) = 0,(0110)(2)

3

b) ułamek 0,125

8

:

E 2

3 8

E

6 8

E

3 4

E 2

3 4

E

6 4

E 1

E 2

1 2

E 1

0 2 4

E 1

1 2

1

1 i rachunek został zakończony: 0,125(10) = 0,011(2).

Skończoną reprezentację binarną mają tylko te ułamki, których mianowniki są potęgami liczby 2 lub do takiej postaci można je sprowadzić, stosując skracanie. Zastanówmy się jeszcze nad sposobem przechowywania liczb w komputerze. Liczby przechowywane są w systemie dwójkowym, w zapisie stało- lub zmiennopozycyjnym. Dokładność, z jaką liczby są zapamiętywane, zależy od długości słowa maszynowego (wyrażonego w bitach) — najczęściej jest to wielokrotność liczby 8 (bajt — słowo złożone z 8 bitów). W przypadku potrzeby wykonywania obliczeń na liczbach dłuższych od słowa maszynowego stosuje się zapis o podwójnej (lub poczwórnej) precyzji, czyli liczba zapisywana jest w dwóch (lub czterech) słowach maszynowych. Stałopozycyjny (stałoprzecinkowy) zapis liczby polega na tym, że w słowie maszynowym określoną (z góry) liczbę bitów zajmuje część całkowita, a resztę część ułamkowa liczby. Taki zapis stosowany jest najczęściej do zapamiętywania liczb całkowitych (0 bitów dla części ułamkowej). Więcej szczegółów poznamy podczas omawiania całkowitych typów liczbowych w językach programowania. Zmiennopozycyjny (zmiennoprzecinkowy) zapis liczby polega na przedstawieniu liczby w postaci L = PC·M, gdzie P jest podstawą systemu pozycyjnego (np. 10 lub 2), C jest liczbą całkowitą zwaną wykładnikiem lub cechą, natomiast M jest liczbą o wartości bezwzględnej mniejszej od 1 i nazywamy ją częścią ułamkową lub mantysą. Ponadto w zapisie znormalizowanym mantysa spełnia nierówność 20 20 Ebookpoint.pl kopia dla: Rodenko David [email protected]

1 P

M

1.

Systemy zapisu liczb Zobaczmy przykłady kilku liczb w zapisie zmiennopozycyjnym w systemie dziesiętnym (tabela 1.3). Tabela 1.3.  Zapis zmiennopozycyjny liczb w systemie dziesiętnym

Liczba

Mantysa

Cecha

Zapis zmiennopozycyjny

0,2497

0,2497

0

0,2497·100

–0,00024

–0,24

–3

–0,24·10–3

W zapisie binarnym na mantysę i wykładnik przeznaczone są odpowiednie, ustalone liczby bitów, a to umożliwia reprezentowanie skończonej liczby wartości. Im dłuższa mantysa, z tym większą dokładnością możemy przedstawiać liczby. Z kolei im dłuższy wykładnik, tym większy jest przedział (zakres) dostępnego zbioru liczb. Rozważmy prosty model — ośmiobitowe ciągi zcccmmmm, gdzie z oznacza znak liczby (0 — liczba dodatnia, 1 — ujemna), ccc to trzybitowa cecha pamiętana z przesunięciem 4, mmmm to 4 bity znormalizowanej mantysy (pierwsza cyfra mantysy nie jest ukryta). Ciąg bitów 01101010 zinterpretujemy w następujący sposób: 0 — liczba dodatnia, 110(2) = 6(10) — cecha, po uwzględnieniu przesunięcia (6–4) równa 2(10), 1010(2) = 0,625(10) — znormalizowana mantysa odpowiadająca liczbie binarnej 0,1010 (w układzie dziesiętnym otrzymamy 1 1 5 1 2 3 4 0.1010 2 1 2 02 12 02 0,625 10 ), 2 8 8 L = 0,625·22 = 2,5 — liczba w systemie dziesiętnym. Umówmy się, że ciąg 00000000 przedstawia liczbę 0, natomiast nie są poprawnymi liczbami inne ciągi postaci xxxx0xxx (x oznacza cyfrę 0 lub 1), tzn. takie, w których pierwsza cyfra mantysy jest zerem. Nasza reprezentacja pozwala zatem na zapisanie 129 różnych wartości. Najmniejszą zapisaną liczbą dodatnią będzie 0,03125 (ciąg 00001000), a największą liczbą dodatnią 7,5 (ciąg 01111111). Kolejne szczegóły poznamy, omawiając zmiennopozycyjne typy liczbowe w językach programowania.

Od problemu do programu… — słownik początkującego programisty Na początku pojawia się problem, czyli zadanie, jakie mamy rozwiązać. Zanim człowiek (programista) nauczy komputer rozwiązywania wybranych problemów, musi sam umieć je rozwiązać lub co najmniej wskazać drogę w postaci elementarnych kroków potrzebnych do uzyskania rozwiązania. Sformułujmy przykładowe zadanie: Oblicz sumę cyfr danej liczby naturalnej. 21 21 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… 5R]ZLą]DQLH Należy przedstawić podaną liczbę jako ciąg liczb jednocyfrowych, wykorzystując wszystkie cyfry występujące w danej liczbie, a następnie obliczyć ich sumę, np.: 2739 Æ {2, 7, 3, 9} Æ 2+7+3+9 = 21 Człowiek zrobi to „na oko”, komputer musi przy użyciu dostępnych działań „wydobyć” z liczby jej cyfry, zapamiętać je jako liczby jednocyfrowe (w pamięci operacyjnej lub w pliku dyskowym), a następnie dodać zapamiętane liczby. Proces sumowania można też prowadzić „równolegle” z procesem odczytywania cyfr. Mamy już (w postaci opisu) sposób rozwiązania zadania, czyli znamy algorytm postępowania w czasie obliczania sumy cyfr danej liczby naturalnej. Algorytm — w matematyce oraz informatyce skończony, uporządkowany ciąg jasno zdefiniowanych czynności, koniecznych do wykonania pewnego zadania.

Przedstawione rozwiązanie jest poprawne, ale musimy jeszcze precyzyjniej uchwycić pewne szczegóły. Sumowanie będziemy przeprowadzać na bieżąco. Cyfry z liczby będziemy uzyskiwać, wykonując dzielenie z resztą. W tym celu zastosujemy dwa operatory: m div n — dzielenie całkowite liczby m przez liczbę n, m mod n — reszta z dzielenia liczby m przez liczbę n. 5R]ZLą]DQLH Algorytm zapiszemy jako listę kroków. Dane: dowolna liczba naturalna — n. Wynik: wartość sumy cyfr — suma. Zmienna pomocnicza: wartość kolejnej cyfry — cyfra. 1. Wprowadź wartość liczby n. 2. Nadaj wartość początkową zmiennej, suma = 0. 3. Odczytaj cyfrę jedności liczby n: cyfra := n mod 10. 4. Dodaj cyfrę do sumy: suma := suma+cyfra. 5. Usuń z liczby cyfrę jedności: n := n div 10. Uwaga! Wykonana operacja zmienia wartość liczby n, ale pozostawia nie brane dotychczas pod uwagę cyfry. Aby zachować początkową wartość liczby, należałoby zrobić na początku jej kopię zapasową i na końcu odtworzyć wartość z zachowanej kopii. Po pobraniu ostatniej cyfry liczba n przyjmie wartość 0. 6. Czy n > 0? (Czy są dalsze cyfry do sumowania?) Jeśli tak, przejdź do punktu 3. 7. Wyprowadź wartość sumy cyfr: suma. 8. Koniec. Prześledźmy działanie algorytmu na przykładzie (tabela 1.4).

22 22 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Od problemu do programu… — słownik początkującego programisty Tabela 1.4.  Analiza działania algorytmu obliczającego sumę cyfr liczby naturalnej

Krok

Czynność

n

Wartości zmiennych cyfra

suma

1.

Czytaj n

758

?

?

2.

suma := 0

758

?

0

3.

cyfra := n mod 10

758

8

0

4.

suma := suma+cyfra

758

8

8

5.

n := n div 10

75

8

8

6.

n > 0? TAK — przejdź do 3.

75

8

8

3.

cyfra := n mod 10

75

5

8

4

suma := suma+cyfra

75

5

13

5.

n := n div 10

7

5

13

6

n > 0? TAK — przejdź do 3.

7

5

13

3.

cyfra := n mod 10

7

7

13

4

suma := suma+cyfra

7

7

20

5.

n := n div 10

0

7

20

6.

n > 0? NIE.

0

7

20

7.

Wypisz suma

0

7

20

8.

Koniec

0

7

20

Algorytmy można również zapisywać w postaci schematów blokowych, co ułatwia ich zrozumienie i późniejszą implementację. Schemat blokowy — graficzny zapis algorytmu rozwiązania zadania przedstawiający opis i kolejność wykonywania czynności realizujących dany algorytm.

Implementacja — proces pisania programu (kodu źródłowego), czyli programowanie, lub efekt takiego procesu, czyli program.

5R]ZLą]DQLH Algorytm można zapisać również w postaci graficznej jako tzw. sieć działań lub schemat blokowy. Schemat blokowy składa się z opisanych figur geometrycznych połączonych liniami (strzałkami) zgodnie z kolejnością wykonywania czynności wynikających z przyjętego algorytmu rozwiązania zadania (rysunek 1.2). Blok graniczny (kształt owalny) oznacza początek lub koniec programu (START, STOP). Blok wejścia-wyjścia (w kształcie równoległoboku) przedstawia czynność wprowadzania danych do programu i przyporządkowywania ich zmiennym dla późniejszego wykorzystania oraz czynność 23 23 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… wyprowadzenia wyników obliczeń (czytaj n, pisz a). Blok obliczeniowy (przetwarzania danych) ma kształt prostokąta i oznacza wykonanie operacji, w efekcie której zmienią się wartości, postać lub miejsce zapisu danych. Blok decyzyjny (w kształcie rombu) przedstawia wybór jednego z dwóch wariantów wykonywania programu na podstawie sprawdzenia warunku wpisanego we wnętrzu bloku. W schematach blokowych można użyć również innych elementów (rysunek 1.3), takich jak: blok wywołania podprogramu, blok fragmentu (część programu zdefiniowana odrębnie), blok komentarza (komentarze wyjaśniające znaczenie poszczególnych części schematu w celu ułatwienia zrozumienia algorytmu), łącznik wewnętrzny (łączenie odrębnych części schematu znajdujących się na tej samej stronie; powiązane ze sobą łączniki oznaczone są tym samym napisem — symbolem literowym lub liczbą), łącznik zewnętrzny (łączenie odrębnych części schematu znajdujących się na różnych stronach; powiązane ze sobą łączniki oznaczone są tym samym napisem — symbolem literowym lub liczbą i numerem strony).

Rysunek 1.2.  Schemat blokowy algorytmu obliczania sumy cyfr liczby naturalnej

Rysunek 1.3.  Elementy schematu blokowego

Rysowanie schematów blokowych bywa często kłopotliwe i dlatego w publikacjach poświęconych opisom algorytmów stosuje się pseudokod — coś pośredniego pomiędzy językiem naturalnym a językiem programowania. Pseudokod — sposób zapisywania algorytmów z zastosowaniem wybranych słów języka naturalnego i składni zbliżonej do jednego z popularnych języków programowania.

5R]ZLą]DQLH Algorytm obliczania sumy cyfr liczby naturalnej zapiszemy przy użyciu pseudokodu. Słowa kluczowe (instrukcje), operatory div i mod (ich znaczenie wyjaśniliśmy w przykładzie z listą 24 24 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Od problemu do programu… — słownik początkującego programisty kroków) i procedury wejścia-wyjścia wyróżniono pismem pogrubionym, nazwy zmiennych i etykietę zapisano kursywą. Zapis algorytmu rozpoczęliśmy od deklaracji zmiennych i etykiety skok potrzebnej do oznaczenia miejsca powrotu pętli. całkowite n, cyfra, suma etykieta skok początek czytaj n suma := 0 skok: cyfra := n mod 10 suma := suma+cyfra n := n div 2 jeśli n > 0, to idź do skok pisz suma koniec Używanie etykiet i instrukcji skoku nie jest zalecane. Można ten algorytm nieznacznie zmodyfikować i zapisać przy użyciu pętli (instrukcji cyklu) ze sprawdzaniem warunku na końcu: całkowite n, cyfra, suma początek czytaj n suma := 0 powtarzaj cyfra := n mod 10 suma := suma+cyfra n := n div 2 aż do n = 0 pisz suma koniec W tym drugim wariancie na schemacie blokowym należałoby zamienić miejscami napisy tak i nie oraz zastąpić warunek n > 0 warunkiem n = 0. Kolejnym ważnym pojęciem jest język programowania. Do chwili obecnej na świecie powstało około 2500 różnych języków programowania. Najpopularniejsze z nich przedstawimy w dalszej części publikacji, a wybranych języków (Pascal, C i C++) będziemy używali do przedstawiania algorytmów i pisania programów.

25 25 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz…

Język programowania jest sztucznym językiem pozwalającym na zapisywanie algorytmów i innych zadań, jakie komputer ma wykonywać.

Język programowania jest zbiorem słów (tzw. słów kluczowych) wywodzących się zwykle z języka angielskiego i innych symboli (znaków działań i relacji matematycznych, znaków interpunkcyjnych) oraz zbiorem zasad określających, w jaki sposób z tych symboli zbudować program komputerowy. Reguły te opisują, jak należy konstruować poprawne wyrażenia, oraz określają sposób ich interpretowania przez komputer. Nietrudno się domyślić, czym jest program komputerowy. Program komputerowy jest algorytmem zapisanym (wyrażonym) w określonym języku programowania.

Ale to jeszcze nie koniec. Człowiek sformułował zadanie (problem), znalazł jego rozwiązanie, stworzył na tej podstawie algorytm rozwiązywania tego problemu. Następnie zapisał ten algorytm w znanym sobie języku programowania. W ten sposób powstał czytelny dla niego kod źródłowy. Kod źródłowy to tekst programu komputerowego, zapisany w wybranym języku programowania. Ta postać programu jest czytelna dla człowieka-programisty znającego ten język programowania.

Teraz należy ten kod przetłumaczyć na rozkazy zrozumiałe przez procesor komputera. Proces taki nazywamy translacją. W wyniku tych działań powstaje kod maszynowy. Kod maszynowy, zwany również kodem wykonywalnym lub binarnym, jest postacią programu komputerowego przeznaczoną do bezpośredniego wykonania przez konkretny typ procesora. Zawiera kody rozkazów procesora oraz ich argumenty i jest nieczytelny dla człowieka.

Istnieją różne możliwości translacji kodu źródłowego na kod maszynowy. Można czytać kod źródłowy i na bieżąco tłumaczyć (interpretować) kolejne instrukcje na rozkazy procesora. Wymaga to obecności w pamięci komputera programu zwanego interpreterem (tak dzieje się np. w przypadku niektórych wersji języka BASIC lub LOGO). Wielokrotne powtarzanie fragmentu kodu programu (np. podczas wykonywania instrukcji cyklu lub wywoływania podprogramu) wymaga wielokrotnego tłumaczenia tego samego kodu, co wydłuża czas pracy programu. Użytkownik programu zwykle ma dostęp do kodu źródłowego, z czego autor nie zawsze musi być zadowolony. Kolejną wadą jest wymóg posiadania przez użytkownika programu interpretera i bibliotek uruchomieniowych (ang. runtime library).

26 26 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Od problemu do programu… — słownik początkującego programisty

Interpreter jest programem tłumaczącym i wykonującym na bieżąco kolejne wiersze (instrukcje) programu źródłowego.

Biblioteka uruchomieniowa jest zestawem programów (funkcji) narzędziowych wspomagających program komputerowy w trakcie jego działania. Procedury biblioteczne współpracują z systemem operacyjnym i dostarczają programowi operacje wejścia-wyjścia i funkcje matematyczne.

Drugą możliwością jest jednorazowe przetłumaczenie całego kodu źródłowego i zapisanie go w postaci wykonywalnej. Proces taki nazywamy kompilacją programu. Tak funkcjonuje większość języków programowania. Realizując nasze przykłady zapisane w języku Pascal lub C++, będziemy dokonywali właśnie ich kompilacji. Kompilator jest programem tłumaczącym kod źródłowy zapisany w języku programowania na język maszynowy. Efektem kompilacji jest plik obiektowy.

Zanim otrzymamy ostateczną wersję programu wykonywalnego, musimy połączyć skompilowany kod naszego programu z kodem niezbędnych bibliotek. Często realizowane jest to przez środowisko programistyczne, w jakim pracujemy, w sposób automatyczny i tego etapu prawie nie dostrzegamy. Program odpowiedzialny za tę czynność nazywa się konsolidatorem lub linkerem (ang. link). Konsolidacja (lub inaczej linkowanie) jest procesem, który ma na celu połączenie plików obiektowych i bibliotek statycznych (czyli skompilowanych modułów) w jeden plik wykonywalny lub inny plik obiektowy.

Przykładem jeszcze innego podejścia do tworzenia programów jest język Java. Kod źródłowy programu jest kompilowany do postaci kodu pośredniego, zwanego kodem bajtowym. Kod bajtowy jest następnie interpretowany przez maszynę wirtualną Javy. Wirtualna maszyna Javy jest dostępna dla wielu platform (np. dla systemów Microsoft Windows, Linux, Solaris). Dzięki temu pośredni kod programu jest uniwersalny.

Kilka zdań o językach programowania Procesor rozpoznaje rozkazy podawane w postaci liczb binarnych. Każdy typ procesora ma właściwy dla siebie zestaw instrukcji, które służą do realizowania prostych zadań. Do typowych zadań wykonywanych przez procesor należą: przeniesienie zawartości komórki pamięci do rejestru (lub odwrotnie), dodanie zawartości dwóch rejestrów procesora, przesunięcie układu bitów w rejestrze o jedno miejsce w lewo lub w prawo (co odpowiada mnożeniu lub dzieleniu przez 2) itp. Napisanie programu przy użyciu tych kodów, a więc bezpośrednio w języku maszynowym, jest bardzo pracochłonne. 27 27 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… Aby ułatwić sobie pracę, poszczególnym rozkazom procesora przyporządkowano skróty (tzw. mnemoniki) wywodzące się z języka angielskiego, np.: JMP (ang. jump — skocz), MOV (ang. move — przesuń), ADD (ang. add — dodaj, zsumuj), RET (ang. return — powrót) itd. Skróty te wraz z odpowiednimi parametrami (nazwami rejestrów, adresami, stałymi…) tworzą instrukcje języka zwanego asemblerem. Pisanie programów w asemblerze jest trochę łatwiejsze od tworzenia ich bezpośrednio w języku maszynowym. Kod asemblerowy musi być oczywiście przetłumaczony na język maszynowy — do tego celu służy specjalny program, również nazywany asemblerem. Asemblery należą do języków niskiego poziomu i są ściśle związane z architekturą komputera. Nas będą interesowały języki wysokiego poziomu, w których każda instrukcja jest tłumaczona na kilka instrukcji kodu maszynowego, a kod źródłowy może być wykorzystywany (interpretowany lub kompilowany) w różnych środowiskach (niezależnie od sprzętu lub systemu operacyjnego). Pierwszym językiem wysokiego poziomu był FORTRAN (ang. FORmula TRANslator) stworzony w latach 50. ubiegłego stulecia. Język stosowany jest do chwili obecnej. Wykorzystuje się go głównie w obliczeniach naukowo-inżynierskich. Przez lata powstała wielka liczba bibliotek pozwalających rozwiązać niemal każdy problem z zakresu obliczeń numerycznych. BASIC (ang. Beginner’s All — purpose Symbolic Instruction Code) jest językiem programowania wysokiego poziomu opracowanym w 1964 roku przez Johna George’a Kemeny’ego i Thomasa E. Kurtza w Dartmouth College. Język uzyskał dużą popularność w czasach rozwoju komputerów ośmiobitowych, które „trafiły pod strzechy” i odsłoniły odrobinę magii programowania dla amatorów. Powstał szereg dialektów języka BASIC (ponad 200). Interpretery języka znajdowały się początkowo w pamięci ROM komputerów, a po upowszechnieniu się systemu operacyjnego DOS pojawiły się wersje na dyskietkach, np. GW-BASIC. Wraz z rozwojem sprzętu i systemów operacyjnych Microsoftu ewoluował również BASIC (interpreter QBasic 1.1, interpreter i kompilator QuickBASIC 4.50, Visual Basic for Windows, Visual Basic for Application włączony do aplikacji Microsoft Office i VB.NET — środowisko do programowania dla platformy .Net). Istnieją też wersje języka rozwijane przez innych producentów. Zobaczmy, jak wygląda kod naszego przykładu (obliczanie sumy cyfr liczby naturalnej) w różnych wersjach języka BASIC (tabela 1.5): Tabela 1.5.  Algorytm sumowania cyfr liczby naturalnej w różnych wersjach języka BASIC

GW-BASIC 3.23 10 20 30 40 50 60 70

INPUT "n = ", N LET SUMA = 0 LET CYFRA = N MOD 10 LET SUMA = SUMA + CYFRA LET N = N \ 10) IF N > 0 THEN GOTO 30 PRINT "Suma cyfr: ", SUMA

28 28 Ebookpoint.pl kopia dla: Rodenko David [email protected]

QuickBASIC 4.50 (z wykorzystaniem instrukcji skoku)

INPUT "n = ", n suma = 0 skok: cyfra = n MOD 10 suma = suma + cyfra n = n \ 10 IF n > 0 THEN GOTO skok PRINT "Suma cyfr: ", suma

Kilka zdań o językach programowania

QuickBASIC 4.50

Turbo Basic 1.1 (Borland)

(z wykorzystaniem pętli)

(zmodyfikowany warunek w pętli)

INPUT "n = ", n suma = 0 DO cyfra = n MOD 10 suma = suma + cyfra n = n \ 10 LOOP WHILE n > 0 PRINT "Suma cyfr: ", suma

10 20 30 40 50 60 70 80

input "n = ", n suma = 0 do cyfra = n mod 10 suma = suma + cyfra n = n \ 10 loop until n = 0 print "Suma cyfr: ", suma

Analizę kodu i rozpracowanie poszczególnych instrukcji pozostawmy dociekliwości Czytelnika. Pascal to uniwersalny język programowania wysokiego poziomu opracowany w 1970 roku przez Niclausa Wirtha. Nazwa języka pochodzi od nazwiska francuskiego fizyka, matematyka i filozofa Blaise’a Pascala. Jest to jeden z najważniejszych języków programowania, bowiem wykształciły się na nim szerokie rzesze informatyków. Od początku służył do nauki programowania strukturalnego. Notacja wzorowana na Pascalu jest powszechnie używana w publikacjach, gdzie pełni rolę uniwersalnego języka opisu algorytmów. Do popularności tego języka przyczyniła się firma Borland i opracowane przez nią kompilatory Turbo Pascala (wersje od 1.0 do 7.0 rozwijane w latach 1983 – 92). Środowisko Borland Pascal 7.0 (1992) posiadało kompilator i biblioteki umożliwiające programowanie dla Windows. Kolejne wersje dla systemu Windows nie zyskały dużej popularności, dopiero wydane w 1995 roku środowisko Borland Delphi 1.0 rozpoczęło rozwój programowania wizualnego dla Windows. Alternatywą dla wersji komercyjnych Turbo Pascala, które aktualnie nie są już rozwijane, jest Free Pascal. Ten kompilator języka Pascal jest dostępny na wielu platformach sprzętowych dla różnych systemów operacyjnych. Free Pascal jest udostępniany na prawach licencji GPL. Obecnie rozwijane jest (na licencji GNU GPL) zintegrowane środowisko programistyczne (IDE) o nazwie Lazarus wzorowane na Delphi. Lazarus jest środowiskiem dostępnym dla różnych systemów operacyjnych. Język C został zaprojektowany przez Dennisa Ritchiego. Pierwsze wersje języka powstawały w latach 1969 – 1973. W 1978 roku B. Kernighan i D. Ritchie opublikowali dokumentację języka C Programming Language (przetłumaczoną na język polski: Język ANSI C). C stał się dominującym językiem programowania systemów operacyjnych i aplikacji. W C zaimplementowano jądro systemu operacyjnego Unix (1973). Jest to uniwersalny język programowania. Język C++ został opracowany przez Bjarne Stroustrupa w latach osiemdziesiątych ubiegłego wieku (pierwsza wersja w 1979 r.). C++ udostępnia nam różne style programowania: programowanie proceduralne, obiektowe i generyczne, a nawet programowanie na poziomie asemblera. Język C++ jest w pewnym sensie rozszerzeniem języka C. Każdy kod programu zapisany w C można również skompilować w C++.

29 29 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… 3U]\NáDG Zanim zaczniemy poznawać kolejne języki programowania, zobaczmy, jak wygląda rozwiązanie naszego zadania (suma cyfr podanej liczby całkowitej) w Pascalu, C i C++. Przedstawimy dwa przykłady w Pascalu. Czytelnik zapewne zauważy tu podobieństwo pomiędzy zapisanym wcześniej algorytmem w pseudojęzyku i kodem źródłowym w Pascalu. Pascal program suma_cyfr; var n, cyfra, suma: Word; label skok; begin write('n = '); readln(n); suma := 0; skok: cyfra := n mod 10; suma := suma + cyfra; n := n div 10; if n > 0 then goto skok; writeln('Suma cyfr: ', suma); readln; end.

program suma_cyfr; var n, cyfra, suma: Word; begin write('n = '); readln(n); suma := 0; repeat cyfra := n mod 10; suma := suma + cyfra; n := n div 10; until not (n > 0); writeln('Suma cyfr: ', suma); readln; end.

Program działający w sposób poprawny, ale napisany w złym stylu — instrukcje skoku (goto) nie są zalecane.

Program napisany w dobrym stylu, ale ze względu na użycie pętli ze sprawdzaniem warunku na końcu wymagał negacji tego warunku…

Kody źródłowe w C i C++ są na pierwszy rzut oka mniej czytelne. Szczegółami zajmiemy się wkrótce. Nie powinno być jednak problemu ze zrozumieniem ogólnego zarysu przedstawionych kodów. C #include int main() { int n, cyfra, suma = 0; printf("n = "); scanf("%d", &n); do { cyfra = n % 10; suma += cyfra; n /= 10; } while (n > 0); printf("Suma cyfr: %d", suma); system("pause"); return 0; }

30 30 Ebookpoint.pl kopia dla: Rodenko David [email protected]

C++ #include int main() { int n, suma = 0; std::cout > n; do { int cyfra = n % 10; suma += cyfra; n /= 10; } while (n > 0); std::cout RSVXPğUVWOLF]EDVXPDBF\IUEIOLF]ED@ end

Procedura suma_cyfr działa w następujący sposób: jeżeli liczba jest pusta — nie zawiera żadnych znaków (warunek emptyp :liczba ma wartość TRUE) — wykonywane jest polecenie op 0 (output 0) i przez funkcję zwracana jest wartość 0, w przeciwnym razie zwracana jest suma (op sum …) pierwszej cyfry danej liczby (ğUVWOLF]ED) i wartości sumy cyfr danej liczby z pominięciem pierwszej cyfry (EIOLF]EDEI EXWğUVW) — wywołanie rekurencyjne procedury suma_cyfr. Przykładowy przebieg wywoływania procedury przedstawiono w tabeli 1.6. Tabela 1.6.  Przebieg rekurencyjnego obliczania sumy cyfr w języku Logo

Kolejne wywołania procedury suma_cyfr suma_cyfr 2513 2+suma_cyfr 513 5+suma_cyfr 13 1+suma_cyfr 3 VXPDBF\IUĵ SXVWHVïRZR

Powroty i obliczanie sum 11 2+9 = 11 5+4 = 9 1+3 = 4 3+0

31 31 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… Drugi przykład jeszcze lepiej prezentuje charakter programowania w Logo. Zadanie jest rozbite na dwa etapy: zamiana liczby na listę jej cyfr i dodawanie elementów listy (co może być wykorzystywane w jeszcze innych sytuacjach). Oto definicje procedur, sposób ich działania (każdej z osobna i w „zespole”) w celu rozwiązania naszego zadania. to sumuj :lista LIHOVHHPSW\SOLVWD>RS@>RSVXPğUVWOLVWDVXPXMEIOLVWD@ end

Wywołanie pr sumuj [12 33 5] (listę tworzymy przez umieszczenie jej elementów oddzielonych odstępami w nawiasach prostokątnych) powoduje wyświetlenie liczby 50 (bo 12+33+5 = 50). to cyfry :liczba LIHOVHHPSW\SOLF]ED>RS>@@>RSISXWğUVWOLF]EDF\IU\EIOLF]ED@ end

Procedura cyfry tworzy listę cyfr podanej liczby. Wywołanie funkcji poleceniem pr cyfry 3729 spowoduje wyświetlenie wyniku (tj. elementów listy) 3 7 2 9, natomiast po użyciu polecenia show cyfry 3729 zobaczymy listę w postaci [3 7 2 9]. Rozwiązanie zadania uzyskamy, wpisując pr sumuj cyfry 3729. W trzecim przypadku zastosowano pętlę i działanie procedury jest zupełnie podobne do rozwiązań w BASIC-u lub Pascalu. to suma_cyfr :liczba make "suma 0 GRZKLOH>PDNHVXPDVXPDğUVWOLF]EDPDNHOLF]EDEIOLF]ED@>QRW ´emptyp :liczba] op :suma end

Przykłady zostały sprawdzone w FMSLogo version 6.25.0. Pierwszy będzie działał w dowolnej wersji Logo (użyte operacje na słowach są standardowo implementowane), natomiast w drugim przykładzie kłopot może sprawić procedura do.while. Skutkiem ubocznym działania tej procedury będzie pozostawiona w przestrzeni roboczej zmienna suma.

Pierwszy program — klasyczne przykłady w popularnych językach Zajmiemy się programowaniem w języku Pascal oraz w C lub C++. Jak już wiemy, programy źródłowe są plikami tekstowymi i mogą być zapisane w dowolnym edytorze tekstu. Jedynym warunkiem jest zapisanie tekstu w kodzie ASCII. Aby nie robić sobie zamieszania, plikom bę-

32 32 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Pierwszy program — klasyczne przykłady w popularnych językach dziemy nadawać rozszerzenia powszechnie przyjęte dla wybranych języków programowania: .pas — Pascal, .c — język C, .cpp — C++. Na początku będziemy pisali programy działające w konsoli (dawniej pod kontrolą systemu operacyjnego DOS). Zmieniło się środowisko uruchamiania tych programów, ale sposób pisania pozostał ten sam. Zaczniemy od wersji minimalnej, czyli programu, który nic nie robi, da się skompilować i będzie wzorem do dalszej rozbudowy. Pascal — wystarczą dwa słowa kluczowe oddzielone przynajmniej jednym odstępem lub przejściem do nowego wiersza i kropka. Krócej się nie da. begin end.

Kropka oznacza definitywny koniec pliku i wszystko, co będzie po niej zapisane, kompilator zignoruje. Begin i end są nawiasami syntaktycznymi (składniowymi) i w tym przypadku wyznaczają główny blok programu. Wewnątrz tego bloku będziemy umieszczali instrukcje, a przed tym blokiem jest miejsce na wszelkie deklaracje… Możemy dodać jeszcze nagłówek (nie jest on obowiązkowy) zbudowany ze słowa kluczowego program, identyfikatora i średnika kończącego linię nagłówka. Identyfikator jest ciągiem znaków złożonym z liter4, cyfr lub symbolu podkreślenia, zaczynającym się od litery (odstęp w tym ciągu nie jest dozwolony). W tym przypadku będzie to identyfikator programu, podobnie będziemy konstruować nazwy zmiennych, funkcji lub innych definiowanych przez użytkownika elementów. W Pascalu nie ma znaczenia, czy używamy małych, czy wielkich liter — identyfikatory: Program, PROGRAM, program, proGRAm kompilator potraktuje jako ten sam identyfikator. program leniwy_program_2; begin end.

C lub C++ — najkrótszą poprawną konstrukcją jest program złożony z dwóch słów kluczowych i dwóch par nawiasów. Słowa kluczowe piszemy zawsze małymi literami. int main (){}

Każdy program musi zawierać funkcję main i od tej funkcji rozpoczyna się wykonanie kodu programu. Słowo int5 jest nazwą typu zmiennych (liczby całkowite) i w tym przypadku jest to typ wartości, jaką zwróci funkcja main do systemu po zakończeniu pracy programu. Nawiasy okrągłe zawierają listę (w tym przypadku pustą) parametrów wywołania funkcji. Natomiast nawiasy klamrowe pełnią taką samą rolę jak słowa begin i end w Pascalu i wyznaczają tzw. ciało funkcji. Podany kod jest nie do końca poprawny, gdyż zapomnieliśmy o wartości zwracanej przez funkcję. W tym przypadku uzupełni to za nas kompilator. Musimy jednak mieć świadomość, że 4

Tylko litery alfabetu angielskiego, bez znaków diakrytycznych występujących w  innych językach, np. w  języku polskim: ą, ć, ę… 5 Niektóre kompilatory pozwalają pominąć w kodzie słowo „int”, ale — domyślnie — zwrócona wartość i tak będzie typu całkowitego.

33 33 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… poprawnie zapisany kod powinien zawierać funkcję return i zwracaną wartość 0, a ponadto należy zakończyć instrukcję średnikiem. int main (){ return 0; }

Pisanie wszystkiego w jednym wierszu nie jest dobrym zwyczajem (dla kompilatora nie stanowi to problemu). Stosowanie końców linii i wcięć poprawia czytelność kodu źródłowego (ważne dla programisty). Lepiej zatem zapiszmy to tak: int main () { return 0; }

Ważnym elementem, szczególnie dla programisty czytającego cudzy kod lub własny po pewnym czasie, są komentarze. W Pascalu dysponujemy komentarzami blokowymi ograniczonymi przy użyciu nawiasów klamrowych { i } lub nawiasów okrągłych z gwiazdką — (* i *). Długość komentarzy nie jest ograniczona. Nie wolno mieszać symboli, np. (* … } lub { … *), oraz zagnieżdżać komentarzy tego samego typu — konstrukcja {…{…}…} jest zabroniona. Komentarz nie może również zaczynać się od znaku $, ponieważ ten znak używany jest w dyrektywach kompilatora — o tym powiemy więcej we właściwym czasie. Konsekwentne używanie tylko jednego z tych komentarzy jest korzystne — gdy będziemy chcieli wyłączyć z kompilacji obszerniejszy fragment kodu, zrobimy to przy użyciu komentarza drugiego typu. ^7RMHVWNRPHQWDU]GOD&LHELHDbQLHGODNRPSLODWRUD .RPSLODWRU3DVFDODWHQIUDJPHQWNRGX]LJQRUXMH`

begin 7RWHĝMHVWSRSUDZQ\NRPHQWDU]Į

end.

W języku C i C++ komentarz blokowy ograniczamy symbolami /* i */. Dodatkowo w C++ wprowadzono komentarz liniowy o zasięgu od znaku // do końca wiersza. Również współczesne kompilatory języka C akceptują tę formę komentarza. int main ()

7HQWHNVWMHVWNRPHQWDU]HPDĝGRNRñFDOLQLL

{  7HQNRPHQWDU]PLHĂFLVLÚ ZbNLONXZLHUV]DFK WHNVWXěUöGïRZHJRSURJUDPXĮ 

return 0; }

34 34 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Pierwszy program — klasyczne przykłady w popularnych językach 3U]\NáDG Do klasyki nauki programowania należy przykład wyświetlający na ekranie napis Hello World!, czyli Witaj Świecie!. Jest to zazwyczaj pierwsza okazja do zobaczenia struktury programu w przyswajanym języku i poznania standardowej procedury (funkcji) wyświetlającej napisy na ekranie. Pascal — do wyświetlania tekstów na ekranie używamy standardowej procedury write, jako parametr wywołania podajemy łańcuch znaków, czyli np. ciąg znaków ASCII ujęty w apostrofy. Po wyświetleniu tekstu kursor zostanie ustawiony za ostatnim znakiem. Natomiast użycie procedury writeln (z takim samym parametrem) spowoduje przeniesienie kursora na początek nowego wiersza (oczywiście dopiero po wyświetleniu napisu). program hello; begin write('Hello World!'); readln; end.

Procedura writeln może być użyta również bez parametrów i wtedy powoduje wyłącznie przejście kursora na początek nowego wiersza (wysyła kod CR/LF). Procedura readln może być pominięta, użyto jej jednak po to, aby zobaczyć (zatrzymać) efekt działania programu na konsoli. Po naciśnięciu klawisza Enter (procedura odczyta ten fakt) program przejdzie dalej, czyli zakończy pracę. Będzie to bardzo istotne, gdy skompilowany program zechcemy uruchamiać z poziomu środowiska Windows, a natychmiast po skończeniu działania programu środowisko będzie zamykało okno konsoli. C — do wyświetlania tekstów stosujemy funkcję printf, a to wymaga włączenia informacji o standardowej bibliotece #include . Łańcuch znaków jest ciągiem znaków ujętych w cudzysłowy. Po wyświetleniu tekstu kursor zostanie ustawiony za ostatnim znakiem. Przejście do nowego wiersza można wywołać, wstawiając w łańcuchu sekwencję \n (znak nowego wiersza, ang. newline). #include #include int main() { printf("Hello World!\n"); system("pause"); return 0; }

C++ — można wyświetlać tekst analogicznie jak w języku C, informacje o standardowej bibliotece włączamy dyrektywą #include . Reszta kodu pozostanie bez zmian.

35 35 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 1. Podstawowe pojęcia, czyli mały elementarz… My zastosujemy jednak strumień wyjściowy znaków cout (ang. character output stream) występujący w standardowej przestrzeni nazw (std) i wymagający użycia dyrektyw #include . Symbol > a; LI Db! ^

C++

 2EOLF]HQLDEÚGÈZ\NRQDQHW\ONRZWHG\JG\ZDUWRĂÊDbMHVWGRGDWQLD 

pole = a*a; cout > a; `ZKLOH Db  

Wewnątrz podanych instrukcji cyklu możemy wstawić (po instrukcji odczytującej wartość zmiennej z klawiatury) instrukcje warunkowe wyświetlające komunikaty o błędzie (FTP: p2_5a.pas, p2_5a.c i p2_5a.cpp). Przykłady te (zamieszczone na serwerze FTP) podano wyłącznie „z kronikarskiego obowiązku”, ponieważ zrealizowano w nich pętle przy użyciu etykiet i skoku goto13. Skoro znamy już pętle, to spróbujmy zastosować je do wielokrotnego powtarzania obliczeń. 3U]\NáDG Wielokrotnie obliczamy pole powierzchni i obwód koła o danym promieniu. Przyjmijmy, że wprowadzenie długości promienia 0 będzie sygnałem do zrezygnowania z dalszych obliczeń. We wzorach (S π r 2 — pole koła o promieniu r, L 2π r — obwód koła, długość okręgu o promieniu r) występuje liczba π , której w przyszłości poświęcimy nieco uwagi. Teraz do obliczeń przyjmiemy π 3,14159 (2π 6,28319). Zabezpieczając program przed wprowadzaniem błędnych danych (ujemna wartość promienia), pozwolimy na wpisywanie liczb nieujemnych (przypomnijmy umowę o zerze sygnalizującym rezygnowanie z dalszych obliczeń). Promień zadeklarujemy jako liczbę zmiennoprzecinkową (typ real w Pascalu, typ double w C i C++). Wczytywanie danych, obliczenia i wyświetlenie wyniku można zrealizować tak:

13

Przykłady zawarto w plikach p_2_5x.pas, p_2_5y.pas, p_2_5x.c, p_2_5y.c na FTP. Przykłady te w swojej realizacji odbiegają od schematu, gdyż wyjście z pętli następuje pomiędzy czytaniem danych a wyświetlaniem ewentualnego komunikatu o błędzie. Zaoszczędzono w ten sposób jedną instrukcję warunkową, co jednak wcale nie daje czytelniejszego kodu. W przykładach nie korzystano z instrukcji typu if… then… else…, gdyż to w zupełności nie pasowałoby do stylu z epoki skoków… Przykładów w języku C++ nie zrealizowano — to byłoby zdecydowanie wbrew intencjom autorów języka (chociaż działałyby one poprawnie).

55 55 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 2. Proste obliczenia — pola i obwody figur geometrycznych

Pascal

repeat ZULWH 3RGDMGïXJRĂÊSURPLHQLDU   readln(r); if r < 0 then ZULWHOQ %ïÈG3URPLHñQLHPRĝHE\ÊXMHPQ\  until r >= 0; if r > 0 then begin ZULWHOQ 3ROHNRïD6   U U  ZULWHOQ 2EZöGNRïD/   U  end;

C, C++

do { SULQWI 3RGDMGïXJRĂÊSURPLHQLDU   scanf("%lf", &r); if (r < 0) SULQWI %ïÈG3URPLHñQLHPRĝHE\ÊXMHPQ\?Q  } while (r < 0); if (r > 0) { SULQWI 3ROHNRïD6 OI?Q U U  SULQWI 2EZöGNRïD/ OI?Q U  }

C++

do { FRXW3RGDMGïXJRĂÊSURPLHQLDU   cin >> r; if (r < 0) FRXW%ïÈG3URPLHñQLHPRĝHE\ÊXMHPQ\?Q } while (r < 0); if (r > 0) { FRXW3ROHNRïD6  U UHQGO FRXW2EZöGNRïD/  UHQGO }

Użyte w przykładzie (C++) endl jest tzw. manipulatorem — wstawia do strumienia wyjściowego cout znak \n i opróżnia strumień. We wszystkich trzech wersjach kodu możemy zobaczyć dwa etapy: ■ ■

wczytywanie wartości nieujemnej długości promienia koła; wykonywanie obliczeń wyłącznie dla dodatniej wartości promienia.

Sytuacja, gdy promień przyjmie wartość 0, została dopuszczona i stanowi ona sygnał do zakończenia obliczeń. Wystarczy powyższy kod wstawić jako instrukcję złożoną do pętli. Resztę programu pozostawiono do zrealizowania Czytelnikowi (FTP: p2_6.pas, p2_6.c i p2_6.cpp).

56 56 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Pętle, czyli powtarzanie sekwencji wykonywanych czynności repeat

Pascal

^&]\WDQLHGDQ\FKREOLF]HQLDLbZ\ĂZLHWODQLHZ\QLNöZ`

XQWLOU ^NRQLHFREOLF]HñJG\UMHVWUöZQH` do {

C, C++

 &]\WDQLHGDQ\FKREOLF]HQLDLbZ\ĂZLHWODQLHZ\QLNöZ 

} while (r > 0);  .RQLHFREOLF]HñJG\UMHVWUöZQH 

W Pascalu w module System zdefiniowana jest bezparametrowa funkcja Pi: real, która zwraca wartość 3,1415926535897932. Możliwe jest samodzielne zdefiniowanie stałej pi o innej liczbie cyfr po przecinku, jeśli precyzja naszych obliczeń jest mniejsza. ^'HNODUDFMD`

const pi = 3.14159;

Pascal

^2EOLF]HQLD`

ZULWHOQ 3ROHNRïD6  SL U U  ZULWHOQ 2EZöGNRïD/   SL U 

W takim przypadku zdefiniowana przez nas nazwa pi przysłoni nazwę pi z modułu system. Możliwość korzystania z obu obiektów (funkcji z modułu i zdefiniowanej stałej) pokazuje prosty program:

Program (Pascal)

Wyniki

const pi = 3.14159; ^'HğQLFMDVWDïHM` begin writeln(pi); ^:DUWRĂÊ]GHğQLRZDQHMVWDïHM` writeln(system.pi); ^:DUWRĂÊSL]bPRGXïXV\VWHP` end. 3.1415900000000000E+0000 3.1415926535897932E+0000

Można też zamiast stałej zdefiniować własną wersję funkcji pi. Szczegółowo o definiowaniu funkcji powiemy w dalszej części książki, teraz tylko zobaczmy przykład:

Program (Pascal)

function pi: real;^'HğQLFMDIXQNFML` begin pi := 3.14159; ^:\QLN]ZUDFDP\SU]H]QD]ZÚIXQNFML` end; begin writeln(pi); ^:DUWRĂÊ]ZUöFRQDSU]H]Z\ZRïDQÈIXQNFMÚ` writeln(system.pi); ^:DUWRĂÊSL]bPRGXïXV\VWHP` end.

57 57 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 2. Proste obliczenia — pola i obwody figur geometrycznych

Wyniki

3.1415900000000000E+0000 3.1415926535897932E+0000

W standardowych bibliotekach C i C++ nie zdefiniowano ani stałej pi, ani funkcji takiej jak w Pascalu14. Zróbmy to sami, wzorując się na poprzednich przykładach. #include const double pi = 3.14159265;  'HğQLFMDVWDïHMSLRb]DVLÚJXJOREDOQ\P  int main() { printf("%0.8lf\n", pi); return 0; }

Można też postąpić inaczej. Zdefiniujemy makrodefinicję (za pomocą dyrektywy preprocesora: GHğQHĮ). Na tej podstawie wszystkie wystąpienia symbolu PI w kodzie źródłowym (tuż przed kompilacją) zostaną zastąpione stałą wartością 3.14159265. Zmiana obowiązuje tylko na czas kompilacji — zapisany kod źródłowy nadal będzie zawierał symbole PI (nazwę makra zapisujemy wielkimi literami — taki zwyczaj, nie obowiązek). #include  'HğQLRZDQLHPDNUD 

GHğQH3, int main() { printf("%0.8lf\n", PI); return 0; }

Można również zdefiniować bezparametrową funkcję, która zwróci przybliżoną wartość liczby π (wynik typu double). Należy zwrócić uwagę na sposób wywołania w języku C tej funkcji — mimo że nie przekazujemy parametrów, nawiasy okrągłe są niezbędne: pi(). Zapisanie pi bez nawiasów spowoduje zwrócenie adresu funkcji (wskaźnika), a adres ten na dodatek zostanie błędnie zinterpretowany (adres jest liczbą całkowitą, a specyfikator %lf służy do wyświetlania liczb zmiennoprzecinkowych). #include 14 W niektórych plikach nagłówkowych jest zdefiniowana stała M_PI — popularne podręczniki na ten temat milczą. Czytelnik może samodzielnie sprawdzić, czy w środowisku, którego używa, ta stała jest zdefiniowana.

58 58 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Porównania, operatory logiczne i budowanie warunków złożonych  =GHğQLRZDQLHIXQNFML]ZUDFDMÈFHMZDUWRĂÊSL 

double pi() { return 3.14159265; } int main() { printf("%0.8lf\n", pi()); return 0; }

Przytoczone w języku C przykłady bez problemu można zrealizować w C++.

Porównania, operatory logiczne i budowanie warunków złożonych Mamy cztery operatory porównania o tym samym priorytecie: < — jest mniejsze od, — jest większe od i >= — jest większe lub równe (dostępne w Pascalu, C, C++ i innych językach). W wyniku działania tych operatorów otrzymamy wartość logiczną false lub true (Pascal) albo liczbę typu całkowitego 0 lub 1 (C, C++). 3U]\NáDG Porównujemy dwie wybrane liczby przy użyciu wszystkich operatorów porównania i wyświetlamy wynik porównania na ekranie.

Pascal

var a, b: Real; begin Db E  ZULWHOQ Db  D E  E  ZULWHOQ DbMHVWPQLHMV]HRGE DbE  ZULWHOQ DbMHVWPQLHMV]HOXEUöZQHE Db E  ZULWHOQ DbMHVWZLÚNV]HRGE Db!E  ZULWHOQ DbMHVWZLÚNV]HOXEUöZQHE Db! E  readln; end.

59 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 2. Proste obliczenia — pola i obwody figur geometrycznych

C, C++

C++

#include #include int main() { system("chcp 1250"); GRXEOHDb E  SULQWI Db OIE OI?QDE  SULQWI DbMHVWPQLHMV]HRGE?W?WG?QDbE  SULQWI DbMHVWPQLHMV]HOXEUöZQHE?WG?QDb E  SULQWI DbMHVWZLÚNV]HRGE?W?WG?QDb!E  SULQWI DbMHVWZLÚNV]HOXEUöZQHE?WG?QDb! E  system("pause"); return 0; } #include using namespace std; int main() { system("chcp 1250"); GRXEOHDb E  FRXWDb DbE EHQGO FRXWDbMHVWPQLHMV]HRGE?W?W DbE HQGO FRXWDbMHVWPQLHMV]HOXEUöZQHE?W Db E  ´endl; FRXWDbMHVWZLÚNV]HRGE?W?W Db!E HQGO FRXWDbMHVWZLÚNV]HOXEUöZQHE?W Db! E  ´endl; system("pause"); return 0; }

Do równego ustawienia wyników porównań (C, C++) użyto kolejnego znaku specjalnego — tabulatora \t. Ponadto podczas wstawiania do strumienia cout wyniku porównania należało użyć nawiasów, bo operator 0) { pole = sqrt(pole);  'RNRñF]HQLHREOLF]DQLDSRODWUöMNÈWD  SULQWI 3ROHWUöMNÈWD6 OI?QSROH  `HOVHSULQWI %ïÈG3RGDQHOLF]E\QLHVÈGïXJRĂFLDPLERNöZ ´WUöMNÈWD?Q  p = (a+b+c)/2; pole = p*(p-a)*(p-b)*(p-c);  7RHWDSSRĂUHGQLREOLF]HñĮ  if (pole > 0) { pole = sqrt(pole);  'RNRñF]HQLHREOLF]DQLDSRODWUöMNÈWD  FRXW3ROHWUöMNÈWD6 SROHHQGO `HOVHFRXW%ïÈG3RGDQHOLF]E\QLHVÈGïXJRĂFLDPLERNöZ ´WUöMNÈWD?Q

Kompletne kody programów umieszczono na serwerze FTP: p2_10a.pas, p2_10a.c i p2_10.cpp.

69 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 2. Proste obliczenia — pola i obwody figur geometrycznych

Stosowanie wybranych funkcji matematycznych i definiowanie własnych funkcji Szczegółowe omówienie funkcji nastąpi w rozdziale 4. Pojęcie funkcji w językach programowania nieco odbiega od jego matematycznej definicji. Funkcja (w języku programowania) może nie mieć żadnych argumentów i zwracać stałą wartość (w Pascalu bezparametrowa funkcja Pi: Real zwraca przybliżenie liczby π ) lub informację o stanie systemu (MemAvail: LongInt — zwraca ilość pamięci dostępnej dla programu w systemie). Ponadto funkcje w językach programowania mogą wykonywać różne inne czynności na danych (efekt zamierzony przez programistę lub uboczny) i zwracać wynik. Zwrócony wynik w takiej sytuacji może być informacją o efekcie działania funkcji. W Pascalu mamy wyraźny podział na funkcje i procedury, np.: Funkcja sqrt(zmienna: Real): Real — oblicza i zwraca pierwiastek kwadratowy (i to jest w pełni zgodne z matematycznym rozumieniem pojęcia funkcji). Procedura readln(a) — czyta ze standardowego strumienia wejściowego dane (najczęściej z klawiatury) i nadaje zmiennej a odczytaną wartość (ewentualny błąd sygnalizowany jest w zmiennej IOResult lub powoduje przerwanie pracy programu). W języku C (w C++ również) nie rozróżniamy procedur i funkcji — wszystkie tego typu obiekty nazywamy funkcjami. Funkcje, które nie zwracają żadnych wartości (odpowiednik procedur Pascala), definiujemy jako zwracające typ void (pusty typ danych). Możemy zatem mówić o różnych przypadkach funkcji: Funkcja double sqrt (double x); — oblicza i zwraca pierwiastek kwadratowy. Funkcja int scanf (const char * format, …);18 — czyta formatowane dane ze standardowego strumienia stdin, zwrócona wartość jest liczbą przeczytanych poprawnie wartości zmiennych podczas jednego wywołania funkcji lub zerem, gdy czytanie się nie powiodło. Funkcja niezwracająca wartości (zdefiniowana przez użytkownika): void komunikat() {printf("To jest komunikat\n");}19. Do dobrego stylu programowania należy umiejętność dzielenia bardziej obszernego problemu na mniejsze elementarne problemy, tworzenia funkcji (procedur) rozwiązujących te problemy i składania rozwiązania z odpowiednich wywołań tych funkcji (procedur). Po raz kolejny rozwiążemy zadanie z przykładu 2.4. 3U]\NáDG Najpierw zbudujemy kilka funkcji, z których następnie utworzymy rozwiązanie zadania. 18 19

W taki sposób w dokumentacji podaje się definicję funkcji. Odpowiednikiem tej funkcji w Pascalu byłaby bezparametrowa procedura:

procedure komunikat; begin writeln('To jest komunikat'); end;

70 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Stosowanie wybranych funkcji matematycznych i definiowanie własnych funkcji Funkcja CzytajBok będzie miała za zadanie pobranie długości boku (liczby dodatniej) z klawiatury, a zwrócona wartość zostanie przypisana odpowiedniej zmiennej. Parametrem wywołania będzie znak, którym symbolicznie oznaczamy wczytywaną wartość. Funkcja CzyTrojkat20 zbada, czy trójkąt o podanych bokach istnieje — zwróci odpowiednią wartość logiczną. Parametrami wywołania będą trzy dodatnie liczby (funkcja tego faktu nie będzie sprawdzać, bo to zagwarantuje nam funkcja CzytajBok. Funkcja WzorHerona obliczy i zwróci pole powierzchni trójkąta lub 0, w przypadku gdy trójkąt nie istnieje albo dane są błędne (np. ujemne). Przy okazji definiowania funkcji CzytajBok poznajemy nowy typ danych char służący do przechowywania pojedynczych znaków. Stałe znakowe tego typu zapisujemy, ujmując znak (literę, cyfrę lub inny znak) między dwa apostrofy. Wywołanie funkcji (niezależnie od języka) będzie wyglądało dokładnie tak samo, np. CzytajBok('a') — na ekranie pojawi się napis Podaj długość boku a =, program zaczeka na wprowadzenie liczby z klawiatury i naciśnięcie Enter. Czynności te umieszczone są w pętli i będą ewentualnie powtarzane do czasu wprowadzenia liczby dodatniej (podanie niedodatniej wartości spowoduje wyświetlenie komunikatu błędu: Błąd! Długość boku ma być liczbą dodatnią!). Poprawnie wpisana wartość zostanie zwrócona jako rezultat funkcji.

Pascal

C

20

function CzytajBok(opis: char): real; var x: real; begin repeat ZULWH 3RGDMGïXJRĂÊERNX RSLV    readln(x); if x x if (x 0 then begin ^ğ DUVLQK TĮ LWG`

end else ^3R]RVWDïZDULDQWS` begin if wD > 0 then begin ^ğ DUFRVK TĮ LWG`

end else ^3U]\SDGHNZ' ` begin ^ğ DFRV TĮ LWG`

end end

C, C++

Jak wyżej, ale w stylu języka C, C++.

Przykładowy program (zawarty w pliku na FTP: p_3_9.pas, p_3_9.c i p_3_9.cpp) napisany jest zgodnie z powyższym schematem i wzorami w tabelach. Można zdecydowanie zmniejszyć liczbę wykonywanych obliczeń przez wprowadzenie dodatkowych zmiennych, modyfikację wzorów itp. Zadanie to jednak pozostawiamy Czytelnikowi. 96 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozwiązywanie równań wyższych stopni Możemy też zacząć od rozważenia możliwych przypadków dla wyróżnika D (C, C++) lub wD w Pascalu (co przedstawione jest w plikach FTP: p_3_9a.pas, p_3_9a.c i p_3_9a.cpp). if wD = 0 then begin if p = 0 then {q = 0} begin ^3LHUZLDVWHNSRWUöMQ\[ `

end else^SĽLQDF]HME\ÊQLHPRĝH` begin ^ğ DUFFRV TĮ LWG`

Pascal

end end else if wD > 0 then if p > 0 then begin ^ğ DUVLQK TĮ LWG`

end else if p < 0 then begin

^ğ DUFRVK TĮ LWG`

end else^3R]RVWDïZDULDQWS ` begin ^3LHUZLDVWHNU]HF]\ZLVW\LbGZD]HVSRORQH`

end else ^3U]\SDGHNZ' ` begin ^ğ DUFFRV TĮ LWG`

end

C, C++

Jak wyżej, ale w stylu języka C.

Należy zwrócić uwagę na porównywanie wyróżnika D (wD w Pascalu) z zerem (podobnie dla p). O ile program zapisany w Pascalu (FTP: p_3_9a.pas) dla równania x 3 3x 2 3x 1 0 popraw1, to program zapisany w C lub C++ (FTP: p_3_9a.c nie wyznaczał pierwiastek potrójny x i p_3_9a.cpp) podawał błędny wynik. Zastąpienie w instrukcji warunkowej if (D == 0) { if (p = 0) { /*q = 0*/ relacji porównania warunkiem if (fabs(D) < 1e-15) { if (fabs(p) < 1e-15) { /*q = 0*/ poprawi działanie programu. W obliczeniach zmiennoprzecinkowych wartości, które teoretycznie powinny być równe 0, okazały się liczbami nieznacznie od zera różnymi. O tym fakcie musimy pamiętać w niektórych przypadkach.

97 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 3. Podejmowanie decyzji, czyli nieco więcej o instrukcjach warunkowych 3U]\NáDG Rozwiązanie równania stopnia czwartego w postaci ogólnej możemy zrealizować, korzystając ze sposobu zaczerpniętego z poradnika matematycznego8: Pierwiastki równania x 4 bx 3 cx 2 dx e

0 (a

1) pokrywają się z pierwiastkami dwóch rów-

x by d y 0, gdzie A 2 A 3 2 pierwiastkiem rzeczywistym równania sześciennego 8y 4cy nań kwadratowych x 2

b A

8y b

2

4c 9, a y jest dowolnym

2bd 8e y e 4c b

2

d

2

0.

Realizując ten algorytm, wygodnie byłoby napisać (na podstawie przykładów 3.3 i 3.9) podprogramy (funkcje, procedury) odpowiedzialne za rozwiązanie równania kwadratowego i sześciennego, a następnie zastosować je wg powyższego opisu. O procedurach powiemy więcej w rozdziale 6. Częściowe rozwiązanie równania czwartego stopnia znajduje się w plikach na serwerze FTP: p3_10.pas, p3_10.c i p3_10.cpp.

Wybór jednej z wielu opcji… Przy podejmowaniu decyzji w programie wygodna może być instrukcja wyboru (nazywana też instrukcją decyzyjną). Umożliwia ona wybór instrukcji do wykonania spośród wielu opcji. Przypuśćmy, że mamy pewną liczbę (n) warunków do sprawdzenia (Z, Z, …, ZQ) i warunki te nawzajem się wykluczają. Jeśli któryś z tych warunków jest spełniony, wykonujemy odpowiadającą mu instrukcję (LQVWUXNFMDB, LQVWUXNFMDB, …, LQVWUXNFMDBQ). Zadanie to możemy wykonać, stosując wielokrotne powtórzenie instrukcji warunkowych: Pascal if Z thenLQVWUXNFMDB; ifZthenLQVWUXNFMDB; ... ifZQthen LQVWUXNFMDBQ;

C lub C++ if (Z) LQVWUXNFMDB; if (Z) LQVWUXNFMDB; ... if (ZQ) LQVWUXNFMDBQ;

Ten sam efekt da wielokrotne zagnieżdżanie instrukcji warunkowej else if ... Pascal if Z then else if Z else if ... else if ZQ

LQVWUXNFMDB

then LQVWUXNFMDB ... then LQVWUXNFMDBQ;

8

C lub C++ if (Z) LQVWUXNFMDB; else if (Z) LQVWUXNFMDB; else if … ... else if (ZQ) LQVWUXNFMDBQ;

Bronsztejn I.N., Siemiendiajew K.A., Matematyka. Poradnik encyklopedyczny, PWN, Warszawa 1970, s. 173 – 174. 2 Niestety może się okazać, że 8y b 4c < 0, więc A jest liczbą zespoloną i nie będziemy umieli rozwiązać (w danej chwili) równania kwadratowego o współczynnikach zespolonych. 9

98 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Wybór jednej z wielu opcji… Możemy też opcjonalnie, gdy żaden z warunków nie będzie spełniony, wykonać instrukcję domyślną (LQVWUXNFMDBG). W pierwszym z omawianych przypadków wymagałoby to zbudowania złożonego warunku wykluczającego spełnienie któregokolwiek z warunków: Z, Z, …, ZQ. Pascal

C lub C++

if not(ZRUZRUĮRUZQ  thenLQVWUXNFMDBG lub

if !(Z__Z__Į__ZQ LQVWUXNFMDBG

lub

if not(Z) and not(Z) and … and ´not(ZQ) thenLQVWUXNFMDBG;

if (!(Z   Z Į  ZQ LQVWUXNFMDBG

W drugim przypadku wystarczy dopisać na końcu kod else LQVWUXNFMDBG; — należy przy tym pamiętać o odmiennych wymaganiach (zależnie od języka) w zakresie użycia znaku średnika przed słowem kluczowym else. Pascal else if ZQthenLQVWUXNFMDBQ elseLQVWUXNFMDBG;

C lub C++ else if (ZQ)LQVWUXNFMDBQ; elseLQVWUXNFMDBG;

Może się zdarzyć, że omawiane warunki będą miały postać relacji porównania: Z\UDĝHQLH = ZDUWRĂÊBL(dla i = 1, 2, …, n) oraz wartości te będą stałymi typu porządkowego, czyli liczbami całkowitymi lub znakami (w tym przypadku porównywane są kody znaków). W takiej sytuacji mamy do dyspozycji instrukcję wyboru. Pascal

case Z\UDĝHQLHRI ZDUWRĂÊB: LQVWUXNFMDB; ZDUWRĂÊB: LQVWUXNFMDB;

... ZDUWRĂÊBQ: LQVWUXNFMDBQ;

else LQVWUXNFMDBG;

end;

C lub C++ switch (Z\UDĝHQLH) { caseZDUWRĂÊB: LQVWUXNFMDB; break; FDVHZDUWRĂÊB: LQVWUXNFMDB; break; ... caseZDUWRĂÊBQ: LQVWUXNFMDBQ; break; default: LQVWUXNFMDBG; }

Użycie instrukcji domyślnej jest w obu przypadkach opcjonalne (można pominąć else LQVWUXNFMDBG; lub default: LQVWUXNFMDBG;). Zwróćmy uwagę (w Pascalu) na użycie słowa kluczowego end w parze ze słowem kluczowym case, bez słowa begin. 99 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 3. Podejmowanie decyzji, czyli nieco więcej o instrukcjach warunkowych Należy też wyjaśnić rolę słowa kluczowego break w instrukcji switch (w języku C, C++). Jeśli np. Z\UDĝHQLH przyjmie ZDUWRĂÊB i wykona się LQVWUXNFMDB, to występujące po niej słowo kluczowe break spowoduje przerwanie dalszego działania instrukcji switch i przejście do następnej (po instrukcji switch) instrukcji programu. Brak słowa break w tym miejscu spowoduje wykonywanie dalszych instrukcji: LQVWUXNFMDB, LQVWUXNFMDB… aż do końca lub pojawienia się słowa break. Zatem świadome pominięcie słowa break może być w pewnych sytuacjach pożyteczne. Jest też możliwe przypisanie jednej instrukcji do kilku wartości wyrażenia, czyli stworzenie listy lub zakresu wartości odpowiadających danej instrukcji (w Pascalu) albo powtórzenie słowa case (w C, C++). Najlepiej zilustruje to fragment przykładowego kodu. Pascal

case wyr of 1, 3, 4: writeln('Opcja 1, 3, 4'); 2: writeln('Opcja 2'); 5..7, 11: writeln('Opcja 5, 6, 7, 11'); end;

C lub C++ switch (wyr) { case 1: case 3: case 4: printf("Opcja 1, 3, 4\n"); break; case 2: printf("Opcja 2\n"); break; case 5: case 6: case 7: case ´11: printf("Opcja 5, 6, 7, ´11\n"); break; }

3U]\NáDG Napiszmy prosty program, który na podstawie numeru miesiąca (zmienna typu całkowitego mc) napisze nazwę tego miesiąca lub poinformuje, że taki miesiąc nie istnieje. Numer miesiąca użytkownik wprowadzi z klawiatury. Zamianę liczby na nazwę miesiąca i wyświetlenie odpowiedzi wykonamy za pomocą instrukcji wyboru (FTP: p3_11.pas, p3_11.c, p3_11.cpp). case mc of ZULWH VW\F]Hñ  2: write('luty'); 3: write('marzec');

Pascal

^LbWDNGDOHM`

ZULWH JUXG]LHñ  else ZULWHOQ 7DNLPLHVLÈFQLHLVWQLHMH PF   end;

100 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Wybór jednej z wielu opcji…

C

switch (mc) { case 1: SULQWI VW\F]Hñ  break; case 2: printf("luty"); break;  LbWDNGDOHM 

case 12: SULQWI JUXG]LHñ  break; default: SULQWI 7DNLPLHVLÈFQLHLVWQLHMHG?QPF  }

C++

Jw., z ewentualną zmianą sposobu wyprowadzania wyników.

3U]\NáDG Napiszmy program, który na podstawie numeru miesiąca i roku (zmienne typu całkowitego mc i rok) napisze, ile dni ma dany miesiąc, lub poinformuje, że taki miesiąc nie istnieje. Dane wprowadzimy z klawiatury.

Pascal

case mc of 1, 3, 5, 7, 8, 10, 12: ile_dni := 31; 2: if ((rok mod 4 = 0) and (rok mod 100 0)) or (rok mod ´400 = 0) then ile_dni := 29 else ile_dni := 28; 4, 6, 9, 11: ile_dni := 30; else begin ZULWHOQ 1LHLVWQLHMHPLHVLÈFRbSRGDQ\PQXPHU]H PF ´'!'); ile_dni := 0; end; end; LILOHBGQL!WKHQZULWHOQ 7HQPLHVLÈFPD LOHBGQL ´'dni.');

101 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 3. Podejmowanie decyzji, czyli nieco więcej o instrukcjach warunkowych

switch (mc) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: ile_dni = 31; break; case 2: if (((rok%4 == 0) && (rok%100 != 0)) || (rok%400 == ´0)) ile_dni = 29; else ile_dni = 28; break; case 4: case 6: case 9: case 11: ile_dni = 30; break; default: { SULQWI 1LHLVWQLHMHPLHVLÈFRbSRGDQ\PQXPHU]H ´%d!\n", mc); ile_dni = 0; } } if (ile_dni != 0) SULQWI 7HQPLHVLÈFPDGGQL?QLOHBGQL 

C

C++

Jw., z ewentualną zmianą sposobu wyprowadzania wyników.

Wybór liczby dni w miesiącu nie wymaga komentarza. Natomiast na uwagę zasługuje sposób ustalenia liczby dni w lutym, która zależy od tego, czy dany rok jest rokiem zwykłym (28 dni), czy przestępnym (29) dni. Rok jest przestępny, gdy argument rok jest podzielny przez 4 i nie jest podzielny przez 100 lub jest podzielny przez 400. Przykładowo lata 2000 i 2008 były przestępne, ale rok 1900 nie był przestępny i 2100 też nie będzie przestępny. Przypomnijmy, operatory mod (Pascal) i % (C, C++) służą do obliczania reszty z dzielenia. Zatem rok jest przestępny wtedy, gdy wyrażenie ((rok mod 4 = 0) and (rok mod 100 0)) or (rok mod 400 = 0) (Pascal), ((rok%4 == 0) && (rok%100 != 0)) || (rok%400 == 0) (C, C++) lub ((rok%4 == 0) and (rok%100 != 0)) or (rok%400 == 0) (C++). Operatory and i or są również dostępne w C po włączeniu pliku nagłówkowego #include (wprowadzonego do biblioteki standardowej w specyfikacji C90). Pełne kody programu znajdują się na FTP: p3_12. pas, p3_12.c i p3_12.cpp. 3U]\NáDG Zdefiniujmy funkcję sgn (łac. signum — znak):

sgn x

1, dla x < 0 0, dla x 0 1, dla x 0

102 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Wybór jednej z wielu opcji…

Pascal

C, C++

function sgn(x: real): integer; begin if x < 0 then sgn := -1 else if x = 0 then sgn := 0 else sgn := 1; end; int sgn(double x) { if (x < 0) return -1; else if (x == 0) return 0; else return 1; } int sgn(double x) { return (x > 0)-(x < 0); } int sgn(double x) { return (x < 0) ? -1 : (x > 0) ? 1 : 0; }

Przykład w Pascalu i pierwszy przykład w C/C++ nie wymagają dodatkowego komentarza. W języku C/C++ możemy skorzystać z faktu, że wartości wyrażeń logicznych są liczbami całkowitymi (1 — prawda, 0 — fałsz) i można na nich wykonywać operacje arytmetyczne. W tabeli 3.8 przedstawiono analizę wartości wyrażenia (x > 0)-(x < 0), które odpowiada wartości funkcji sgn(x). Tabela 3.8.  Analiza wartości wyrażenia (x > 0) – (x < 0) w zależności od znaku argumentu x

Wartość wyrażenia Wartość x

[!

[

[! ± [

x jest ujemne

0

1

–1

x jest równe 0

0

0

0

x jest dodatnie

1

0

1

Kolejne rozwiązanie opiera się na użyciu operatora warunkowego. Operator wyrażenia warunkowego ? (C/C++) przyjmuje trzy argumenty: a?b:c. Najpierw oceniana jest wartość logiczna wyrażenia a; jeśli jest ono prawdziwe, zwracana jest wartość wyrażenia b, natomiast jeśli wyrażenie a nie jest prawdziwe, zwracana jest wartość wyrażenia c.

103 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 3. Podejmowanie decyzji, czyli nieco więcej o instrukcjach warunkowych Zatem wyrażenie (x > 0)?1:0 przyjmuje wartość 1 dla liczby x dodatniej i wartość 0 dla x niedodatniego (ujemne lub 0). To wyrażenie zostało zagnieżdżone w kolejnym wyrażeniu warunkowym. Obliczając wartość wyrażenia (x < 0)?-1:(x > 0)?1:0, otrzymujemy wynik -1 dla x ujemnego (i wtedy wartość (x > 0)?1:0 nie jest obliczana), natomiast dla x nieujemnego obliczana jest wartość wyrażenia (x > 0)?1:0, która wynosi 1 dla x dodatniego i 0 dla x równego zero. Otrzymane rezultaty odpowiadają definicji funkcji signum. Funkcję signum można zdefiniować również inaczej:

0, dla x 0 x , dla x 0 x

sgn x

Pascal

C, C++

function sgn(x: real): integer; begin if x = 0 then sgn := 0 else sgn := round(x/abs(x)); end; int sgn(double x) { if (x == 0) return 0; else return (int)(x/fabs(x)); } int sgn(double x) { return (x == 0)?0:(int)(x/fabs(x)); }

Funkcja round(x/abs(x)) dokonuje zamiany (zaokrąglenia) zmiennoprzecinkowej wartości wyrażenia x/abs(x) na wartość całkowitą (Pascal). Analogicznie w wyrażeniu (int)(x/ fabs(x)) następuje rzutowanie (konwersja) wartości zmiennoprzecinkowej na wartość typu całkowitego (C, C++). Przykłady działania funkcji sgn(x) zawarto w plikach zamieszczonych na FTP: p_3_13.pas, p_3_13.c i p_13.cpp. 3U]\NáDG Zastosowanie funkcji sgn(x) oraz instrukcji wyboru pokażemy na przykładzie rozwiązywania równania kwadratowego (omówione szczegółowo w przykładzie 3.5). Zamiast instrukcji warunkowych i relacji porównujących wyróżnik (delta) z zerem, zastosujemy wybór sposobu obliczania pierwiastków przy użyciu instrukcji wyboru na podstawie znaku wyrażenia sgn(delta). 104 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Wybór jednej z wielu opcji…

Pascal

delta := b*b-4*a*c; case sgn(delta) of 1: begin x1 := (-b-sqrt(delta))/(2*a); x2 := (-b+sqrt(delta))/(2*a); writeln('Równanie ma dwa pierwiastki rzeczywiste:'); writeln('x = ', x1:0:4, #10, 'x = ', x2:0:4); end; 0: begin x1 := -b/(2*a); write('Równanie ma pierwiastek rzeczywisty'); '); writeln(dwukrotny: 'x = ', x1:0:4); end; -1: begin re := -b/(2*a); im := abs(sqrt(-delta)/(2*a)); writeln('Równanie ma dwa pierwiastki zespolone:'); writeln('x = ', re:0:4, ' - ', im:0:4, 'i'); writeln('x = ', re:0:4, ' + ', im:0:4, 'i'); end; end;

C, C++

delta = b*b-4*a*c; switch (sgn(delta)) { case 1: x1 = (-b-sqrt(delta))/(2*a); x2 = (-b+sqrt(delta))/(2*a); printf("Dwa pierwiastki rzeczywiste:\n"); printf("x = %0.4lf\nx = %0.4lf\n", x1, x2); break; case 0: x1 = -b/(2*a); printf("Pierwiastek rzeczywisty dwukrotny: "); printf("x = %0.4lf\n", x1); break; case -1: re = -b/(2*a); im = fabs(sqrt(-delta)/(2*a)); printf("Dwa pierwiastki zespolone:\n"); SULQWI [ OIOğ?QUHLP  SULQWI [ OIOğ?QUHLP  break; }

C++

Jw., z ewentualną zmianą sposobu wyświetlania wyników.

105 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 3. Podejmowanie decyzji, czyli nieco więcej o instrukcjach warunkowych Pełne kody programów umieszczono na FTP: p_3_14.pas, p_3_14.c i p_14.cpp. Wszystkie użyte w przytoczonym kodzie zmienne (C) muszą być zadeklarowane przed wywołaniem funkcji switch. Można to ograniczenie ominąć poprzez utworzenie bloków z instrukcji odpowiadających danej opcji i deklarowanie zmiennych lokalnie wewnątrz bloku (o ile z tych wartości nie będziemy chcieli skorzystać w dalszej części programu): case -1: { double re = -b/(2*a); double im = fabs(sqrt(-delta)/(2*a)); printf("Dwa pierwiastki zespolone:\n"); SULQWI [ OIOğ?QUHLP  SULQWI [ OIOğ?QUHLP  break; }

Nie ma znaczenia, czy instrukcja break znajdzie się w bloku, czy poza nim (przykład na FTP p3_14a.c). Tak napisany (z instrukcją selekcji) kod programu wydaje się bardziej czytelny. Zastosowanie podobnej metody przy większej liczbie opcji powinno być jeszcze bardziej korzystne. Pozostaje tylko kwestia zbudowania odpowiedniej funkcji o większym zbiorze wartości, która zastąpi funkcję signum.

Dialog programu z użytkownikiem — dane tekstowe Podczas pracy z programem użytkownik nie tylko wprowadza dane (w naszych przykładach są to zwykle liczby), ale czasem musi podjąć decyzję o dalszym przebiegu programu. Program wyświetla pytanie i oczekuje odpowiedzi w postaci naciśnięcia odpowiedniego klawisza lub wpisania słowa i naciśnięcia klawisza Enter. 3U]\NáDG Program zadaje pytanie, na które można odpowiedzieć pozytywnie (tak, ang. yes) lub odmownie (nie, ang. no). Zazwyczaj oczekiwaną odpowiedzią jest naciśnięcie klawisza T lub N (czasem Y lub N) i zaakceptowanie tej odpowiedzi naciśnięciem klawisza Enter. Deklarujemy zmienną odp (odpowiedź) typu char — pojedynczy znak tekstowy (litera, cyfra lub inny znak, w tym znak sterujący). Odczytujemy ten znak ze standardowego strumienia danych i porównujemy z oczekiwaną odpowiedzią. Następnie przy użyciu instrukcji warunkowych podejmujemy odpowiednie decyzje — w naszych przykładach są to tylko słowne komunikaty opisujące wybraną opcję. 106 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Dialog programu z użytkownikiem — dane tekstowe W Pascalu do odczytania znaku możemy wykorzystać standardową procedurę read(odp) lub readln(odp). Ta druga postać jest korzystniejsza, ponieważ zmiennej odp przyporządkowuje tylko pierwszy odczytany znak, a resztę znaków ignoruje (usuwa z bufora). Jeśli odczytamy znak za pomocą read(znak), to w buforze pozostanie reszta znaków, a co najmniej kod klawisza Enter. Znaki te będą odczytywane przez kolejne wystąpienia procedur read lub readln, a to może zakłócić pracę programu, np. użytkownik zamiast odpowiedzi T Enter naciśnie klawisze T A K Enter. Po odczytaniu i zinterpretowaniu odpowiedzi pozytywnej tak w buforze pozostaną dwie litery, gdy następną odczytywaną informacją będzie liczba — mamy kłopot! Dlatego na wszelki wypadek należy opróżnić bufor. Zatem zamiast kodu read(odp); readln; zastosujemy readln(odp); — krótszy zapis, a efekt dokładnie taki sam. Procedura readln bez parametrów usuwa z bufora wszystkie znaki aż do kodu klawisza Enter wraz z tym kodem. W C i C++ istnieje więcej funkcji umożliwiających odczytywanie pojedynczych znaków: scanf("%c", &odp); — funkcja odczytuje dane ze standardowego wejścia (stdin), specyfikator konwersji %c określa, że ze strumienia danych zostanie wczytany jeden znak; fscanf(stdin, "%c", &odp);

— funkcja odczytuje ze strumienia danych jeden znak. Ponieważ jako strumień danych (pierwszy parametr) podano standardowy strumień wejściowy stdin, działanie jest dokładnie takie samo jak w przypadku funkcji scanf; odp = getchar();

lub odp = getc(stdin); albo odp = fgetc(stdin); — wywołanie tych trzech funkcji ma identyczne rezultaty, znak zwracany jest jako wartość unsigned char (skonwertowana na int). Jak wcześniej wspomniano, bufor należy opróżnić. Zrobimy to, wywołując funkcję IĠXVK VWGLQ . W C ++ w celu odczytania znaku ze strumienia możemy dodatkowo skorzystać z obiektu cin, pochodzącego z klasy iostream: cin >> odp; — operator >> pobiera ze standardowego strumienia wejściowego dane do zmiennej odp, w tym przypadku jeden znak; odp = cin.get();

lub cin.get(odp); — znak zostaje pobrany ze strumienia przy użyciu funkcji składowej (metody) get() obiektu cin. Również w tym przypadku musimy opróżnić bufor, np. przy użyciu funkcji składowej cin. ignore(1024, '\n'); lub IĠXVK VWGLQ . W pierwszym przypadku po prostu zostaną zignorowane 1024 znaki lub wszystkie znaki do naciśnięcia klawisza Enter (w zależności od tego, co najpierw nastąpi)10, natomiast druga funkcja usunie wszystkie znaki ze strumienia.

10

Złośliwy użytkownik wpisze 1025 znaków i dopiero wtedy naciśnie Enter — 1024 znaki zostaną zignorowane, ostatni znak pozostanie w strumieniu, ale to jest mało prawdopodobne…

107 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 3. Podejmowanie decyzji, czyli nieco więcej o instrukcjach warunkowych

Pascal

var odp: char; begin write('Czy...? Odpowiedz t - tak/n - nie: '); readln(odp); LIRGS  W WKHQZULWHOQ 7ZRMDRGSRZLHGěWDN HOVHLIRGS  Q WKHQZULWHOQ 7ZRMDRGSRZLHGěQLH else writeln('Nie rozumiem Twojej odpowiedzi!'); readln; end.

C, C++

#include int main() { system("chcp 1250"); char odp; int n; printf("Czy...\? Odpowiedz t - tak/n - nie: "); scanf("%c", &odp); IĠXVK VWGLQ  if (odp == 't') SULQWI 7ZRMDRGSRZLHGěWDN?Q  else if (odp == 'n') SULQWI 7ZRMDRGSRZLHGěQLH?Q  else printf("Nie rozumiem Twojej odpowiedzi!\n"); system("pause"); return 0; }

108 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Dialog programu z użytkownikiem — dane tekstowe

C++

#include using namespace std; int main() { system("chcp 1250"); char odp; cout > odp; cin.ignore(1024, '\n');  OXEIĠXVK VWGLQ   if (odp == 't') FRXW7ZRMDRGSRZLHGěWDN?Q else if (odp == 'n') FRXW7ZRMDRGSRZLHGěQLH?Q else cout odp; cin.ignore(1024, '\n');  OXEIĠXVK VWGLQ   } while ((odp != 't') && (odp != 'n'));

Zmodyfikowane przykłady zamieszczono na FTP: p3_15a.pas, p3_15a.c, p3_15a.cpp. 3U]\NáDG Poprzednia wersja programu nie wygląda zbyt elegancko. Użytkownik musi naciskać dwa klawisze (wybrany znak i Enter), może w ramach odpowiedzi napisać dowolny tekst, z którego znaczenie ma wyłącznie pierwszy znak — resztę, aby nie zakłócała dalszej pracy, musimy pominąć. Aby wyeliminować te niedogodności, sięgniemy po funkcję czytającą ze strumienia jeden znak (wystarczy naciśnięcie klawisza) i niewypisującą tego znaku na ekranie. W Pascalu jest to funkcja readkey z modułu Crt12, natomiast w C lub C++ musimy skorzystać z pewnej niestandardowej biblioteki, dołączyć plik nagłówkowy conio.h (console input and output) i użyć funkcji getch (FTP: p3_16.pas, p3_16.c, p3_16.cpp). Moduły w Pascalu służą do grupowania procedur i funkcji w biblioteki, a także do dzielenia dużych programów na powiązane logicznie części. Moduł nie jest samoistnym programem. Użycie modułu w programie wymaga deklaracji (USES). Po zadeklarowaniu modułu w programie dostępne są typy, stałe, zmienne, procedury lub funkcje zdefiniowane w module. Pascal oferuje wiele gotowych modułów, np. moduły System (moduł włączany do każdego programu automatycznie, bez deklarowania), Crt, Dos, Graph… W rozdziale 5. poznamy sposób budowania własnych modułów.

12

Użycie modułu Crt w programach powoduje błędne wyświetlanie polskich znaków w konsoli — dotyczy to wyłącznie programów kompilowanych w Free Pascalu. W Turbo Pascalu takich zakłóceń nie ma.

110 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Dialog programu z użytkownikiem — dane tekstowe

uses crt;

Pascal

repeat write('Czy...? Odpowiedz t - tak/n - nie: '); odp := lowercase(readkey()); writeln(odp); until (odp = 't') or (odp = 'n'); #include #include  1LHVWDQGDUGRZ\SOLNQDJïöZNRZ\ 

C, C++

do { printf("Czy...\? Odpowiedz t - tak/n - nie: "); odp = tolower(getch()); putchar(odp); putchar('\n'); } while ((odp != 't') && (odp != 'n')); #include #include  1LHVWDQGDUGRZ\SOLNQDJïöZNRZ\ 

C++

do { cout = EPS);

Funkcja atan_2 oblicza arcus tangens dla argumentu x > 1. Różnica jest niewielka, sumowanie 1 rozpoczynamy od wartości , kolejny wyraz jest równy , a licząc następne wyrazy, zamiast 2 x mnożyć przez x 2, dzielimy przez tę wartość (tym razem potęgi x występują w mianowniku).

Pascal

function atan_2(x: real): real; const eps = 1e-15; var a, kwx, suma: real; n: integer; begin ^6]HUHJ]ELHĝQ\GOD[!` suma := PI/2; Db [^3LHUZV]\Z\UD]V]HUHJX` kwx := -x*x; ^/LF]EDSU]HFLZQDGRNZDGUDWX[` n := 1; ^/LF]QLNNROHMQ\FKZ\UD]öZ` repeat suma += suma+a; ^'RGDQLHZ\UD]X` Db D  Q   Q NZ[^1DVWÚSQ\Z\UD]` n := n + 1; until abs(a) < eps; atan_2 := suma; end;

C, C++

double atan_2(double x)  6]HUHJ]ELHĝQ\GOD[!  { double a, kwx, suma; int n; suma = PI/2;  3RF]ÈWNRZDZDUWRĂÊVXP\  Db [ 3LHUZV]\Z\UD]V]HUHJX  kwx = -x*x;  /LF]EDSU]HFLZQDGRNZDGUDWX[  n = 1;  /LF]QLNNROHMQ\FKZ\UD]öZ  do { suma += a;  'RGDQLHZ\UD]X  Db D  Q   Q NZ[ n += 1;  1DVWÚSQ\Z\UD]  } while (fabs(a) >= EPS); return suma; }

284 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozwinięcie funkcji w szereg liczbowy — szeregi funkcyjne Możliwy jest tak jak poprzednio drugi wariant obliczeń.

Pascal

repeat ^'RGDQLHZ\UD]X` suma := suma+a/n; Db DNZ[^/LF]QLNNROHMQHJRZ\UD]X` n := n+2; ^0LDQRZQLNNROHMQHJRZ\UD]X` until abs(a/n) < eps; do {

C, C++

suma += a/n; / 'RGDQLHZ\UD]X  Db NZ[ /LF]QLNNROHMQHJRZ\UD]X  n += 2;  0LDQRZQLNNROHMQHJRZ\UD]X  } while (fabs(a/n) >= EPS);

Funkcja atan_3 obliczająca arcus tangens dla x < –1 od funkcji atan_2 różni się wyłącznie znakiem początkowej wartości sumy (

). 2 Na koniec zbudujemy funkcję arctg, która obliczy wartość funkcji arcus tangens dla dowolnego argumentu x, dobierając do wykonania zadania odpowiednią spośród trzech zbudowanych wcześniej funkcji (FTP: p10_10.pas, p10_10.c i p10_10.cpp).

Pascal

function arctg(x: real): real; begin if x > 1 then arctg := atan_2(x) else if x = 1 then arctg := PI/4 else if x = -1 then arctg := - PI/4 else if x < -1 then arctg := atan_3(x) else arctg := atan_1(x); end;

C, C++

double arctg(double x) { if (x > 1) return atan_2(x); else if (x == 1) return PI / 4; else if (x == -1) return - PI / 4; else if (x < -1) return atan_3(x); else return atan_1(x); }

Jeśli zauważymy nieparzystość funkcji arcus tangens, możemy tę funkcję uprościć, a ponadto zbędna okaże się funkcja atan_3. 285 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 10. Ciągi i szeregi liczbowe

Pascal

function arctg(x: real): real; begin if x < 0 then arctg := -arctg(-x) else if x > 1 then arctg := arctan_2(x) else if x = 1 then arctg := PI/4 else arctg := atan_1(x); end;

C, C++

double arctg(double x) { if (x < 0) return -arctg(-x); else if (x > 1) return atan_2(x); else if (x == 1) return PI/4; else return atan_1(x); }

Funkcja arcus tangens jest dostępna w bibliotekach języków programowania. W Pascalu występuje pod nazwą arctan, a w C i C++ jako atan (należy dołączyć plik nagłówkowy lub ). Możemy porównać wyniki naszych obliczeń z wynikami standardowej funkcji (FTP: p10_10a.pas, p10_10a.c i p10_10a.cpp).

286 Ebookpoint.pl kopia dla: Rodenko David [email protected]

ROZDZIAŁ 11.

PODSTAWOWE OPERACJE NA PLIKACH Wykonując złożone obliczenia, staniemy przed problemem przechowywania danych i wyników. Do tego celu doskonale nadają się pliki tekstowe i binarne. Przedstawione w tym rozdziale przykłady pozwolą nam na używanie plików w swoich programach.

Programowanie obiektowe nie zostało w tej książce omówione. Ponieważ pisząc programy w C++, trudno tego tematu uniknąć, w Dodatku D, który znajduje się na FTP (ftp://ftp.helion.pl/przyklady/maalpr.zip), wprowadzono podstawowe pojęcia z tego zakresu. Opisano tam najważniejsze operacje wejścia-wyjścia korzystające z obiektów.

Zapisywanie i odczytywanie plików tekstowych 3U]\NáDG Rozpocznijmy od prostego przykładu — utworzymy plik tekstowy dane.txt i umieścimy w nim jeden wiersz tekstu: Matematyka i programowanie.

Pascal

var plik: text; begin assign(plik, 'dane.txt'); rewrite(plik); ZULWH SOLN 0DWHPDW\NDLbSURJUDPRZDQLH  close(plik); end. #include

C, C++

int main () { FILE * plik; plik = fopen("dane.txt", "w"); ISULQWI SOLN0DWHPDW\NDLbSURJUDPRZDQLH  fclose(plik); return 0; }

287 Ebookpoint.pl kopia dla: Rodenko David [email protected]

Rozdział 11. Podstawowe operacje na plikach

C++

#include #include using namespace std; int main () { ofstream plik("dane.txt"); SOLN0DWHPDW\NDLbSURJUDPRZDQLH plik.close(); return 0; }

W Pascalu deklarujemy zmienną plik typu text (plik tekstowy) i kojarzymy tę zmienną z fizyczną nazwą pliku: assign(plik, 'dane.txt');1. Następnie otwieramy plik do zapisu: rewrite(plik); — powoduje to utworzenie w podanej lokalizacji nowego pustego pliku, a jeśli plik już istniał, jego zawartość zostanie usunięta. Przy użyciu procedur write(plik, ...) lub writeln(plik, ...) umieszczamy dane tekstowe w pliku. Na koniec zamykamy plik za pomocą instrukcji close(plik);. W C lub C++ ten sam efekt końcowy uzyskamy w podobny sposób — najpierw zadeklarujemy wskaźnik plik do struktury FILE2. Pusty plik do zapisu utworzymy za pomocą funkcji fopen, która ma dwa parametry: nazwę pliku i tryb otwarcia (w oznacza otwarcie pliku do zapisu — write). Jeśli operacja utworzenia pliku powiedzie się, funkcja fopen zwróci wskaźnik do struktury FILE opisującej utworzony plik. Do wpisywania danych do pliku służy funkcja fprintf(plik, ...) działająca w taki sam sposób jak znana nam funkcja printf wypisująca informacje na ekranie. Na koniec zamykamy plik. Zamiast funkcji fprintf do wypisywania łańcuchów znaków możemy używać funkcji ISXWV 0DWHPDW\NDLbSURJUDPRZDQLH plik); — wskaźnik do pliku jest tutaj drugim parametrem. W języku C++ dysponujemy klasą ofstream3 (output file stream). Możemy utworzyć obiekt plik tej klasy, podając konstruktorowi nazwę pliku jako parametr (ofstream plik("dane. txt");4). Operator