040 PROGRAMOWANIE Prof. dr hab. Marek Wisła Programowanie • Programowanie to zapisywanie algorytmów w postaci programów w językach zrozumiałych przez komputer (czyli językach programowania), a właściwie zrozumiałych przez specjalne programy zwane translatorami, które mogą je (te programy) przekształcić (przetłumaczyć) na ciągi rozkazów zapisane w języku znanym przez komputer, zwanym językiem maszynowym, w celu zmuszenia komputera do wykonania pewnych czynności, np. rozwiązania równania kwadratowego, obliczenia podatku, narysowania koła na ekranie monitora, wysłania poczty e-mail itp. Programowanie jako proces • W ogólności programowanie to złożony proces obejmujący: • projektowanie: • analiza wymagań • specyfikacja programu • kodowanie, czyli zapis w wybranym języku programowania • przetłumaczenie, czyli translację na język maszynowy • testowanie • weryfikacja - czy program jest zgodny ze specyfikacją? • walidacja - czy program jest zgodny z oczekiwaniami użytkownika? • dokumentowanie • dokumentacja użytkownika • dokumentacja techniczna • integrowanie i wdrażanie • pielęgnowanie programu. Algorytm • W potocznym rozumieniu algorytm to metoda rozwiązywania jakiegoś problemu (zadania, wykonywania pewnych czynności), inaczej mówiąc, to nie dopuszczający wieloznacznej interpretacji formalny przepis (recepta) postępowania. • Nie wymaga się przy tym rozumienia treści rozwiązywanego zagadnienia, wystarczy ściśle wypełniać podane w algorytmie wskazówki. • Algorytm obrazuje metodę rozwiązywania danego problemu za pomocą skończonej liczby operacji, gdy rozwiązanie istnieje lub też metodę zatrzymującą postępowanie po skończonej liczbie operacji, gdy rozwiązanie nie istnieje. Program • Algorytm zapisany w języku programowania nazywamy programem. Język programowania • Język programowania to formalny (sztuczny) język służący do formułowania (zapisywania) algorytmów oraz innych zadań, jakie komputer ma wykonać. • Postać programu wyrażona w języku programowania określana jest jako kod źródłowy. • Język programowania to zbiór zasad określających kiedy ciąg symboli stanowi program, czyli zapis opisujący obliczenia, oraz jakie obliczenia ten zapis oznacza. • Podobnie jak języki naturalne, język programowania składa się ze zbiorów reguł syntaktycznych oraz semantycznych, które opisują, jak należy budować poprawne wyrażenia języka oraz jak komputer ma je „rozumieć”. Syntaktyka • Aby dany ciąg znaków mógł być rozpoznany jako program napisany w danym języku, musi spełniać reguły składni. • Składnia (syntaktyka) opisuje: • rodzaje dostępnych symboli – alfabet języka • zasady, według których symbole mogą być łączone w większe struktury • Składnia najczęściej opisywana jest formalnie za pomocą wyrażeń regularnych oraz notacji BNF (Backus-Naur Form) lub Extended BNF. • Przykład: notacja BNF • <wyrażenie> ::= <składnik> | <wyrażenie> + <składnik> • <składnik> ::= <czynnik> | <składnik> * <czynnik> • <czynnik> ::= ( <wyrażenie> ) | a | b | c Semantyka • Semantyka języka programowania definiuje precyzyjnie znaczenie poszczególnych symboli oraz ich funkcję w programie. • Semantykę najczęściej definiuje się słownie, ponieważ większość jej elementów jest trudna lub wręcz niemożliwa do ujęcia w jakikolwiek formalizm. • Metody określania semantyki języka programowania: • semantyka aksjomatyczna - zbiór aksjomatów i reguł wnioskowania • semantyka denotacyjna - znaczenie przypisują funkcje waluacji • semantyka operacyjna - interpretacja z pomocą abstrakcyjnej maszyny • semantyka algebraiczna - wykorzystuje pojęcia algebraiczne. Typy danych • Język programowania operuje na jakimś zestawie danych, dlatego niezbędne jest podzielenie danych na odpowiednie typy, zdefiniowanie ich właściwości oraz operacji, jakie można na nich realizować. • Podstawowe typy danych: • liczby całkowite (stałopozycyjne) – ograniczony zakres • liczby zmiennopozycyjne – znacznie szerszy, ale ograniczony zakres • wartości logiczne – false i true • ciągi znaków – teksty, łańcuchy znaków Złożone typy danych • Złożone typy danych: • tablice – ciągi, macierze, …, wartości tego samego typu • listy – ciągi o zmiennej długości, wartości różnych typów • rekordy – zestawy wartości różnych typów • zbiory – odpowiedniki zbiorów w matematyce • inne, zależnie od języka Liczby zmiennopozycyjne • Liczba zmiennopozycyjne (zmiennoprzecinkowe) – reprezentacja liczby rzeczywistej zapisanej za pomocą notacji naukowej. • Wartość liczb zmiennopozycyjnej jest obliczana według wzoru: 𝑥 = 𝑧 ⋅ 𝑚 ⋅ 𝑏𝑒 gdzie: • z – znak liczby 1 lub -1 • b – baza (b=2 dla systemów komputerowych) • m – znormalizowana mantysa, liczba ułamkowa (𝟏 ≤ 𝒎 < 𝒃) • e - wykładnik - liczba całkowita. • Przykłady (dla bazy 𝑏 = 10 i ilości cyfr mantysy 4): • 60,89523 = 6,089 ⋅ 101 • 0,0000125 = 1,25 ⋅ 10−5 Implementacje sprzętowe • W implementacjach sprzętowych liczby zmiennopozycyjne wyraża się liczbami dwójkowymi (𝑏 = 2). Ma to następujące zaletę: • mantysa należy do przedziału [1,2), jest więc postaci 1.xxxxx.... (x – bit o dowolnej wartości). Ponieważ część całkowita jest znana, i równa zawsze 1, więc nie jest zapamiętywana - daje to dodatkowy bit na część ułamkową. Operacje na liczbach zmiennopozycyjnych • Arytmetyka zmiennopozycyjna nie jest łączna i rozdzielna! Oznacza to, że można podać przykłady liczb 𝑥, 𝑦 i 𝑧 takich, że • 𝑥+𝑦 +𝑧 ≠𝑥+ 𝑦+𝑧 • 𝑥⋅𝑦 ⋅𝑧 ≠𝑥⋅ 𝑦⋅𝑧 • 𝑥⋅ 𝑦+𝑧 ≠ 𝑥⋅𝑦 + 𝑥⋅𝑧 • Innymi słowy, kolejność wykonywania operacji wpływa na końcowy wynik. • Przy obliczeniach zmiennopozycyjnych występują też anomalie: • zaokrąglenia, • nieprawidłowe operacje, • przepełnienie, • niedomiar. Podział języków programowania • Języki wewnętrzne komputera (języki maszynowe): rozkazy procesora reprezentowane w formie binarnej • Języki asemblera (klasa języków): kody binarne rozkazów procesora i adresy binarne komórek pamięci zastąpiono kodami i adresami symbolicznymi • Języki wysokiego poziomu (autokody): operują instrukcjami ukierunkowanymi na potrzeby dziedziny, której mają służyć, a nie instrukcjami dobranymi, jak w przypadku asemblera, według specyfiki rozkazów procesora. Język maszynowy • Program w języku maszynowym ma postać ciągu liczb (reprezentowanych w postaci binarnej), które odpowiadają rozkazom i danym bezpośrednio pobieranym przez procesor wykonujący ten program: • każdy typ procesora ma swój własny język maszynowy, zatem kod maszynowy nie może być wykonywany przez procesor innego typu • kod jest praktycznie nieczytelny dla człowieka • kod maszynowy jest generowany za pomocą translatora, który przekształca czytelny dla programisty zapis w języku programowania wysokiego poziomu w kody rozkazów maszynowych Języki asemblera (asemblery) • Klasa języków programowania niskiego poziomu, których jedno polecenie odpowiada zasadniczo jednemu rozkazowi procesora • języki te powstały na bazie języków maszynowych danego procesora poprzez zastąpienie kodów binarnych rozkazów i adresów binarnych komórek pamięci mnemonikami (adresami i kodami symbolicznymi) • bezpośrednia odpowiedniość mnemoników oraz kodu maszynowego umożliwia zachowanie wysokiego stopnia kontroli programisty nad działaniem procesora. • Asemblery umożliwiają pełne wykorzystanie możliwości procesora (szybkość), wady to pracochłonność programowania i konieczność wnikania w budowę procesora. Asembler • Asembler to także program dokonujący tłumaczenia języka asemblera na język maszynowy, czyli tzw. asemblacji • Przykładowe polecenia asemblera MASM dla procesora 8086/8088: MOV c, z MOV ax, cx ADD c, z SUB c, z prześlij do c wartość z (c:=z) prześlij do rejestru ax zawartość rejestru cx (ax:=cx) dodaj do c zawartość z, (c:=c+z) odejmij od c wartość z, (c:=c-z) Przykład programu: mov mov mov mov int ax, es, al, ah, 21h 0D625h ax 24 0 Języki wysokiego poziomu • Języki wysokiego poziomu operują instrukcjami ukierunkowanymi na potrzeby dziedziny, której mają służyć: • składnia i słowa kluczowe mają maksymalnie ułatwić rozumienie kodu programu dla człowieka, tym samym zwiększając poziom abstrakcji • programowanie w tych językach zwalnia programistę od myślenia o szczegółach sprzętowych, za wygodę płaci się jednak efektywnością programu. Języki wysokiego poziomu • Od początku lat 50-tych XX w. opracowano setki języków (wiele już nie istnieje) i ciągle powstają nowe. Przyczyny tego faktu to: • nowe dziedziny zastosowań • nowe możliwości sprzętowe (programowanie współbieżne) • nowe techniki programowania (programowanie obiektowe, języki wizualne ze wsparciem graficznym). Paradygmat programowania • Paradygmat programowania to określony wzorzec programowania, który definiuje sposób patrzenia programisty na przepływ sterowania i wykonywanie programu komputerowego. • Najbardziej znane paradygmaty programowania to: • programowanie imperatywne • programowanie proceduralne • programowanie obiektowe • programowanie funkcyjne • programowanie w logice (programowanie logiczne) • programowanie zdarzeniowe. Programowanie imperatywne • Programowanie imperatywne jest najbardziej pierwotnym sposobem programowania. • program postrzegany jest jako ciąg poleceń dla komputera • programista jawnie, krok po kroku definiuje jak wykonać zadanie • obliczenia to sekwencja poleceń (instrukcji) zmieniających krok po kroku stan komputera, aż do uzyskania oczekiwanego wyniku (stan komputera to zawartość jego całej pamięci oraz rejestrów procesora) • sposób patrzenia na programy związany jest ściśle z budową sprzętu komputerowego o architekturze von Neumanna, w którym poszczególne instrukcje (w kodzie maszynowym) to właśnie polecenia zmieniające ów globalny stan (przykładowo, instrukcje podstawienia działają na danych pobranych z pamięci i umieszczają wynik w tejże pamięci, abstrakcją komórek pamięci są zmienne) • Programowanie imperatywne uważane jest za synonim programowania proceduralnego. Elementy języka imperatywnego • W językach imperatywnych można wyróżnić wiele wspólnych elementów: • instrukcje przypisania wykonują pewne zadanie na zlokalizowanych w • • • • pamięci danych i zapisują tam wynik działania, możliwe jest wykonywanie złożonych obliczeń wyrażeń składających się z operacji arytmetycznych i logicznych oraz funkcji, instrukcje warunkowe wykonują pewien blok kodu tylko wtedy, kiedy spełniony jest określony warunek, w przeciwnym razie blok ten jest pomijany podczas wykonywania, instrukcje pętli umożliwiają wielokrotne wykonanie tego samego kodu, w zależności od potrzeb, „wielokrotność” może oznaczać pewną określoną z góry ilość powtórzeń lub wykonywanie fragmentu kodu do czasu spełnienia pewnych warunków, możliwe jest przekazanie sterowania do zupełnie innej części programu, co jest realizowane poprzez skok bezwarunkowy (tego mechanizmu nie dopuszcza programowanie strukturalne) oraz wywołanie procedury. Przykład programu w Pascalu program trojmian_kwadratowy; {$APPTYPE CONSOLE} uses SysUtils; var a, b, c, delta, x1, x2 : real; begin writeln ('Podaj wspolczynniki a, b, c trojmianu kwadratowego: '); readln (a, b, c); delta := b*b-4*a*c; if delta < 0 then writeln ('BRAK MIEJSC ZEROWYCH !') else if delta > 0 then begin x1 := (-b-sqrt(delta))/(2*a); x2 := (-b+sqrt(delta))/(2*a); writeln ('X1 = ' , x1); writeln ('X2 = ' , x2); end else begin x1 := -b/(2*a); writeln ('X1 = ' , x1); end; readln; end. Programowanie obiektowe • Programowanie obiektowe: • program to zbiór powiązanych się ze sobą obiektów, czyli jednostek zawierających określone dane i umiejących wykonywać na nich (tych danych) określone operacje • umożliwia modelowanie zjawisk świata rzeczywistego w uporządkowany, hierarchiczny sposób – od idei do szczegółów technicznych. Cechy programowania obiektowego • Cechami charakterystycznymi programowania obiektowego są: • powiązanie (enkapsulacja) danych (czyli stanu) z operacjami na nich (czyli poleceniami) w całość, stanowiącą odrębną jednostkę – obiekt, • mechanizm dziedziczenia, czyli możliwość definiowania nowych, bardziej złożonych obiektów na bazie obiektów już istniejących, • polimorfizm, czyli realizowanie przez operację różnych działań w zależności od tego, do jakiego typu obiektu aktualnie się odnosimy. • Najważniejsze języki obiektowe to: C++, Java, C#, VB.NET, PHP i Python. Przykład programu w Pythonie #!/usr/bin/env python class koszyk: def __init__ (self): self.koszyk = [] def dodaj(self,obiekt): self.koszyk.append(obiekt) def rozmiar(self): return len(self.koszyk) s = koszyk() s.dodaj(„alfa") s.dodaj(„beta") print s.rozmiar() del s Programowanie funkcyjne • Programowanie funkcyjne: • program to złożona funkcja (w sensie matematycznym), która otrzymawszy dane wejściowe wylicza pewien wynik • zasadniczą różnicą w stosunku do poprzednich paradygmatów jest brak stanu maszyny: nie ma zmiennych, a co za tym idzie nie ma żadnych efektów ubocznych • konstruowanie programów to składanie funkcji, zazwyczaj z istotnym wykorzystaniem rekurencji • charakterystyczne jest również definiowanie funkcji wyższego rzędu, czyli takich, dla których argumentami i których wynikami mogą być funkcje. • Najbardziej znane języki funkcyjne to : Lisp, Scheme i Haskell. Przykład programu w Scheme (define silnia (lambda (x) (if (= x 0) 1 (* x (silnia (- x 1)))))) (define fib (lambda (n) (cond ((= n 0) 0) ((= n 1) 1) (else (+ (fib (- n 1)) (fib (- n 2))))))) (silnia 5) (fib 10) Programowanie w logice • Podobnie jak w programowaniu funkcyjnym, nie „wydajemy rozkazów”, a • • • • • jedynie opisujemy, co wiemy i co chcemy uzyskać (języki funkcyjne i logiczne nazywa się łącznie językami deklaratywnymi). Na program składa się zbiór zależności (przesłanki – reguły i fakty) oraz pewne stwierdzenie (cel). Wykonanie programu to próba udowodnienia celu w oparciu o podane przesłanki, a więc pewien rodzaj automatycznego wnioskowania. Język programowania w logice (a ściślej — jego interpreter) to właściwie system automatycznego dowodzenia twierdzeń, działający w oparciu o nieco uproszczony rachunek predykatów pierwszego rzędu. Zastosowania programowania w logice obejmują przede wszystkim sztuczną inteligencję (np. systemy ekspertowe, rozpoznawanie obrazów) i przetwarzanie języka naturalnego. Językiem, który uzyskał największą popularność jest Prolog. Przykład programu w Prologu rodzic(X, Y) :- ojciec(X, Y). rodzic(X, Y) :- matka(X, Y). malzenstwo(X, Y) :- ojciec(X, Z), matka(Y, T), Z = T. malzenstwo(X, Y) :- matka(X, Z), ojciec(Y, T), Z = T. przodek(X, Y) :- rodzic(X ,Y). rodzenstwo(X, Y) :- matka(M, X), matka(M, Y), ojciec(O, X), ojciec(O, Y), X \= Y. ojciec(marek, jurek). ojciec(marek, zosia). matka(jola, jurek). matka(jola, zosia). rodzenstwo(jurek, zosia). Programowanie zdarzeniowe • Programowanie (sterowane zdarzeniami) polega zastąpieniu zasadniczego toku sterowania wieloma drobnymi programami obsługi zdarzeń, uruchamianymi w chwili wystąpienia odpowiedniego zdarzenia • Zdarzenia mogą być wywoływane przez urządzenia wejściawyjścia (np. naciśnięcie klawisza, ruch myszką) lub przez same programy obsługi zdarzeń, oprócz zbioru programów obsługi zdarzeń potrzebny jest też zarządca, który będzie je uruchamiał • Przykładem programu sterowanego zdarzeniami może być system operacyjny, który jest, do pewnego stopnia, sterowany zdarzeniami: reaguje na przerwania. Rolę zarządcy spełnia tu procesor komputera. • Z kolei sam system operacyjny jest zarządcą w stosunku do uruchomionych pod jego kontrolą procesów. Przegląd języków wysokiego poziomu • FORTRAN (1954-1957, John Backus) - pierwszy język • • • • imperatywny wysokiego poziomu, zastosowania to obliczenia naukowo-techniczne. ALGOL 60, ALGOL 68 (1958-1968) - umożliwiał łatwe programowanie algorytmów numerycznych. COBOL (1960-1962) - zorientowany na problemy ekonomiczne i zarządzanie, używany przez banki i firmy handlowe PL/1 - język uniwersalny, łączy zalety Algolu, Fortranu i Cobolu BASIC (1964, J. Kemeny, T. Kurtz) - język uniwersalny, dla początkujących, zdobył popularność dopiero po zastosowaniu go w mikrokomputerach jako język umieszczony w pamięci ROM. Przegląd języków wysokiego poziomu • PASCAL (1969-1971, Niklaus Wirth) - posiada zalety dydaktyczne, wymusza programowanie systematyczne i strukturalne metodą zstępującą „od ogółu do szczegółu”, szeroka dziedzina stosowalności. • C (1972, Dennis Ritchie, Ken Thompson, Brian Kernighan) łączy cechy języka wysokiego poziomu z możliwościami asemblera, służy między innymi do pisania programów systemowych, np. systemu UNIX. • MODULA-2, MODULA-3 (1978, Niklaus Wirth) - ulepszone wersje Pascala, pozwalają na konstruowanie niezależnych modułów. • ADA (1979, Jean Ichbiah) - powstał na zlecenie Departamentu Obrony USA, służy do programowania komputerów nadzorujących aparaturę techniczną (systemy czasu rzeczywistego) Przegląd języków wysokiego poziomu • SIMULA (1967) - zapoczątkował programowanie obiektowe, • • • • • zastosowanie przy konstrukcji kompilatorów innych języków, programów symulujących systemy biologiczne, ekonomiczne, społeczne, komunikacyjne. Clliper - język do programowania i manipulowania bazami danych. SQL - standard dla relacyjnych baz danych. Visual Basic - język obiektowy, pozwala tworzyć aplikacje dla WINDOWS. Lisp – (1960, J. McCarthy) wykorzystuje programowanie funkcyjne, oparty na rachunku lambda, użyteczny do obliczeń symbolicznych oraz budowania programów do automatycznego dowodzenia twierdzeń, liczenia symbolicznego całek nieoznaczonych itp. Prolog – zastosowania podobne do Lispu – sztuczna inteligencja. Przegląd języków wysokiego poziomu Języki zorientowane obiektowo: • C++ (1985, Bjarne Stroustrup) • Perl (1987, Larry Wall) • Python (1990, Guido van Rossum) • Java (1994, Sun Microsystems) • PHP (1995, Rasmus Lerdorf) • C# (2001, Anders Hejlsberg, Microsoft) • VB.NET (2001, Microsoft) Przegląd języków wysokiego poziomu • Języki znaczników: HTML, VRML, XML, X3D, XHTML • Języki skryptowe do programowania stron www: • VB Script • Java Script • Języki skryptowe do tworzenia multimediow: ActionScript, Lingo