POLITECHNIKA WARSZAWSKA Instytut Automatyki i Robotyki ZASADY PROGRAMOWANIA STRUKTURALNEGO (ZAP - zima 2015) Język programowania: Środowisko programistyczne: C/C++ Qt Creator Wykład 2: Liczby, znaki, napisy, wyrażenia. Pętle typu while.
Program wykładu 2 Temat 1: Podstawowe pojęcia i proste programy. Pojęcia algorytmu, programu, kodu wykonywalnego. Kompilacja i wykonanie programu. Sieci działań. Struktura programu. Komentarze i dokumentacja programu. Zmienne i ich nazwy, podstawowe typy: całkowite, rzeczywiste, znakowe i napisowe. Definicje zmiennych. Instrukcje: czytania, pisania, przypisania. Klasyfikacja typów. Stałe, wyrażenia i funkcje standardowe. Instrukcje: warunkowa, złożona. Instrukcje cykliczne: pętla for, pętle sterowane warunkiem. Instrukcje przerywające wykonanie pętli. Instrukcja wielokrotnego wyboru. Temat 2: Tablice, rekordy i pliki. Tablice: definiowanie tablic, zmienna tablicowa i indeksowana. Operacje na tablicach. Algorytmy sortowania i przeszukiwania w tablicach, porównanie złożoności obliczeniowej. Struktury i rekordy, operacje wykonywane na rekordach. Definiowanie plików, zasady dostępu, operacje wejścia wyjścia. Temat 3: Funkcje i rekurencja. Zasady definiowania i wywołania funkcji. Parametry formalne i aktualne. Wiązanie parametrów przez wartość i referencję. Zasięg nazw, zasłanianie. Rekurencja jako jedna z podstawowych technik konstruowania algorytmów. Zasada działania rekurencji i warunek końca. Przykłady algorytmów rekurencyjnych.
Program wykładu (c.d.) 3 Temat 4: Wskaźniki i listy. Zmienne dynamiczne i wskaźniki. Przydział i zwalnianie pamięci. Dynamiczna rezerwacja tablic. Listy jednokierunkowe: zasada tworzenia, podstawowe operacje na listach. Listy dwukierunkowe i cykliczne. Iteracyjne i rekurencyjne algorytmy przetwarzania list. Temat 5: Struktury drzewiaste. Drzewa binarne i binarne drzewa sortowane (BST). Podstawowe operacje na drzewach z wykorzystaniem rekurencji. Drzewa wyważone (AVL i RBT). Kopce, B-drzewa, zastosowania do baz danych. Złożoność obliczeniowa. Temat 6: Przegląd algorytmów, algorytmy grafowe. Przegląd zasad konstruowania algorytmów: programowanie typu dziel i zwyciężaj, programowanie dynamiczne, algorytmy z powrotami, metody zachłanne. Grafy i algorytmy grafowe: problem najkrótszej ścieżki, minimalne drzewa rozpinające grafów. Temat 7: Wstęp do ZAP II. Modułowa budowa programów. Elementy programowania obiektowego. Temat 8: Przygotowanie do egzaminu. Przykłady zadań egzaminacyjnych z rozwiązaniami. Przegląd najczęściej popełnianych błędów, wskazówki, jak ich unikać.
Literatura uzupełniająca 2 1. P. Wnuk, B. Putz: Informatyka 2 - Programowanie. Wersja w języku C/C++. Multimedialny podręcznik internetowy, OKNO PW, 2004-2010, dostępny online w SKS: http://iair.mchtr.pw.edu.pl/studenci/witryna/login.php 2. B. Putz, A. Putz jr, P. Wnuk : Algorytmy i struktury danych. Multimedialny podręcznik internetowy, OKNO PW, 2006-2010, dostępny online w SKS (jak wyżej). 3. S. Prata: Język C++. Wydanie V, Helion 2006. 4. J. Grębosz: Symfonia C++ standard. Tom I. Edition 2000 Kraków, 2005-2008. 4. B. Eckel: Thinking in C++. Edycja polska. Helion 2002. 6. N. Wirth: Algorytmy+struktury danych=programy. WNT 2004. 7. P. Wróblewski: Algorytmy, struktury danych i techniki programowania. Wyd. IV, Helion 2010. 8. T.H.Cormen et al.: Wprowadzenie do algorytmów. WNT 2007, PWN 2012 (nowe wydanie).
Kompilacja i konsolidacja programu (linkowanie) 3
Biblioteki i pliki nagłówkowe 6 Biblioteki dołączamy do programu poprzez dołączanie tzw. plików nagłówkowych. Plik nagłówkowy - plik zawierający deklaracje zmiennych i funkcji tworzących bibliotekę. Standardowy format dołączania plików nagłówkowych typowych dla C++: #include <iostream> // iostream jest nazwą dołączanej biblioteki języka C++ Starszy format dołączania takich plików nagłówkowych: #include <iostream.h> Biblioteki odziedziczone z języka C można dołączać dwojako: w sposób tradycyjny dla języka C: #include <stdlib.h> // stdlib.h jest plikiem nagłówkowym #include <math.h> lub w stylu C++, poprzedzając nazwę biblioteki literą c : #include <cstdlib> #include <cmath> nie należy łączyć w jednym programie obu tych stylów, bo odpowiednie biblioteki nie są równoważne (te w stylu C++ zawierają tzw. szablony)
Typy 7 TYP jest zdefiniowany przez zbiór przyporządkowanych mu wartości TYPY predefiniowane (najważniejsze) int - podzbiór liczb całkowitych: -2147483648.. 2147483647 (32 bity, czyli od -2 31 do 2 31-1) double - podzbiór liczb rzeczywistych: 2.23*10-308.. 1.79 *10 308 (co do modułu) (64 bity, 15-16 cyfr znaczących) bool char - dwie wartości: true (lub 1 liczbowo) oraz false (lub 0 liczbowo) - zbiór znaków w kodzie ASCII (8 bitów) Oprócz konwersji (rzutowania) z typu bool na int (jak wyżej) możliwa jest konwersja z typu int na typ bool: wartość równa 0 traktowana jest jako fałsz (false) dowolna wartość całkowita różna od 0 traktowana jest jako prawda (true).
Mantysa i cecha mogą być poprzedzone znakiem + lub, np. +1.23e-5 (czytaj: 1,23 razy 10 do potęgi 5) -543e3 543E-003 0.16437e+12 Liczby 8 LICZBY CAŁKOWITE ciąg cyfr poprzedzony znakiem + lub zapis dziesiętny: 66 +123-543 zapis ósemkowy: 0 przed liczbą: 0107 (7*8 0 +0*8 1 + 1*8 2 czyli 71 dziesiętnie) zapis szestnastkowy: 0x przed liczbą: 0x47 (7*16 0 +4*16 1 czyli 71 dziesiętnie) 0xFF (15*16 0 +15*16 1 czyli 255 dziesiętnie) 0xDA (10*16 0 +13*16 1 czyli 218 dziesiętnie) Zatem 107 i 0107 to zupełnie różne liczby! LICZBY RZECZYWISTE - zapisuje się: w postaci dziesiętnej, z kropką zamiast przecinka, np.: 1.128-12.3 +0.03 w postaci wykładniczej; wówczas składają się z: mantysy - liczba dziesiętna, gdzie w zapisie użyto kropki, a nie przecinka litery e lub E - która oznacza podstawę wykładnika równą 10 cechy - jest liczbą całkowitą, oznacza wykładnik potęgi cecha zawsze jest liczbą dziesiętną (nawet jeśli zaczyna się zerem)
Znaki 9 Pojedynczy znak można zapisać następująco: w apostrofach, np. 'A' '@' '0' za pomocą kodu ASCII: każdemu znakowi przyporządkowany jest kod dziesiętny z przedziału <0, 255>; Znaki w kodzie ASCII uporządkowane są następująco:... 0.. 9... A.. Z... a.. z... UWAGA: Nie ma w tym kodzie polskich znaków diakrytycznych (tzw. ogonkowych) Zależność między typami char i int określa rzutowanie (konwersja), np. (char)255 lub char (225) oznacza znak o kodzie 225, czyli, zaś int ('A') jest równe 65. Ale UWAGA: Przejście ze znaków na ich kody i odwrotnie nie wymaga konwersji: int kod = ' A' ; // zmienna kod przyjmie wartość 65; char zn= 65; zn=zn+1; // zmienna zn to litera 'A' // teraz zn to znak następny w kodzie ASCII, czyli litera ' B'
Znaki c.d. 10 W zapisie znaku można też podać kod ósemkowy lub szestnastkowy: np.: '\33' oznacza znak o kodzie ósemkowym 33, czyli Esc '\xe1' oznacza znak o kodzie szestnastkowym e1, czyli Uwaga: jak widać, wpisując kod ósemkowy lub szestnastkowy, po znaku \ nie należy pisać 0 Znaki specjalne (niektóre): \n - przejście do nowej linii \t - tabulacja \b - Backspace \\ - Backslash \' - apostrof \" - cudzysłów \a - sygnał dźwiękowy (alarm) Znaki te można je umieścić wewnątrz napisów lub zapisać w osobno w postaci z pojedynczymi lub podwójnymi apostrofami, np.: znak tabulacji zapisujemy jako '\t' lub "\t" (to ostatnie bardziej czytelne)
Wczytywanie znaków 11 Znaki można wczytywać dwojako: tylko znaki widoczne za pomocą instrukcji: cin >> znak; gdzie znak jest zmienną typu char (wczytuje pod zmienną pierwszy widoczny znak) wszystkie znaki widoczne oraz tzw. znaki białe (spacja, Enter, Tab) za pomocą instrukcji: cin.get (znak); gdzie znak jest zmienną typu char Natomiast instrukcja bezargumentowa: cin.get ( ); oznacza: poczekaj na wczytanie jakiegoś znaku; ta instrukcja zatrzymuje więc działanie programu, dopóki nie wciśniemy jakiegoś znaku i nie wyślemy go Enterem (najlepiej od razu wcisnąć Enter). Ponadto: cin.ignore ( ); ignoruje jeden znak w strumieniu wejściowym, co pozwala pominąć np. Enter z wcześniejszego wczytywania znaków.
Napisy 12 W języku C++ (ale nie w C) istnieje tzw. umowny typ string: string - określa napis (inaczej: łańcuch, tekst, string) - dowolny ciąg znaków ujęty w cudzysłowy (uwaga: string nie jest słowem kluczowym, traktujemy go umownie jako typ) Napis może zawierać znaki specjalne, np. "napis \" Podaj liczbe \" zacheca do pisania " // napis zawierający w środku cudzysłowy "Podaj promien kola \n" // napis zawierający znak przejścia do nowej linii Napisy można ze sobą porównywać. Wynik jest zgodny z uporządkowaniem w kodzie ASCII, np. "Jan" < "Jon", "Jan" < "Janina" oraz "Jan" < "jan". Niepoprawnie działa jednak porównywanie napisów zawierających polskie znaki diakrytyczne. UWAGA: W niektórych kompilatorach, aby można było korzystać z umownego typu string, trzeba dołączyć bibliotekę <string> - jednak w Qt nie jest to potrzebne. Ale: czym innym jest biblioteka <cstring>, która obsługuje funkcje obsługi napisów typowe dla języka C i nie jest tu używana.
Wczytywanie napisów 13 Napisy można wczytywać dwojako: od pierwszego widocznego znaku do pierwszego niewidocznego, białego znaku (odstępu, Entera itp.) za pomocą instrukcji: cin >> napis; linia po linii - za pomocą instrukcji: getline (cin, napis); gdzie napis jest zmienną typu string; gdzie napis jest zmienną typu string; Funkcja getline wczytuje do zmiennej typu string wszystkie znaki w danym wierszu aż do napotkania Entera (ale Entera nie dopisuje do tej zmiennej). string dane; cout << "napisz imie i nazwisko i wcisnij Enter "; cin >> dane; cout << " Witaj, " << dane; // wydrukuje się tylko imie cout << "napisz imie i nazwisko i wcisnij Enter "; getline (cin, dane); cout << " Witaj, " << dane; // wydrukuje się imie i nazwisko
Definiowanie stałych 14 STAŁE definiujemy, jeśli chcemy je nazwać i używać ich nazw w programie. Definicja stałych const typ nazwa = wyrażenie; Definicja stałej musi zawierać jej inicjalizację, bo stałych nie można już potem zmieniać. zalecany styl Przykłady definicji stałych: nazwy stałych - tylko wielkie litery i znaki _ const int N = 5; const int K = 2*(N+1); const double XMIN = 0.001; const string PYTANIE = "Czy wykonac ponownie - t/n"; zalecany styl Należy definiować jako stałe wszystkie liczby w programie różne od 0 i 1
Wybrane standardowe funkcje matematyczne Wymagają dołączenia biblioteki cmath: #include <cmath> Wyniki działania wszystkich funkcji są typu double 15 M_PI - stała M_E - stała e Nazwa Argumenty Opis acos X arcus cosinus z X (wynik w radianach) asin X arcus sinus z X (wynik w radianach) atan X arcus tangens z X (wynik w radianach) atan2 Y, X kąt (od -pi do pi), którego tangens = Y/X ceil cos X X - wartość kąta w radianach cosinus kąta X wartość X zaokrąglona w górę, czyli do najbliższej liczby całkowitej nie mniejszej niż wartość X exp X wartość e podniesiona do potęgi X fabs X wartość bezwzględna liczby X log X - wartość dodatnia logarytm naturalny z X log10 X - wartość dodatnia logarytm o podstawie 10 z X pow X - podstawa, Y - wykładnik X podniesione do potęgi Y floor sin X X - wartość kąta w radianach sinus kąta X wartość X zaokrąglona w dół, czyli do najbliższej liczby całkowitej nie większej niż X sqrt X - wartość nieujemna pierwiastek kwadratowy z X tan X - wartość kąta w radianach tangens kąta X ceil (-4.3) = -4.0 pow (27, 1.0/3)=3.0 floor (-4.3) = -5.0 UWAGA: Funkcje matematyczne mogą też pochodzić z innych bibliotek, np. w cstdlib mamy m. in. funkcję abs (dla argumentu całkowitego) oraz div (dzielenie całkowite).
Wyrażenia logiczne Służą do zapisywania warunków w rozgałęzieniach. Oprócz operatorów arytmetycznych i operatorów relacji mogą zawierać operatory logiczne. OPERATORY LOGICZNE: && (iloczyn logiczny) (suma logiczna)! (negacja) W C++ można je zapisywać również jako alternatywne słowa kluczowe and or not 16 PRIORYTET OPERATORÓW - wewnątrz jednej pary nawiasów okrągłych 1.! (negacja) 2. operatory arytmetyczne + - * / % we właściwej kolejności 3. operatory relacji nierównościowych: > >= < <= 4. operatory relacji równy nierówny: ==!= 5. && (iloczyn logiczny) 6. (suma logiczna) UWAGA: W sytuacjach wątpliwych najpewniejszym sposobem jest używanie nawiasów okrągłych. x >= a && x <= b // x leży w przedziale <a, b> x < a x > b // x nie leży w przedziale <a, b> // nawiasy powyżej są niekonieczne, bo operatory relacji są silniejsze niż operatory logiczne && i // ale można je dodać dla przejrzystości. // zn jest małą lub dużą literą: zn >= 'a' && zn <= 'z' zn >= 'A' && zn <= 'Z albo: (zn >= 'a' && zn <= 'z') (zn >= 'A' && zn <= 'Z ) // można dodać nawiasy, ale nie są konieczne: // zn nie jest małą i nie jest też dużą literą: (zn < 'a' zn > 'z' ) && (zn < 'A' zn > 'Z ) // tu nawiasy są konieczne, bo && jest silniejsze niż
Porównanie a przypisanie - niebezpieczne pułapki 17 Nie wolno mylić operatorów porównania i przypisania: = operator przypisania (wykonaj podstawienie) == operator porównania (sprawdź warunek równości) Przykład: x = 10 ; // instrukcja - przypisz wartość 10 zmiennej x (podstaw 10 pod x ) x = 10 // wyrażenie - ma wartość wyrażenia po prawej stronie znaku =, // czyli 10 (albo true - zależnie od kontekstu) x = = 10 // wyrażenie - ma wartość true, jeśli x jest równe 10 Zatem UWAGA: załóżmy, że chcemy napisać instrukcję warunkową postaci: jeśli x jest równe 10, to zrób coś tam... if (x = =10)... if (x = 10)... // zapis poprawny - instrukcja za nawiasem wykona się tylko // wtedy, gdy x będzie równe 10 // tu jest błąd - instrukcja za nawiasem wykona się zawsze, // bo wyrażenie w nawiasie ma wartość true, // taki bląd NIE BĘDZIE jednak SYGNALIZOWANY!
Instrukcje iteracyjne (pętle): wprowadzenie 18 Umożliwiają wielokrotne powtórzenie ciągu instrukcji Wymagają uzupełnienia o instrukcje organizujące samą pętlę Suma trzech wczytanych wartości... int x1, x2; cin >>x1>>x2>>x3; int s=x1+x2+x3; Suma 100 wczytanych wartości... int x, s=0; cin>>x; s=s+x; cin>>x; s=s+x; cin>>x; s=s+x; // i co dalej?! //s+=x; //s+=x; //s+=x; Suma zależna od ilości powtórzeń w pętli... int x, s=0; POCZĄTEK PĘTLI cin>>x; s=s+x; KONIEC PĘTLI... 100 POWTÓRZEŃ - suma 100 wartości
Rodzaje instrukcji cyklicznych (iteracyjnych) 19 Pętla o znanej liczbie cykli (iteracji): pętla for Pętle o nie znanej z góry liczbie cykli: pętla while tzw. pętle sterowane warunkiem pętla do-while
Instrukcja while 20 Instrukcja Dopóki spełniony warunek, wykonuj Sprawdza warunek przed wykonaniem instrukcji objętej jej zakresem. while (warunek) instrukcja zalecany styl Dopóki spełniony jest warunek, powtarzaj instrukcja, czyli: sprawdź warunek jeśli spełniony, wykonaj instrukcja sprawdź warunek jeśli spełniony, wykonaj instrukcja itd. Może nie wykonać się ani razu!
Instrukcja do-while 21 Instrukcja Wykonuj, dopóki spełniony warunek Sprawdza warunek po wykonaniu instrukcji. do instrukcja while (warunek) ; Powtarzaj instrukcja, dopóki jest spełniony warunek, czyli: wykonaj instrukcja sprawdź warunek jeśli spełniony, wykonaj instrukcja sprawdź warunek itd. Zawsze wykona się przynajmniej jeden raz!
Instrukcja złożona w pętlach while 22 W pętlach typu while może być tylko jedna instrukcja. Ale - może to być instrukcja złożona, czyli w postaci {...} while (warunek) {...... } zalecany styl lepiej użyć bardziej uniwersalnej pętli while niż do-while do {......... } while (warunek) ; UWAGA: warunek (w if-ach, pętlach) może być liczbą lub ogólnie wyrażeniem dającym się zamienić na liczbę, przy czym: wartość równa 0 traktowana jest jako fałsz (stała false) wartość różna od 0 traktowana jest jako prawda (stała true).
Pętle while i do-while: podsumowanie (1) 23 Wczytać ciąg liczb dodatnich zakończony niedodatnią i obliczyć ich sumę. Pytanie - wliczać do sumy tę ostatnią niedodatnią (wersja A) czy nie (wersja B)? // A - wczytuje liczby aż do napotkania niedodatniej // i je sumuje, łącznie z tą niedodatnią zalecany styl cin>>a; s = a; // pierwsza liczba zawsze wchodzi do sumy while (a>0) { cin>>a; s = s+a; } s=0; do { cin>>a; s = s+a; } while (a>0); // wersja poprawna, chociaż nie zalecana // wersja błędna (ani A, ani B) - przy wejściu do // pętli while zmienna a nie jest określona s=0; while (a>0) { cin >> a; s = s+a; } // B - wczytuje liczby aż do napotkania // niedodatniej i sumuje tylko te dodatnie zalecany styl s=0; cin>>a; //pierwsze wczytanie - przed pętlą while (a>0) { s = s+a; cin>>a; // kolejne wczytane a będzie // sprawdzone w następnym cyklu pętli } s=0; do { cin>>a; if (a>0) s = s+a; } while (a>0); // wersja poprawna, ale pętla do nie jest zalecana // i do tego musimy dwa razy sprawdzić warunek // a>0
Pętle while i do-while: podsumowanie (2) 24 Warunek w pętli while jest warunkiem, przy którym pętla ma się kręcić w kółko. Jest on więc negacją warunku, przy którym należy wyjść z pętli. Wykonując negację warunku, w którym są operatory logiczne, należy pamiętać, że negacja sumy logicznej zamienia się w iloczyn negacji - i na odwrót. Przykład: - jeśli pętla ma się wykonywać aż do napotkania małej litery, to oznacza, że pętla ma się wykonywać, dopóki znak nie jest małą literą, czyli: while (znak< a znak > z ) Warunek ten można też zapisać jako negację warunku wyjścia z pętli: while (! (znak>= a && znak<= z )) Błędem natomiast jest zapis w postaci: while (znak< a && znak > z ) gdyż warunek w nawiasie nie jest nigdy spełniony!!! Przykład: // pętla wymusza na użytkowniku // napisanie małej litery: do { cout<< "Napisz mala litere\n"; cin>>znak; } while (znak< a znak > z ); //pętla do jest tu wygodniejsza w zapisie od while