ISO/ANSI C zm. dynamiczne. ISO/ANSI C zm. dynamiczne. ISO/ANSI C zm. dynamiczne. ISO/ANSI C zm. dynamiczne. ISO/ANSI C zm.

Podobne dokumenty
ISO/ANSI C obsługa błędów ISO/ANSI C. ISO/ANSI C obsługa błędów. ISO/ANSI C obsługa błędów. ISO/ANSI C obsługa błędów. ISO/ANSI C obsługa błędów

ISO/ANSI C obsługa błędów. ISO/ANSI C obsługa błędów. ISO/ANSI C obsługa błędów. ISO/ANSI C obsługa błędów. ISO/ANSI C obsługa błędów

ISO/ANSI C - biblioteki ISO/ANSI C. ISO/ANSI C - biblioteki. ISO/ANSI C - biblioteki. ISO/ANSI C - biblioteki. ISO/ANSI C - biblioteki

Wykład VII. Programowanie. dr inż. Janusz Słupik. Gliwice, Wydział Matematyki Stosowanej Politechniki Śląskiej. c Copyright 2014 Janusz Słupik

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

ISO/ANSI C - funkcje. Funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje. ISO/ANSI C - funkcje

Biblioteka standardowa - operacje wejścia/wyjścia

ISO/ANSI C dostęp do plików ISO/ANSI C. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików

ISO/ANSI C dostęp do plików ISO/ANSI C. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików

Podstawy programowania skrót z wykładów:

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

Katedra Elektrotechniki Teoretycznej i Informatyki. wykład 12 - sem.iii. M. Czyżak

Podział programu na moduły

Temat: Dynamiczne przydzielanie i zwalnianie pamięci. Struktura listy operacje wstawiania, wyszukiwania oraz usuwania danych.

Wstęp do Programowania, laboratorium 02

1 Podstawy c++ w pigułce.

Co nie powinno być umieszczane w plikach nagłówkowych:

I - Microsoft Visual Studio C++

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Programowanie proceduralne INP001210WL rok akademicki 2018/19 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

Języki C i C++ Wykład: 2. Wstęp Instrukcje sterujące. dr Artur Bartoszewski - Języki C i C++, sem. 1I- WYKŁAD

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy INNE SPOSOBY INICJALIZACJI SKŁADOWYCH OBIEKTU

Laboratorium 3: Preprocesor i funkcje ze zmienną liczbą argumentów. mgr inż. Arkadiusz Chrobot

Część 4 życie programu

Funkcje zawarte w bibliotece < io.h >

C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów. C++ - przeciążanie operatorów

Obsługa plików. Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Laboratorium Podstaw Informatyki Strona 1. Kraków 2013

Funkcje zawarte w bibliotece < io.h >

Programowanie w C++ Wykład 9. Katarzyna Grzelak. 14 maja K.Grzelak (Wykład 9) Programowanie w C++ 1 / 30

Ćwiczenie 4. Obsługa plików. Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Laboratorium Podstaw Informatyki Strona 1.

Łącza nienazwane(potoki) Łącza nienazwane mogą być używane tylko pomiędzy procesami ze sobą powiązanymi.

Języki i metodyka programowania. Typy, operatory, wyrażenia. Wejście i wyjście.

1 Wskaźniki. 1.1 Główne zastosowania wskaźników

Programowanie w C++ Wykład 8. Katarzyna Grzelak. 15 kwietnia K.Grzelak (Wykład 8) Programowanie w C++ 1 / 33

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Zasady programowania Dokumentacja

Pobieranie argumentów wiersza polecenia

1 Podstawy c++ w pigułce.

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Skrypty i funkcje Zapisywane są w m-plikach Wywoływane są przez nazwę m-pliku, w którym są zapisane (bez rozszerzenia) M-pliki mogą zawierać

Struktury. Przykład W8_1

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 3. Karol Tarnowski A-1 p.

Programowanie strukturalne i obiektowe

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 2. Karol Tarnowski A-1 p.

Programowanie w C++ Wykład 8. Katarzyna Grzelak. 7 maja K.Grzelak (Wykład 8) Programowanie w C++ 1 / 31

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Podstawy programowania. Wykład: 5. Instrukcje sterujące c.d. Stałe, Typy zmiennych c.d. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Wykład. Materiały bazują częściowo na slajdach Marata Dukhana

Język C zajęcia nr 11. Funkcje

Wskaźniki w C. Anna Gogolińska

Zmienne, stałe i operatory

Programowanie w C++ Wykład 6. Katarzyna Grzelak. kwiecień K.Grzelak (Wykład 6) Programowanie w C++ 1 / 40

Tablice, funkcje - wprowadzenie

Wstęp do programowania INP001213Wcl rok akademicki 2017/18 semestr zimowy. Wykład 12. Karol Tarnowski A-1 p.

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

Podstawy programowania komputerów

Wykład 4: Klasy i Metody

Stałe, znaki, łańcuchy znaków, wejście i wyjście sformatowane

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

Instrukcja do laboratorium Systemów Operacyjnych (semestr drugi)

Wskaźniki. Programowanie Proceduralne 1

Informacje wstępne #include <nazwa> - derektywa procesora umożliwiająca włączenie do programu pliku o podanej nazwie. Typy danych: char, signed char

Tablice. Monika Wrzosek (IM UG) Podstawy Programowania 96 / 119

Języki i metodyka programowania. Wprowadzenie do języka C

Obsługa wyjątków. Język C++ WW12

Wskaźniki do funkcji. Wykład 11. Podstawy programowania ( język C ) Wskaźniki do funkcji (1) Wskaźniki do funkcji (2)

TEMAT : KLASY DZIEDZICZENIE

PMiK Programowanie Mikrokontrolera 8051

Funkcja (podprogram) void

2 Przygotował: mgr inż. Maciej Lasota

C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy. C++ - klasy INNE SPOSOBY INICJALIZACJI SKŁADOWYCH OBIEKTU

Wstęp do programowania. Wykład 1

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Laboratorium 1 Temat: Przygotowanie środowiska programistycznego. Poznanie edytora. Kompilacja i uruchomienie prostych programów przykładowych.

Język ludzki kod maszynowy

Podstawy programowania w języku C++

Wykład 15. Literatura. Kompilatory. Elementarne różnice. Preprocesor. Słowa kluczowe

1 Wskaźniki i zmienne dynamiczne, instrukcja przed zajęciami

Języki programowania. Przetwarzanie plików amorficznych Konwencja języka C. Część siódma. Autorzy Tomasz Xięski Roman Simiński

ISO/ANSI C dostęp do plików ISO/ANSI C. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików. ISO/ANSI C dostęp do plików

Wykład 8: klasy cz. 4

Funkcje. Spotkanie 5. Tworzenie i używanie funkcji. Przekazywanie argumentów do funkcji. Domyślne wartości argumentów

4. Wyrzuć wyjątek jeśli zmienna ist nie istnieje bloki: try, catch i wyrzucanie wyjątku

Programowanie proceduralne INP001210WL rok akademicki 2015/16 semestr letni. Wykład 6. Karol Tarnowski A-1 p.

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Ćwiczenie 1. Podstawy. Wprowadzenie do programowania w języku C. Katedra Metrologii AGH

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Zmienne i struktury dynamiczne

Podstawy wykorzystania bibliotek DLL w skryptach oprogramowania InTouch

Język C++ zajęcia nr 2

Programowanie komputerowe. Zajęcia 1

Wstęp do programowania

Podstawy programowania. Wykład: 9. Łańcuchy znaków. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

Język C, tablice i funkcje (laboratorium, EE1-DI)

Transkrypt:

Usuwanie wskazanego elementu z listy Przed usuwaniem: Po usunięciu: Usuwanie wskazanego elementu z listy (filmu z roku 2010) rok = 2010; // wskazuje na rok // filmu do usunięcia if (glowa->rok == rok) { usuw = glowa; glowa = usuw->nast; } else { wsk = glowa; while ((wsk->nast!= NULL)) { if (wsk->nast->rok == rok) break; wsk = wsk->nast; }; if(wsk->nast!= NULL) { // znaleziono! usuw = wsk->nast; wsk->nast = wsk->nast->nast; free(usuw); }; } 147 UKSW, WMP. SNS, Warszawa 148 Zaleta list jednokierunkowych: zużywają pamięć proporcjonalnie do potrzeb Wada list jednokierunkowych: wskaźniki mogą przesuwać się jedynie do przodu Listy dwukierunkowe: możliwość poruszania się wskaźnika w obydwu kierunkach zużywają minimalnie więcej pamięci, są za to szybsze w dostępie do danych Listy dwukierunkowe: struct film_t { char tytul[80]; int rok; struct film_t *nast; struct film_t *poprz; }; dane nast poprz 149 150 Listy dwukierunkowe: struct film_t *wsk;.. wsk = wsk->nast; /* Przemieszczanie się w przód: */.. wsk = wsk->poprz; /* Przemieszczanie się w tył: */ Listy dwukierunkowe dodawanie: struct film_t *wsk, *nowy; nowy wskazuje na nowy element do umieszczenia wsk wskazanie na element po którym mamy umieścić nowy nowy->nast = wsk->nast; nowy->poprz = wsk; wsk->nast->poprz = nowy; wsk->nast = nowy; 151 152 1

Listy dwukierunkowe dodawanie: struct film_t *wsk, *nowy; nowy wskazuje na nowy element do umieszczenia wsk wskazanie na element przed którym mamy umieścić nowy nowy->nast = wsk; nowy->poprz = wsk->poprz; wsk->poprz->nast = nowy; wsk->poprz = nowy; Listy dwukierunkowe usuwanie: struct film_t *wsk, *nowy; wsk wskazanie na element, po którym znajduje się element do usunięcia usun wskaźnik pomocniczy usun = wsk->nast; wsk->nast = wsk->nast->nast; wsk->nast->poprz = wsk; free(usun); 153 154 Listy dwukierunkowe usuwanie: struct film_t *wsk, *nowy; wsk wskazanie na element, przed którym znajduje się element do usunięcia usun wskaźnik pomocniczy usun = wsk->poprz; wsk->poprz = wsk->poprz->poprz; wsk->poprz->nast = wsk; free(usun); Wnioski: Listy są przydatne szczególnie wtedy, gdy: Dodawane są lub usuwane dowolne elementy ciągu, oraz nie jest znana ich liczba. Tablice i pliki również umożliwiają wykonanie tych operacji, ale.... dla tablicy musimy przepisywać elementy leżące za wstawianym lub usuwanym elementem, a dla plików musimy przepisywać całe pliki. Tablice są wygodniejsze do realizacji operacji wyszukiwania, przeglądania i dostępu do wybranego elementu. (dla tablicy posortowanej oszacowanie z góry kosztu tzw. wyszukiwania binarnego to log(n), natomiast dla list stosowane jest wyszukiwanie liniowe n). 155 156 ISO/ANSI C typy pochodne Najbardziej nawet złożone struktury mają swój typ, który można zapisać za pomocą istniejących podstawowych typów danych int tab[] = {1,2,3}; int *y[3] = { tab, tab, tab }; int (*z)[3] = &tab; ISO/ANSI C Typy pochodne Takim złożonym typom można nadać aliasy (nazwy) za pomocą deklaracji typedef typedef int T[]; typedef int *T3Wsk[3]; typedef int (*WskT3)[3]; T tab = {1,2,3}; T3Wsk tab1 = {tab, tab, tab }; WskT3 tab2 = &tab; 158 2

Poprawa obsługi błędów to jedna z najpewniejszych metod tworzenia solidnie działającego kodu ISO/ANSI C Obsługa błedów Źródła błędów w działających programach sytuacje niezgodne z oczekiwaniami autora programu, ale potencjalnie możliwe do wystąpienia Przyczyna: fałszywe oczekiwania autora programu wynikające z lenistwa, albo z wishful thinking czyli pobożnych życzeń autora, że wszystkie warunki prawidłowej pracy zostaną spełnione przez użytkownika, zanim uruchomi on program, albo.. 160 Obsługa błędów w C polega na kontrolowaniu rezultatów wykonania wywoływanych funkcji i powiązaniu z tymi rezultatami kodu obsługującego błąd. Otwieranie pliku (może się udać, lub nie). FILE* stream; char s[100]; stream = fopen( nazwa_pliku, "r" )); fscanf(stream, %s, s); Czy ten kod jest bezpieczny? FILE* stream; char s[100], file_name[100]; stream = fopen( nazwa_pliku, "r" )); if (stream == NULL) /* sprawdzamy, czy otwieranie się powiodło */... Co powinno się znaleźć w miejscu kropeczek? 1. Przerwanie pracy całego programu 2. Wyświetlenie komunikatu o problemie i powrót sterowania do początku działania programu 3. Powrót sterowania do miejsca, w którym pobierana jest nazwa pliku 161 162 Podstawowa strategia obsługi błędów sprawdzanie wartości zwracanej przez funkcję biblioteczną, której wywołaniu mogło towarzyszyć pojawienie się błędu Jeżeli informacje zwracane przez funkcję biblioteczną są zbyt proste, dodatkowo wykorzystywana jest zmienna globalna przechowująca kod błędu: errno Zmienna errno ma ustawiany kod błędu w momencie wywołania żądania systemowego, takiego jak np. próba otwarcia pliku. Należy ja od razu odczytać, bo następne wywołanie może spowodować kolejną zmianę jej wartości. Mając kod błędu możemy zażądać tekstu odpowiadającego temu kodowi i w ten sposób dowiedzieć się czegoś więcej 163 Constant System error message Value E2BIG Argument list too long 7 EACCES Permission denied 13 EAGAIN No more processes or not enough memory or maximum nesting level 11 reached EBADF Bad file number 9 ECHILD No spawned processes 10 EDEADLOCK Resource deadlock would occur 36 EDOM Math argument 33 EEXIST File exists 17 EINVAL Invalid argument 22 EMFILE Too many open files 24 ENOENT No such file or directory 2 ENOEXEC Exec format error 8 ENOMEM Not enough memory 12 ENOSPC No space left on device 28 ERANGE Result too large 34 EXDEV Cross-device link 18 164 3

Jeżeli chcemy wyświetlić użytkownikowi komunikat z tekstem odpowiadającym kodowi błędu: char *strerror( int errnum ); Funkcja zwraca tekst, który odpowiada opisowi kodu błędu. file = fopen(fname, "r"); if (file == NULL) { printf("error while trying to open '%s': %s\n", fname, strerror(errno));. Zamiast: file = fopen(fname, "r"); if (file == NULL) { printf("error while trying to open '%s': %s\n", fname, strerror(errno));. Można też napisać: file = fopen(fname, "r"); if (file == NULL) { printf("error while trying to open '%s': %s", fname, strerror(null));. Wtedy zostanie wypisany komunikat właściwy dla ostatniego wywołania, które wygenerowało błąd, zakończony znakiem nowej linii. 165 166 Standardowy strumień dla komunikatów o błędach: stderr Tam zwykle wypisujemy komunikaty o błędach, np.: fprintf( stderr, %s, Błąd otwarcia pliku ); W przypadku wystąpienia błędów przy wywołaniu funkcji bibliotecznych, zręczniej jest wysłać systemowy komunikat o błędzie do strumienia stderr za pomocą funkcji perror: void perror( const char *string ); Zmienna string zawiera komunikat użytkownika. Zaraz za nim zostanie wypisany komunikat systemowy o błędzie. Wywołanie: perror( Błąd otwarcia pliku" ); wypisze w oknie konsoli komunikat będący złożeniem dwóch komunikatu użytkownika i komunikatu systemowego : Błąd otwarcia pliku: No such file or directory 167 W trakcie działania na pomyślnie otwartym pliku (zapisywania lub odczytywania) mogą również pojawić się błędy. Aby sprawdzić flagę błędu: int ferror( FILE *stream ); FILE * pfile; pfile=fopen("myfile.txt", wb"); if (pfile==null) perror ("Error opening file"); else { fwrite (buffer,sizeof(buffer),1,pfile); if (ferror (pfile)) perror ("Error writing to myfile.txt"); UKSW, WMP. SNS, Warszawa 168 Aby oczyścić flagi błędu i flagę końca pliku dla otwartego pliku: void clearerr( FILE *stream ); Flagi błędu i końca pliku nie są automatycznie czyszczone, ale pozostają póki nie zostanie wywołana funkcja clearerr, fseek, fsetpos, lub rewind. W przeciwnym razie każda kolejna próba działania na takim pliku będzie ciągle zwracała raz ustawiony kod błędu. 169 Wyszukiwanie błędów w nowym kodzie Oprócz błędów wynikających z zewnętrznych warunków w których pracuje program są jeszcze błędy, których źródłem jest sam autor kodu, tj. błędy związane z niepoprawnym zakodowaniem algorytmu Typowy błąd: dopuszczenie do sytuacji, w której program nadał zmiennej wartość spoza zakresu przewidzianego przez programistę, tj. dla której nie ma właściwej obsługi i stąd program próbował: dzielić przez zero obliczyć pierwiastek z liczby ujemnej robić coś równie głupiego 170 4

Intuicyjne rozwiązanie: dodanie sprawdzenia, czy zmienna ma wartość należąca do dozwolonego zakresu: void printd(int n) { if (n<=0) { /* n może mieć tylko wartości dodatnie */ printf( uwaga: n<=0!\n ); return; } To zwiększa liczbę linii kodu, komplikuje kod i spowalnia działanie programu. Takie sprawdzenia stają się zbędne, kiedy już program jest wytestowany i wiadomo, że funkcja NIGDY nie zostanie wywołana z argumentem o wartości 0 lub mniejszej. 171 Potrzebny jest prosty mechanizm, który będzie sprawdzał poprawność warunku logicznego, sygnalizował, kiedy jest nie spełniony, oraz znikał, kiedy tworzona jest finalna postać programu. void assert( int expression ); <assert.h> Sprawdza wartość wyrażenia logicznego i jeżeli jest spełnione, nie robi nic. W przeciwnym przypadku przerywa działanie programu i wyrzuca komunikat do standardowego strumienia wyjściowego. Komunikat zawiera: treść warunku, nazwę pliku źródłowego i numer linii. 172 void printd(int n) { assert(n>0); Jeżeli zmienna n ma wartość większą od zera, nie dzieje się nic. W przeciwnym przypadku dostaniemy w oknie konsoli komunikat: Assertion failed: n>0, file main5.c, line 34 This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information. Czy przerwanie działania programu to nie jest zbyt drastyczne rozwiązanie? W praktyce znacznie gorszym pomysłem byłoby dopuszczenie do dalszego działania programu w sytuacji, kiedy nie są spełnione podstawowe założenia projektanta i program wymaga poprawek. Ten komunikat zawiera wszystkie niezbędne informacje, aby ułatwić znalezienie błędu w swoim programie. 173 174 Użycie assert(n>0); jest prostsze i wymaga mniej pisania niż: if (n<=0) { printf( uwaga: n<=0! ); exit; } Ponadto znika w finalnej wersji programu. Aby assert zadziałał, kompilacja kodu musi być wykonywana w trybie generującym dodatkowe informacje dla debuggera. Ten tryb pozwala np. na krokowe wykonanie kodu i obserwowanie wartości zmiennych w poszczególnych krokach wykonania, co jest przydatne podczas tworzenia i poprawiania kodu. Finalna wersja kodu (tzw. release) jest generowana zawsze przy wyłączonym trybie generowania dodatkowych informacji dla debugera (wtedy program jest mniejszy i działa szybciej). W tym trybie wszystkie wywołania assert są ignorowane przez kompilator dzięki odpowiednim dyrektywom preprocesora w kodzie - żaden kod dla wywołań tej instrukcji nie jest generowany. 175 176 5

Uwaga! Ponieważ po wyłączeniu trybu debugera asercje znikają z programu, nie wolno umieszczać w nich instrukcji dokonujących zmiany wartości zmiennych w programie, lub dokonujących jakichkolwiek innych działań na danych, np.: func(void) { int c; assert((c = getchar())!= EOF); putchar(c); } Tryb kompilacji można ustawić poprzez napisanie odpowiedniej instrukcji w pliku programu Ta instrukcja to utworzenie określonej stałej. Tworzymy ją używając dyrektywy preprocesora #define Użycie #define - przykłady: #define MyName Krzysztof #define TrybCichy W drugim przypadku stała TrybCichy ma wartość pustą, ale istnieje, dlatego można teraz sprawdzać jej zdefiniowania pisząc: #ifdef TrybCichy #endif 177 178 Aby wyłączyć sprawdzanie asercji można przed odwołaniem się do biblioteki assert.h utworzyć stałą NDEBUG. #define NDEBUG #include <assert.h> Trzeba jednak być świadomym ewentualnych skutków ubocznych takiego postępowania. Jeżeli nie wiesz, jakie skutki może powodować w kodzie użycie dyrektyw wpływających na działanie kompilatora, to jest pierwszy powód, dla którego nie powinieneś ich używać. ISO/ANSI C Podział kodu programu na pliki 179 Duży kod warto dzielić na fragmenty o wspólnej funkcjonalności (np. funkcje numeryczne, funkcje we/wy, funkcje dostępu do plików, itp.) Fragmenty należy umieszczać w oddzielnych plikach. Pliki dołączamy do naszego pliku za pomocą dyrektywy #include, np.: #include "sortowanie.c" Połączenia dokonuje preprocesor kodu. Preprocesor kodu składa postać pośrednią pliku do kompilacji zgodnie z dyrektywami. Dyrektywa zaczyna się od znaku # i nie kończy się średnikiem. Każda dyrektywa występuje w osobnej linii. Łączenie przykład: Kod z pliku wskazanego przez #include jest łączony z kodem naszego pliku tworząc w momencie kompilacji postać pośrednią, tj. jedną dużą całość ułożoną sekwencyjnie wg kolejności dołączeń: Plik A.txt: Był skrzypek rodem z Prabutów, #include "B.txt" od skrzypiec zamiast butów. Plik B.txt: miał nogi za duże do butów. Wszystkie go uwierały, więc nosił futerały 181 Jaki tekst wygeneruje preprocesor kodu przetwarzając plik A.txt? 182 6

Przy wielopoziomowych włączeniach kodu pojawia się problem. A korzysta z B i C. B korzysta z D, a także C korzysta z D. Wtedy D zostanie dołączony dwa razy. W finalnej postaci kodu całego programu przygotowanej przez preprocesor funkcje z D pojawią się dwa razy wystąpi błąd kompilacji. lato.h: Lato Lato wszędzie Zwariowało oszalało moje serce Lato Lato wszędzie A ty dziewczę zaraz wpadniesz w moje ręce main.cpp: #include "Zwrotka1.h" #include "Zwrotka2.h" #include "Zwrotka3.h" Zwrotka1.h: Rzecz między nami była cicha Westchnąłem do ciebie Tak jak się wzdycha I było nam ciasno, miło Dużo się spało i często się piło No i czego, czego jeszcze chcesz? Zwrotka3.h: Zwrotka2.h: Pisze i wymyślam słowa piosenki Żebyś pomyślała jak jestem wielki I nie wiesz że to właśnie ja Chce dać ci wielki wina balon No i czego, czego jeszcze chcesz? Ptaki zaryczały świtem na niebie Zaśpiewałem kilka dźwięków tylko dla ciebie I w oczy twoje zamglone spoglądam Krzyczę do ucha "Ciebie pożądam" Tylko ciebie ciebie jeszcze chcę 183 184 Przy wielopoziomowych włączeniach kodu pojawia się problem. Deklaracja: char flip(char, struct klucz ); A korzysta z B i C. B korzysta z D i C korzysta z D. Wtedy D zostanie dołączony dwa razy. Rozwiązanie: należy włączać same deklaracje, a nie definicje funkcji. Natomiast definicje podać tylko raz, na końcu kodu programu. Definicja: char flip(char c, struct klucz k) { int i; for(i=0;i<24; i++) if (c==k.mapa[i]) return k.mapa[(i+k.skok)%10]; return c; }; 185 186 Tworząc biblioteki, dla każdego zestawu funkcji tworzymy dwa pliki: z deklaracjami i z definicjami. Np. biblioteka z funkcjami do sortowania mogłaby mieć pliki: sortowanie.h i sortowanie.c w pliku z metodą main dołączamy tylko nagłówki, np.: #include "sortowanie.h" Jak i kiedy dołączamy plik z definicjami.c? Do tego służy projekt w środowisku programistycznym. W ramach zakładanego projektu wskazujemy wszystkie pliki.c oraz wszystkie pliki.h, które zawierają niezbędny kod naszego programu. Proces kompilacji jest dwuetapowy: 1. Poszczególne pliki *.c kompilowane są kolejno; kody funkcji bibliotecznych nie są jeszcze konieczne (wystarczą same nagłówki). 2. Pliki wynikowe kompilacji są łączone (linkowane) w jeden plik *.exe 187 188 7

libc.h libc.c #include "libc.h libb.h #include "libc.h" libb.c #include "libb.h liba.h #include "libc.h" liba.c #include "liba.h Budowa pliku nagłówkowego *.h Co zrobić, żeby plik `libc.h jednak nie został wklejony dwa razy? Wykorzystać dyrektywy preprocesora: #ifndef nazwapliku_h #define nazwapliku_h /* tutaj deklaracje funkcji */ #endif Preprocesor wklei plik `libc.h dwa razy podczas analizy pliku `main.c. Problem: A co z typami danych? main.c #include "liba.h" #include "libb.h #define definiuje (tworzy) nową stałą nazwapliku_h #ifndef sprawdza, czy nie jest zdefiniowana stała nazwapliku_h. Jeżeli nie, włącza kod znajdujący się poniżej tej dyrektywy, do kodu wyjściowego. #endif znacznik końca tekstu objętego funkcją #ifndef 189 190 lato.h: #ifndef LATO_H #define LATO_H Lato Lato wszędzie Zwariowało oszalało moje serce Lato Lato wszędzie A ty dziewczę zaraz wpadniesz w moje ręce #endif main.cpp: #include "Zwrotka1.h" #include "Zwrotka2.h" #include "Zwrotka3.h" Zwrotka1.h: Rzecz między nami była cicha Westchnąłem do ciebie Tak jak się wzdycha I było nam ciasno, miło Dużo się spało i często się piło No i czego, czego jeszcze chcesz? Zwrotka3.h: Zwrotka2.h: Pisze i wymyślam słowa piosenki Żebyś pomyślała jak jestem wielki I nie wiesz że to właśnie ja Chce dać ci wielki wina balon No i czego, czego jeszcze chcesz? Ptaki zaryczały świtem na niebie Zaśpiewałem kilka dźwięków tylko dla ciebie I w oczy twoje zamglone spoglądam Krzyczę do ucha "Ciebie pożądam" Tylko ciebie ciebie jeszcze chcę 191 Budowa pliku nagłówkowego *.h Podsumowując: użycie dyrektyw #ifndef, #define, #endif gwarantuje, że niezależnie ile razy pojawi się dyrektywa #include "libc.h" treść pliku zostanie dołączona w tylko jednym egzemplarzu. Nazwy zmiennych, które definiujemy za pomocą preprocesora muszą być unikatowe podczas kompilacji projektu dla każdego używanego pliku. Najpopularniejszą metodą zapewnienia sobie unikatowych nazw zmiennych, jest używanie nazwy pliku dla zmiennej preprocesora. 192 Budowa pliku źródłowego *.c #include "nazwapliku.h" /* tutaj definicje funkcji */ Alternatywą dla: #ifndef nazwapliku_h #define nazwapliku_h /* tutaj deklaracje funkcji */ #endif w środowisku Visual Studio jest #pragma once Zapobiega wielokrotnemu załączeniu treści całego pliku. Ale nie należy do standardu.. 193 194 8

Zmienne zewnętrzne Zmienne są widziane w kodzie znajdującym się poniżej ich deklaracji w obrębie bloku danych a najlepszym razie w obrębie pliku Zmienne zadeklarowane w pliku poza ciałem funkcji nazywane są zmiennymi globalnymi Aby zmienna globalna z jednego pliku była widziana w drugim, musi zostać zadeklarowana z modyfikatorem extern Zadeklarowanie: extern double x; stanowi informację dla kompilatora, że zmienna ta jest lub będzie zdefiniowana w innym pliku/module 195 Zmienne zewnętrzne Deklaracja zmiennej z modyfikatorem extern : deklaracja zmiennej nie jest związana z jej definicją, żadna zmienna nie jest tworzona, tzn. pamięć nie jest przydzielana, ta sama zmienna może być zadeklarowana jako extern wiele razy w wielu plikach, ale zdefiniowana może być tylko raz, zmiennej deklarowanej nie wolno inicjować: extern double x = 0; /* Nie! */ Po takiej inicjalizacji kompilator zignoruje słowo kluczowe extern i potraktuje powyższa deklarację jak definicję. Kompilator po znalezieniu właściwej definicji nie zwróci komunikatu o błędzie, ponieważ będzie ona znajdowała się w innym pliku, a więc będzie traktowana jako definicja innej zmiennej, co prawda o tej samej nazwie, ale innej bo w innym pliku. 196 9