Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

Wprowadzenie do programowania dr inż. Zbigniew Wesołowski

Podobne prezentacje


Prezentacja na temat: "Wprowadzenie do programowania dr inż. Zbigniew Wesołowski"— Zapis prezentacji:

1 Wprowadzenie do programowania dr inż. Zbigniew Wesołowski

2 Cel przedmiotu Podstawowym celem przedmiotu jest nauczyć podstaw programowania strukturalnego z języku C.

3 Wybrane zagadnienia z programu przedmiotu Pojęcia podstawowe technik programowania Elementarne wprowadzenie do algorytmiki Zasada dekompozycji i abstrakcji Typy danych w C Instrukcje języka C Liniowe struktury danych Funkcje Rekursja Strumienie i pliki

4 Bezpośrednie powiązania przedmiotu z innymi przedmiotami Algorytmy i struktury danych Programowanie obiektowe Programowanie współbieżne Bazy danych Systemy operacyjne i język C Technologie portali internetowych

5 Literatura obowiązkowa Kernigham B.W., Ritchie D.M. (2003): Język ANSI C. WNT, Warszawa. Delannoy C. (1993): Ćwiczenia z języka C. WNT, Warszawa. Kernigham B.W., Pike R.(2002) : Lekcja programowania. WNT, Warszawa. Lippman S.B., Lajoie J.(2001): Podstawy języka C++. WNT, Warszawa. Tondo C.L., Leung B.P.(2003): Język ANSI C. Ćwiczenia i rozwiązania. WNT, Warszawa. Tondo C.L., Leung B.P.(2001): Podstawy języka C++. Ćwiczenia i rozwiązania. WNT, Warszawa. Wirth N.(2002): Algorytmy + struktury danych = programy. WNT, Warszawa.

6 Literatura zalecana Drozdek A., Simon D. L. (1996): Struktury danych w języku C. WNT, Warszawa. Knuth D.E. (2002): Sztuka programowania. Tomy 1,2,3. WNT, Warszawa. Hunt A., Thomas D. (2002): Pragmatyczny programista. Od czeladnika do mistrza. WNT, Warszawa. Stroustrup B. (2000): Język C++. WNT, Warszawa. Borowik W., Borowik B. (2001): Meandry języka C++. MIKOM, Warszawa. Lippman S.B.: Model obiektu w C++. WNT, Warszawa. Bentley J. (2001): Perełki oprogramowania. WNT, Warszawa.

7 Literatura dla zawansowanych Meyers S.(1998): Język C++ bardziej efektywny. WNT, Warszawa. Lippman S.B. (1999): Model obiektu w C++. WNT, Warszawa. Plauger P.J.(1997): Biblioteka standardowa C++. WNT, Warszawa. B.Stroustrup (1997): Projektowanie i rozwój języka C++. WNT, Warszawa. Sutter H.(2002): Wyjątkowy język C++. WNT, Warszawa.

8 Dwugłos o C/C++ Języki programowania dzielą się na takie, które wszyscy krytykują, oraz takie, których nikt nie używa. Przypisywane Bjarne Stroustrupowi W C kodujemy nasze własne błędy, natomiast w C++ możemy je odziedziczyć. Przypisywane prof. G.Karamowi z Uniwersytetu w Carleton:

9 Dlaczego C? W językach C i C++ zapisano niemal wszystkie liczące się w świecie systemy operacyjne i ich oprzyrządowanie programowe. Andrew Tanenbaum wręcz stwierdza: oprogramowanie podstawowe pisze się w C (...real operating systems are often written in C, rarely in Modula 2 and never in Pascal [A.S.Tanenbaum, Modern Operating Systems, s.36. Englewood Cliffs, NJ, Prentice-Hall 1992]).

10 Pojęcia podstawowe

11 Nauka NAUKA, w polskiej tradycji, to przyswajanie jakichkolwiek treści (wiedzy), nabywanie umiejętności oraz to, czego się uczy lub naucza. W krajach zachodnioeuropejskich, angielskie i franc. science wywodzące się z łac. scientia (scio 'wiem') to wiedza poddana ustalonym wymaganiom treściowym i metodologicznym, ale niekoniecznie przeznaczona do nauczania. W językach i kulturach pozaeuropejskich nauka może mieć całkiem inne określenia, które do nas nie dotarły ze względu na bariery kulturowe.

12 Dziedziny i dyscypliny naukowe Naukę dzielimy na dziedziny nauki –nauki techniczne –nauki matematyczne, –nauki przyrodnicze, –nauki społeczne, –.... Dziedziny dzielimy na dyscypliny naukowe. –nauki techniczne Architektura Budowa maszyn Inżynieria lądowa Informatyka....

13 Informatyka Informatyka (ang. computer science, informatics) jest to dyscyplina naukowa w dziedzinie nauk technicznych zajmująca się gromadzeniem, przechowywaniem, przetwarzaniem i wizualizacją informacji za pomocą elektronicznych maszyn cyfrowych.

14 Informacja (Encyklopedia PWN) INFORMACJA [łac.] to obiekt abstrakcyjny, który w postaci zakodowanej ( dane) może być przechowywany (na nośniku danych), przesyłany (np. głosem, falą elektromagnetyczną, prądem elektrycznym), przetwarzany (w trakcie wykonywania algorytmu) i użyty do sterowania (np. komputerem steruje program, będący zakodowaną informacją).

15 Definicja informacji stosowana w teorii informacji Informacja to miara niepewności zajścia pewnego zdarzenia spośród skończonego zbioru zdarzeń możliwych.

16 Informacja Informacja jest pojęciem pierwotnym, płynnym i otwartym na wiele różnorodnych interpretacji. Jedną z najbardziej ogólnych definicji tego pojęcia przedstawili Schizias i Lochovsky. Według nich informacja to każdy czynnik zwiększający zasób naszej wiedzy.

17 Dane Dane to skwantyfikowana, mierzalna informacja.

18 Instancja Instancja (ang. instance), zwana też wystąpieniem, jest nazwanym obszarem pamięci operacyjnej. Termin ten wyraża związek konkretnego bytu z jego definicją (deklaracją) lub typem.

19 Zmienna W informatyce zmienna to opatrzony ustaloną, jednoznaczną nazwą instancja, której mogą być nadawane (przypisywane) różne wartości określonego typu (np. liczbowe, logiczne).

20 Zasięg zmiennej Zasięg zmiennej (ang. variable scope) określa miejsce i czas, w których możliwe jest odczytanie wartości zmiennej i jej modyfikacja. Wyróżnia się dwie techniki określania zasięgu zmiennych: Technika statycznego określania zasięgu Technika dynamicznego określania zasięgu

21 Statyczny zasięg zmiennej W technice statycznej, zasięg zmiennej wynika z umiejscowienia jej nazwy w programie. Przykładowo, jeśli nazwa zmiennej zostanie umiejscowiona w pewnej funkcji, to jej zakres dotyczy tylko tej funkcji i funkcji w niej zagnieżdżonych. Przykład int a; // zmienna globalna o zasięgu statycznym int f(void) { int b; // zmienna lokalna o zasięgu statycznym return 0; }

22 Dynamiczny zasięg zmiennej Dynamiczne określenie zasięgu zmiennej polega na czasowym przesłonięciu nazwy zmiennej o zasięgu statycznym. Przykład int a; // zmienna globalna o zasięgu leksykalnym int f(void) { int a; // zmienna lokalna o zasięgu dynamicznym return 0; }

23 Nazwy zmiennych w programie Dobrze jest stosować rzeczowniki dla oznaczania nazw wszelkich danych (np. licznik), czasowniki dla funkcji i procedur (np. dziel, dodaj), przymiotniki dla zmiennych logicznych (np. prawdziwy). Jednolite nazwy są mało czytelne. Najlepiej jest używać nazw, które coś oznaczają, powiadamiając tym samym użytkownika (intencjonalnie) o możliwościach funkcji, czy też wskazując sens zmiennych, itd. Nazwy bardzo długie, chociaż mogą być komunikatywne, są mało praktyczne jeśli nie stosujemy wyróżnień takich jak duże litery czy podkreślenie (underscore) np. nazwa dodajdwieliczby jest mniej czytelna od nazwy DodajDwieLiczby lub od nazwy dodaj_dwie_liczby.

24 Wielkości stałe Należy nadawać nazwy wielkościom stałym (wykorzystując np. kwalifikator const ), a szczególnie tzw. liczbom magicznym (np. rozmiary tablic).

25 Oprogramowanie Oprogramowanie (ang. software) to zestaw instrukcji oraz danych przeznaczonych do wykonania dla komputera. Oprogramowanie występuje w dwóch postaciach: postać źródłowa, przeznaczona do przygotowania i obróbki przez ludzi, głównie programistów postać binarna, przeznaczona do wykonywania przez komputery, choć użytkownikiem jej działania może również być człowiek Programy przekształcające oprogramowanie z postaci źródłowej na binarną to kompilatory. Niektóre oprogramowanie, np. napisane w całości w językach interpretowanych, może występować tylko w jednej postaci, spełniającej oba zadania.

26 Czym jest programowanie? Programowaniem nazywamy ciąg wszystkich czynności związanych z rozwiązywaniem problemów przy użyciu komputera.

27 Historia języków programowania Algol Fortran LISP Simula Pascal C Smalltalk-72 Smalltalk- 76 Prolog Object Pascal Objective C C++ Eiffel Ada Ada 95 Java

28 Przyczyny zróżnicowania języków programowania Nowe osiągnięcia sprzętowe, np. komputery wieloprocesorowe umożliwiające równoległe i współbieżne wykonywanie programów Nowe i zróżnicowane dziedziny zastosowań.

29 Wprowadzenie do notacji Backusa-Naura (BNF- Backus-Naur Form)

30 Notacja BNF Notacja Backhusa-Naura jest formalizmem pozwalającym wyrazić zarówno składnię, tzn. zasady budowania słów i zdań jak również semantykę, tzn. zasady interpretacji różnorodnych konstrukcji służących m.in. do definiowania języków programowania. W notacji BNF każda konstrukcja języka, zwana kategorii składniowa, jest opisana za pomocą reguły lub kilku reguł. Reguły BNF mają następującą postać składającą się z czterech części (wliczając znak równości i kropkę): LHS = RHS. LHS (lewa strona reguły, ang. left-hand side) jest symbolem słownym oznaczającym nazwę definiowanej kategorii składniowej. Z kolei RHS (right-hand side) jest ciągiem składającym się z symboli reprezentujących różne kategorie definiowanego języka, z tak zwanych symboli terminalnych ujętych w cudzysłowy, oraz z wymienionych niżej operatorów BNF. Prawa strona reguły określa formę, jaką mogą przyjmować definiowana kategoria składniowa (jeśli występuje dla niej kilka różnych reguł, to może ona przyjąć dowolną z określonych przez te reguły postaci).

31 Operatory BNF Operatory BNF mogące występować w prawej stronie reguł (RHS) są następujące: alternatywa - zapisana symbolem | oznacza, że konstrukcja może wystąpić w jednej dwóch, lub więcej, form; konkatenacja - operator domyślny, przyjmowany w przypadku ciągu symboli wypisanych jeden po drugim, oznacza, że definiowana konstrukcja może przyjąć formę będącą takąż sekwencją form reprezentowanych przez te symbole; opcja - zapisana nawiasami kwadratowymi [ ] oznacza, że zawartość tych nawiasów może wystąpić w całości lub w ogóle nie wystąpić w opisywanej formie; powtórzenie - zapisane nawiasami klamrowymi { }, oznacza, że zawartość tych nawiasów może wystąpić w całości powtórzona dowolną ilość razy (0 lub więcej); grupowanie - zapisane zwykłymi nawiasami ( ) nie ma żadnego specjalnego znaczenia w regułach BNF i służy w nich do zmiany interpretacji RHS ze względu na priorytety operatorów.

32 Przykład Liczba całkowita. Poniższy zestaw reguł BNF określa pojęcie liczby całkowitej. Liczba_calkowita = [znak-liczby] liczba_calkowita_bez_znaku. znak-liczby = + | -. liczba_calkowita_bez_znaku = ciag_cyfr. ciag_cyfr = cyfra {cyfra}. cyfra = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9. Przykłady liczb całkowitych: 0; +123; -45.

33 Przykład Liczba zmiennopozycyjna. Poniższy zestaw reguł BNF określa pojęcie liczby zmiennopozycyjnej. liczba_ zmiennopozycyjna = [znak_liczby] liczba_ zmiennopozycyjna _bez_znaku. liczba_ zmiennopozycyjna_bez_znaku = liczba-calkowita-bez-znaku. ciag-cyfr [ e mnoznik_skalujacy ] | liczba_calkowita_bez_znaku e. mnoznik-skalujacy. mnoznik-skalujacy = liczba_calkowita. Przykłady liczb rzeczywistych: 0.0; +1.23; 4e5; e-5.

34 Forma i znaczenie Zwróćmy uwagę w powyższych przykładach reguł BNF na fakt, że do obydwu kompletach reguł załączone są, wyjaśnienia słowne sensu tych reguł. Same reguły wyjaśniają, tylko formę, nie dotyczą zaś w żaden sposób znaczenia. Taka jest rola gramatyki - określa ona poprawną formę zdań języka, lecz nie daje informacji, które zdania są sensowne, a które nie, i jakie jest ich znaczenie. Jednym z często przytaczanych przykładów zdania ilustrującego to zjawisko jest: Bezbarwne zielone pomysły śpią wściekle. Zdanie to, jakkolwiek niezrozumiałe i bezsensowne, jest całkowicie poprawne i zgodne z gramatyką języka polskiego.

35 Diagramy syntaktyczne

36 Identyfikator Przykłady: A A1 Mnoznik litera cyfra

37 Litera Przykłady: a b A B.... a b Z _

38 Cyfra Przykłady:

39 Paradygmaty programowania

40 Najważniejsze paradygmaty programowania W celu usystematyzowania procesu programowania powstało wiele technik tworzenia programów określanych łącznie terminem programowania systematycznego. Obecnie uważa się, że najważniejszymi z nich są: programowanie proceduralne (ang. procedural programming) programowanie strukturalne (ang. structured programming) programowanie zorientowane obiektowo (ang. object- oriented programming) - w C nie ma mechanizmów wspierających ten paradygmat

41 Programowanie proceduralne (ang. procedural programming) Programowanie proceduralne jest metodą tworzenia oprogramowania bazująca na koncepcji jednostek programowych (modułów) i zasięgu zmiennej. Program proceduralny składa się z jednej lub wielu modułów. Każdy moduł składa się z jednej lub wielu procedur (funkcji, podprogramów, metod). Procedury mogą być zagnieżdżone. Każdy program może posiadać jeden lub więcej poziomów lub zakresów zawierających bloki. Na każdym poziomie mogą być definiowane zmienne niewidoczne z innych poziomów.

42 Programowanie strukturalne

43 Podstawy teoretyczne programowania strukturalnego Podstawy teoretyczne programowania strukturalnego dali C. Bohm i G. Jocopini. W roku 1966 wykazali, że wszystkie problemy obliczeniowe dają się doprowadzić do równoważnej postaci, w której występują tylko trzy struktury algorytmiczne: Sekwencje Wybór (selekcje) Powtórzenia (iteracje)

44 List Dijkstry W marcu 1968 r. w czasopiśmie Communication of the ACM (11,3, pp ) został opublikowany słynny obecnie list prof. E.W. Dijkstry, pt. Uznając GOTO za szkodliwe. Autor omówił w nim anomalie zdania goto, skrytykował jego zły wpływ na czytelność i niezawodność programów oraz zaproponował usunięcie go ze wszystkich języków programowania. Na podstawie dowodu Bohma i Jocopiniego Dijkstra stworzył on w latach podstawy programowania strukturalnego.

45 Programowanie strukturalne (ang. structured programming) Programowanie strukturalne to technika tworzenia programów bazująca na zasadach programowania proceduralnego. Najbardziej znanymi podejściami są: Programowanie strukturalne Jacksona, bazujące na uporządkowanych strukturach danych Programowanie strukturalne Dijkstray, bazujące na podziale programu na podsekcje, z których każda posiada pojedynczy punkt wejścia i pojedynczy punkt wyjścia. Generalnie programowanie strukturalne polega na podziale kodu na małe jednostki (funkcje), realizujące proste, pojedyncze zadania.

46 Elementarny wstęp do algorytmiki

47 Literatura Banachowski L., Diks K., Ryter W.(2003): Algorytmy i struktury danych. WNT, Warszawa. Cormen T.H., Leiserson Ch. E., Rivest R.L. (2002): Wprowadzenie do algorytmów. WNT, Warszawa. Wirth N.(2002): Algorytmy + struktury danych = programy. WNT, Warszawa. Harel D. (2001): Rzecz o istocie informatyki. Algorytmika. WNT, Warszawa.

48 Algorytmika Algorytmika ta nauka o algorytmach ( Harel D. (2001): Rzecz o istocie informatyki. Algorytmika. WNT ) – obszarze ludzkich dociekań, wiedzy i doświadczeniach w zakresie budowy algorytmów. Harel pisze, że algorytmika tkwi w centrum wszystkich działów informatyki i... można o niej powiedzieć, że jest ważna dla większości nauk matematyczno-przyrodniczych, ekonomii i techniki.

49 Etymologia słowa algorytm Pojęcie algorytm pochodzi od nazwiska arabskiego matematyka Muhammeda ibn Musy al-Chorezmi (z Chorezmu), który opisał system dziesiętnego kodowania liczb i sztukę liczenia w tym systemie (ok. roku 820 n. e.)

50 Definicje algorytmu Algorytm jest pojęciem abstrakcyjnym. Jest to dokładny przepis podający sposób rozwiązania określonego zadania w skończonej liczbie kroków. Algorytm - informatyczny opis rozwiązania zadania [Turski] Obecnie przez algorytm rozumie się opis danych wraz z opisem czynności, które należy wykonać z tymi danymi, aby osiągnąć określony cel.

51 Rodzaje algorytmów Rozróżnia się rodzaje algorytmów: numeryczne, operujące na liczbach (np. algorytm Euklidesa) nienumeryczne, operujące na danych nieliczbowych (np. algorytm sortowania lub wyszukiwania dokumentów w dużych zbiorach). Algorytm jest sekwencyjny, gdy kolejność czynności jest określona jednoznacznie, a niesekwencyjny (równoległy, współbieżny), gdy porządek ten nie jest określony między pewnymi operacjami.

52 Najważniejsze cechy algorytmów (1/2) możliwość wyrażania algorytmu w różnych językach, np. języku naturalnym, w językach programowania, poprzez sieć działań; algorytm zapisany w języku programowania nazywa się programem możliwość wyrażania algorytmu przy pomocy skończonej liczby symboli, bez używania zwrotów odwołujących się do analogii, jak "itd." realizowalność, tzn. fizyczna możliwość wykonania poleceń możliwość wielokrotnego wykonywania (na ogół z różnymi danymi); pojedyncze wykonanie algorytmu nazywa się procesem; wyznaczone przezeń działania są sformułowane na poziomie elementarnym, uwzględniającym możliwości realizacyjne

53 Najważniejsze cechy algorytmów (2/2) Algorytm jest statycznym opisem procesu rozwiązania zadania. Dynamikę tego procesu osiąga się poprzez realizację programu będącego zakodowaną postacią algorytmu. Algorytm może być krótki, lub opisywać długi (nawet niekończący się) proces. Przykładów dostarcza statystyka zajmująca się opracowywaniem dużych zbiorów danych.

54 Formy zapisu i prezentacji algorytmów Metody słowne Metody graficzne

55 Metody opisowe (słowne) Metody te oparte są na: języku etnicznym lub branżowym języku formalnym, np. języku programowania

56 Metody graficzne (wykreślne) tablice decyzyjne sieci działań (schematy blokowe, schematy wykonawcze) strukturo-programy, zwane schematami Nassi- Schneidermana (N-S) schematy czynnościowe (w tym schematy obiegu dokumentów) schematy przetwarzania (systemów przebiegu) technika grafów tablice krzyżowe matryce sprzężeń

57 Sieci działań

58 Definicja sieci działań SIEĆ DZIAŁAŃ to graficzne przedstawienie przepisu rozwiązania zadania (algorytmu) w postaci tzw. skrzynek zawierających polecenia poszczególnych czynności, połączonych strzałkami wskazującymi kolejność ich wykonywania. Podstawowe rodzaje skrzynek: skrzynki krańcowe (skrzynki początkowe i skrzynki końcowe) skrzynki warunkowe skrzynki wyboru skrzynki bezwarunkowe (skrzynki operacyjne) skrzynki wejścia-wyjścia skrzynki kolekcyjne skrzynki łącznikowe skrzynki podprogramów

59 Skrzynki krańcowe Są dwa rodzaje skrzynek krańcowych. skrzynka początkowa (START) pokazuje miejsce rozpoczęcia wykonywania algorytmu. skrzynka końcowa (STOP) pokazuje miejsce lub miejsca zakończenia wykonywania algorytmu. START STOP

60 Skrzynki warunkowe Skrzynki warunkowe zawierają polecenia sprawdzenia wartości określonych wyrażeń logicznych. Wyrażeni e logiczne ? Tak Nie

61 Skrzynki wyboru Skrzynki wyboru obrazują wybór jednego z możliwych wariantów w zależności od wartości zmiennej lub wyrażenia Zmienna = (Wyrażenie =) w 1 w 2... w i... w n

62 Skrzynki bezwarunkowe (skrzynki operacyjne) Skrzynki bezwarunkowe zawierają operacje inne niż sprawdzenie warunków logicznych. Operacja

63 Skrzynki wejścia-wyjścia Skrzynki wejścia-wyjścia obrazują operacje wprowadzania i wypisywania danych. Wprowadź

64 Skrzynki kolekcyjne Skrzynka kolekcyjna łączy dwie różne drogi algorytmu.

65 Skrzynki łącznikowe Wyróżnia się dwa rodzaje skrzynek łącznikowych: skrzynki łącznikowe na stronie obrazują przejście pomiędzy fragmentami algorytmu w ramach tej samej strony skrzynki łącznikowe międzystronicowe obrazują przejście pomiędzy fragmentami algorytmu znajdującymi się na różnych stronach Nr Strona | Nr

66 Skrzynki podprogramów Skrzynki podprogramów obrazuje wywołanie podprogramu i realizowane przez nią zadania. Nazwa podprogramu

67 Przykład Narysować sieć działań (schemat blokowy) algorytmu obliczającego sumę dwóch liczb całkowitych.

68 Zadania obliczeniowe

69 Określenie terminu zadanie obliczeniowe Algorytmy określają sposób rozwiązania pewnych rodzajów zadań, zwanych zadaniami obliczeniowymi lub algorytmicznymi. Zbiór danych wejściowych x Zbiór danych wyjściowych y Zadanie obliczeniowe składa się ze: scharakteryzowania dopuszczalnego, być może nieskończonego, zestawu danych wejściowych x; specyfikacji pożądanych wyników y jako funkcji obliczalnej f danych wejściowych Rys. Rozwiązanie algorytmiczne f

70 Rodzaje poleceń w algorytmach Algorytmy zawierają dwa rodzaje instrukcji (poleceń do wykonania): Instrukcje akcji (czynności), będące zleceniami wykonania działań elementarnych (podstawowych) Instrukcje sterujące, determinujące porządek wykonywania instrukcji czynności.

71 Problemy informatyki

72 Podział problemów informatyki Nie wszystkie problemy dają się rozwiązać algorytmicznie. Ogół problemów dzieli się (H. Simon, Newell (1958): Heuristic problem solving. Operations Research, t.4) na: dobrze ustrukturalizowane o nieokreślonej strukturze o słabo określonej strukturze

73 Problemy dobrze ustrukturalizowane Problemy dobrze ustrukturalizowane dają się przedstawić w formie ilościowej, a ich rozwiązanie – w postaci algorytmu. Do ich rozwiązania stosuje się metody badań operacyjnych (operations research).

74 Problemy o nieokreślonej strukturze Problemy o nieokreślonej strukturze można je przedstawić w postaci słownego opisu; rozwiązuje się je przy pomocy metod heurystycznych. Istotą metod heurystycznych jest dochodzenie do rozwiązania poprzez sformułowanie hipotezy.

75 Problemy o słabo określonej strukturze Problemy o słabo określonej strukturze to problemy mieszane o przewadze aspektów jakościowych; do ich rozwiązywania stosowane są metody analizy systemowej. Analiza systemowa jest nauką systemową, której przedmiotem jest postrzeganie i wyjaśnianie istotnych problemów związanych z wyborem decyzji lub linii postępowania.

76 Zastosowania algorytmów Algorytmy znajdują zastosowanie w różnych dziedzinach działalności człowieka: W informatyce, szczególnie w związku z tworzeniem oprogramowania; W matematyce i naukach przyrodniczych, gdzie przyjmują postać wzorów i formuł; W zarządzaniu, wymagającym posługiwania się różnorodnymi zasadami i metodami; W administracji, w której istotną rolę odgrywa wydawanie i posługiwanie się różnorodnymi przepisami.

77 Przykład algorytmizacji działań - algorytm wynalazku Altszullera (sposób rozwiązania dowolnego zadania) 1.Faza formułowania zadania P: - wybór i zdefiniowanie P, - uściślenie warunków odnoszących się do P, jak i towarzyszących procesowi jego rozwiązywania. 2.Faza analizy P: analiza uściślonej wersji P. 3.Faza oceny P: - ocena istotności P, - eliminacja sprzeczności technicznych, - (ewentualnie powrót do fazy formułowania zadania P lub do fazy analizy P). 4.Faza syntezy: konstruowanie rozwiązania P. 5.Faza decyzji: - jeżeli rozwiązanie uzyskane w poprzedniej fazie spełnia warunki przyjęte w fazie formułowania zadania P, to przyjęcie tego rozwiązania do realizacji; - w przeciwnym przypadku powrót do fazy formułowania zadania.

78 Zasady poprawnego programowania

79 Cechy dobrych programów Poprawność (zgodność z wymogami użytkownika) Niezawodność (zdolność do wspomagania pracy użytkownika) Przenośność (łatwość instalacji na różnych komputerach) Łatwość konserwacji (podatność na zmiany) Czytelność (prostota jest jedną z najważniejszych cech dobrych programów) Efektywne wykorzystanie zasobów (procesor, pamięć) Szybkość

80 Styl programowania (1/4) Styl programowania to sposób pisania kodu, który będzie czytelny zarówno dla jego twórcy, jak i innych programistów. Dobry styl jest podstawą dobrego programowania. Dobrego stylu programowania można nauczyć się wyłącznie poprzez zdobycie praktyki. Nie ma uniwersalnej reguły gwarantującej pisanie dobrych programów. Istnieje kilka prostych i przydatnych zasad.

81 Styl programowania (2/4) Należy używać nazw opisowych dla definicji globalnych, krótkich zaś dla lokalnych. Należy programować w sposób jednolity. Używać nazw czynności dla funkcji. Dbać o precyzję. Stosować wcięcia, aby uwidocznić strukturę. Używać naturalnej postaci wyrażeń. Używać nawiasów, aby rozwiać niejasności. Dzielić wyrażenia złożone. Pisz jasno! Należy uważać na efekty uboczne.

82 Styl programowania (3/4) Stosować jednolity sposób rozmieszczania wcięć i nawiasów klamrowych. Należy używać idiomów. Programując konstrukcje wielokrotnego wyboru, używać instrukcji else-if. Należy wystrzegać się makroinstrukcji grających rolę funkcji. Treść i argumenty makroinstrukcji ujmować w nawiasy. Nadawać nazwy liczbom magicznym. Definiować liczby jako stałe. Używać stałych znakowych zamiast liczb całkowitych. Do obliczania rozmiarów instancji (zmiennych, typów) używać operatorów języka. Nie pisać banałów!

83 Styl programowania (4/4) Komentować funkcje i dane globalne. Nie komentować złego kodu, lecz go poprawiać. Komentarz nie może być sprzeczny z kodem. Wyjaśniać, nie gmatwać! To samo rób wszędzie tak samo. Zwalniaj zasoby w tej samej warstwie, w której były przydzielone. Objaśnij swój program komuś innemu. Trzymaj się standardu.

84 Styl akapitowy w C (C indent style, C bracing style) Styl akapitowy określa zasady agregacji kodu w postaci bloków w celu budowy prostej i czytelnej struktury programu. Wyodrębnia się kilka rodzajów stylu akapitowego: Styl BSD/Allmana Styl Kernela/K&R Styl Whitesmithsa Styl GNU

85 Styl BSD/Allmana W stylu tym nawiasów klamrowych używa się po segmentach inicjujących. Przykład while(x == y) { something(); somethingelse(); } Główną zaletą tego stylu jest wyraźne rozdzielenie zawartości bloku od segmentów inicjujących, w powyższym przykładzie od instrukcji while.

86 Styl Kernela/K&R W tym stylu nawias klamrowy otwierający umieszczony jest an tym samym poziomie co segment inicjujący, natomiast nawias klamrowy zamknięty umieszczony jest pod ostatnią instrukcją bloku. Przykład while(x == y) { something(); somethingelse(); } Główną zaletą tego stylu jest podkreślenie tego, że blok jest logicznie związany z segmentem inicjującym. Wadą tego stylu w porównaniu ze stylem BSD jest trudność w lokalizacji początku bloku.

87 Styl Whitesmithsa Styl ten jest podobny do BSD: while(x == y) { something(); somethingelse(); } W stylu tym sposób umieszczenia nawiasów klamrowych umożliwia łatwe, wizualnie wydzielenie blok.

88 Styl GNU Jest to styl pośredni między stylami BSD i Whitesmithsa. while(x == y) { something(); somethingelse(); } Nawiasy klamrowe umieszczane są w jednej kolumnie zajmującej pozycję środkową między lewym skajem położeniem, a kolumną, w której pisane są kolejne instrukcje bloku. Styl ten zachowuje zalety stylu BSD i satysfakcjonuje zwolenników stylu Whitesmithsa.

89 Kryzys oprogramowania

90 Początki kryzysu oprogramowania Inżynieria oprogramowania wyrosła z tzw. kryzysu oprogramowania powstałego w latach 60. i 70. XX w., kiedy to wiele projektów programistycznych kończyło się fiaskiem. Wraz ze wzrastającą wszechobecnością komputerów, wzrastało zapotrzebowanie na coraz lepsze oprogramowanie.

91 Efektywność projektów informatycznych W latach 90. XX w. ok. 90% poważnych firm programistycznych w USA uważa, że często zdarzają się im opóźnienia w realizacji projektów informatycznych. W Polsce co prawda nie przeprowadzono podobnych badań, lecz z pobieżnej obserwacji wynika, że podobne problemy mają polskie firmy softwarowe.

92 Najważniejsze przyczyny kryzysu oprogramowania (1/2) Duża zawodność systemów komputerowych Duża złożoność SI Niepowtarzalność przedsięwzięć informatycznych Nie przejrzystość procesu budowy oprogramowania, co oznacza że proces ten jest bardziej sztuką niż zalgorytmizowaną działalnością inżynierską Nieprzewidywalność czasu wytwarzania oprogramowania. Pozorna łatwość wytwarzania i dokonywania poprawek w oprogramowaniu Eklektyczny charakter narzędzi i języków programowania Uzależnienia organizacji od systemów komputerowych i przyjętych technologii informatycznych

93 Najważniejsze przyczyny kryzysu oprogramowania (2/2) Frustracje projektantów oprogramowania i programistów wynikające ze zbyt szybkiego postępu w zakresie języków, narzędzi i metod oraz uciążliwości i długotrwałości procesów produkcji, utrzymania i pielęgnacji oprogramowania. Problemy współdziałania niezależnie zbudowanych modułów oprogramowania. Problemy przystosowania istniejących i działających systemów do nowych wymagań, tendencji i platform sprzętowo-systemowych

94 Przykład nieudanego projektu programistycznego - IBM OS 360 Jednym z najbardziej spektakularnych przykładów projektów zakończonych niepowodzeniem jest system operacyjny OS 360 dla komputerów IBM 360 Series. Projekt ten był realizowany przez dziesięć lat i był jednym z najbardziej złożonych projektów informatycznych kiedykolwiek realizowanych. I mimo iż składał się z ok programów, to nigdy nie spełnił oczekiwań użytkowników. Ostatecznie, w późnych latach 90. ubiegłego wieku, został on włączony do Linuxa.

95 Kryzys oprogramowania - podsumowanie Kryzys oprogramowania od końca lat 60. Coraz lepszy i tańszy sprzęt coraz większe potrzeby użytkowników coraz większe programy Duże programy dużo błędów -> duże koszty Koniecznością było opracowanie metodologii systematycznej pracy. Narodziny inżynierii oprogramowania. Programowanie funkcjonalne - programowanie deklaratywne - programowanie proceduralne / techniki strukturalne - programowanie modularne - programowanie zorientowane obiektowo – programowanie komponentowi – CASE - UML

96 Jak walczyć ze złożonością ? Zasada dekompozycji: rozdzielenie złożonego problemu na podproblemy, które można rozpatrywać i rozwiązywać niezależnie od siebie i niezależnie od całości. Zasada abstrakcji: eliminacja, ukrycie lub pominięcie mniej istotnych szczegółów rozważanego przedmiotu lub mniej istotnej informacji; wyodrębnianie cech wspólnych i niezmiennych dla pewnego zbioru bytów i wprowadzaniu pojęć lub symboli oznaczających takie cechy. Zasada ponownego użycia: wykorzystanie wcześniej wytworzonych schematów, metod, wzorców, komponentów projektu, komponentów oprogramowania, itd. Zasada sprzyjania naturalnym ludzkim własnościom: dopasowanie modeli pojęciowych i modeli realizacyjnych systemów do wrodzonych ludzkich własności psychologicznych, instynktów oraz mentalnych mechanizmów percepcji i rozumienia świata.

97 Język C

98 Język C został zaprojektowany przez Dennisa Ritchiego w latach 60. XX wieku w celu pisania oprogramowania systemu UNIX.

99 Główne cechy języka C Skupia się na realizacji paradygmatów programowania proceduralnego z udogodnieniami stylu programowania strukturalnego. Dostęp do oprogramowania niskopoziomowego poprzez wykorzystanie wskaźników do lokalizacji obszarów pamięci zajętych przez to oprogramowanie. Stosowanie standardowych funkcji bibliotecznych umożliwiających rozwiązanie typowych zadań. Wykorzystanie języka preprocesora umożliwiających definiowanie makr, włączanie różnych plików źródłowych oraz wykonywanie kompilacji warunkowej. Statyczny, leksykalny zasięg zmiennych. Stosowanie operatorów o złożoności O(1).

100 Historia C 1968 – 1973 okres tworzenia C 1973 rekomiplacja jądra UNIX napisanego w C 1978 Dennis Ritchie and Brian Kernighan publikują książkę The C Programming Language (zwana białą księgą C lub K&R). od początku lat 80. XX w, C zdobywa popularność poza Bell Labs; po końca lat 80. ubiegłego wieku Bjarne Stroustrup i współpracownicy z Bell Labs opracowują język programowania zorientowany obiektowo, nazwany później C++.

101 Wersje C K&R C ANSI C i ISO C C99

102 K&R C Ta wersja C została opisana w książce Kernighana I Ritchie'go The C Programming Language (1978). Wprowadzono w nim następujące elementy do C: Typy strukturalne long int data type unsigned int data type operator ( =+) został przekształcony w do postaci ( +=); K&R C został udokumentowany w standardzie ANSI C

103 ANSI C i ISO C W roku 1989, C został po raz pierwszy przedstawiony standard ANSI w dokumencie ANSI X "Programming Language C". ANSI C stanowił rozwiniecie K&R C. ANSI C został wzbogacony o następujące elementy: funkcje zwracające wartości typu void (void functions); funkcje zwracające wartości typu struct i union; typ void * ; kwalifikator const ; separowanie nazw pól struktur; biblioteka stdio stała się biblioteką standardową; plik nagłówkowy stddef.h I szereg innych plików stało się standardowymi plikami nagłówkowymi.

104 C99 W roku 1999 opublikowany został standard ISO 9899:1999, który w marcu 2000 roku został zaadaptowany przez ANSI jako standard. Nowe elementy tego standardu: Funkcje inline; Nowe typy danych, w tym long long int, boolean i typ reprezentujący liczby zespolone (complex); Tablice o zmiennej długości (variable-length arrays); Komentarze jednowierszowe (//); Wiele nowych funkcji bibliotecznych i plików nagłówkowych, w tym stdint.h.

105 C++ C++, obiektowy język programowania zaprojektowany przez B. Stroustrupa i in., w warstwie proceduralnej osadzony w notacji języka C, wpływ języków Algol 68 i Simula 67 oraz Ada, ML i Clu. Charakterystyczne cechy: dociążanie funkcji i operatorów, dziedziczenie z wielu klas, obsługa wyjątków, klasy parametryczne. Wersja pierwotna (1979) nosiła nazwę C z klasami (C with classes), a jej kompilatory generowały kod w języku C. Standaryzowany od 1994, standard ostateczny ISO/ANSI C++ (ANSI 14882) z 1 września Liczne kompilatory, m. in. Borland C++, Microsoft C++, Watcom C++, implementacje systemów operacyjnych, baz danych. Java, Smalltalk

106 Przewodnik po języku C

107 Strumienie (ang. streams) Strumień to ciąg danych tego samego lub różnych typów danych, pozwalający na sekwencyjny dostęp do tych wartości. Wyróżnia się strumienie: binarne – są to sekwencje nie przetworzonych bajtów znaków - są to ciągi znaków podzielone na wiersze tekstowe – ciąg wierszy, w którym każdy wiersz zawiera zero lub więcej znaków i kończy się znakiem końca wiersza Strumienie przetwarza się najczęściej w ten sposób, że wiąże się je z zewnętrznymi plikami dyskowymi lub wewnętrznymi buforami, które można traktować jako plik dla czasowego przechowywania danych.

108 Przekierowanie Skompilowany program, np. proba.exe, można uruchomić z wiersza poleceń DOS: c>proba.exe > proba.txt, co spowoduje, ze zamiast na ekran dane będę wpisywane do utworzonego pliku tekstowego. Dane z pliku tekstowego (np. dane.txt) można wczytać do programu wykonywalnego przy pomocy polecenia: c:> proba.exe < dane.txt Dwie ww. operacje można połączyć (zdarza się to rzadko): c:> proba.exe wy.txt

109 "Hello, World!" w C Poniższy prosty przykład drukuje tekst "Hello, World!" na standardowym urządzeniu wyjściowym (zwykle jest to monitor). Program ten po raz pierwszy pojawił się w K&R C. #include int main(void) { printf("Hello, World!\n"); return 0; }

110 Formatowane wyjście: funkcja printf() Składnia funkcji printf(): int printf(const char *format[, argument,...]); Funkcja ta pod nadzorem argumentu format, formatuje i wypisuje swoje argumenty do standardowego wyjścia (stdout). Format zawiera obiekty dwojakiego rodzaju: znaki, które są kopiowane do strumienia wyjściowego specyfikacje przekształceń, z których każda wskazuje sposób przekształcenia i wypisania kolejnego argumentu funkcji. Każdą specyfikację przekształcenia (slajd+1) rozpoczyna znak %, a kończy znak charakterystyczny dla danego przekształcenia (slajd+2) Przykład int l=1; char c[]="hello"; printf("%d %s",l,c);

111 Specyfikacje przekształcenia w printf() Między znakiem % i znakiem przekształcenia mogą (w podanej kolejności) wystąpić: Minus (-), zalecający przesunięcie argumentu do lewego krańca jego pola Liczba określająca minimalny rozmiar pola. W razie potrzeby pole będzie uzupełnione z lewej strony (lub z prawej, gdy żądano dosunięcia w lewo) Kropka (.) oddzielająca rozmiar pola dla precyzji. Liczba określająca precyzję, tj max. liczbę znaków dla tekstu, liczbę cyfr po kropce dla wartości zmiennopozycyjnej lub minimalną liczbę cyfr dla wartości całkowitej. Jedna z liter: –h – jeśli argument całkowity należy wypisać jako short, –l – jeśli jako long.

112 Podstawowe przekształcenia dla printf() ZnakDana wejściowaTyp argumentu d, iLiczba całkowita dziesiętnaInt oLiczba całkowita ósemkowa (bez wiodącego O)Int xLiczba całkowita szesnastkowa (bez wiodących 0X)Int uLiczba całkowita dziesiętna bez znakuUnsigned int cJeden znakChar sCiąg znaków do napotkania \0 lub wyczerpania liczby znaków określonych przez precyzję Char * f[-]m.dddddd (precyzja – domyślnie 6 znaków)Double E, e[-]m.dddddde±xxDouble G, gWypisywana w formacie %e jeśli wykładnik jest mniejszy niż -4 albo większy lub równy precyzji; w przeciwnym przypadku wypisywany w formacie %f; nie wypisuje nieznaczących zer i kończącej kropki. Double pWskaźnik, postać zależna od implementacjiVoid * %Nie będzie żadnego przekształcenia argumentu; wypisywany znak %

113 Formatowane wejście: funkcja scanf() Składnia funkcji scanf(): int scanf(const char *format[, address,...]); - Funkcja ta wczytuje znaki ze standardowego wejścia, interpretuje je zgodnie ze specyfikacjami zawartymi w argumencie format i zapamiętuje znaki w miejscach wskazanych przez argument address. Funkcja kończy swe działanie, gdy zinterpretuje cały format. Wartości zwracane przez funkcję: W przypadku sukcesu, funkcja zwraca liczbę wprowadzonych wartości. W przypadku niepowodzenia, funkcja zwraca: - 0 – jeśli nie odczytano żadnych wartości - EOF – jeśli odczytany został znak końca ciągu znaków. Przykład int i,l; l=scanf("%d",&i); fflush(stdin); //ZALECANE-czyści standardowy bufor wej. printf("%d %d",l,i);

114 Podstawowe przekształcenia dla scanf() ZnakDana wejściowaTyp argument u dLiczba całkowita dziesiętnaInt * iLiczba całkowita; może wystąpić w postaci ósemkowej (z wiodącym O) lub szesnastkowej (z wiodącymi 0X) Int * oLiczba całkowita ósemkowa (z lub bez wiodącego O)Int * uLiczba całkowita dziesiętna bez znakuUnsigned int * xLiczba całkowita szesnastkowa (z lub bez wiodących 0X)Int * cZnaki; następne znaki (domyślnie 1) umieszcza się we wskazanej tablicy; nie obowiązuje zasada pomijania białych znaków; aby przeczytać najbliższy czarny znak należy zastosować %1s Char * sTekst (ale nie napis, tzn. bez cudzysłowów); argument powinien wskazywać na tablicę znakową o rozmiarze wystarczającym do przyjęcia tekstu z dodanym na końcu znakiem \0. Char * e f gLiczba zmiennopozycyjna z opcjonalnymi: znakiem, kropką dziesiętna i wykładnikiem Float * %Nie będzie żadnego przypisania

115 Niektóre funkcje obsługujące znakowe operacje we/wy (1/2) getche() - funkcja zwraca znak wprowadzony z konsoli i wyświetla go na ekranie; nie czeka na enter; getchar() – funkcja zwraca znak wprowadzony z konsoli i wyświetla go na ekranie; wymaga wprowadzenia enter; działa nie tylko z konsolą, ale również z ogólnymi urządzeniami we/wy; getch() – funkcja zwraca znak wprowadzony z konsoli, ale nie wyświetla go na ekranie; putch() – funkcja wysyła pojedynczy znak na ekran; putchar() – funkcja wyprowadza znak na ekran; działa nie tylko z konsolą, ale również z ogólnymi urządzeniami we/wy.

116 Niektóre funkcje obsługujące znakowe operacje we/wy (2/2) #include void main (void) { char c1, c2; clrscr(); printf("/n znak 1: "); c1 = getche();//nie czeka na enter printf("\n znak2: "); c2 = getchar();//czeka na enter printf("\n\n znaki: "); putch(c1); putchar(c2); getch(); }

117 Konwencje leksykalne Program w C składa się z jednej lub większej liczby jednostek leksykalnych zapisanych w plikach. Program jest tłumaczony w kilku fazach. W pierwszej fazie interpretowane są wiersze preprocesora zaczynające się znakiem # oraz wykonywane są makrodefinicje i makrorozwiązania. Po zakończeniu tej fazy program jest zredukowany do ciągu jednostek leksykalnych

118 Jednostki leksykalne Istnieje sześć jednostek leksykalnych: 1. Nazwy (identyfikatory) 2. Słowa kluczowe 3. Stałe 4. Napisy 5. Operatory 6. Separatory Ignorowane są białe znaki (odstępy, znaki tabulacji poziomej i pionowej, znaki nowego wiersza i nowej strony, komentarze), chyba że rozdzielają jednostki leksykalne.

119 Komentarze Istnieją dwa rodzaje komentarzy: 1. Jednowierszowe: zaczynają się od dwuznaku (//) 2. Wielowierszowe: zaczynają się od dwuznaku (/*), a kończą dwuznakiem (*/); Komentarze nie mogą być zagnieżdżone i nie mogą występować w napisach i stałych znakowych. Przykład int i=j/k; //dzielenie przez k int i = j//* dzielenie przez k*/k;

120 Nazwy (identyfikatory) Nazwa jest ciągiem liter i cyfr i znaku podkreślenia (_). Znak podkreślenia zalicza się do liter. Ograniczenia: nazwa może składać się z max. 32 lub 64 znaków pierwszym znakiem musi być litera rozróżniane są wielkie i małe litery. Identyfikatory rozpoczynające się od znaku pojedynczego lub podwójnego znaku podkreślenia (_), (__) są zarezerwowane na użytek biblioteki standardowej.

121 Słowa kluczowe (1/5) Słowo kluczowe Odpowiednik polski Opis autoAutomatyc zny Specyfikacja klasy zmiennej (zmienna automatyczna) breakPrzerwijInstrukcja(przerwij wykonanie bloku/pętli) caseWariantInstrukcja (element konstrukcji warunkowej switch-case) charZnakTyp danych (pojedynczy bajt, interpretowany domyślnie jako kod ASCII znaku) constStałaModyfikator (dodawany do deklaracji w przypadku stałych) continu e KontynuujInstrukcja (wróć do początku pętli programowej)

122 Słowa kluczowe (2/5) defaultDomyślnyEtykieta (element konstrukcji warunkowej switch-case) doWykonajInstrukcja (element instrukcji do-while) doublepodwójnyTyp danych (liczba o podwójnej długości i podwójnej precyzji) elseJeśli nie, toInstrukcja (element instrukcji if-else-if...) enumWyliczeniowyTyp danych (typ porządkowy, wyliczeniowy) externZewnętrznySpecyfikator klasy zmiennej (rodzaj pamięci)

123 Słowa kluczowe (3/5) floatZmiennoprzec inkowy Typ danych (liczby zmiennoprzecinkowe) forDlaInstrukcja (nagłówek pętli programowej) gotoIdź doInstrukcja (przejście do etykiety) IfJeśliInstrukcja (element konstrukcji warunkowej if-else-if...) IntCałkowityTyp danych (liczby całkowite, zazwyczaj 2-bajtowe) longDługiTyp danych (liczba o powiększonej długości i precyzji) registerRejestrowySpecyfikator rodzaju pamięci (zmienna do rejestru)

124 Słowa kluczowe (4/5) returnZwróćInstrukcja (zwrot wartości przez funkcję) shortKrótkiTyp danych (liczba całkowita, krótka) signedZe znakiemSpecyfikator typu danych (pierwszy bit liczby to znak +/-) staticstatycznySpecyfikator rodzaju pamięci (zmienna statyczna) structStrukturaSpecyfikator typu danych (złożony z danych różnych typów) switchPrzełączInstrukcja (element konstrukcji warunkowej switch-case) typedefDefiniuj typInstrukcja (definiuje nowy typ danych)

125 Słowa kluczowe (5/5) unionUniaSpecyfikator typu danych unsignedBez znakuSpecyfikator typu danych (liczba bez znaku, tj. tylko dodatnia) voidNieokreślonySpecyfikator typu danych volatileUlotnySpecyfikator klasy zmiennej (rodzaj pamięci); zmienna, która może ulegać niezauważalnemu nadpisaniu, dotyczy np. zmiennych rejestrowych. whileDopókiSłowo poprzedzające warunek (element pętli)

126 Stałe Wyróżnia się pięć rodzajów stałych: 1. Całkowite 2. Znakowe 3. Tekstowe 4. Zmiennopozycyjne 5. Wyliczeniowe Typ stałej zależy od jej postaci, wartości i przyrostka.

127 Stałe całkowite Stałe całkowite można zapisywać w postaci: Dziesiętnej: używa się cyfr 0,1,...,9; Ósemkowej: używa się cyfr 0,1,...,7. Stałą uważa się za ósemkową, jeżeli rozpoczyna się od 0 (liczba zero); Szesnastkowej: używa się cyfr 0,1,...,9 i liter A,B,...,F. Stałą uważa się za szesnastkową, jeżeli rozpoczyna się od 0X (liczba zero i znak x); Stała całkowita możne być opatrzona przedrostkiem U do oznaczenia, że jest bez znaku (unsigned). Może być opatrzona przyrostkiem L do oznaczenia, że jest długa (long). Przykłady L U5 013 //liczba ósemkowa 0X13 //liczba szesnastkowa

128 Stałe znakowe Stała znakowa składa się z jednego znaku, zawartym w apostrofach np. x. Stałe znakowe nie zawierają m.in. Apostrofów () i znaków nowego wiersza. Dla wyrażenia tych i innych znaków stosuje się sekwencje specjalne (zwanych niekiedy manipulatorami).

129 Sekwencje specjalne \b – (BS - Backspace) – cofnięcie \f – (FF - Form Feed) – nowa strona \n – (NL(LF) - New Line) – nowa linia \r – (CR - carriage return) – powrót karetki \t – (HT - tabulator) – tabulator poziomy \v- (VT - vertical tabulator)- tabulator pionowy \p - adres \a – (BEL - alarm) – sygnał dźwiękowy \0 – NULL

130 Sekwencje specjalne \\ – ukośnik w lewo (\) \? – znak zapytania (?) \ – apostrof \ – cudzysłów \0oo – stała ósemkowa \Xhh – stała szesnastkowa

131 Stałe zmiennopozycyjne Stała zmiennopozycyjna składa się z części całkowitej, kropki, części ułamkowej, litery E, wykładnika potęgi (ewentualnie ze znakiem) i opcjonalnego przyrostka F (dla oznaczenia typu float) lub L (dla oznaczenia typu long). Przykład 1.2; 88.5L; 1.0e+3; 12.34e-2;

132 Stałe wyliczeniowe Nazwy zadeklarowane jako wyliczniki ( enum ) są stałymi typu int. Przykład enum dni {pn=1,wt=2,sr=3,cz=4,pi=5,so=6,ni=7} dni_tyg;

133 Napisy Napis (stała napisowa) jest ciągiem znaków ujętym w znaki cudzysłowu, np. to jest napis. Typem napisu jest tablica znakowa, a klasą pamięci – static. Do końca napisu jest dopisywany (o ile nie jest podany jawnie) znak \0 (NULL), aby program przeglądający tekst mógł znaleźć jego koniec.

134 Znaczenie identyfikatorów Identyfikatory odnoszą się w szczególności do: Funkcji; Etykiet; Struktur; Unii; Klas; Wyliczeń; Stałych wyliczeń; Nazw typów; Nazw zmiennych.

135 Podstawowe atrybuty zmiennej Zmienna jest miejscem w pamięci; jego interpretacja opiera się na dwóch podstawowych atrybutach: - Klasie pamięci - Typie Klasa pamięci decyduje o żywotności zmiennej, natomiast typ nadaje znaczenie wartości zmiennej.

136 Podstawowe cechy zmiennej Podstawowe cechy zmiennej to: - zasięg - łączność Zasięg to część programu, w której zmienna jest znana. Łączność określa, czy ta sama nazwa z innym zasięgiem odnosi się do tej samej zmiennej lub funkcji, czy też nie.

137 Klasy pamięci Istnieją dwie klasy pamięci: –Automatyczna; –Statyczna. Klasy pamięci są określone przez słowa kluczowe (auto, static i register) razem z kontekstem zawierającym deklarację zmiennej.

138 Zmienne automatyczne Zmienne automatyczne są lokalne w bloku i znikają, gdy sterowanie opuści blok. Deklaracje zawarte w bloku tworzą zmienne automatyczne, jeśli nie podano klasy pamięci lub użyto specyfikatora auto. Zmienne zadeklarowane jako register są automatyczne i umieszczane (jeśli to możliwe) w szybkich rejestrach maszyny. Przykład { int i;// zmienne automatyczne auto int a; register int b;// zmienna register }

139 Zmienne statyczne Zmienne statyczne mogą być lokalne dla bloku lub zewnętrzne dla wszystkich bloków; w każdym przypadku zachowują swoje wartości po opuszczeniu i ponownym wejściu do bloku. Wewnątrz bloku zmienne statyczne deklaruje się za pomocą słowa static. Zmienne zadeklarowane na zewnątrz wszystkich bloków, są zawsze statyczne. Przykład int i;// zmienna statyczna int main(void) { int i;// zmienna automatyczna static int a;// zmienna statyczna return 0 }

140 Typy danych

141 Rodzaje typów danych W C wyróżnia się dwa rodzaje typów danych: typy podstawowe (predefiniowane) typy pochodne (definiowane przez uzytkownika)

142 Typy podstawowe (1/2) Nazwa typu Wielkość (w bitach) Zakres wartościPrzykładowe zastosowanie unsigned char 80 do 255Zbiór znaków ASCII char8-128 do 127Małe wartości short int16-32,768 do 32,767Zmienne sterujące pętli unsigned int320 do 4,294,967,295Zmienne sterujące pętli int32-2,147,483,648 do 2,147,483,647 Zmienne sterujące pętli

143 Typy podstawowe (2/2) unsigned long 320 to 4,294,967,295Odległości astronomiczne enum32-2,147,483,648 do 2,147,483,647 Uporządkowane zbiory wartości long32-2,147,483,648 do 2,147,483,647 Liczebności populacji float323.4 *10-38 do 1.7 * 10+38Obliczenia naukowe z dokładnością 7 cyfr double641.7 * do 3.4 * Obliczenia naukowe z dokładnością 15 cyfr long double803.4 * do 1.1 * Obliczenia naukowe z dokładnością 18 cyfr

144 Typy pochodne Oprócz typów podstawowych można utworzyć potencjalnie nieskończoną klasę typów pochodnych, konstruowanych z typów podstawowych. Są to: Tablice – złożone ze zmiennych danego typu; Funkcje – zwracające wartości danego typu; Wskaźniki – do zmiennej danego typu; Struktury – zawierające zestawy zmiennych różnych typów; Unie – zawierające dowolny z zestawu zmiennych o różnych typach; Klasy (w C++) - zawierające zestawy zmiennych różnych typów i funkcji zwracających wartości danego typu.

145 Kwalifikatory typów Z typem zmiennej mogą być związane kwalifikatory (modyfikatory). Deklarując zmienną jako: const (stały) wskazujemy, że ta zmienna nie będzie zmieniała wartości; volatile (ulotny) wskazujemy, że zmienna będzie miała specjalne właściwości, ważne przy optymalizacji. Przykład const int i=1; volatile int stan_miernika;

146 Zmienne i l-wartości Zmienna jest nazwanym obszarem pamięci; l- wartość (ang. l-value) jest wyrażeniem odnoszącym się do zmiennej. Oczywistym przykładem l- wartości jest identyfikator o odpowiednim typie i klasie pamięci. Przykład int *wsk; // l-wartości: int j=2; *wsk=j;

147 Przekształcenia typów W niektórych przypadkach zachodzi potrzeba przekształcenia wartości z jednego typu do innego. Przykład int main(void){ int i=2,j=1; float f; f=((float)j/float(i)); return(0); }

148 Promocja typu całkowitego Jeśli typ int może reprezentować wszystkie wartości oryginalnego typu, to dana wartość jest przekształcana do typu int; w przeciwnym przypadku tę wartość przekształca się do typu unsigned int.

149 Przekształcenia całkowite Dowolna wielkość całkowita jest przekształcana do danego typu bez znaku przez znalezienie takiej najmniejszej nieujemnej wartości, która jest przystająca (kongruentna) do tej wielkości całkowitej modulo największa wartość reprezentowalna w danym typie zwiększona o 1.

150 Wartości całkowite i zmiennopozycyjne Przy przekształcaniu wartości typu zmiennopozycyjnego do typu całkowitego gubi się jej część ułamkową. Przykład int k; float f; f=1.5; k=f;//k=1

151 Typy zmiennopozycyjne Przy przekształcaniu wielkości zmiennopozycyjnej do typu zmiennopozycyjnego o mniejszej precyzji i jej wartość zawiera się w reprezentowalnym przedziale, wynikiem jest najbliższa większa albo mniejsza reprezentowalna wartość. Jeśli wynik nie zawiera się w tym przedziale, skutek nie jest zdefiniowany. Przykład float f; double d; f=d; // przekształcenie wartości typu double do float

152 Przekształcenia arytmetyczne Wiele operatorów powoduje przekształcenia swoich argumentów i określa typ wyniku według podobnych zasad. W efekcie chodzi o dopasowanie typów argumentów do wspólnego typu, który jest także typem wyniku. Przykład int a=2; float r; r=a+2.5;//przekształcenie do typu float

153 Wskaźniki i wartości całkowite Wartość całkowitą można dodać do lub odjąć od wskaźnika. Przykład int x[3]={1,2,3}; int *wsk; wsk=x; for (int i=0;i<3;i++) { printf("x=%d\n",*wsk); wsk++; // wsk wskazuje na nastepny el. tablicy };

154 Typ void Wartości (nie istniejącej) zmiennej typu void nie można wykorzystać w żaden sposób ani nie można przekształcić, jawnie czy niejawnie, do innego typu różnego od void. Ponieważ wyrażenie typu void oznacza nie istniejącą wartość, takich wyrażeń można użyć jedynie w miejscach, gdzie wartość nie jest wymagana, na przykład jako instrukcję wyrażeniową (np. deklarowanie funkcji) (przykład 1) lub jako lewy argument operatora przecinkowego (przykład 2). Przykład 1 void komunikat(char *kom) {printf(kom);}; Przykład 2 int init(void) { return 1; }

155 Wskaźniki do typu void Każdy wskaźnik do zmiennej może być przekształcony do typu (void *) bez utraty informacji. Przekształcenie wyniku z powrotem do wskaźnika oryginalnego typu przywraca oryginalny wskaźnik. Przykład int x=1; int *wsk; void *vwsk; wsk=&x; vwsk=wsk; wsk=(int*)vwsk; printf(wsk=%d,*wsk);

156 Operatory

157 Operatory i ich priorytety (1/7) Kolejność wykonywania działań w wyrażeniach jest uzależniona od priorytetów operatorów. PriorytetOperatorOpisPrzykład 1.1( )Ogranicza wyrażenia Izoluje wyrażenia warunkowe Wskazuje na wywołanie funkcji x* (y- 3.5 ) if ( x > 10 )... getch ( ); 1.2[]Wskazuje na wymiar/indeks macierzychar Tekst[12]: 1.3a.Kropka-odwołanie do elementu struktury, unii puts( STRUKTURA.Text ): 1.3b->Wskazanie elementu struktury, uniistr_a->d 1.4::Widoczność/dostęp (scope)puts ( : : Text );

158 Operatory i ich priorytety (2/7) 2.1!Operator negacji logicznejif (!x) ~Operator dopełnienia jedynkowego - negacja bitowa (NEG) X = ~Y & Z; 2.3a-Jednoargumentowy minusprintf( 96d, -Y ); 2.3b+Jednoargumentowy plusa=+a; 2.4a++Operator jednoargumentowy pre-inkrementacja A = ++X ;

159 Operatory i ich priorytety (3/7) 2.4b++Operator jednoargumentowy post-inkrementacja for ( i=1; i < 10; i ++) a--Operator jednoargumentowy pre-dekrementacja A =--X; 2.5b--Operator jednoargumentowy post-dekrementacja X--; 2.6&Operator adresuScanf(%d,&x); 2.7*Operator adresowania pośredniego Char *string=tekst;

160 Operatory i ich priorytety (4/7) 2.8sizeofOperator sizeofN=sizeof(x); 3.1*Operator multiplikatywny mnożenieX=y * z; 3.2/Operator multiplikatywny dzielenieX=y / z; 3.3%Operator multiplikatywny moduloX=y % z; 4.1+Operator addytywny dodawanieX=y + z; 4.2-Operator addytywny odejmowanieX=y - z;

161 Operatory i ich priorytety (5/7) 5.1<>Operator przesunięcia w prawox=x >>2; 6.1Operator relacji większe niż If (x > 0) <=Operator relacji mniejsze lub równeIf (x <= 0) >=Operator relacji większe lub równeIf (x >= 0)...

162 Operatory i ich priorytety (6/7) 7.1==Operator przyrównania równeIf (x == 0) !=Operator przyrównania nie równeIf (x != 0) &Bitowy operator koniunkcji (AND)x=x&y; 8.2^Bitowy operator różnicy symetrycznej (XOR) x=x ^ y; 8.3|Bitowy operator alternatywy (OR)x=x | y;

163 Operatory i ich priorytety (7/7) 9.1&&Operator iloczynu logicznego (logiczne AND) If ((x>0) && (y<0)) 9.2||Operator sumy logicznej (logiczne OR)If ((x>0) || (y<0)) 10? :Operator warunkowy (odpowiednik if (x==0) x++ else x--) (x==0) ? x++ : x--; 11a=Operator przypisaniaX = 2; 11b+=, -=, *=,/=, %=, >>=, <<= Operatory przypisaniaX+=5; 12,Przecinek – oddziela argumenty funkcji na liście argumentów Printf(i=%d,i);

164 Niektóre operatory i ich priorytety -podsumowanie 1.Kolejność wyrażeń (); wskazujący na wymiar tablicy - [] 2.unarne (+, -), post- i pre-inkrementacja (++,--) 3.arytmetyczne (*,/, %) 4.arytmetyczne (+, -) 5.przesunięcia ( >) 6.relacji (, =) 7.relacji (==, !=) 8.bitowe (&, ^, |) 9.iloczynu i sumy logicznej (&&, ||) 10.warunkowy (?:) 11.przypisania (=,+=, -=, *=, /=, >>=, <<=) 12.przecinek (,)

165 Wyrażenia proste Wyrażeniami prostymi są: identyfikatory, stałe, napisy lub wyrażenia w nawiasach. Przykład int x=1; int *wsk; char t[]={"tekst"}; *wsk=2; x=x+*wsk; printf(x=%d,x); printf("%s\n\n",*t);//tekst

166 Wyrażenia przyrostkowe Wyrażeniami przyrostkowymi są: Odwołania do tablic Wywołania funkcji Odwołania do klas i struktur Zwiększanie i zmniejszanie przyrostkowe Operatory w wyrażeniach przyrostkowych są lewostronnie łączne.

167 Odwołania do tablic Wyrażenie przyrostkowe, po którym następuje wyrażenie ujęte w nawiasy prostokątne, jest wyrażeniem przyrostkowym oznaczającym indeksowane odwołanie do tablicy. Przykład char t[]={"tekst"}; //tablica znakowa char *te="inny tekst"; //wskazanie do ciągu znaków int x[3]={1,2,3}; //tablica jednowymiarowa int y[2][3] ={{1,2,3},{4,5,6}}; //tab. wielowymiarowa

168 Wywołania funkcji Wywołanie funkcji składa się z wyrażenia przyrostkowego, zwanego oznacznikiem (identyfikatorem) funkcji, po którym następuje ujęta w nawiasy okrągłe lista (być może pusta) wyrażeń przypisania. Wyrażenia przypisania, oddzielone od siebie przecinkami, tworzą argumenty funkcji. Przykład void swap(int v[], int i, int j) //deklaracja funkcji... swap(v,0,rand()%n); //wywołanie funkcji

169 Odwołania do klas i struktur (.) Wyrażenie przyrostkowe będące nazwą lub referencją do klasy, struktury lub unii, po którym następuje kropka i identyfikator, jest wyrażeniem przyrostkowym umożliwiającym dostęp do składowych tych struktur. Przykład class C_Kolejka {//deklaracja klasy public: int licznik; int kolejka[10]; }; C_Kolejka kol;//deklaracja klasy int main(void) { kol.licznik=0; }//odwołanie do klasy

170 Odwołania do klas i struktur (->) Wyrażenie przyrostkowe będące wskaźnikiem do klasy, struktury lub unii, po którym następuje strzałka (złożona ze znaków - i >) i identyfikator, jest wyrażeniem przyrostkowym umożliwiającym dostęp do składowych tych struktur. Przykład class C_Kolejka {//deklaracja klasy public: int licznik; int kolejka[10]; }; C_Kolejka kol,*wkol; //deklaracje klas int main(void) { kol.licznik=1; wkol=&kol; printf(licznik=%d,wkol->licznik); //odwołanie do pola klasy }

171 Zwiększanie i zmniejszanie przyrostkowe Użyty po wyrażeniu przyrostkowym operator (++) lub (--) powoduje, odpowiednio, zwiększenie lub zmniejszenie wartości argumentu o jeden. Przykład x++;//x=x+1; - post-inkrementacja --x;//x=x-1; - pre-dekremantacja

172 Operatory jednoargumentowe Operatory jednoargumentowe są prawostronnie łączne. Są to: Przedrostkowe operatory zwiększania (++) i zmniejszania (--); Operator adresu (&); Operator adresowania pośredniego (*); Jednoargumentowy plus (+); Jednoargumentowy minus(-); Operator dopełnienia jedynkowego (~); Operator negacji logicznej (!); Operator sizeof.

173 Przedrostkowe operatory zwiększania i zmniejszania Operatorem (++) poprzedzający argument powoduje zwiększenie argumentu o jeden, natomiast operator (--) powoduje zmniejszenie argumentu o jeden. Przykład x++; //post-inkrementacja x--;//post-dekrementacja ++x;//pre-inkrementacja x=y- --x; //pre-dekrementacja

174 Operator adresu Jednoargumentowy operator (&) podaje adres swojego argumentu. Argument musi być l- wartością nie odnoszącą się ani do pola bitowego, ani do zmiennej zadeklarowanej jako register; argument może być funkcją. Wynikiem jest wskaźnik do zmiennej lub funkcji wskazanych l-wartością. Przykład int *r,x=0; r=&x;

175 Operator adresowania pośredniego Jednoargumentowy operator (*) oznacza adresowanie pośrednie i daje w wyniku zmienną lub funkcję wskazywaną przez argument. Wynik jest l-wartością, jeśli argument jest wskaźnikiem do typu arytmetycznego, klasy, struktury lub unii lub wskaźnika. Jeśli typem argumentu jest wskaźnik do typu T, to typem wyniku jest T. Przykład int main(void){ int *w1,*w2, i=1; w1=&i; w2=w1; printf("w1=%p w2=%p",w1,w2); //w1=0012FF88 w2=0012FF88 getch(); return(0); }

176 Jednoargumentowy plus Argument jednoargumentowego operatora (+) musi być typu arytmetycznego lub wskaźnikowy. Wynikiem jest wartość argumentu. Typem wyniku jest promowany typ argumentu. Przykład int i,x=-1; int *wsk; wsk=&x; i=+(*wsk); //i=-1

177 Jednoargumentowy minus Argument jednoargumentowego operatora (-) musi być typu arytmetycznego. Wynikiem jest wartość argumentu ze zmienionym znakiem. Wynikiem promocji jest typ całkowity. Przykład int i,x=-1; int *wsk; wsk=&x; i=-(*wsk); //i=1

178 Operator dopełnienia jedynkowego Argumentem operatora (~) jest typ całkowity. Wynikiem jest dopełnienie jedynkowe wartości argumentu. Promowany jest typ całkowity. Przykład int x=0x f; printf(~x=%x,~x); //wynik ~x=fffffff0

179 Operator negacji logicznej Argumentem operatora (!) jest typ całkowity lub wskaźnikowy. Wynikiem jest 1, jeśli wartością argumentu jest 0, a 0 w przeciwnym przypadku. Przykład 1 int *wsk,x=0; printf(!x=%x\n,!x); //wynik !x=1 printf(!wsk=%x,!wsk); //wynik !wsk=1 Przykład 2 int *wsk,x=1; wsk=&x; printf(!x=%x\n,!x); //wynik !x=0 printf(!wsk=%x,!wsk); //wynik !wsk=0

180 Operator sizeof() Argumentem jest albo nazwa zmiennej, albo wyrażenie (które nie jest obliczane) albo nazwa typu ujęta w nawiasy. Wynikiem jest stała całkowita bez znaku oznaczająca liczbę bajtów stosowaną do reprezentowania w pamięci struktury będącej argumentem operatora. Operator nie może odnieść się do funkcji, niekompletnego typu lub pola bitowego. Przykład int *w=NULL, i=1; printf("s_w=%d s_i=%d",sizeof(w),sizeof(i)); //s_w=4 s_i=4

181 Rzutowanie Poprzedzenie wyrażenia jednoargumentowego nazwą typu ujętą w nawiasy, lub poprzedzenie tego wyrażenia nazwą typu i ujęcie całego wyrażenia w nawiasy, powoduje przekształcenie wartości tego wyrażenia do wskazanego typu. Takie przekształcenie nazywane jest rzutem. Przykład float f=1.5; int x,y; x=(int)f;//rzutowanie w stylu C y=int(3.5*f);//rzutowanie w stylu C++ printf(x=%d\n,x); //wynik x=1 printf(y=%d\n,y); //wynik y=5

182 Operatory multiplikatywne Operatory multiplikatywne: - mnożenia (*), - dzielenia (/) - modulo (%). Argumenty wszystkich operatorów muszą być arytmetyczne; argumenty operatora (%) muszą być całkowite.

183 Operatory addytywne Operatory addytywne: - dodawania (+) - odejmowania (-) są lewostronnie łączne.

184 Operatory przesunięcia Operatory przesunięcia w lewo ( >) są lewostronnie łączne. Ich argumenty musza być całkowite i podlegają promocji typu całkowitego. Wynik nie jest zdefiniowany, jeśli prawy argument jest ujemny, a także jeśli jest większy lub równy liczbie bitów w typie lewego argumentu. Wartością E1<>E2 jest E1 przesunięte w prawo o E2 bitów, co jest równoważne podzieleniu E1 przez 2^E2.

185 Operatory relacji Wszystkie operatory relacji: - mniejsze niż ( ); - mniejsze lub równe ( =); dają w wyniku wartość 1 jeśli relacja jest prawdziwa, zaś 0 jeśli jest fałszywa. Typem wyniku jest int. Możliwe jest porównywanie wskaźników, ale tylko wtedy gdy dotyczą zmiennych tego samego typu!

186 Operatory przyrównania Operatory przyrównania: - równe (==) - nie równe (!=), są podobne do operatorów relacji z tym, że mają niższy priorytet, tak więc zapis a

187 Bitowy operator koniunkcji (AND) Bitowy operator koniunkcji (&) daje w wyniku bitową koniunkcję argumentów. Operator odnosi się tylko do argumentów całkowitych. Przykład m k m&k

188 Bitowy operator różnicy symetrycznej (XOR) Bitowy operator różnicy symetrycznej (^) daje w wyniku bitową różnicę symetryczną argumentów. Operator odnosi się tylko do argumentów całkowitych. Przykład m k m^k

189 Bitowy alternatywy (OR) Bitowy operator alternatywy (|) daje w wyniku bitową alternatywę argumentów. Operator odnosi się tylko do argumentów całkowitych. Przykład m k m|k

190 Operator warunkowy Operator warunkowy o postaci: wyrażenie_1 ? wyrażenie_2 : wyrażenie_3 Najpierw obliczane jest wyrażenie_1. Jeśli jego wartość jest różna od 0 (tzn., że ma wartość TRUE), to wynikiem jest wartość drugiego wyrażenia ( wyrażenie_2 ), w przeciwnym przypadku wynikiem jest wartość trzeciego wyrażenia ( wyrażenie_3 ). Przykład //if-else: if (x < y) z = x; else z = y; //odpowiednik: z = (x < y) ? x : y;

191 Wyrażenia przypisania Wszystkie operatory przypisania są prawostronnie łączne. Są nimi: =, *=, /=, +=,-=, >=, &=, ^= i |=. Dla wszystkich operatorów przypisania lewy argument musi być l- wartością. Wyrażenie o postaci: E1op=E2 jest równoważne z wyrażeniem E1=E1 op E2, z tym tylko, że wyrażenie E1 jest obliczane raz.

192 Operator przecinkowy Operatory przecinkowe stosuje się do rozdzielania elementów listy argumentów funkcji lub listy inicjatorów. Np. w wywołaniu funkcji: f(a,(t=3,t+2),c); występują trzy argumenty; drugi ma wartość 5.

193 Wyrażenia stałe Wyrażenie stałe wyrażone są za pomocą stałych. Są one stosowane w następujących kontekstach: Po słowie case; Jako rozmiar tablicy; Jako długość pola bitowego; Jako wartość stałej wyliczania; W inicjatorach i niektórych wyrażeniach dla preprocesora.

194 Deklaracje i definicje Deklaracje określają sposób interpretacji identyfikatorów, nie zawsze jednak rezerwują pamięć związaną z identyfikatorami. Deklaracje, które rezerwują pamięć nazywa się definicjami. Deklaracja musi zawierać przynajmniej jeden deklarator. Deklarator zawiera przynajmniej kwalifikator typu i nazwę deklatarora.

195 Przykłady deklaracji Składnia deklaracjiPrzykładTyp wynikowy typ nazwa;int count;typ typ nazwa[];int count[];Otwarta tablica określonego typu typ nazwa[3];int count[3];Tablica o ustalonym rozmiarze typ *nazwa;int *count;Wskaźnik do danego typu typ *nazwa[];int *count[];Otwarta tablica wskaźników do danego typu typ *(nazwa[]);int *(count[]);Jw. typ (*nazwa)[];int (*count) [];Wskaźnik do typu otwartej tablicy typ &nazwa;int &count;Referencja do danego typu (tylko C++) typ nazwa();int count();Typ wartości zwracanej przez funkcję typ *nazwa();int *count();Funkcja zwracająca wskaźnik typ *(nazwa());int *(count());Jw. typ (*nazwa)();int (*count) ();Wskaźnik do funkcji zwracającej określony typ danych

196 Specyfikatory klasy pamięci Specyfikatorami klasy pamięci są: auto register static extern typedef W deklaracji może wystąpić co najwyżej jeden specyfikator pamięci. Jeśli nie podano żadnego, to obowiązują następujące zasady: dla zmiennych zadeklarowanych wewnątrz funkcji przyjmuje się klasę auto; dla zmiennych zadeklarowanych na zewnątrz (wszystkich) funkcji przyjmuje się klasę extern lub static

197 Specyfikatory auto i register Specyfikatory auto i register nadają deklarowanej zmiennej automatyczną klasę pamięci i mogą być stosowane wyłącznie wewnątrz funkcji. Takie deklaracje są jednocześnie definicjami i powodują zarezerwowania pamięci. Deklaracja register jest równoważna z deklaracją auto, ale deklaruje, że instancja będzie często używana i w miarę możliwości powinna być umieszczona w rejestrach procesora. Do instancji zadeklarowanych jako register nie wolno stosować operatora adresu (&). Przykład int main() { register int k; auto int i; i = 5; return i; }

198 Zmienne automatyczne (1/2) Zmienne zdefiniowane lokalnie w swoim bloku (zasięg blokowy), np. zmienne lokalne funkcji. Powstają i są inicjowane wartościami początkowymi (o ile są podane) za każdym razem, gdy sterowanie wejdzie do ich bloku. W momencie, gdy następuje koniec realizacji bloku – zmienne znikają (automatyczny czas trwania). Domyślnie wszystkie zmienne lokalne funkcji są klasy auto. Można w tym przypadku pominąć w deklaracjach zmiennych słowo kluczowe auto. Umieszczenie tego słowa podkreśla zazwyczaj, że zmienna lokalna dubluje deklarację zmiennej zewnętrznej. Zmienne automatyczne nie posiadają łączności. Nie można zadekla­rować dwóch zmien­nych o tej samej nazwie w tym samym bloku. Zmienne automatyczne o tych samych nazwach, zdefiniowane w różnych blokach, są różnymi obiektami. Deklaracja zmiennej wewnątrz bloku przesłania jej deklarację zewnętrzną, także globalną. Domyślnie zmienne automatyczne nie otrzymują żadnej wartości początkowej. Nie można zakładać, że wartość początkowa zmiennej automatycznej jest równa 0.

199 Zmienne automatyczne (2/2) int y = 21; // definicja zmiennej globalnej void main() { int x; // deklaracja zmiennej klasy auto // definicja zmiennej klasy auto - przykrywa def. zmiennej globalnej: auto int y = 5; printf(x=%d,x); // wartość x przypadkowa printf(y=%d,y); // y = 5 printf(::y=%d,::y) // ::y = 21 (operator :: tylko w C++) { int y = 11; // definicja lokalna printf(y=%d,y); // y = 11 } printf(y++=%d,y++); // y++= 6 }

200 Specyfikator extern Specyfikator extern użyty na zewnątrz (wszystkich) funkcji nadaje zmiennej łączność zewnętrzną. Przykład extern int _fmode; extern void Factorial(int n); extern "c" void cfunc(int); /* c - ochrona nazwy funkcji przed jej zniekształceniem w programach C++ */

201 Zmienne zewnętrzne (1/2) Zmienne zdefiniowane poza funkcjami (tylko to decyduje, że zmienne są zewnętrzne, a nie specyfikator extern). Istnieją i zachowują swoje wartości podczas wykonywania całego programu (statyczny czas trwania). Powstają i są inicjowane wartościami początkowymi raz przed rozpoczę­ciem wykonywania programu. W przypadku braku jawnej inicjalizacji otrzymują wartość początkową 0. Inicjacja zmiennych zewnętrznych może nastąpić tylko raz w miejscu ich definicji. Zmienne te mogą być wykorzysty­wane do komunikacji między funkcjami należącymi do różnych plików programu (łączność zewnętrzna). Deklaracja zmiennej ze słowem kluczowym extern, np. extern int w, nie powoduje zarezerwowania w pamięci miejsca dla zmiennej. Informuje ona kompilator, że definicja zmiennej zewnętrznej znajduje się w innym miejscu programu, być może w innym pliku. Jeżeli zmienna jest rzeczywiście zdefiniowana w innym pliku lub po definicji funkcji, w której jest wykorzystywana, to użycie specyfikatora extern jest obowiąz­kowe. W pozostałych przypadkach może być pominięte. Deklaracja extern x jest równoważna deklarcji extern int x.

202 Zmienne zewnętrzne (2/2) /**********plik1.cpp************/ int y = 5; void main(void) { extern int a;// deklaracja zmiennej zewnętrznej extern double z;// deklaracja zmiennej zewnętrznej printf(y=%d,y)// y = 5 printf(a=%d,a)// a = 0 printf(z=%d,z)// z =145.2 } int a;// definicja zmiennej int a = 0 /**********plik2.cpp*************/ double z = 145.2;// definicja zmiennej

203 Specyfikator static Specyfikator static nadaje deklarowanej zmiennej statyczną klasę pamięci i może być stosowany zarówno wewnątrz jak i na zewnątrz funkcji. Wewnątrz funkcji specyfikator ten powoduje zarezerwowania pamięci – oznacza więc definicję. Przykład static void printnewline(void) {}; int mai(void) { static int i; }

204 Zmienne statyczne (1/2) Zmienne o zasięgu blokowym (tak jak zmienne automatyczne), ale w odróżnieniu od zmiennych automatycznych istniejące i zachowujące swoje wartości przez cały czas wykonywania programu (statyczny czas trwania). Zmienne statyczne są inicjowane wartościami początkowymi tylko raz w miejscu ich definicji. W przypadku, gdy sterowanie opuści blok, w którym zmienna jest zdefiniowana aktualna wartość zmiennej zostanie zachowana. W przypadku, gdy zmienne statyczne nie są inicjowane żadnymi wartościami, podobnie jak zmienne zewnętrzne, otrzymują domyślnie wartość 0. Zmienne statyczne można definiować lokalnie wewnątrz funkcji. Są one wówczas widoczne tylko wewnątrz tych funkcji. Można również zdefiniować zmienną statyczną zewnętrzną. Zmienna tego typu, w odróżnieniu od zwykłej zmiennej zewnętrznej, jest widoczna tylko przez funkcje znajdujące się w tym samym pliku, co jej definicja.

205 Zmienne statyczne (2/2) static int p; // zmienna statyczna zewnętrzna void pisz(double x ) { static int licznik; // zmienna statyczna licznik = 0 printf(x=%d,x); licznik++; // zwiększ licznik wywołań funkcji }

206 Specyfikator typedef Specyfikator typedef nie rezerwuje pamięci. Przykład typedef unsigned char byte; typedef char str40[41]; typedef struct { double re, im; } s_zespolone;

207 Specyfikatory typów Specyfikatorami typów są: - Void - Char - Short - Int - Long - Float - Double - Signed - Unsigned. Wraz z int można użyć jednego ze specyfikatorów long lub short.; znaczenie jest takie samo jakby słowo int było pominięte. Słowo long można użyć z double. Jeden z identyfikatorów signed lub unsigned moze być użyty z int bądź z jego odmianą long, short lub char. Specyfikator signed można użyć do wymuszenia znaku arytmetycznego dla zmiennej typu char; dla pozostałych typów całkowitych jest zbędny, choć dozwolony. Jeśli specyfikator typu został pominięty to przyjmuje się int.

208 Kwalifikatory typów: const i volatile Ze specyfikatorami typów można użyć kwalifikatory typów: const lub volatile. Zmienną z kwalifikatorem const można inicjować, lecz nie można mu później przypisywać wartości. Kwalifikator volatile służy do wyłączenia deklarowanej zmiennej z ewentualnej optymalizacji wykonywania kodu. Służy to zabezpieczeniu zmiennej przed niepożądanymi działaniami podejmowanymi przez kompilator w celu, np. zwiększenia efektywności wykonywania kodu. Przykład volatile int temp; printf(%d,temp);

209 Statyczne struktury danych

210 Rodzaje statycznych struktur danych Najważniejszymi statycznymi strukturami danych w C są: Tablice Struktury Unie

211 Tablice

212 Definicja tablicy TABLICA to struktura danych złożona z elementów tego samego typu, przy czym dostęp do każdego z tych elementów odbywa się przez podanie jego numeru (indeksu w tablicy).

213 Deklarowanie tablic Deklaratory tablic mają składnię: Nazwa_typu deklarator_tablicy; Tablice można konstruować z typów predefiniowanych i typów definiowanych przez użytkownika. Przykład int t[9], *wskt[15]; int tw[2][2];

214 Inicjowanie tablic (1/2) Elementy tablic mogą być inicjowane w miejscu ich deklaracji. Wartości na liście inicjacyjnej muszą być zgodne z liczbą, kolejnością i typem danych tablicy. Przykład. Inicjowanie tablic tekstowych. char t[]={"tekst"}; //jednowymiarowa tablica znakowa printf("%s\n",*t); char *te="inny tekst"; //wskazanie do ciągu znaków printf("%s\n",te); //równoważne sposoby inicjowania wielowymiarowych tab. teks.: char kolory[][15]={{"CZEROWNY"},{"NIEBIESKI"},{"ZIELONY"}}; char *kolory[15]={{"CZEROWNY"},{"NIEBIESKI"},{"ZIELONY"}}; printf("%s\n",kolory[0]); //CZEROWNY

215 Inicjowanie tablic (2/2) Przykład. Inicjowanie tablic liczbowych. int x[3]={1,2,3}; //tablica jednowymiarowa int y[2][3] ={{1,2,3},{4,5,6}}; //tab. wielowymiarowa

216 Struktury

217 Deklaracje struktur Struktura jest obiektem składającym się ze zbioru nazwanych składowych o różnych typach. Specyfikator struktury ma postać: struct [ ] { [ ] ; } [ ] ; Przykład struct s_przyjaciele { char nazwisko [20], imie[20], nr_telefonu[20]; int wiek, wzrost; } moj_przyjaciel; //dostęp do elementu: strcpy(moj_przyjaciel.nazwisko,Abacki); // deklarowanie danych: struct s_przyjaciele moi_przyjaciele[100];

218 Inicjowanie struktur (1/2) Pola struktur i unii mogą być inicjowane w miejscu ich deklaracji. Wartości na liście inicjacyjnej muszą być zgodne z liczbą, kolejnością i typami danych pól struktury lub unii. Przykład struct ts_osoba { char nazwisko[15]; char imie[15]; long PESEL; unsigned rok, miesiac, dzien; } os1={"Abacki", "Adam", , 1990, 8,20}, os2={"Babacki", "Bogdan", , 1991, 4,14};

219 Inicjowanie struktur (2/2) Jeśli na liście inicjacyjnej jest mniej stałych niż pól struktury lub unii, to w przypadku struktur statycznych i zewnętrznych pozostałe składowe są inicjowane wartościami zerowymi. W pzrypadku struktur automatycznych zależny to od kompilatora (najczęściej składowe są inicjowane wartościami zerowymi). Przykład #include... ts_osoba os3={"Cabacki"};//struktura statyczna int main(void){ ts_osoba os4={"Dabacki"};//struktura automatyczna printf("naz=%s im=%s pesel=%d",os3.nazwisko, os3.imie, os3.PESEL); //naz=Cabacki im= pesel=0 printf("naz=%s im=%s pesel=%d",os4.nazwisko, os4.imie, os4.PESEL); //naz=Dabacki im= pesel=0 getche(); return 0; }

220 Nadawanie wartości składowym struktur - przykład #include... ts_osoba os; int main(void){ //posrednia aktualizacja wartosci pola: strcpy(os.nazwisko,"Abacki"); //podstawiania bezposrednie: os.PESEL= ; os.rok=1990; os.miesiac=10; os.dzien=12; return 0; }

221 Rezerwacja pamięci dla struktur Pola struktury są umieszczane w pamięci zgodnie z kolejnością występowania w deklaracji. Każde pole struktury zajmuje osobny obszar pamięci. Łączny rozmiar struktury jest równy sumie rozmiarów jej składowych. Rozmiar struktury jest zawsze większy lub równy sumie rozmiarów jej składowych. Przykład struct ts_osoba { char nazwisko[15]; char imie[15]; long PESEL; unsigned rok, miesiac, dzien; } os; … //size=46 ( ): printf("size=%d",sizeof(ts_osoba)); //adresy pol: printf("adr nazwisko=%p\n",&os.nazwisko[0]); printf("adr imie=%p\n",&os.imie[0]); printf("adr PESEL=%p\n",&os.PESEL);

222 Struktury zagnieżdżone Składowymi struktur mogą być również inne struktury. Odwołanie do pola struktury, która sama jest składową innej struktury odbywa się z wykorzystaniem dwóch kropek. Przykład struct ts_data { unsigned rok, miesiac, dzien; }; struct ts_osoba { char nazwisko[15]; char imie[15]; long PESEL; ts_data data; } os; … os.data.rok=1990;

223 Unie

224 Deklaracje unii Unia jest obiektem, który w danej chwili zawiera jedną ze swoich kilku składowych o różnych typach. Specyfikator unii jest analogiczne jak specyfikator struktury: union [ ] { ;... } [ ] ; Przykład union u_int_lub_long { int i; long l; } liczba;

225 Inicjowanie unii (1/2) W uniach wszystkie składowe są pamiętane we wspólnym obszarze pamięci. Dlatego unię inicjuj się jedną daną odpowiadającą typowi pierwszego pola unii. Dla unii statycznych i zewnętrznych pola niezainicjowane są ustawiane na wartości zerowe. Przykład union tu_znaki_liczba { char znaki[4]; int liczba; } zl={"ABC\0"};

226 Inicjowanie unii (2/2) Jeśli dane inicjujące są innego typu, to (o ile to możliwe) wykonywana jest automatyczna konwersja. Przykład #include … tu_znaki_liczba zl1={65};//znaki[0]='A' int main(void){ //znaki[0]=(char)65.66='A' –rzutowanie: tu_znaki_liczba zl2={65.56}; printf("znak=%c",zl1.znaki[0]); //znak=A printf("znak=%c",zl2.znaki[0]); //znak=A getche(); return 0; }

227 Rezerwacja pamięci dla unii Pola unii są zajmują wspólny obszar pamięci, w związku z tym rozmiar unii jest równy rozmiarowi jej największej składowej, a wszystkie składowe mają ten sam adres. Przykład union tu_znaki_liczba { char znaki[4]; int liczba; } z; … printf("size=%d",sizeof(z)); //size=4 (max{4,4}) printf("adr z.znaki=%p\n",&z.znaki[0]); //adr z.znaki=004D06E4 printf("adr z.liczba=%p\n",&z.liczba); //adr z.znaki=004D06E4

228 Unie anonimowe (C++) W języku C++ istnieje możliwość deklarowania statycznych unii bez nazwy typu i bez definiowania nazwy zmiennej. Pola unii są wówczas traktowane jak zwykłe zmienne, które zajmują ten sam obszar pamięci. W odwołaniach do składowych unii są wykorzystywane jedynie nazwy składowych. Dzięki wykorzystaniu unii anonimowej uzyskujemy oszczędność pamięci. Przykład static union { char znaki[4]; int liczba; }; int liczba;//błąd! int main(void){ liczba=2;

229 Pola bitowe

230 Ograniczenia dla wielkości pól bitowych W przypadku pól typu całkowitego można w deklaracji struktury (unii) określić ile bitów będą one zajmowały. Zakres wartości, które można przechowywać w danym polu zależy od liczby bitów przydzielonych polu oraz od tego czy jest to pole ze znakiem, czy bez znaku. Najmniejsza ilość pamięci przeznaczona na pojedyncze pole wynosi 1 bit. Największa wynosi 16 (instancje int). Jeśli pole 1-bitowe jest typu signed int (char), to może ono przyjmować wartości 0 lub –1. Jeśli pole jest typu unsigned int (char), to może ono przyjmować wartości 0 lub 1. Jeśli nie podamy czy pole typu int (char) jest signed czy unsigned, to sposób interpretacji zależy od kompilatora (na ogół int jest interpretowane jako signed int). Pole bitowe definiuje się podając typ danej, nazwę pola i po dwukropku liczbę bitów wykorzystywanych przez pole.

231 Pole bitowe w strukturze - przykład struct ts_bit { unsigned ubit : 1; int ibit : 1; } b; … b.ubit=1; b.ibit=1; //ubit=1 ibit=-1: printf("ubit=%d ibit=%d",b.ubit,b.ibit);

232 Dostęp do pól bitowych Dostęp do pól bitowych jest realizowany w taki sam sposób jak do zwykłych pól. Kompilator automatycznie dodaje kod umożliwiający odczytanie zawartości pola. Rozmiar struktury jest zawsze większy lub równy niż suma rozmiarów pól składowych. struct atrybut { int bit1 : 1; int bity2_10 : 9; int bit11 : 1; int bity12_23 : 12; // rozmiar struktury 23 bity } a; … a.bit1 = 1; a.bity2_10 =32; a.bit11 = 1; a.bity12_23=1024; printf("%d\n", a.bit1); // -1 printf("%d\n", a.bity2_10); // 32 printf("%d\n", a.bit11); // -1 printf("%d\n", a.bity12_23); // 1024 printf("%d\n", sizeof(a));//3

233 Deklaracje klas (w C++) Klasa jest obiektem składającym się ze zbioru nazwanych składowych: - pól (fields) lub atrybutów (attributes) różnych typów; - metod (usług, operacji) (methods, services, operations) zwracających wartości różnych typów. Specyfikator klasy ma postać: class nazwa_klasy [(lista_inicjalizacyjna)] [:nazwa_klasy_bazowej [(lista_wyrazen_inicjujacych)]] [;] Przykład class student { private: float srednia; // pole public: int stu_ocena_sr(int ID_stu); // metoda };

234 Wyliczenia

235 Deklarowanie typu wyliczeniowego Wyliczenia są szczególnymi typami o wartościach ze zbioru nazwanych stałych – tzw. wyliczników. Postać specyfikatora wyliczania została zapożyczona od struktur i unii: enum [ ] { [= ],...}; Dopuszczalne jest, by dwa różne identyfikatory miały taką samą wartość (np. stosowane po to, aby wykonywać tę sama akcję dla różnych wartości typu wyliczeniowego). W przypisaniach mogą wystąpić wyrażenia stałe, kompatybilne z typem int. Wyliczenie obejmuje ciąg wartości stałych o logicznie powiązanym znaczeniu. Przykład enum trzy_kolory {CZERWONY, ZIELONY=10, NIEBIESKI=10} kol1,kol2=ZIELONY,kol3=NIEBIESKI,kol4,kol5; … kol1=CZERWONY; printf("%d\n",kol1);//0 printf("%d\n",kol2);//10 printf("%d\n",kol3);//10 kol4=0; printf("%d\n",kol4);//0 kol5=2; printf("%d\n",kol5);//2

236 Przykład (1/3) Kolorowe napisy. enum te_kolory {CZER=1, ZIEL=2, NIEB=3}; … te_kolory kol; int c; randomize(); c=1+random(16)%3; kol= (te_kolory)(c); // losowanie kolor u switch (kol) { default: textcolor(LIGHTGRAY); cprintf("Kolor szary \n\r"); break; case CZER : textcolor(RED); cprintf("Kolor czerwony \n\r"); break; case ZIEL : textcolor(GREEN); cprintf("Kolor zielony \n\r"); break; case NIEB: textcolor(BLUE); cprintf("Kolor niebieski \n\r"); break; }

237 Przykład (2/3) Wypisywanie nazw kolorów zdefiniowanych jako wyliczniki. enum te_kolory {CZER=1, ZIEL=2, NIEB=3}; … te_kolory kol; char *kolory[15]={{""},{"CZEROWNY"},{"ZIELONY"},{"NIEBIESKI"}}; for (kol=CZER; kol<=NIEB; kol++) printf("%s\n",kolory[kol]);

238 Przykład (3/3) Wykorzystanie typu wyliczeniowego w charakterze typu argumentu funkcji. void wypisz_kolor(te_kolory k) { char *kolory[15]={{""},{"CZEROWNY"},{"ZIELONY"},{"NIEBIESKI"}}; printf("%s\n",kolory[k]); }

239 Deklaratory

240 Definicja deklaratora Deklarator to notacja zapisu typów, funkcji, tablic i innych elementów składni języka. Deklaratory pojawiają się po specyfikatorach klas pamięci i typów.

241 Deklaratory wskaźników Deklaratory wskaźników mają składnię: Nazwa_typu *deklarator_wskaznikow; Przykład int i=1; int *a[3]; int *wsk; int *const cwsk=&i; /*cwsk wskazuje zawsze to samo miejsce pamięci, chociaż wartość tego miejsca można zmieniać*/ const int c=3, *pwsk; /* wartości c nie można zmieniać, ale można ją zainicjować; wartośc pwsk może się zmieniać, ale wskazanego miejsca pamięci nie można zmieniać*/

242 Deklaratory tablic Deklaratory tablic mają składnię: Nazwa_typu deklarator_tablicy; Przykład int t[9]; char tc[10];

243 Deklaratory funkcji Deklarator funkcji ma postać: Nazwa_typu deklarator_funkcji; gdzie deklarator_funkcji ma postać: nazwa_funkcji (lista_parametrow) Przykłady deklaracji parametrów formalnych funkcji: 1.int func(void)//bez parametrów, np. przykład: printf(f=%d,func()); 2.int func(int t1, int t2, int t3=1) /* trzy parametry typu int, ostatni z domyślną wartością */ przykład: printf(f=%d,func(1,2)); 3.int func(int* ptr1, int& tref) // wskaźnik i referencja przykład: printf(f=%d,func(&a,b)); //int a,b;

244 Inicjowanie Deklarator zmiennej może zawierać inicjator. Inicjator, który poprzedza operator (=), jest albo wyrażeniem stałym, albo listą inicjatorów zawartą w nawiasach klamrowych. Przykład int i=0; const float pi=3.14; float ppi=2*pi; int t[3]={1,2,3}; int ta[]={2,3,5}; char string[] = to jest napis\n; int y[2][3] ={{1,3,5},{2,4,6}}; /* inicjowanie tablicy wielowymiarowej*/ int y[2][3]={{1},{2}}; //inicjowanie pierwszej kolumny

245 Nazwy typów Podanie nazwy typu danych jest konieczne w kilku przypadkach: 1.Przy wymuszaniu przekształcenia typu przez rzutowanie; 2.Przy deklarowaniu typów w deklaratorze funkcji; 3.W argumencie operatora sizeof. Przykład 1.int x; x=int(x+1.5); 2.void fun(void); 3.l=sizeof(double);

246 Nazwy typedef Deklaracje, w których specyfikatorem klasy pamięci jest typedef, nie deklarują zmiennej, lecz definiują identyfikatory jako nazwy typów pochodnych (użytkownika). O takich identyfikatorach mówimy, że są nazwami typedef. Składnia nazw typedef ma postać: typedef ; Przykład typedef unsigned char byte; typedef char str40[41]; typedef struct { double re, im; } complex;

247 Instrukcje

248 Jeśli nie określono inaczej, instrukcje są wykonywane sekwencyjnie. Instrukcje wykonuje się w celu osiągnięcia pewnych efektów, najczęściej stosowane są do zapisu algorytmu i do sterowania wykonywaniem programu. Instrukcje nie mają wartości. Rozróżnia się następujące grupy instrukcji: Instrukcje etykietowane; Instrukcje wyrażeniowe; Instrukcje złożone; Instrukcje wyboru; Instrukcje powtarzania; Instrukcje skoku.

249 Instrukcje etykietowane Etykieta służy do oznaczania miejsc w obrębie funkcji, gdzie zostanie przeniesione sterowanie wykonywaniem programu po wykonaniu stosownej instrukcji goto. Etykieta ma identyfikator skonstruowany wg tych samych zasad jak identyfikator zmiennej, przy czym kończy się on dwukropkiem (:). Etykietą można poprzedzić każdą instrukcję w tej samej funkcji, w której występuje instrukcja goto. Instrukcje mogą być poprzedzone przedrostkami etykietowanymi na dwa sposoby: identyfikator_etykiety : instrukcja; case wyrazenie_stale : instrukcja; default : instrukcja; Etykiety przypadków (case) i domyślna (default) są używane w instrukcji switch. Przykład 1 et1: // etykieta a=5;... goto et1;

250 Instrukcje złożone (bloki) Aby można było użyć kilku instrukcji, tam gdzie powinna być jedna, wprowadzono instrukcję złożoną, zwaną blokiem. Składnia bloku jest następująca: {[lista_deklaracji] [lista_instrukcji] } Przykład int i=1; void main(void){ int i=2; {int i=3; printf("i_globalne=%d, i_lokalne=%d",::i,i); }

251 Instrukcje wyboru Instrukcje wyboru służą do wybrania jednej z kilku ścieżek wykonywania programu. Wyróżnia się następujące instrukcje wyboru: Instrukcja if ; Instrukcja switch.

252 Instrukcja if Istnieją dwie postacie syntaktyczne instrukcji if : - if (wyrazenie) instrukcja; - if (wyrazenie) instrukcja; else instrukcja1; W obu postaciach instrukcji if wyrażenie musi mieć typ arytmetyczny lub wskaźnikowy. Jeśli wartość wyrażenie jest różna od zera (TRUE), to wykonywana jest instrukcja. W drugiej postaci, jeśli wartość wyrażenia jest równa zeru (FALSE), to wykonywana jest instrukcja1. Przykład if (val==1) printf(TRUE); else printf(FALSE)

253 Instrukcja switch Instrukcja switch powoduje przekazanie sterowania do jednej z szeregu instrukcji, zależnie od wartości wyrażenia, które musi mieć typ całkowity. Składnia instrukcji switch: switch ( ) { case : ; [break;]. [default : ;] } Przykład switch (ch) { case a : printf(A\n); break; case b : printf(B\n); break; default : printf(KONIEC\n); };

254 Pętle programowe

255 Rozdaje pętli programowych Wyróżnia się trzy rodzaje pętli programowych: Pętle z wartownikiem Pętle z licznikiem Pętle ogólne

256 Pętle z wartownikiem Pętle z wartownikiem, w której dane są czytane i przetwarzane aż do napotkania szczególnego, niedozwolonego elementu. Wartownik to wartość powodująca zakończenie pętli. Nie jest ona przetwarzana, a pętla zostaje przerwana. Pętle z wartownikiem wymagają wczytania danej przed pierwszym wykonaniem testu. Tę operację nazywa się odczytem początkowym. Pseudokod pętli z wartownikiem ma postać: do { wczytaj daną; przetwórz daną; } while (dana nie jest wartownikiem);

257 Pętle z licznikiem Pętle z licznikiem, stosuje się w sytuacjach, gdy a priori znana jest liczba powtórzeń pętli. Licznika używamy do kontroli liczby przebiegów pętli. Najczęściej do implementacji pętli z licznikiem wykorzystuje się instrukcję for, chociaż można użyc instrukcji while, aby wyraźnie rozdzielić inicjację od testu i treści pętli. Pseudokody pętli z licznikiem mają postacie: for (inicjowanie licznika; warunek końcowy; wyrażenie modyfikujące licznik) przetwarzanie danych; a=10; i=0; while (i

258 Pętle ogólne Pętle ogólne, to wszystkie pętle, które nie są ani pętlami z wartownikiem, ani pętlami z licznikiem. Pętle ogólne implementowane są zazwyczaj z użyciem instrukcji while.

259 Instrukcje powtarzania (organizacji pętli programowych Instrukcje powtarzania wskazują iteracyjne wykonywanie programu. Istnieją trzy instrukcje organizacji pętli: while (wyrazenie) instrukcja; do instrukcja while (wyrazenie); for([wyrazenie1]; [wyrazenie2]; [wyrazenie3]) instrukcja;

260 Instrukcja while W instrukcji while wykonanie instrukcji powtarza się dopóty, dopóki wartość wyrażenia jest różna od zera (TRUE); wyrażenie musi mieć typ arytmetyczny lub wskaźnikowy. Przykład i=0; while (i<3) { printf(%d,a[i]); i++; };

261 Instrukcja do-while W instrukcji do-while wykonanie instrukcji powtarza się dopóty, dopóki wartość wyrażenia jest różna od zera (TRUE); wyrażenie musi mieć typ arytmetyczny lub wskaźnikowy. Przykład i=0; do { printf(%d,a[i]); i++; } while (i<3);

262 Instrukcja for W instrukcji for wyrazenie1 oblicza się tylko raz; jest to część inicjująca stan pętli. Nie ma ograniczeń dotyczących typu tego wyrażenie. Drugie wyrażenie musi mieć typ arytmetyczny lub wskaźnikowy; jest ono obliczane przed każdym przebiegiem pętli i jeśli stanie się zerem (FALSE), to wykonanie instrukcji for zostaje przerwane. Trzecie wyrażenie jest obliczane po każdym przebiegu pętli i określa zmianę stanu pętli. Nie ma ograniczeń dotyczących typu tego wyrażenie. Dowolne z trzech wyrażeń można pominąć. Pominięcie drugiego wyrażenia jest równoważne z zastąpieniem go stałą różną od zera. Składnia: for ([wyrazenie1]; [wyrazenie2]; [wyrazenie3]) instrukcja; Przykład for (i=0;i<3;i++) printf(%d,a[i]);

263 Instrukcje skoku Instrukcje skoku powodują bezwarunkowe przekazanie sterowania do innego miejsca w programie. Instrukcjami skoku są: - continue - break -return - goto identyfikator_etykiety

264 Instrukcja continue Instrukcja continue może wystąpić tylko w instrukcjach powtarzania. Powoduje przekazanie sterowania do miejsca wznawiania najciaśniej otaczającej ją pętli. Przykład void main (void) { for (i = 0; i < 20; i++) { if (array[i] == 0) continue; array[i] = 1/array[i]; }

265 Instrukcja break Instrukcja break może wystąpić tylko w instrukcjach powtarzania i instrukcji switch i powoduje przerwanie najciaśniej otaczającej ją takiej instrukcji; sterowanie przekazywane jest do następnej po przerwanej instrukcji. Przykład void main (void){ int l=0; for (i = 0; i < 20; i++) { if (array[i] == 0) break; l++; array[i] = 1/array[i]; }; printf(l=%d,l); }

266 Instrukcja return Instrukcja return powoduje powrót do miejsca wywołania. Jeśli po słowie return występuje wyrażenie, to wartość tego wyrażenia jest przekazywana do miejsca wywołania. Wartość wyrażenia jest przekształcana do typu zwracanego przez funkcję. Przykład double sqr(double x) { return (x*x); }

267 Instrukcja goto W instrukcji goto identyfikator etykiety musi być umieszczony w tej samej funkcji. Sterowanie jest przekazywane do instrukcji opatrzonej tą etykietą. Przykład powtorz: //etykieta i=x+2; … goto powtorz;

268 Deklaracje zewnętrzne Tekst programu przekazany do kompilatora jest jednostką tłumaczenia. Jednostka ta składa się z ciągu deklaracji zewnętrznych, które są albo instancjami, albo definicjami funkcji. Składnia deklaracji zewnętrznej jest taka sama, jak wszystkich innych deklaracji, jednakże tylko na tym poziomie można zdefiniować funkcję. Przykład int licznik;//deklaracje zewnętrzne int potega(float p, int w); void main (void){}

269 Definicje funkcji Definicja funkcji ma postać: typ identyfikator_funkcji([parametry]) Dozwolonymi specyfikatorami klas pamięci w specyfikatorach deklaracji są tylko extern i static. Funkcja może zwracać wartości typu arytmetycznego, klasę, strukturę, unię, wskaźnik lub void; natomiast nie może zwrócić funkcji ani tablicy. Przykład int max (int, int, int); //deklaracja funkcji int main(void) { return (0); } int max (int a, int b, int c) //definicja funkcji { int m; m=(a>b) ? a:b; return (m>c) ? m:c; };

270 Deklaracje zewnętrzne Deklaracje zewnętrzne opisują właściwości przestrzeni nazw, tj. instancji, funkcji, nazwy typedef, stałych wyliczeń (elementy enum), etykiet, identyfikatorów (etykietki) klas, struktur, unii i wyliczeń (enum). Klasę pamięci dla zmiennych zadeklarowanych na zewnątrz można pominąć lub można podać jako extern lub static. Zmienne zadeklarowane zewnętrznie są zawsze statyczne. Można je uczynić wewnętrznymi dla danej jednostki kompilacji (pliku) za pomocą słowa kluczowego static; to nadaje im łączność wewnętrzną. Zmienne stają się globalne dla całego programu, gdy pominie się deklarację klasy pamięci lub stosuje słowo kluczowe extern nadające im łączność zewnętrzną.

271 Zasięg i łączność nazw Cały program nie musi być tłumaczony w tym samym czasie: tekst źródłowy może być trzymany w kilku plikach zawierających jednostki tłumaczenia, a uprzednio przetłumaczone podprogramy można dołączać z bibliotek. Komunikacja między funkcjami programu może być realizowana zarówno przez wywołania, jak i przez manipulowanie danymi zewnętrznymi. Istnieją zatem dwa rodzaje zasięgu nazw: 1.Wewnętrzny – dotyczy tego fragmentu programu, w którym znany jest identyfikator; jest to tzw. zasięg leksykalny identyfikatora. 2.Zewnętrzny – dotyczy powiązań między identyfikatorami pochodzącymi z oddzielnie kompilowanych jednostek tłumaczenia.

272 Program w C

273 Model programu Program w C zawiera sekwencje deklaracji i definicji, które mogą określać dane lub funkcje.

274 System typów w C System typów opisuje wartości, które mogą być przyjmowane przez określone dane. W języku C istnieje nieskończony zbiór typów, z których każdy można połączyć z konkretną zmienna. Te typy oraz zasady ich konstruowania tworzą systemów typów języka C. Podstawowe typy danych języka C: Znaki (char ( ), unsigned char(0-255)); Liczby całkowite (int(-2* *10 9 ), short int(-32K-32K), unsigned int (0- 4*10 9 ), long(-2* *10 9 ), unsigned long (0-4*10 9 )); Liczby zmiennopozycyjne (float(3.4* * ), double(1.7* * ), long double(3.4* * )); Wyliczenia (enum) Wskaźniki (near (32 bity), far (32bity))

275 Reguły formułowania typów Reguły formułowania typów wymagają istnienia pewnych typów, które mogą być albo typami podstawowymi (predefiniowanymi), albo wcześniej skonstruowanymi za pomocą tych reguł. Przykłady reguł formułowania typów w C: Tablice Struktury Unie Wskaźniki Specyfikator typedef

276 Specyfikator typedef umożliwia tworzenie synonimów dla nazw typów. Przykłady: typedef unsigned int odleglosc; typedef struct { double re, im; } s_zespol; typedef s_zespol t_zespol[10];

277 Łączenie danych Język C zawiera bogaty zestaw operatorów umożliwiających manipulowanie danymi i łączenie ich wartości. Operatory podstawowe: 1.Operatory arytmetyczne 2.Operatory logiczne 3.Operatory porównania 4.Operatory działań na poziomie bitowym 5.Operatory przypisania 6.Operatory rzutowania (koercji)

278 Operatory arytmetyczne Operatory arytmetyczne (atithmetic operators): –dwuargumentowe (*, /, +, -) –jednoargumentowe (+, -) –operator modulo (i % j) –operatory zwiększania i zmniejszania o jednostkę (++, --)

279 Operatory logiczne Operatory logiczne (logical operators): –koniunkcji (&&): wyrażenie x && y zwraca wartość 1, jeśli oba operandy są różne od zera. Uwaga: wartość y w ogóle nie jest obliczana, jeśli x ma wartość 0; –alternatywy (||):wyrażenie x || y zwraca wartość 1, jeśli przynajmniej jeden operand jest różny od zera. Uwaga: wartość y w ogóle nie jest obliczana, jeśli x ma wartość różną od zera; –jednoargumentowy operator negacji (!): wyrażenie !x zwraca 0, jeśli x jest rózne od zera. –trójargumentowy operator warunkowy (?):wyrażenie x? y : z zwraca wartość y, jeśli x jest różne od zera oraz wartość y, jeśli x jest zerem.

280 Operatory porównania Operatory porównania (comparasion operators). Wynikiem stosowania każdego z sześciu operatorów porównania (==,!=,, =) jest 0 jeśli relacja jest nieprawdziwa i 1 jeśli relacja jest prawdziwa.

281 Operatory działań na poziomie bitowym Operatory działań na poziomie bitowym (bitwise manipulation operators). Operatory te traktują liczby całkowite jak ciągi bitów odpowiadające ich reprezentacjom binarnym. Należą do nich operatory: Koniunkcji poziomu bitowego (&) Alternatywy poziomu bitowego (|) Różnicy symetrycznej (^) Przesunięcia bitowego w lewo (<<) Przesunięcia bitowego w prawo (>>)

282 Operatory przypisania Operatory przypisania (assignment operators) (=,+=,-=,/=,*=, %=, >=)

283 Operatory koercji Operatory koercji (rzutowania) (coersion operators). Koercja to proces przekształcania wartości jednego typu na odpowiadająca jej wartość innego typu. Przykład x=float(i)/2 ;

284 Funkcje

285 Określenia funkcji Funkcja to nazwana operacja lub nazwany kod. Funkcja jest grupą powiązanych instrukcji (fragmentem kodu programu), które realizują określone zadanie. Funkcje są podprogramami, które mogą być wielokrotnie wykonywane dla różnych danych wejściowych.

286 Deklaratory funkcji Deklarator funkcji ma postać: Nazwa_typu deklarator_funkcji; gdzie deklarator_funkcji ma postać: nazwa_funkcji (lista_parametrow) Przykłady deklaracji parametrów formalnych funkcji: 1.int func(void)//bez parametrów, np. przykład: printf(f=%d,func()); 2.int func(int t1, int t2, int t3=1) /* trzy parametry typu int, ostatni z domyślną wartością */ przykład: printf(f=%d,func(1,2)); 3.int func(int* ptr1, int& tref) // wskaźnik i referencja przykład: printf(f=%d,func(&a,b)); //int a,b;

287 Parametry (argumenty) funkcji Funkcja może przyjmować i zwracać pewne wartości. Wartości przekazywane są do funkcji jako parametry. Wyróżnia się dwa rodzaje parametrów: Parametry formalne; Parametry aktualne. Parametr formalny (formal parameter) to nazwa i typ parametru określone w ramach jej definicji lub deklaracji. Parametr aktualny (actual parameter) wyrażenie występujące w miejscu pewnego parametru formalnego funkcji, albo wartość parametru obliczona w momencie wywołania funkcji.

288 Przekazywanie parametrów Przekazywanie parametrów (parameters passing) to przesyłanie wartości lub wyrażeń użytych w wywołaniu funkcji do środowiska wykonywania funkcji. W języku C istnieją dwie techniki przekazywania parametrów: Wywołanie przez wartość (call-by-value); Wywołanie przez referencję (call-by- reference), gdzie przekazywana jest referencja do zmiennej. Wywołanie przez wskaźnik (tylko w C++)

289 Wywołanie przez wartość (1/2) Przykład 1. int sign(float x){ int s; float eps=0.0001; if (x<0.0) s=-1; else if (fabs(x)<=eps) s=0; else s=1; return s;} //czy poprawnie? Przykład 2. int plus1(int s){ s=s+1; return s;} int s=1; printf("s=%d s+1=%d",s,plus1(s)); Wywołanie przez wartość realizowane jest poprzez przekazanie wartości, np. w postaci kopii wartości zmiennej. Argument formalny jest traktowany jak zmienna lokalna funkcji, tzn. wszelkie operacje na argumencie tracą ważność po zakończeniu funkcji.

290 Poprawna postać funkcji sign() int sign(float x) { const float eps=1.0e-5; if (fabs(x) <=eps) return 0; else if (x

291 Wywołanie przez wartość (2/2) Przykład 2. Tablice jednowymiarowe. float srednia(int n, float t[]) { unsigned int i; float sre; sre=0.0; for (i=0;i

292 Wywołanie przez referencję (1/2) Wywołanie przez referencję polega na przekazywaniu referencji do zmiennej. Przykład void zer(int a, int &b) { a=0; b=0; } … int a=1,b=2; printf("a=%d b=%d",a,b);//a=1 b=2 zer(a,b); printf("a=%d b=%d",a,b);//a=1 b=0

293 Wywołanie przez referencję (2/2) Tablice przekazywane są przez referencję. Przykład. Transponowanie macierzy. //deklaracja (specyfikacja) funkcji: void transp(int n, int t[][3]); int main(void){ int t[3][3]; … transp(3,t); } //definicja funkcji: void transp(int n, int t[][3]){ int i,j,tmp; for (i=0;i<3;i++) for (j=i;j<3;j++) { tmp=t[i][j]; t[i][j]=t[j][i]; t[j][i]=tmp; }}

294 Wywołanie przez wskaźnik Jest to odmiana przekazywania parametrów przez wartość. Funkcja otrzymuje kopie wskaźnika, który może określać adres zmiennej należącej do innej funkcji. Zmiana zawartości obszaru wskazywanego przez parametry wskaźnikowe prowadzi do zmiany wartości zmiennych utworzonych w innych funkcjach programu. Przekazywanie parametrów przez wskaźniki jest stosowane: do przekazywania struktur danych o dużych rozmiarach (np. tablic); w celu umożliwienia zwrócenia więcej niż jednej wartości. t_struct lista(t_struct *head, t_struct *x) Argumentami aktualnymi mogą być wskazania do odpowiednich typów (wartości zmiennych wskaźnikowych odpowiednich typów): head=lista(head,x);

295 Własności funkcji typ zwracanej wartości jest podawany przed nazwą funkcji; lista parametrów funkcji może być pusta; można ten fakt podkreślić bezpośrednio posługując się słowem kluczowym void; funkcja może zwracać wynik dowolnego typu z wyjątkiem tablic i funkcji; jeśli nie podano typu funkcji, to domyślnie przyjmowany jest typ int; lista parametrów funkcji ma następującą postać: typ_1 par1, typ_2 par2,... parametry deklarowane są tak jak zmienne; każdy parametr musi mieć indywidualnie zadeklarowany typ; parametry w liście są rozdzielone przecinkami; początek i koniec funkcji określają nawiasy klamrowe; w C parametry funkcji mogą być przekazywane przez wartość lub referencję, w C++ dodatkowo przez wskaźnik; wewnątrz funkcji nie mogą pojawiać się definicje innych funkcji; za pomocą słowa kluczowego return zwracana jest wartość funkcji; return wartość kończy działanie funkcji; jeżeli funkcja nie musi zwracać wyniku, to jako typu wartości należy podać void.

296 Deklaracja funkcji Deklaracja funkcji jest pojęciem logicznym. Stanowi informację dla kompilatora, że funkcja o określonej nazwie, typie wartości oraz liczbie i typie parametrów może zostać użyta (ale nie musi) w danym module programu. Deklaracja funkcji musi kończyć się średnikiem. Deklaracja funkcji nazywana jest prototypem funkcji. double sum(double, double); //prototyp funkcji Pliki nagłówkowe *.h, dołączane dyrektywą #include, zawierają prototypy funkcji, których kody wynikowe dołączane są na etapie łączenia z bibliotek standardowych. Prototyp funkcji mówi kompilatorowi jakiego typu wartość jest zwracana przez funkcję oraz jakie są jej parametry. Dzięki temu możliwe jest podczas kompilacji sprawdzenie czy w poprawny sposób wykorzystywana jest wartość zwracana przez funkcję oraz czy podawane są parametry odpowiednich typów. Ponadto, prototypy pozwalają szybko zorientować się jakie funkcje są dostępne i jak z nich korzystać.

297 Definicja funkcji Definicja funkcja mówi o tym co funkcja robi. Zawiera instrukcje umożliwiające realizację odpowiedniego fragmentu programu. W odróżnieniu od deklaracji przydzielany jest obszar pamięci, w którym znajdzie się kod wynikowy funkcji. Przykład double sum (double x, double y)// definicja funkcji { return (x+y); }

298 Uwagi (1/2) Jeżeli typy argumentów przekazywanych do funkcji nie są zgodne z typami określonymi w definicji czy prototypie, to przeprowadzane są konwersje do typów określonych w prototypie. Podczas kompilacji w trybie języka C++ wywołanie funkcji bez prototypu lub definicji traktowane jest jako błąd. Ponadto, błędy zgłaszane są w następujących przypadkach: –podano za mało parametrów, –podano zbyt wiele parametrów, –występuje niezgodność typu parametrów (mimo konwersji). Zaleca się dołączanie prototypów funkcji poprzez włączanie odpowiedniego zbioru nagłówkowego dyrektywą #include.

299 Uwagi (2/2) Jeżeli podane są prototypy to, definicje o takich samych nagłówkach powinny znajdować się poza funkcją main. W prototypach zazwyczaj nie podaje się nazw argumentów, a jedynie ich typy. Nazwy podane w proto­typach nie muszą być powtórzone w defini­cji, gdyż i tak sprawdz­ane są typy danych. Prototyp: double sum(double x, double y); Definicja: double sum(double a, double b) { return (a+b); } Zaleca się, aby nazwy argumentów w prototypach i definicjach były zgodne. W języku C++ mogą istnieć funkcje o tych samych nazwach i różnych parametrach, które zwane są funkcjami przeciążonymi. Na podstawie typu funkcji i liczby parametrów kompilator wybiera właściwą funkcję.

300 Funkcje z atrybutem inline (1/2) Wywołanie funkcji wiąże się z umieszczeniem argumentów na stosie oraz wykonaniem skoku pod odpowiedni adres. W przypadku funkcji o niewielkich rozmiarach czas związany z jej wywołaniem może być porównywalny z czasem wykonania funkcji. Jeżeli chcemy, aby kod funkcji był umieszczany w miejscu jej wywołania, to należy poprzedzić definicję funkcji atrybutem inline. Pozwala to zwiększyć szybkość odwołań do funkcji. Funkcje z atrybutem inline nie powinny zawierać słów kluczowych: do, while, for, goto, switch, case, break, continue. Wystąpienie jednego z wymienionych słów spowoduje, że kompilator potraktuje funkcję jako zwykłą funkcję statyczną.

301 Funkcje z atrybutem inline (2/2) inline double pot(double x) { return x*x*x; } inline int max(int a, int b) { return (a > b ? a : b); } void main(void) { int a = 2; int b = 7; printf(x^3=%d,pot(a)); // x^3 = 8 printf(Max=%d, max(9, max(a,b))); // Max = 9 } W przypadku wywołania max(9, max(a,b)) funkcja max zostanie dwukrotnie włączona do kodu wynikowego.

302 Rekurencja

303 Definicja rekurencji Rekurencja (rekursja) to sposób definiowania (potencjalnie) nieskończonych struktur danych i podprogramów przy użyciu ich wywołań.

304 Definicja rekurencyjna Definicja rekurencyjna składa się z dwóch części. W pierwszej, zwanej przypadkiem brzegowym (base case) lub początkowym, są wyliczane elementy podstawowe, stanowiące części składowe wszystkich pozostałych elementów zbioru. W drugiej, zwanej krokiem indukcyjnym (recursion), są podane reguły są podane reguły umożliwiające konstruowanie owych bytów z elementów podstawowych lub bytów zdefiniowanych wcześniej. Reguły te można stosować wielokrotnie, tworząc nowe byty. Przykład. Do skonstruowania zbioru liczb naturalnych potrzebny jest element podstawowy jeden i operacja zwiększania o jeden: 1. 1 N 2. jeśli n N, to (n+1) N. 3. nie ma żadnych innych bytów w zbiorze N oprócz tych, których istnienie wynika z reguł 1 i 2. Definicji rekurencyjnych używa się często do: Definiowania struktur danych Definiowania podprogramów

305 Rekurencyjne (rekursywne) typy danych Rekurencyjny (rekursywny) typ danych to taki typ danych, którego definicja zawiera odwołanie do typu definiowanego. Przykład. Drzewo genealogiczne. struct s_rod { char nazwisko[20]; char imie[20]; s_rod *ojciec, *matka; };... struct s_rod *my_dg, *my_dg_ojciec; my_dg=(s_rod *)malloc(sizeof(struct s_rod)); strcpy(my_dg->nazwisko,"Abacki"); my_dg_ojciec=(s_rod *)malloc(sizeof(struct s_rod)); strcpy(my_dg_ojciec->nazwisko,"Abacki-senior"); my_dg->ojciec=my_dg_ojciec; free(my_dg_ojciec); free(my_dg);

306 Rekurencyjne definiowanie podprogramów (1/2) Rekurencyjne definiowanie podprogramów stosowane jest najczęściej do rozwiązywania problemów poprzez rozłożenie ich na prostsze podproblemy tego samego rodzaju. Ta metoda jest czasem bardzo pomocna (np. w algorytmie szybkiego sortowania quicksort), a czasem bardzo kłopotliwa. Przykład. Definicja rekurencyjna funkcji silnia: n! = n*(n-1)*...*1 = n* (n-1)! Inna postać definicji rekurencyjnej:

307 Rekurencyjne definiowanie podprogramów (2/2) Przykład. Definicja rekurencyjna liczb wymiernych: Stosując tę definicję uzyskuje się ciąg liczb wymiernych: 1,2, 5/2, 29/10, 941/290,....

308 Techniki rekurencyjnego wywoływania podprogramów Rekurencyjne wywoływania podprogramów mogą być realizowane na wiele sposobów: bezpośrednio albo w sposób bardziej zawikłany, jedno- lub wielokrotnie. Może być wiele poziomów rekurencji i rzędów złożoności. Techniki rekurencyjnego wywoływania podprogramów: Rekurencja końcowa Rekurencja niekońcowa Rekurencja pośrednia Rekurencja zagnieżdżona

309 Rekurencja końcowa Rekurencja końcowa (tail recursion) cechuje się obecnością tylko jednego wywołania rekurencyjnego na samym końcu podprogramu. Zatem, w miejscu wywołania nie pozostają już żadne instrukcje do wykonania; nie ma żadnych wcześniejszych wywołań rekurencyjnych, bezpośrednich ani pośrednich. Przykład. Rekurencja końcowa void tail(int i) { if (i>0) {printf("%d\n",i); tail(i-1);} };... tail(3);///3 2 1

310 Rekurencja niekońcowa (1/2) Rekurencja niekońcowa cechuje się obecnością tym, że w ciele funkcji występuje jedno lub większa liczba wywołań rekurencyjnych w dowolnym miejscu ciała funkcji. Przykład. Rekurencja niekońcowa. void NoTail(int i) { if (i>0) { NoTail(i-1); printf("%d\n",i); NoTail(i-1);} };... NoTail(3); //

311 Rekurencja niekońcowa (2/2) Przykład. wypisywanie ciągu znaków w odwrotnej do wczytanej kolejności. void reverse(void) { char c; if ((c=getchar())!='\n') { reverse(); putchar(c); }}

312 Rekurencja pośrednia Omawiane dotychczas techniki wywołań rekurencyjnych polegały na bezpośrednim wywoływaniu funkcji przez samą siebie. Funkcja może byc wywoływana pośrednio – przez łańcuch innych wywołań. Przykład. Obliczanie wartości funkcji trygonometrycznych – sinus, cosinus, tangens. przy czym warunek brzegowy określa wyrażenie:

313 Rekurencja zagnieżdżona (1/2) Rekurencja zagnieżdżona ma miejsce wówczas, gdy funkcja jest nie tylko zdefiniowana przez samą siebie, ale także jest użyta jako jeden z parametrów.

314 Rekurencja zagnieżdżona (2/2) Przykład. Funkcja Ackermanna. Funkcja Ackermanna Na początku XX wieku wydawało się, że funkcje pierwotnie rekurencyjne stanowią klasę wszystkich funkcji obliczalnych. Plan Hilberta był skonstruować całą matematyce na podstawie elementarnych operacji typu tych z funkcji pierwotnie rekurencyjnych, ale funkcja Ackermann'a, która wydaje się bardzo dobrze zdefiniowaną (podwójną rekurencją) i intuicyjne obliczalną nie jest pierwotnie rekurencyjną, nie może zatem być rozpatrywana jako podstawa matematyki. Funkcja Ackermanna, zwana również funkcją Ackermanna-Petera, jest ważną w teorii obliczeń funkcją rekurencyjną, która ma dwa argumenty będące liczbami naturalnymi i zwraca liczbę naturalną.

315 Istota rekurencyjnego wywołania funkcji Rekurencyjne wywołanie funkcji polega na ponownym jej wywołaniu jeszcze przed jej zakończeniem. Przy każdym wywołaniu rekurencyjnym tworzona jest kopia środowiska funkcji łącznie ze wszystkimi parametrami i zmiennymi lokalnymi. Wywołanie każdej funkcji powoduje umieszczenie na stosie: informacji umożliwiającej powrót z funkcji do miejsca wywołania, parametrów funkcji, zmiennych lokalnych (automatycznych) istniejących w momencie wywołania funkcji. Ze względu na ograniczoną pojemność stosu funkcje rekurencyjne nie powinny posiadać zbyt dużej liczby parametrów i zmiennych lokalnych ani też zmiennych o dużych rozmiarach.

316 Rekurencyjne definiowanie funkcji Przykład rekurencyjnej definicji funkcji silnii (factorial fiunction) f(n): f(0) = 1 f(n) = n *f(n-1) dla każdego naturalnej liczby n > 0 Dla n=3 otrzymujemy: f(3) = 3 · f(3-1) = 3 · f(2) = 3 · 2 · f(2-1) = 3 · 2 · f(1) = 3 · 2 · 1 · f(1-1) = 3 · 2 · 1 · f(0) = 3 · 2 · 1 · 1 = 6

317 Wywołania funkcji i realizacja rekurencji Wszystkie współczesne języki programowania umożliwiają implementację funkcji rekurencyjnych. Do rekurencyjnego wywołania w programie funkcji wykorzystywany jest stos. Stan każdej wywoływanej funkcji jest opisany wartością wszystkich zmiennych automatycznych i parametrów funkcji oraz adres powrotu, wskazujący miejsce wznowienia wykonywania procesu, który wywołał funkcję. Obszar danych zawierający te informacje nosi nazwę rekordu wywołań (aktywacji; activation record) lub ramki stosu (stack frame). Jest on umieszczany na stosie programu. Rekord wywołania istnieje aż do zakończenia działania funkcji, której dotyczy. W program C/C++ najdłużej istnieje rekord wywołania dla funkcji main().

318 Zawartość rekordu wywołania Rekord wywołania zawiera zwykle następujące dane: Wartości wszystkich parametrów funkcji (adres tablicy i kopie wszystkich pozostałych danych). Wartości wszystkich zmiennych lokalnych (automatycznych) lub adres miejsca ich przechowywania (zależy to od kompilatora). Adres powrotu – adres instrukcji następnej po instrukcji wywołującej funkcję. Dynamiczne dowiązanie – wskaźnik do rekordu wywołania procesu wywołującego funkcję. Zwracana wartość – tylko, gdy nie jest to wartość typu void.

319 Potęga Rekurencyjne wywoływanie funkcji obliczającej podnoszącą dowolną liczbę x do nieujemnej całkowitej potęgi n. Definicja funkcji potęgującej: Poziom wywołania Parametry wywoływanej funkcji Wartości zwracane przez wywoływaną funkcję 1potega(x,4)x*x*x*x 2 potega(x,3)x*x*x 3 potega(x,2)x*x 4 potega(x,1)x 5 potega(x,0)1 Poziomy wywołania funkcji potęgującej x 4

320 Schemat Hornera Wielomian: w n (x)=a 0 x n +a 1 x n-1 +…+a n-1 x+a n można zapisac w postaci: w n (x)=(…((a 0 x+a 1 )x+a 2 )x+.…+a n-1 )x+a n lub w postaci rekurencyjnej: w n (x)= w n-1 (x)x+ a n, n=1,2,…, gdzie w 0 (x)=a 0.

321 Rekurencyjny algorytm Euklidesa Przykładowo NWD(18,12)=NWD(6,12)=NWD(0,6)=6.

322 Niebezpieczeństwa rekurencji Ze stosowaniem rekurencji związanych jest wiele niebezpieczeństw. Dwa najważniejsze spośród nich to: dublowanie obliczeń przepełnienie stosu

323 Dublowanie obliczeń Dublowanie obliczeń polega na niepotrzebnym wykonywaniu tych samych obliczeń. Przykład. Obliczanie wartości ciągu Fibonacciego. Liczby Fibonacciego są zdefiniowane w sposób rekurencyjny: F0=0 F1=1 Fi=Fi-1+ Fi-2, dla i>=2. Początkowe litery ciągu Fibonacciego: 0,1,1,2,3,5,8,13,21,34,55,… Liczby Fibonacciego rosną wykładniczo.

324 Implementacja funkcji obliczającej liczby Fibonacciego Funkcja fib() zwraca wartość ciągu Fibonacciego dla i>=0. int fib(int i) { If (i<2) return i; else return fib(i-1)+ fib(i-2); } Drzewo obliczeń dla funkcji fib(4).

325 Liczba wywołań funkcji fib() Wykazuje się, że w celu obliczenia wartości fib(n) liczba wywołań funkcji fib() wynosi f(n)=2*fib(n+1)-1. Liczba ta jest ogromna już dla niewielkich n, co obrazują tabela i wykres. nf(n)

326 Programowanie dynamiczne Aby radzić sobie z takimi problemami można wykorzystać programowanie dynamiczne, które najogólniej polega na odejściu od rekurencji na rzecz programowania iteracyjnego. Programowanie dynamiczne można zastosować do tych problemów, które można podzielić na wiele podproblemów. Wyniki rozwiązań podproblemów są zapamiętywane w odpowiednich strukturach danych (najczęściej w tablicy), unikając w ten sposób wielokrotnych obliczeń dla tego samego podproblemu. Programowanie dynamiczne polega więc na wykonania obliczeń każdego podproblemu tylko raz i zapamiętaniu jego wyniku w tabeli. W każdym kolejnym kroku można z tej tabeli korzystać.

327 Przykład Poniższa funkcja FIB_DYN zwraca wartość ciągu Fibonacciego dla i>=0 obliczoną metodą programowania dynamicznego. FIB_DYN(i) { f[0] 1 f[1] 1 k 2 while (k

328 Przepełnienie stosu Programy rekurencyjne zwykle wykorzystują dużo zasobów pamięciowych. Przykład. Poniższa funkcja zwraca wartość funkcji MacCarthyego dla całkowitego x. MacCarthy(x) { If (x>100) return (x-10) else return MacCarthy(MacCarthy(x+11))} Liczba wywołań funkcji MacCarthy()

329 Wskaźniki

330 Wskaźnik Wskaźniki są mechanizmem ustanawiającym relacje między komórkami pamięci. 1.Wskaźnik jest komórką, której zawartość jednoznacznie identyfikuje inną komórkę. 2.Wskaźniki to adresy wskazujące miejsce położenia zmiennych dynamicznych w pamięci operacyjnej. 3.Wskaźniki zawierają adresy logiczne, które są zapisane w postaci segment : offset, gdzie część segmentowa określa numer bloku pamięci o rozmiarze 64 KB względem, którego liczone jest przesunięcie offset. Na podstawie adresów logicznych zmiennych i funkcji można wyznaczyć ich adresy fizyczne. Przykład. int *wsk;

331 Klasyfikacja wskaźników (1/2) Wskaźniki dzielą się na: wskaźniki danych (zawierają adresy zmiennych), wskaźniki kodu (zawierają adresy funkcji). Wskaźniki mogą być dalekie (czterobajtowe), definiowane z wykorzysta­niem słowa kluczowego far i złożone z dwubajtowej części segmentowej i dwubajtowej części offsetowej – dane typu unsigned (np. void far *daleki), lub bliskie (dwubajtowe), definiowane za pomocą słowa kluczowego near i złożone tylko z dwubajtowego offsetu (np. void near *bliski). W przypadku braku słowa near lub far (ANSI C) rodzaj wskaźnika zależy od przyjmowanego w kompilatorze modelu pamięci (np. tiny, small, medium).

332 Klasyfikacja wskaźników (2/2) W przypadku, gdy w definicji wskaźnika występuje (tylko BC++): near (np. double near *y) wskaźniki są zawsze bliskie, far (np. double far *x, int far *d) wskaźniki są zawsze dalekie, huge (np. double huge *u) są zawsze dalekie znormalizowane, tzn. ich offsety należą zawsze do przedziału od 0 do 15 (przy wszelkich operacjach na wskaźnikach tego typu następuje automatyczna korekcja offsetu i segmentu).

333 Adresy logiczne i fizyczne Wskaźniki zawierają adresy logiczne, które mają następującą postać: adres_logiczny = segment : offset Interpretacja części segmentowej i offsetowej zależy od trybu adresowania. Sposób wyznaczania adresu fizycznego na podstawie znajomości adresu logicznego (segmentu i offsetu – dane typu unsigned) zależy od trybu adresowania. Wyróżnia się dwa tryby adresowania: adresowanie rzeczywiste adresowanie wirtualne

334 Adresowanie rzeczywiste W trybie adresowania rzeczywistego (aplikacje dla systemu DOS; kompilator BC) segment określa numer bloku pamięci o rozmiarze 64KB względem, którego liczone jest przesunięcie określone przez offset. Każdy taki blok może zaczynać się od adresu fizycznego (adresu bazowego) podzielnego przez 16 (przykładowe adresy bazowe 0, 16, 32, 48, itd.). Adres bazowy jest równy 16 segment, natomiast adres fizyczny oblicza się ze wzoru 16 segment + offset. Na przykład wskaźnik 0xb800:0x0001 reprezentuje adres fizyczny równy 0xb8001.

335 Adresowanie wirtualne W trybie adresowania wirtualnego (aplikacje dla systemu Windows; kompilator BCW) segment jest numerem selektora, który identyfikuje deskryptor definiujący adres początku (adres bazowy) bloku pamięci związanego ze wskaźnikiem. Selektor określa pozycję deskryptora bloku w tablicy deskryptorów, natomiast offset – przesunięcie w bloku. Adres fizyczny oblicza się ze wzoru adres_bazowy + offset.

336 Zakresy adresowania pamięci przy pomocy wskaźników Wskaźniki bliskie umożliwiają dostęp do komórek pamięci w obrębie bloku 64KB (zmiana offsetu od 0 do 0xFFFF). W trybie adresowania rzeczywistego wskaźniki dalekie umożliwiają dostęp do komórek pamięci o adresach z zakresu od 0 do 0xFFFFF (1 MB), natomiast w trybie adresowania wirtualnego dostęp do komórek pamięci o adresach 32-bitowych, tj. od 0 do 0xFFFFFFFF (4GB).

337 Notacja graficzna dla wskaźników Notacja graficzna pozwala zobrazować operacje wykonywane na wskaźnikach. W notacji tej występują kwadraty odpowiadająca komórkom pamięci związanym z poszczególnymi zmiennymi, zarówno statycznymi jak i dynamicznymi. Wskaźniki, tzn. wartości zmiennych wskaźnikowych, przestawia się za pomocą strzałek. Wskazane przez nie miejsca PAO są dostępne jedynie poprzez wskaźniki. Zmienne statyczne posiadają etykiety będące ich nazwami.

338 Przykład i1i2 5 i1 5 5 i2 7 i1 5 i2 5 i1 5 i2 nieużyte k i1=malloc(sizeof(int)); *i1=5; i2= malloc(sizeof(int)); *i2=5; i2=i1; *i2=7; int *i1, *i2;

339 Definiowanie wskaźników (1/3) Przykłady: char*wsk_c;// wskaźnik do zmiennej typu char unsigned char *wsk_uc; // wsk. do zmiennej typu unsigned char int *wsk_i;// wskaźnik do zmiennej typu int long *wsk_l;// wskaźnik do zmiennej typu long float *wsk_f;// wskaźnik do zmiennej typu float double *wsk_d;// wskaźnik do zmiennej typu double Analogicznie definiuje się wskaźniki typu far i huge. float far *wsf; double huge *wsh; Wskaźnik danego typu powinien być wykorzystywany do przechowywania adresów zmiennych tego samego typu.

340 Definiowanie wskaźników (2/3) W celu inicjacji wskaźnika należy przypisać mu adres zmiennej wykorzystując operator &, który podaje adres swojego argumentu. Jeżeli x jest zmienną, to &x jest stałą określającą adres (położenie w pamięci) zmiennej x. Stała &x może być wykorzystana do zainicjowania wskaźnika pokazującego na początek bloku pamięci, w którym przechowywana jest zmienna x. Przykład int x = 5;// zmienna x typu int zainicjowana stałą 5 int *px = &x;// zmienna wskaźnikowa px zainicjowana stałą &x float v = 1.2;// zmienna v typu float zainicjowana stałą 1.2 // px = &v;// błąd – nie można inicjować wskaźnika do typu int // adresem (wskaźnikiem) do typu float (&v jest typu float *)

341 Definiowanie wskaźników (3/3) Jeżeli w miejscu deklaracji wskaźnika jego wartość początkowa nie zostanie określona (ustawiona na adres zmiennej odpowiedniego typu), to zostanie on zainicjowany automatycznie zgodnie z regułami obowiązującymi dla zmiennych. W szczególności, wskaźniki statyczne i zewnętrzne są domyślnie inicjowane zerami (wsk = NULL), natomiast wartości początkowe wskaźników lokalnych nie są określone (są wartościami przypadkowymi). Wygodnie jest inicjować wskaźniki lokalne wartością NULL, gdyż można taki przypadek łatwo wykryć w programie. Na przykład: if (wsk!=NULL) { wykonaj instrukcje; }.

342 Arytmetyka wskaźników Jedynymi dopuszczalnymi operacjami arytmetycznymi na wskaźnikach są: dodawanie i odejmowanie liczb naturalnych, co powoduje przesunięcie wskaźnika odejmowanie dwóch wskaźników pokazujących na elementy tej samej tablicy. Przykład int *w1,*w2; int t[3]={1,2,3}; w1=&t[0];//w1=t; w2=&t[2]; printf("t=%d\n",(w2-w1));//2

343 Porównywanie wskaźników Do porównywania wskaźników można wykorzystać wszystkie operatory relacji: ==, !=,, >=. Warto zauważyć, ze operatory, >= mają sens tylko dla wskaźników pokazujących na elementy tej samej tablicy. Przykład int *wsk, *wsk1,*wsk2; int t[3]={1,2,3}; wsk1=t; wsk2=&t[n]; if (wsk==NULL) printf(wskaznik nie pokazuje na nic); if (wsk1<=wsk2) … int t[3]={1,2,3}; int *wsk; for (wsk=t;wsk

344 Najważniejsze obszary zastosowania wskaźników Wyróżnia się cztery podstawowe domeny zastosowania wskaźników: 1.praca z tablicami 2.operacje na tekstach 3.wskaźniki w argumentach funkcji; 4.dynamiczne przydzielanie (alokacja) i zwalnianie (dealokacja) obszarów pamięci

345 Praca z tablicami (1/2) Wskaźniki można wykorzystać do indeksowania tablicy. Przykład 1 int *wsk; int t[10]; wsk=&t[n]; //wsk wskazuje na n. element tablicy t[] wsk++; //wsk wskazuje na (n+1). element tablicy t[] Przykład 2 int *wi; int t[3]={1,2,3}; wi=t; printf("t[0]=%d ma adres=%p\n",*wi,wi); *wi=4; printf("t[0]=%d\n",t[0]);

346 Praca z tablicami (2/2) Dostęp do tablic za pomocą wskaźników jest szybszy niż dostęp realizowany za pomocą indeksów. W przypadku posługiwania się wyrażeniem tab[i] za każdym razem jest obliczane wyrażenie &tab[0] + i*sizeof(int), które określa adres i-tego elementu tablicy. for (i=0; i<8; i++) printf("tab[%d] = %3d\n", i, tab[i]); Natomiast, w przypadku posługiwania się wskaźnikiem int *wsk=tab realizowana jest tylko operacja wsk++, która powoduje przesunięcie wskaźnika do kolejnej pozycji w tablicy. W przypadku programu: for (i=0, wsk=&tab[0]; i<8; i++) printf("*(wsk+%d) = %3d\n", i, *wsk++);

347 Operacje na tekstach (1/3) Wskaźniki pozwalają znacznie skrócić operacje na łańcuchach. char lan[] = "tekst"; char bufor[20]; unsigned h = 0; char *src = &lan[0]; char *dst = &bufor[0]; h = strlen(src); // strlen() zwraca liczbę znaków tekstu - bez znaku '\0' printf("Dlugosc lancucha = %d %d\n", h, strlen(lan) ); Zawartość łańcucha lan można przekopiować do łańcucha bufor na kilka sposobów.

348 Kopiowanie z wykorzystaniem indeksów tablic (2/3) for (i=0; i<=h; i++) bufor[i] = lan[i]; // dla i == h kopiowany znak \0 Nazwa tablicy jest stałym wskaźnikiem do jej pierwszego elementu. Można więc wyprowadzić teksty za pomocą printf("lan = %s\nbufor = %s\n", lan, bufor); lub uwzględniając, że src = &lan[0] oraz dst = &bufor[0] printf("lan = %s\nbufor = %s\n", src, dst);

349 Kopiowanie z wykorzystaniem wskaźników (3/3) Kopiowanie z wykorzystaniem wskaźników char *src = &lan[0]; char *dst = &bufor[0]; Wariant 1. while (*src!=0) { *dst = *src;// przypisanie src++; dst++;// zmiana wskaźników } *dst = '\0'; // dodanie 0 na końcu printf("lan = %s\nbufor = %s\n", lan, bufor); Wariant 2. while (*src) { *dst++ = *src++; } *dst = '\0'; // dodanie 0 na końcu printf("lan = %s\nbufor = %s\n", lan, bufor);

350 Zastosowanie wskaźników w argumentach funkcji Wskaźniki można wykorzystać do definiowania przekazywania wartości argumentów funkcji przez referencję. Przykład Porównajmy dwa sposoby przekazywania wartości: 1. przez wartość: int plus1(int s){ s=s+1; return s;} … int s=1; printf("s=%d s+1=%d",s,plus1(s));// przez refencję: void plus1(int *sf){ *sf=*sf+1;} … int s=1; plus1(&s); printf("s=%d",s);//2

351 Argument formalny będący wskaźnikiem do obiektu const Ponieważ, przy przekazywaniu do funkcji tablicy, przekazywany jest jedynie adres tablicy, więc w ciele funkcji jeśli element tablicy będzie l-wartością, to zmieni się wartość tego elementu. Jeśli nie chcemy, by funkcja modyfikowała zawartość tablicy należy przekazać ją jako wskaźnik do obiektu stałego. Przykład void t_pokaz(const int *wsk, int n){ int i,s=0; for (i=0;i

352 Zmienne dynamiczne

353 Wprowadzenie Wskaźniki na ogół zawierają adresy zmiennych (obiektów), dla których pamięć jest przydzielana automatycznie przez kompilator na podstawie ich definicji. Dostęp do takich zmiennych odbywa się za pomocą ich identyfikatorów. Zmienne te posiadają pewne własności wynikające z ich zasięgu, łączności i czasu trwania. Stałe, zmienne statyczne i zewnętrzne mają zarezerwowane miejsce w kodzie wykonywalnym programu (są umieszczane w obszarze danych programu). Zmienne lokalne (automatyczne) funkcji są umieszczane na stosie w momencie, gdy sterowanie wejdzie do bloku, w którym zostały zdefiniowane. Zmienne tej klasy znikają po wyjściu sterowania z bloku. Istnieje możliwość dynamicznego tworzenia i kasowania zmiennych w trakcie działania programu. Służą do tego celu funkcje malloc() calloc(), i free() (język C/C++) oraz operatory new i delete (tylko C++).

354 Funkcja malloc() Postać syntaktyczna funkcji: void *malloc(size_t size); Funkcje malloc() i calloc przydzielają pamięć z obszaru sterty, zwanej kopcem (heap), w obszarze od 0 do 1 MB i zwracają wskazanie do przydzielonego obszaru. Rozmiary przydzielanej pamięci nie przekraczają 64 KB (size_t jest typu unsigned). Jeżeli alokacja nie jest możliwa, to zwracany jest wskaźnik NULL. Funkcja calloc() dodatkowo zeruje przydzieloną pamięć. Funkcje malloc() i calloc() zwracają wskaźniki do typu void dlatego niezbędne są konwersje typu przy podstawieniach do wskaźników innych typów. Przykład. char *str; if ((str = (char *) malloc(10)) == NULL) { printf("Brak wolnej pamieci do alokacji bufora \n"); exit(1); } strcpy(str,"Hello"); printf("ciag= %s\n", str); free(str);

355 Przydzielanie obszarów większych niż 64kB Do przydzielania, w obszarze od 0 do 0xFFFFF (1 MB), bloków pamięci o rozmiarze większym niż 64 KB oraz alokowania pamięci w obszarze tzw. sterty dalekiej, adresowanej przez wskaźniki typu far (np. void far *p), służą w systemie BC++ odpowiedniki przedstawionych funkcji, tj. void far *farmalloc(long K) oraz void far *farcalloc(long N, long K). Do zwolnienia bloku służy funkcja farfree(). #include … void *p = NULL; void *q = NULL; long a, b; a = 65000; b = ; q = farmalloc(b); // alokacja bloku bajtow printf("%p\n",q);// np E8 p = malloc(a); // alokacja bloku bajtow printf("%p\n",p); // np C free(p); p = NULL; farfree(q); q = NULL;

356 Uwagi o dynamicznym przydzielaniu pamięci W trybie adresowania rzeczywistego sterta może zajmować pamięć o adresach od 0 do 1 MB. Natomiast w trybie adresowania wirtualnego może być wykorzystywana cała dostępna pamięć w tym obszar powyżej 1 MB. Rozmiar przydzielanej pamięci jest proporcjonalny do rozmiaru tworzonego obiektu (zawsze nie mniejszy niż rozmiar tworzonej zmiennej). Utworzona zmienna nie posiada nazwy, ale jej adres jest podstawiany do wskaźnika odpowie­dniego typu. Można więc uzyskać do niej dostęp za pomocą operatora wyłuskania

357 Własności zmiennych utworzonych za pomocą funkcji malloc() istnieją od momentu ich utworzenia za pomocą funkcji malloc() do momentu likwidacji za pomocą funkcji free(); nie posiadają nazwy; dostęp do nich jest możliwy wyłącznie za pomocą wskaźników; nie podlegają regułom obowiązującym dla zwykłych zmiennych, a dotyczącym zasięgu, łączności i czasu trwania; nie są automatycznie inicjowane wartościami początkowymi w momencie utworzenia; przydzielony blok pamięci zawiera wartość przypadkową; po zwolnieniu pamięci wskaźnik zmiennej nadal zawiera ten sam adres, ale zwolniona pamięć może być udostępniona dla innych zmiennych (należy uważać, aby nie wstawiać danych do pamięci wskazywanej przez wskaźnik, który wskazuje na zwolniony obszar pamięci).

358 Funkcja free() Do zwalniania pamięci uprzednio przydzielonej przy pomocy funkcji calloc(), malloc() lub realloc() służy w C funkcja free() o postaci: void free(void *block); Funkcja free() nie zwraca żadnej wartości. Uwaga: Programista zawsze musi mieć świadomość, że nie można odwoływać się za pomocą wskaźników do komórek pamięci wewnątrz zwolnionego bloku; próba dostępu do takiej komórki spowoduje błąd!

359 Tworzenie zmiennych dynamicznych i zapamiętywanie ich adresów za pomocą wskaźników (1/3) int *wsk_do_int_1(void) //funkcja zwraca wskaźnik do int { return (int *)malloc(sizeof(int)); } … int *w=NULL; w=wsk_do_int_1(); if (w){ *w=1; printf("w=%d\n",*w);//w=1 } free(w);

360 Tworzenie zmiennych dynamicznych i zapamiętywanie ich adresów za pomocą wskaźników (2/3) void wsk_do_int_2(int* *x){ //funkcja ustawia wskaznik do int *x=(int *)malloc(sizeof(int)); **x=1; } … int *w=NULL; wsk_do_int_2(&w); if (w) { //*w=1; - równoważny sposób printf("w=%d\n",*w);//w=1 } free(w);

361 Tworzenie zmiennych dynamicznych i zapamiętywanie ich adresów za pomocą wskaźników (3/3) void wsk_do_int_3(int* &x){ //funkcja ustawia referencje do int x=(int *)malloc(sizeof(int)); *x=1; } … int *w=NULL; wsk_do_int_3(w); if (w){ //*w=1; - równoważny sposób printf("w=%d\n",*w); } free(w);

362 Uwagi Nie należy stosować funkcji free() dla obiektów, które nie zostały utworzone za pomocą funkcji malloc(). Nie wolno również kasować dwa razy tego samego obiektu. Aby się ustrzec przed taką możliwością wystarczy po skasowaniu obiektu ustawić jego wskaźnik na NULL. Zwalnianie wskaźnika pustego nie powoduje błędu. Należy również uważać, aby nie utracić zawartości wskaźnika, który zawiera adres zmiennej utworzonej za pomocą funkcji malloc(). W takim przypadku nie będzie można zwolnić przydzielonej pamięci, co może doprowadzić do wyczerpania sterty. Zmienna dynamiczna może być utworzona wewnątrz funkcji. Przydzielony obszar pamięci nie znika po zakończeniu funkcji. Należy jednak zadbać o to, aby funkcja przekazała wskaźnik do utworzonego obszaru na zewnątrz.

363 Operatory new i delete (C++)

364 Operator new Operator new ma postać: new nazwa_typu Przykład int *wsk; wsk=new int(32);//*wsk=32

365 Operator delete Postać syntaktyczne operatora delete: delete nazwa_obiektu; Operator delete usuwa obiekt z pamięci zanim zostanie zlikwidowany wskaźnik do tego obiektu. Przykład typedef int tint10[10]; … tint10 *t; t=(tint10 *)new tint10;... delete t;

366 Metody testowania dostępności pamięci sprawdzanie poprawności wykonania operacji przydziału pamięci wykorzystanie set_new_handler wykorzystanie instrukcji try/catch(xalloc)

367 Sprawdzanie poprawności wykonania operacji przydziału pamięci int *wsk; wsk= new int[1234]; if (!wsk) printf(brak pamieci);

368 Przykład Dynamiczne tworzenie tablicy dwuwymiarowej. int m = 3;//liczba wierszy. int n = 5;//liczba kolumn int i,j; int **data; try {// sprawdzanie mozliwosci utworzenia tablicy data = new long double*[m];// krok 1: Tworzenie wierszy. for (j = 0; j < m; j++) data[j] = new long double[n]; // Krok 2: Tworzenie kolumn } catch (xalloc) {cout << "NIe mozna utworzyc tablicy"; exit(-1); } for (i = 0; i < m; i++) for (j = 0; j < n; j++) { data[i][j] = i + j; //inicjoanie i drukowanie tablicy cout << data[i][j] << " "; cout << "\n" << endl; for (i = 0; i < m; i++) delete[] data[i];// Krok 1: Usuwanie kolumn delete[] data;}}// Krok 2: Usuwanie wierszy

369 Wskaźniki stałe Wartości wskaźnika stałego nie wolno zmieniać po początkowym ustalenie jego wartości, ale wartość wskazywanej komórki pamięci może się zmieniać. int i=2; int k=2; int *const wsk = &i; //wsk jest wskaznikiem stalym wsk=&k; //bład! i=2;//poprawnie

370 Wskaźniki do stałej Wskaźnik do stałego wskazuje na miejsce pamięci, którego wartości nie można zmieniać, ale wartość samego wskaźnika może być zmieniana. int x[3]={1,2,3}; const int *wsk //wsk jest wskaznikiem do stalej typu int wsk=x; wsk++; //poruszanie wskaznkiem jest dozwolone *wsk=0; //blad!

371 Wskaźniki do funkcji Ogólna postać deklarowania wskaźnika do funkcji: typ (*nazwa_funkcji) (parametry_funkcji) Przykład 1 int (*fun) (void) Funkcja fun jest wskaźnikiem do funkcji wywoływanej bez żadnych parametrów, a zwracającej wartość typu int.

372 Przykład int pierwsza(void);//deklaracje dwoch funkcji, które maja być wywolywane int druga(void); int main(void) { int i; int (*fun)(void); printf("i="); scanf("%d",&i); switch(i){//przypisanie wartosci funkcji fun() case 1: fun=pierwsza; break; case 2: fun=druga; break; default : fun=NULL; break; } if (fun) (*fun)();//wywolanie funkcji o okreslonym adresie getch(); return(0); } int pierwsza(void){ printf("pierwsza\n");} int druga(void){ printf("druga\n");}

373 Funkcje operujące na adresach pamięci Odczytanie wartości segmentu i offsetu wskaźnika umożliwiają funkcje: unsigned FP_SEG(void far *wsk); // segment wskaźnika unsigned FP_OFF(void far *wsk); // offset wskaźnika Utworzenie wskaźnika o określonym segmencie i offsecie umożliwia funkcja: void far *MK_FP(unsigned segment, unsigned offset). Przykład void far *d = MK_FP(0xB800, 0x0000); unsigned s = FP_SEG(d); // s = 0xB800; unsigned o = FP_OFF(d); // o = 0x0000;

374 Wskaźniki typu void (1/2) Wskaźnik do zmiennej określonego typu zawiera informację o adresie zmiennej oraz jej typie (co ma znaczenie podczas wykonywania operacji arytmetycznych z udziałem wskaźnika oraz interpretacji wskazywanego obszaru pamięci). Z definicji int *wi; // wskaźnik do obiektu typu int wynika, że wi wskazuje na obszar pamięci o rozmiarze sizeof(int), w którym można zapamiętać zmienną typu int. Można bezpośrednio odwołać się do pamięci o adresie wi za pomocą *wi. Istnieje możliwość definiowania wskaźników, które wskazują na typ void. void *x;// wskaźnik na dowolny typ Wskaźniki typu void mogą zawierać adres zmiennej dowolnego typu.

375 Wskaźniki typu void (2/2) W przypadku definicji int i = 5; float z = 1.3; int *wi = &i; float *wz = &z; void *x; możliwe są przypisania x = wi oraz x = wz. Wskaźnikowi typu void można przypisać wskaźnik dowolnego typu z wyjątkiem wskaźnika do zmiennej typu const. const long s = 4;// stała s typu long void *x = &s;//błąd, bo s jest stałą Można jednak wpisać adres obiektu stałego do wskaźnika typu void do stałej: const void *x = &s;

376 Rzutowanie wskaźników (1/2) W celu przypisania wskaźnika typu void innemu wskaźnikowi należy dokonać konwersji typu wskaźnika (rzutowania wskaźnika) do typu wymaganego. Operacja rzutowania wskaźników jest również niezbędna w przypadku, gdy przypisujemy wskaźnik jednego typu do wskaźnika innego typu. double zm = 11.2; void *x = &zm; double *d; int *wi; // d = x;// błąd – brak zgodności typów d = (double *) x;// rzutowanie wskaźnika void * do double * printf("%d\n",*d);//11.2 wi = (int *) d;// rzutowanie double * do int *

377 Rzutowanie wskaźników (2/2) Wskaźniki typu void umożliwiają przekazywanie do funkcji wskaźników dowolnego (niestałego) typu. Wewnątrz funkcji można dokonać konwersji typu wskaźnika (rzutowania wskaźnika) do innego typu w celu interpretacji wskazywanego obszaru pamięci. void CzytajPamiec(void *x) { char *c = (char *) x; int *i= (int *) x; long *z = (long *) x; }

378 Obsługa ekranu w trybie tekstowym

379 Podstawowe zadania obsługi ekranu Dostępne funkcje obsługi ekranu umożliwiają: zmianę trybu tekstowego ekranu, sterowanie atrybutami znaków wysyłanych na ekran, realizację operacji wejścia-wyjścia (klawiatura-ekran), obsługę pamięci ekranu (operacje na blokach pamięci), sterowanie położeniem i kształtem kursora. Ekran w trybie tekstowym jest podzielony na jednakowe komórki, w każdej może znaleźć się jeden znak, posiadający pewien atrybut (kolor i tło). Liczba komórek zależy od aktualnego trybu tekstowego. Na ekranie tekstowym zawsze jest określone pewne okno tekstowe, do którego wysyłane są informacje podczas wykonywania operacji wyjścia na ekran. Standardowo oknem jest cały obszar ekranu, ale można to zmienić.

380 Funkcje organizacji i sterowania atrybutami znaków wyświetlanych na ekranu Organizacja ekranu: window() – określanie obszaru ekranu zajmowanego przez okno tekstowe; textmode() – przełączanie między różnymi trybami pracy ekranu; gettextinfo() – informacja o stanie ekranu w trybie tekstowym. Sterowanie atrybutami znaków wysyłanych na ekran: textattr() – ustawianie atrybutu tekstu; textbackground() – ustawianie koloru tła; textcolor() – ustawianie koloru znaków #include int main(void) { window(10,10,40,11); textcolor(BLACK); textbackground(WHITE); cprintf("This is a test\r\n"); return 0; }

381 Sterowanie pozycją kursora gotoxy() – ustawianie kursora na zadanej pozycji; wherex(), wherey() – pobieranie współrzędnych kursora; Przykład #include int main(void) { clrscr(); gotoxy(35, 12); cprintf("Hello world"); cprintf(Aktualne wspolrzedne X: %d Y: %d\r\n", wherex(), wherey()); getch(); return 0; }

382 Czyszczenie ekranu clreol() – usuwa znaki od pozycji kursora do końca linii; clrscr() – kasowanie ekranu; delline() – usuwa wiersz na pozycji kursora i przesuwa pozostałe; insline() – wstawia wiersz na pozycji kursora i przesuwa pozostałe Przykład clrscr(); cprintf("Nacisnij klawisz..."); //* Przesun kursor do 1. kolumny w 2. linii: gotoxy(1,2); getch(); delline();

383 Operacje we/wy (konsola-ekran) kbhit() – funkcja sprawdzająca czy naciśnięcie klawisza jest dostępne ungetch() – wprowadzanie znaku na początek bufora klawiatury, powodując, że będzie on następnym odczytanym znakiem cprintf() – działa jak printf(), ale znaki wysyłane są do aktualnego okna tekstowego i z odpowiednim atrybutem Przykład cprintf(Nacisnij klawisz:"); while (!kbhit());// nic nie rob cprintf("\nNacisnieto klawisz...\n");

384 Operacje na blokach pamięci obrazu (1/2) gettext() – zapamiętanie prostokątnego fragmentu obrazu w buforze pamięci movetext() – kopiowanie prostokątnego fragmentu obrazu na zadaną pozycję; puttext() – kopiuje zawartość bufora pamięci do ustalonego prostokątnego fragmentu obrazu.

385 Operacje na blokach pamięci obrazu (2/2) #include char buffer[4096]; int main(void) { int i; clrscr(); for (i = 0; i <= 20; i++) cprintf("Line #%d\r\n", i); gettext(1, 1, 80, 25, buffer); gotoxy(1, 25); cprintf("Nacisnij klawisz, aby wyczyscic ekran..."); getch(); clrscr(); gotoxy(1, 25); cprintf("Nacisnij klawisz, aby przywrocic ekran..."); getch(); puttext(1, 1, 80, 25, buffer); gotoxy(1, 25); cprintf("Nacisnij klawisz, aby wyjsc..."); getch(); return 0; }

386 Preprocesor

387 Preprocesor umożliwia makrogenerację, kompilację warunkową oraz włączanie do programów zawartości wskazanych plików. Wiersze programu rozpoczynające się znakiem (#) służą do komunikacji z preprocesorem. Granice wierszy są istotne – każdy wiersz jest rozpatrywany indywidualnie.

388 Definicje i rozwinięcia makr Wiersz sterujący o postaci # define identyfikator ciąg-leksemów zleca preprocesorowi zastępowanie dalszych wystąpień identyfikatora wskazanym ciągiem leksemów; opuszcza się odstępy otaczające ciąg leksemów. Wiersz sterujący o postaci # undef identyfikator zleca preprocesorowi, aby zapomniał definicję identyfikatora. Przykład #define ABSDIFF(a,b) ((a)>(b)?(a)-(b):(b)-(a))... x=ABSDIFF(r,y);... #undef ABSDIFF

389 Więcej o wywoływaniach makrodefinicji Wywołanie makrodefinicji jest podobne do wywołania funkcji. Różnice występują w tych przypadkach, w których wyrażenie będące parametrem wywołania jest obliczane w kilku etapach. Np. #define KWADRAT(a)((a) (a)) … int i=5; long w; w = KWADRAT(i++); // w =25, ale i=7! Wniosek: w odwołaniach do makrodefinicji należy unikać przekazywania parametrów, których wartość jest obliczana wieloetapowo.

390 Włączanie plików Wiersz sterujący o postaci # include zleca wstawienie w jego miejsce całej zawartości pliku o podanej nazwie. Inne dopuszczalne postacie tego polecenia: #include - szukaj w folderze \include kompilatora; #include - szukaj we wskazanym miejscu; #include conio.h – szukaj wg ścieżek ustawionych w parametrze PATH systemu operacyjnego; #include - nowa notacja C++; #include - forma akceptowana tylko przez kompilator C++

391 Kompilacja warunkowa (1/2) Fragmenty programu mogą być kompilowane warunkowo, zgodnie z poniższą schematyczną składnią: #if wyrażenie_stałe_1. skecja_końcowa> #endif

392 Kompilacja warunkowa (2/2) Przykład #define AUTO AUDI #if (AUTO==FIAT) printf (to jest FIAT); #if (AUTO==SEAT) printf (to jest SEAT); #if (AUTO==AUDI) printf (to jest AUDI); #else printf (Nie ma takiej marki w bazie); #error zle podana marka samochodu! #endif #undef AUTO

393 Kompilacja modułów

394 Dobrze zaprojektowany zbiór nagłówkowy powinien być zabezpieczony przed wielokrotnym włączeniem go do tekstu programu. // plik.h - zbiór nagłówkowy #ifndef PLIK_H #define PLIK_H #endif … #undef Jeśli makrodefinicja PLIK_H nie została jeszcze zdefiniowana wówczas kompilator musi przeanalizować wszystkie dyrektywy i instrukcje znajdujące się między #ifndef a #endif. W wyniku przetworzenia wspomnianego obszaru zbadane zostaną wszystkie deklaracje znajdujące się w zbiorze nagłówkowym plik.h oraz zdefiniowane zostanie makro PLIK_H. Przy ponownym włączeniu zbioru plik. h jego zawartość nie będzie analizowana.

395 Przykład (1/7) Przykład programu składającego się z pięciu jednostek kompilacji zapisanych w plikach: prot.h - zawiera deklaracje typów oraz prototypy funkcji: pisz_wynik(), suma1(), suma2() i suma3() pisz.cpp - zawiera definicję funkcji pisz_wynik() mod1.cpp - zawiera definicję funkcji suma1() mod2.cpp - zawiera definicję funkcji suma2() mod3.cpp - zawiera definicję funkcji suma3()

396 Przykład (2/7) /* plik nagłówkowy prot.h zawierający deklaracje typów oraz prototypy funkcji */ #ifndef PROT.H #define PROT.H void pisz_wynik(double); double suma1(double, double); void suma2(double, double, double *); void suma3(double, double, double &); #endif

397 Przykład (3/7) /* plik pisz.cpp zawierający definicję funkcji pisz_wynik */ #ifndef PROT.H #include "PROT.H" #endif #include void pisz_wynik(double w) { printf("Wynik= %10.2lf\n",w); }

398 Przykład (4/7) /* plik mod1.cpp zawierający definicję funkcji suma1 */ #ifndef PROT.H #include "PROT.H" #endif #include double suma1(double x, double y) { pisz_wynik(x+y); return x+y; }

399 Przykład (5/7) /* plik mod2.cpp zawierający definicję funkcji suma2 */ #ifndef PROT.H #include "PROT.H" #endif #include void suma2(double x, double y, double *w) { pisz_wynik(x+y); *w = x+y; }

400 Przykład (6/7) /* plik mod3.cpp zawierający definicję funkcji suma3 */ #ifndef PROT.H #include "PROT.H" #endif #include void suma3(double x, double y, double &w) { pisz_wynik(x+y); w = x+y; }

401 Przykład (7/7) /* program główny glowny.cpp */ #ifndef PROT.H #include "PROT.H #endif #include "mod1.cpp" // dołącz kod modułu 1 #include "mod2.cpp" // dołącz kod modułu 2 #include "mod3.cpp" // dołącz kod modułu 3 #include "pisz.cpp" // dołącz kod modułu 4 #include void main(void) {... }

402 Dyrektywa #error Dyrektywa ta ma postać: #error komunikat Dyrektywa ta powoduje, że po jej napotkaniu kompilacja jest przerywana i wypisywany jest komunikat. Przykład #if (WERSJA==1) … #elif (WERSJA==2) … #else #error dopuszczale sa wersje 1 lub 2 #endif

403 Pusta instrukcja preprocesora Wiersz zawierający jedynie znak # nie wywołuje żadnego skutku.

404 Biblioteka standardowa

405 Biblioteka standardowa nie jest częścią samego języka, natomiast pochodzą z niej deklaracje funkcji oraz definicje typów i makr oferowanych przez standardowe środowisko C. Funkcje, typy i makra należące do biblioteki standardowej są zdefiniowane w standardowych plikach nagłówkowych

406 Pliki nagłówkowe Nagłówki mogą być dołączane w dowolnej kolejności i dowolną liczbę razy. Nagłówek musi być dołączony na zewnątrz jakiejkolwiek deklaracji czy definicji i przed użyciem czegokolwiek, co jest w nim zdefiniowane. Nagłówek nie musi być plikiem źródłowym.

407 Wejście i wyjście:

408 System plików

409 System plików (file system) Pliki dyskowe są zorganizowane w sposób hierarchiczny. Na danym poziomie hierarchii, nazywanej folderem, katalogiem lub kartoteką plików, mogą istnieć indywidualne pliki lub podkatalogi. Każdy podkatalog jest normalnym katalogiem i może zawierać pliki lub podkatalogi. Pliki można tworzyć, kopiować, kasować lub zmieniać ich zawartość (aktualizować). Każdy system plików ma własny interfejs, za pomocą którego zleca się wykonywanie działań na plikach i katalogach. Do najpopularniejszych standardów systemu plików należą: system plików systemu UNIX; systemy plików wywodzące się z operacyjnego systemu MD- DOS: FAT (File Allocation Table (IBM/Microsoft)), FAT32, NTFS (NT File System)

410 Wejście i wyjście: Strumień jest źródłem lub celem danych i może być skojarzony z dyskiem lub innym urządzeniem zewnętrznym. Biblioteka obsługuje strumienie tekstowe i binarne. Strumień tekstowy jest ciągiem wierszy; każdy wiersz zawiera zero lub większa liczbę znaków i kończy się znakiem \0. Strumień binarny jest ciągiem bitów (mapa bitowa). Strumień wiąże się z plikiem lub urządzeniem za pomocą otwarcia; to połączenie przerywa się zamykając strumień.

411 Otwarcie pliku Otwarcie pliku udostępnia wskaźnik do zmiennej typu FILE, w którym gromadzi się wszelkie informacje niezbędne do obsługi strumienia. W momencie rozpoczęcia działania programu otwierane są trzy strumienie: - stdin – standardowe wejście; - stdout – standardowe wyjście; - stderr – standardowe wyjście błędów.

412 Funkcje operujące na plikach Zdefiniujmy zmienną stream: FILE *stream; Następujące funkcje operują na plikach: - FILE *fopen() – otwiera plik; - int fflush() – wypisuje dane z bufora; - int fclose() - wypisuje dane z bufora i zamyka plik; - int remove() – usuwa wskazany plik; - int rename() – zmienia nazwę pliku.

413 Funkcja fopen() (1/2) FILE *fopen(const char *filename, const char *mode) – otwiera plik o nazwie filename i zwraca strumień lub NULL, jeśli otwarcie się nie powiedzie. Parametr mode określa rodzaj dostępu do pliku: r – otwórz plik tekstowy do czytania; w - otwórz plik tekstowy do pisania; skasuj poprzednia zawartość, jeśli istnieje; a –dopisuj; otwórz istniejący lub utwórz nowy plik do dopisywania na jego końcu; r+ - – otwórz plik tekstowy do aktualizacji (czytania i pisania); w+ – utwórz plik tekstowy do aktualizacji (do odczuta i zapisu); skasuj poprzednia zawartość, jeśli istnieje; a+ – otwórz istniejący lub utwórz nowy plik, dopisując dane na końcu pliku. Przykład FILE *we; if ((we = fopen(c:\\AUTOEXEC.BAT", "rt")) == NULL) { fprintf(stderr, Nie można otworzyc pliku.\n"); return 1; }

414 Funkcja fopen() (2/2) - uwagi Ponieważ aktualizacja oznacza możliwość jednoczesnego czytania i pisania z tego samego pliku, między wykonywaniem tych czynności należy wywołać funkcję fflush() lub funkcje pozycjonujące plik. Jeśli w argumencie mode po początkowej literze występuje litera b, np. rb, r+b, oznacza to plik binarny. Długość nazwy plików ograniczona jest do FILENAME_MAX znaków (standardowo 80 znaków w ). Jednocześnie można otworzyć co najwyżej FOPEN_MAX plików znaków (standardowo 48 plików w ).

415 Funkcja fflush() int fflush(FILE *stream) – dla strumieni wyjściowych funkcja powoduje zapisania do pliku wszystkich danych znajdujących się w buforze, które jeszcze nie zostały w nim zapisane. Dla strumieni wejściowych działanie funkcji jest nieokreślone. Funkcja zwraca EOF w przypadku błędu pisania, a zero w pozostałych przypadkach. Wywołanie fflush(NULL) wypisuje dane ze wszystkich strumieni wyjściowych. Przykład FILE *stream; char msg[] = "to jest test"; stream = fopen("test.dan", "w"); fwrite(msg, strlen(msg), 1, stream); fflush(stream);//wypisywanie z bufora bez zamykania pliku... fclose(stream);

416 Funkcja fclose() int fclose(FILE *stream) – zapisuje do pliku wszystkie nie wypisane dane związane ze strumieniem stream ; kasuje wszelkie nie przeczytane dane z buforów wejściowych, zwalnia przydzielone bufory i zamyka plik. Funkcja zwraca EOF w przypadku błędu., a zero w pozostałych przypadkach. Przykład int main(void){ FILE *fp; char buf[11] = " "; fp = fopen(test.txt", "w"); fwrite(buf, strlen(buf), 1, fp); fclose(fp); return 0; }

417 Funkcja remove() int remove(const char *filename) – usuwa wskazany plik tak, że późniejsza próba otworzenia go zakończy się niepowodzeniem. Funkcja zwraca wartość różną od zera w przypadku, gdy usunięcie pliku się nie powiedzie. Przykład int main(void) { char file[80]; printf("Nazwa pliku do usuniecia: "); gets(file); if (remove(file) == 0) printf("Usunieto %s.\n",file); else perror(Blad!"); //drukuje do stderr komunikat return 0; }

418 Funkcja rename() int rename(const char *oldname, const char *newname) – zmienia nazwę pliku z oldname na newname. Funkcja zwraca wartość różną od zera w przypadku, gdy operacja się nie powiedzie. Przykład int main(void) { char oldname[80], newname[80]; printf("Stara nazwa pliku: "); gets(oldname); printf("Nowa nazwa pliku: "); gets(newname); if (rename(oldname, newname) == 0) printf("Zmiana nazwy pliku %s na %s.\n", oldname, newname); else perror(Blad!"); return 0;}

419 Funkcje zapisujące do strumienia

420 Formatowane wyjście fprintf() int fprintf(FILE *stream, const char *format[, argument,...]); - funkcja ta, pod kontrolą parametru format, przekształca i wypisuje pozostałe argumenty do strumienia wyjściowego stream. Wartość zwracana przez funkcję jest równa liczbie wypisanych znaków lub jest liczbą ujemną w przypadku błędu. Przykład int main(void) { FILE *stream; int i = 100; char c = 'C'; float f = 1.234; stream = fopen("dane.txt", "w+"); fprintf(stream, "%d %c %f", i, c, f); fclose(stream); return 0; }

421 Funkcja fwrite() size_t fwrite( const void *ptr, size_t size, size_t n, FILE *stream); - wypisuje do strumienia stream, n jednostek danych każdy o długości size bajtów, rozpoczynając od adresu ptr. Funkcja zwraca liczbę jednostek (a nie bajtów) danych wypisanych do strumienia lub wartość ujemną w przypadku błędu. Przykład struct mystruct { int i; char ch;} s; int main(void) { FILE *stream; if ((stream = fopen("TEST.$$$", "wb")) == NULL) { fprintf(stderr, "Nie mozna utworzyc pliku.\n"); return 1; } s.i = 0; s.ch = 'A'; fwrite(&s, sizeof(s), 1, stream); fclose(stream); return 0; }

422 Funkcje odczytujące ze strumienia

423 Formatowane wejście int fscanf(FILE *stream, const char *format[, argument,...]); - funkcja ta, pod kontrolą parametru format, przypisuje przekształcone wartości kolejnym argumentom. Każdy z tych argumentów musi być wskaźnikiem. Funkcja kończy swe działanie, gdy zinterpretuje cały format. Funkcja zwraca EOF, jeśli napotka koniec pliku lub wystąpi jakiś błąd.; w przeciwnym przypadku zwraca liczbę przekształconych i przypisanych danych wejściowych. Przykład if (fscanf(stdin,"%d",&i)) printf("i=%d\n", i); else printf("Blad odczytu.\n");

424 Funkcja fread() (1/2) size_t fread(void *ptr, size_t size, size_t n, FILE *stream); - funkcja czyta z podanego strumienia n jednostek danych, każdy o długości size bajtów i kieruje je do obszaru pamięci wskazanego przez ptr. Rozmiar czytanych danych wynosi zatem ( n*size ). Funkcja zwraca liczbę odczytanych jednostek danych (a nie bajtów), natomiast w przypadku napotkania końca pliku lub wystąpienia błędu zwraca wartość zero.

425 Funkcja fread() int main(void) { FILE *stream; char msg[] = "to jest test"; char buf[20]; if ((stream = fopen("test.txt", "w+"))== NULL) { fprintf(stderr, "Nie mozna utworzyc pliku.\n"); return 1; } fwrite(msg, strlen(msg)+1, 1, stream); fseek(stream, 0,SEEK_SET);//początek pliku fread(buf, strlen(msg)+1, 1, stream); printf("%s\n", buf); fclose(stream); return 0; }

426 Funkcje realizujące wejście znakowe Podstawowe funkcje wykonujące wejście znakowe: int fgetc() char *fgets()

427 Funkcja fgetc() int fgetc(FILE *stream) - czyta ze strumienia stream następny znak i zwraca jego wartość. W przypadku napotkania końca pliku lub wystąpienia błędu zwraca EOF. Przykład int main(void) { FILE *stream; char string[] = "To jest test"; char ch; stream = fopen("test.dan", "w+"); fwrite(string, strlen(string)+1, 1, stream); fseek(stream, 0, SEEK_SET); while (ch = fgetc(stream)) putch(ch);//wyprowadza znak na ekran fclose(stream); return(0); }

428 Funkcja fgets() char *fgets(char *s, int n, FILE *stream); - czyta co najwyżej (n-1) znaków i wstawia do tablicy s. Funkcja przerywa czytanie po napotkaniu znaku nowego wiersza; znak ten także wstawia do tablicy. Cały tekst jest zakończony znakiem \0 '. Funkcja zwraca s lub NULL w przypadku napotkania końca pliku albo błędu. Przykład int main(void) { FILE *stream; char string[] = "To jest test"; char msg[20]; stream = fopen(dane.txt", "w+"); fwrite(string, strlen(string)+1, 1, stream); fseek(stream, 0, SEEK_SET); fgets(msg, strlen(string)+1, stream); printf("%s", msg); fclose(stream); return 0; }

429 Funkcje realizujące wyjście znakowe Podstawowe funkcje wykonujące wyjście znakowe: - int fputc() - int fputs()

430 Funkcja fputc() int fputc(int c, FILE *stream); - wpisuje znak c (przekształcony do unsigned char) do strumienia stream. Zwraca wypisany znak lub EOF w przypadku błędu. Przykład #include int main(void) { char msg[] = "Hello world"; int i = 0; while (msg[i]) { fputc(msg[i], stdout); i++;} return 0;}

431 Funkcja fputs() int fputs(const char *s, FILE *stream); - wypisuje tekst zawarty w tablicy s (nie musi zawierać '\n') do strumienia stream. Zwraca liczbę nieujemną lub EOF w przypadku błędu. Przykład #include int main(void) { fputs("Hello world\n", stdout); return 0;}

432 Funkcje wyznaczające pozycję w pliku int fseek() long ftell () void rewind () int fgetpos() int fsetpos ()

433 Funkcja fseek() (1/2) int fseek(FILE *stream, long offset, int origin); - wyznacza pozycję w strumieniu stream ; następne czytanie lub pisanie będzie odnosić się do danych rozpoczynających się od nowej pozycji. Dla plików binarnych nowa pozycja wypada w miejscu oddalonym o offset znaków od punktu odniesienia origin, który może mieć wartości: SEEK_SET (początek pliku), SEEK_CUR (bieżąca pozycja) lub SEEK_END (koniec pliku). Dla plików tekstowych wartość offset musi być równa zeru lub wartości zwróconej przez funkcję ftell(). Funkcja fseek() zwraca wartość różną od zera w przypadku błędu.

434 #include long RozmiarPliku(FILE *stream); int main(void) { FILE *stream; stream = fopen("mojplik.txt", "w+"); fprintf(stream, "To jest test"); printf("Wielkosc pliku mojplik.txt wynosi %ld bajtow\n", RozmiarPliku(stream)); fclose(stream); return 0;} long RozmiarPliku(FILE *stream) { long AktPozycja, Dlugosc; AktPozycja = ftell(stream); fseek(stream, 0L, SEEK_END); Dlugosc = ftell(stream); fseek(stream, AktPozycja, SEEK_SET); return Dlugosc;}

435 Funkcja ftell() long ftell(FILE *stream) - zwraca wartość bieżącej pozycji dla strumienia stream lub -1L w przypadku błędu. Przykład #include int main(void) { FILE *stream; stream = fopen("mojplik.txt", "w+"); fprintf(stream,"%s", "Test"); printf("Wskaznik pliku jest na pozycji %ld\n",ftell(stream));//4 fclose(stream); return 0;}

436 Funkcja rewind() void rewind (FILE *stream); - przesuwa znacznik pliku na początek. Przykład #include int main(void) { FILE *fp; char *fname = "TXXXXXX", *newname, first; //tworzy unikalną nazwę pliku wg podanego wzorca: newname = _mktemp(fname); fp = fopen(newname,"w+"); fprintf(fp, %s","abcdefghijklmnopqrstuvwxyz"); rewind(fp); fscanf(fp,"%c",&first); printf("Pierwszy znak: %c\n",first); fclose(fp); remove(newname); //usuwa plik o podanej nazwie return 0;}

437 Funkcja fgetpos() int fgetpos(FILE *stream, fpos_t *ptr); - zapamiętuje bieżącą pozycję strumienia stream w miejscu wskazanym przez *ptr. Z tej wartości można później skorzystać w funkcji fsetpos(). Typ fpos_t jest odpowiednim typem zmiennej do przechowania takiej wartości. W przypadku błędu funkcja fgetpos() zwraca wartość różną od zera. Przykład FILE *stream; //typ przechowujący pozycję pliku fpos_t pozycja;... fgetpos(stream, &pozycja); printf( " Pozycja pliku: %ld\n", pozycja);

438 Funkcja fsetpos() int fsetpos(FILE *stream, const fpos_t *ptr); - ustawia bieżącą pozycję strumienia stream według wartości zapamiętanej przez funkcję fgetpos() w miejscu wskazanym przez *ptr. W przypadku błędu funkcja fsetpos() zwraca wartość różną od zera.

439 #include void showpos(FILE *stream); int main(void) { FILE *stream; fpos_t filepos; stream = fopen("dane.txt", "w+"); fgetpos(stream, &filepos); fprintf(stream, "To jest test"); showpos(stream); if (fsetpos(stream, &filepos) == 0) showpos(stream); else{fprintf(stderr,"Bład we wskazaniu pozycji w pliku.\n"); exit(1); } fclose(stream); return 0;} void showpos(FILE *stream) { fpos_t pos; fgetpos(stream, &pos); printf("Pozycja pliku: %ld\n", pos); }