Ćwiczenie 7. Strumień trójelementowy. A g a EOF... EOF... Wprowadzenie do programowania w języku C. Wskaźnik bieżącej pozycji. bieżącej pozycji.

Podobne dokumenty
Podstawy programowania w języku C++

Plik jest reprezentowany przez strumień znaków (bajtów) o zmiennej długości. Koniec strumienia identyfikowany jest znacznikiem końca pliku EOF.

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

1 Przetwarzanie plików

Podstawy programowania w języku C++

Podstawy programowania w języku C++

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

Biblioteka standardowa - operacje wejścia/wyjścia

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

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

Funkcje zawarte w bibliotece < io.h >

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

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

Funkcje zawarte w bibliotece < io.h >

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

Program wykonujący operację na plikach powinien zachować schemat działania zapewniający poprawną pracę:

Pliki. Informacje ogólne. Obsługa plików w języku C

Operacje na plikach. Informatyka. Standardowe strumienie wejścia i 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

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

Pliki. Informacje ogólne. Obsługa plików w języku C

۰ Elementem jednostkowym takiego pliku jest bajt. ۰ Format pliku binarnego: [bajty pliku][eof]

INFORMATYKA Studia Niestacjonarne Elektrotechnika

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

Programowanie w językach wysokiego poziomu

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

Tablice deklaracja, reprezentacja wewnętrzna

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

Podstawy programowania w języku C++

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

Języki programowania obiektowego Nieobiektowe elementy języka C++

Programowanie Procedurale. Pliki w języku C++

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

1 Podstawy c++ w pigułce.

Formatowane (tekstowe) wejście/wyjście. Binarne wejście/wyjście.

Argumenty wywołania programu, operacje na plikach

Lekcja 10. Uprawnienia. Dołączanie plików przy pomocy funkcji include() Sprawdzanie, czy plik istnieje przy pmocy funkcji file_exists()

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

1. Wprowadzanie danych z klawiatury funkcja scanf

Podstawy programowania w języku C++

Języki programowania. Przetwarzanie tablic znaków. Część druga. Autorzy Tomasz Xięski Roman Simiński

4. Pliki Informacje ogólne o dostępie do plików w PHP Sprawdzanie istnienia pliku file_exists()

Tablice w argumentach funkcji. Tablicy nie są przekazywane po wartości Tablicy są przekazywane przez referencje lub po wskaźniku

Stałe i zmienne znakowe. Stała znakowa: znak

Podstawy programowania

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

PRZYKŁADY OPERACJI PLIKOWYCH z wykorzystaniem biblioteki <stdio.h>

Języki programowania. Tablice struktur, pliki struktur. Część ósma. Autorzy Tomasz Xięski Roman Simiński

Laboratorium 6: Ciągi znaków. mgr inż. Leszek Ciopiński dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

DANE TEKSTOWE W JĘZYKU C/C++ - TABLICE ZNAKOWE

Ćwiczenie nr 6. Poprawne deklaracje takich zmiennych tekstowych mogą wyglądać tak:

Języki programowania. Karolina Mikulska-Rumińska Pokój 573, tel Konsultacje wtorek 9-10.

Wskaźniki w C. Anna Gogolińska

Programowanie w językach

Ćwiczenia podstawowe, zestaw 5, część 1

Struktury czyli rekordy w C/C++

Ghost in the machine

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

1 Funkcje i ich wykorzystanie

Podstawy Programowania C++

W języku C każdy plik fizyczny jest ciągiem bajtów, z których każdy może być niezależnie odczytany. Borland 01234

Programowanie Proceduralne

2 Przygotował: mgr inż. Maciej Lasota

1 Podstawy c++ w pigułce.

Strumienie i pliki. Programowanie Proceduralne 1

7 Przygotował: mgr inż. Maciej Lasota

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Tablice, funkcje - wprowadzenie

#include <stdio.h> void main(void) { int x = 10; long y = 20; double s; s = x + y; printf ( %s obliczen %d + %ld = %f, Wynik, x, y, s ); }

Pliki w C/C++ Przykłady na podstawie materiałów dr T. Jeleniewskiego

iii. b. Deklaracja zmiennej znakowej poprzez podanie znaku

#include <stdio.h> int main( ) { int x = 10; long y = 20; double s; s = x + y; printf ( %s obliczen %d + %ld = %f, Wynik, x, y, s ); }

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

Podstawy Programowania Podstawowa składnia języka C++

Język ludzki kod maszynowy

Pliki wykład 2. Dorota Pylak

Laboratorium 3: Tablice, tablice znaków i funkcje operujące na ciągach znaków. dr inż. Arkadiusz Chrobot dr inż. Grzegorz Łukawski

OPERACJE WEJŚCIA / WYJŚCIA. wysyła sformatowane dane do standardowego strumienia wyjściowego (stdout)

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

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

Ćwiczenie: JavaScript Cookies (3x45 minut)

Programowanie strukturalne i obiektowe

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

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

Język C : programowanie dla początkujących : przewodnik dla adeptów programowania / Greg Perry, Dean Miller. Gliwice, cop

Zasady programowania Dokumentacja

main( ) main( void ) main( int argc, char argv[ ] ) int MAX ( int liczba_1, liczba_2, liczba_3 ) źle!

Laboratorium Systemów Operacyjnych. Ćwiczenie 4. Operacje na plikach

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

Podstawy programowania. Wykład Pętle. Tablice. Krzysztof Banaś Podstawy programowania 1

ŁAŃCUCHY W JĘZYKU C/C++

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Podstawy programowania w języku C++

Część 4 życie programu

Wprowadzenie do programowania w języku C

Język C zajęcia nr 5

Wskaźniki. Programowanie Proceduralne 1

Wykład II. Programowanie II - semestr II Kierunek Informatyka. dr inż. Janusz Słupik. Wydział Matematyki Stosowanej Politechniki Śląskiej

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

Transkrypt:

Ćwiczenie 7 Przetwarzanie plików Ćwiczenie to poświęcone jest poznaniu metod przetwarzania plików. W ramach tego ćwiczenia przewidziane jest wykonanie programów realizujących różne operacje na plikach. Przetwarzanie plików realizowane jest zwykle przez funkcje z biblioteki obsługi standardowego wejścia i wyjścia (identyfikowanej przez stdio.h). Można jednak korzystać z funkcji niższego poziomu (np. io.h) lub napisać własne. Język C nie zawiera żadnego wbudowanego typu plikowego a operacje na plikach nie są częścią języka a realizowane są przez funkcje biblioteczne. 1.1 Reprezentacja plików w postaci strumieni Plik jest reprezentowany przez strumień znaków (bajtów) o zmiennej długości. Koniec strumienia identyfikowany jest znacznikiem końca pliku EOF. Z każdym strumieniem związany jest wskaźnik bieżącej pozycji od tej pozycji realizowane będzie czytanie lub pisanie. Każdy zapis i odczyt zmienia wskaźnik bieżącej pozycji. Z każdym strumieniem związany jest znacznik osiągnięcia końca pliku oraz znacznik błędu. Strumienie mogą być otwierane w trybie: Binarnym strumień jest ciągiem jednakowo traktowanych bajtów, każdy zapis i odczyt realizowany jest bez żadnych konwersji. Tekstowym strumień jest ciągiem linii tekstu zakończonych znacznikiem końca linii znak \n. W trakcie odczytu i zapisu do takiego strumienia mogą zachodzić konwersje spowodowane np. różną fizyczną reprezentacją znacznika końca wiersza (np. para \r\n w plikach tekstowych DOS/Windows, pojedynczy znak \n w systemach Unix owych). Strumień trójelementowy Wskaźnik bieżącej pozycji Strumień pusty Wskaźnik bieżącej pozycji A g a EOF... EOF... Rysunek 1 Wskaźnik bieżącej pozycji i znacznik końca pliku Wprowadzenie do programowania w języku C

Przetwarzanie plików Każda operacja zapisu i odczytu powoduje przesunięcie wskaźnika bieżącej pozycji pliku. Można zatem wyobrazić sobie, że wskaźnik bieżącej pozycji jest wirtualną głowicą zapisującą/odczytującą przemieszczającą się nad plikiem. Z punktu widzenia programisty plik jest ciągiem znaków (bajtów) zakończonym znacznikiem końca pliku. Reprezentacja tego znacznika może być różna w zależności od systemu operacyjnego, np. w systemach DOS/Windows jest to znak ^Z a w systemach Unixowych ^D. Z tego powodu w pliku nagłówkowym stdio.h zdefiniowano stałą symboliczną EOF, konsekwentne posługiwanie się tą stałą pozwala na nie przejmowanie się konkretną reprezentacją znacznika końca pliku. Organizacja pliku, jego struktura i metoda czytania/pisania zależą od programisty. To on decyduje jak interpretować odczytane bajty, on wybiera najdogodniejszy w danym momencie sposób czytania i zapisu. Reprezentacja plików na poziomie biblioteki identyfikowanej przez nagłówek stdio.h przypomina nieco pliki amorficzne (blokowe) z borlandowskich implementacji języka Pascal. O ile jednak w implementacjach tych zakłada się zapis/odczyt bloków, w języku C natomiast można mieszać odczyt pojedynczych znaków, napisów, linii czy bloków. 1.1.1 Otwieranie plików Aby rozpocząć operacje na plikach należy zadeklarować w programie zmienną stanowiącą dojście do takiego pliku. W przypadku obsługi standardowych strumieni deklaruje się zmienną wskaźnikową. Typem wskazywanym jest FILE, jest to zdefiniowany w pliku nagłówkowym stdio.h typ rekordowy, zawierający informacje o otwartym dojściu do pliku. Deklaracja takiej zmiennej wygląda następująco: #include <stdio.h>... FILE * fp = NULL; Wykorzystanie pliku rozpoczyna operacja jego otwarcia, realizowana zwykle przez funkcję fopen, otwarcie pliku dane.txt do odczytu w trybie tekstowym może wyglądać następująco: fp = fopen( "dane.txt", "rt" ); if( fp!= NULL ) /* Otwarcie OK, wykonaj operacje na pliku */... else /* Otwarcie nieudane, obsługa sytuacji błędnej */... Przykłady i ćwiczenia

3 Pierwszy parametr funkcji fopen to nazwa pliku (być może ze ścieżką), drugi parametr to napis zawierający tryb otwarcia r oznacza odczyt, t tryb tekstowy. W przypadku poprawnego otwarcia pliku, funkcja fopen alokuje na stercie rekord opisu pliku (typu FILE), wypełnia go odpowiednimi informacjami i oddaje w postaci rezultatu wskaźnik do tego rekordu. Wskaźnik ten stanowi odtąd dojście do pliku i będzie przekazywany każdej funkcji operującej na pliku, informując ją, na jakim pliku ma wykonywać właściwe dla niej operacje. Zatem w przypadku poprawnego otwarcia pliku wskaźnik fp będzie miała wartość różną od NULL. Wtedy można wykonywać dalsze operacje na pliku. Jeżeli w trakcie otwierania pliku nastąpił błąd, rezultatem funkcji fopen jest wartość NULL. Nie można wtedy przetwarzać pliku. Szczegółowy opis funkcji fopen jest następujący: FILE * fopen( const char *filename, const char *mode ); Otwiera strumień związany z plikiem o nazwie przekazanej parametrem filename. Nazwa może zawierać ścieżkę dostępu do pliku. Strumień otwierany jest w trybie mode. Jeżeli otwarcie zakończyło się sukcesem, funkcja udostępnia wskaźnik do dynamicznie alokowanej struktury typu FILE, stanowiącej programową reprezentacje fizycznego pliku. Jeżeli pliku nie udało się otworzyć, rezultatem funkcji jest NULL. r a w otwarcie istniejącego pliku wyłącznie do odczytu zapis do istniejącego lub utworzenie nowego pliku Ustawienie w pozycji końcowej utworzenie pliku wyłącznie do zapisu, jeżeli plik istnieje jest obcinany do pliku pustego Tabela 1 Tryb otwarcia strumienia r+ otwarcie istniejącego pliku do odczytu i zapisu a+ odczyt i zapis do istniejącego lub utwo-rzenie nowego pliku Ustawienie w pozycji końcowej w+ utworzenie pliku do odczytu i zapisu, jeżeli plik istnieje jest obcinany do pliku pustego Tabela 2 Specyfikacja typu strumienia t otwarcie w trybie tekstowym b otwarcie w trybie binarnym Znak + w trybie otwarcia oznacza aktualizację możliwość czytania i pisania do otwartego strumienia. Jednak zapis odczyt i zapis (albo zapis i odczyt) nie mogą po sobie następować bezpośrednio. Należy użyć funkcji wymiatania bufora fflush lub jednej z funkcji pozycjonowania pozycji fseek, fsetpos, rewind. Jeżeli informacja o trybie otwarcia (t lub b) nie występuje, przyjmowany jest tryb otwarcia zgodnie z wartością globalnej zmiennej _fmode. Jeżeli _fmode posiada wartość O_BINARY, plik jest otwirany w trybie binarnym, jeżeli _fmode posiada Wprowadzenie do programowania w języku C

Przetwarzanie plików wartość O_TEXT, plik jest otwierany w trybie tekstowym. Domyślna wartość to O_TEXT. Symbole O_... są zdefiniowanew pliku fcntl.h. Przedstawiona wcześniej sekwencja otwierania pliku i testowania wskaźnika będącego rezultatem funkcji fopen zwykle bywa zapisywana w skróconej postaci: if( ( fp = fopen( "dane.dat", "rt" ) )!= NULL ) /* Otwarcie OK, wykonaj operacje na pliku */... else /* Otwarcie nieudane, obsługa sytuacji błędnej */... 1.1.2 Zamykanie plików Gdy przetwarzanie pliku zostało zakończone, plik trzeba zamknąć. Nie należy zapominać o tym. W niektórych systemach operacyjnych (np. DOS) liczba jednocześnie otwartych plików dla danego procesu bywa ograniczana, pozostawienie niezamkniętego pliku uszczupla zatem liczbę plików możliwych do otwarcia w tym samym czasie. Dodatkowo komplikuje to dostęp do pliku ze strony innego procesu. Do zamykania pliku służy funkcja fclose. Przykład jej wykorzystania przedstawiono poniżej. if( ( fp = fopen( "dane.dat", "rt" ) )!= NULL ) /* Otwarcie OK, wykonaj operacje na pliku */... /* Zamknij plik jeżeli przetwarzanie zakończono */ else /* Otwarcie nieudane, obsługa sytuacji błędnej */... Dokładniejszy opis funkcji zamykającej plik jest następujący: int fclose( FILE * stream ); Funkcja zamyka strumień i zapisuje wszystkie bufory. Rezultat EOF oznacza błąd zamykania, rezultat równy zero oznacza bezbłędne zamknięcie. Pamięć przydzielona strukturze wskazywanej przez wskaźnik stream jest zwalniana. Przykłady i ćwiczenia

5 1.1.3 Odczyt i zapis pojedynczych znaków Odczyt pojedynczego znaku ze strumienia realizuje funkcja fgetc: int fgetc( FILE *stream ); Funkcja pobiera następny znak ze strumienia identyfikowanego przez stream i uaktualnia wskaźnik bieżącej pozycji w pliku. Znak pobierany jest jako unsigned char i przekształcany jest do typu int. W przypadku napotkania końca strumienia rezultatem jest wartość EOF oraz ustawiany jest znacznik napotkania końca strumienia. W przypadku wystąpienia błędu odczytu rezultatem funkcji jest wartość EOF oraz ustawiany jest znacznik błędu strumienia. Zapis pojedynczego znaku do strumienia realizuje funkcja fputc: int fputc(int c, FILE *stream); Funkcja wyprowadza znak c do strumienia stream zgodnie ze wskaźnikiem bieżącej pozycji w pliku. W przypadku, gdy funkcja fputc zakończyła swoje działanie bez błędu, rezultatem funkcji jest znak c. W przeciwnym wypadku wartość EOF. Przy sekwencyjnym przetwarzaniu plików istotna jest umiejętność testowania np. czy w wyniku ostatniej operacji odczytu napotkano koniec pliku. Służyć do tego może funkcja feof: int feof( FILE * stream ); Rezultatem funkcji jest wartość różna od zera jeżeli strumień jest w pozycji końcowej, zero w przeciwnym wypadku. Strumień jest w pozycji końcowej, jeżeli w wyniku ostatnio przeprowadzonej operacji odczytano znacznik końca pliku. Uwaga funkcja feof działa inaczej niż jej odpowiednik z języka Pascal funkcja Eof. Rezultatem funkcji Eof jest wartość True, jeżeli przeczytano ostatni element w pliku. Rezultatem funkcji feof jest wartość różna od zera, jeżeli prubujemy czytać po odczytaniu znacznika końca pliku. Powoduje to pewne problemy. Załóżmy, że plik d.txt następującą zawartość: 123456789 Fragment programu, który wyprowadzi zawartość tego pliku do stdout i policzy liczbę wyprowadzonych znaków może wyglądać następująco: FILE * fp; long int counter = 0; if( ( fp = fopen( "d.txt", "rt" ) )!= NULL ) while(! feof( fp ) ) Wprowadzenie do programowania w języku C

Przetwarzanie plików putchar( fgetc( fp ) ); counter++; printf( "\nliczba znakow w pliku: %ld", counter ); Wynik jego działania: Rysunek 2 Wyniki działania programu Jak widać program doliczył do rzeczywistej zawartości pliku również znacznik jego końca. Aby tak nie było, można zaproponować inną sekwencję przetwarzania pliku: FILE * fp; long int counter = 0; int c; if( ( fp = fopen( "d.txt", "rt" ) )!= NULL ) while( ( c = fgetc( fp ) )!= EOF ) putchar( c ); counter++; printf( "\nliczba znakow w pliku: %ld", counter ); Kluczowa jest tutaj sekwencja stanowiąca warunek iteracji while. Funkcja fgetc pobiera znak z pliku, jest on zapamiętywany w zmiennej c a następnie porównywany ze stałą EOF. W przypadku odczytania znacznika końca pliku, trafia on do zmiennej c, lecz iteracja jest zrywana i program prawidłowo zadziała wykaże 9 znaków w pliku d.txt. Spróbujmy wykorzystać podobną sekwencję dla wyznaczenia rozmiaru pliku liczonego w bajtach, tym razem wykorzystajmy iteracje for: if( ( fp = fopen( "d.txt", "rt" ) )!= NULL ) for( counter = 0; fgetc( fp )!= EOF; counter++ ) ; printf( "\nrozmiar pliku: %ld bajtow", counter ); Niestety weryfikacja działania tego programu pokaże, iż działa on błędnie, zobacz Rysunek 3. Przykłady i ćwiczenia

7 Rysunek 3 Weryfikacja działania wyznaczania rozmiaru pliku Przyczyną wadliwego działania programu są konwersje znaczników końca linii w trybie tekstowym. W systemach DOS/Windows znacznik końca linii to para \r i \n (czyli CR i LF). W trakcie odczytu w trybie tekstowym, każda para \r\n zamieniana jest na pojedynczy znak \n. Analogicznie jest w przypadku zapisu pojedynczy znacznik \n zapisywany jest do pliku fizycznie w postaci pary \r\n. Ilustruje to Rysunek 4. 0 1 2 3 4 5 6 7 8 9 10 11 Rezultaty odczytu przy otwarciu w trybie binarnym '1' '2' '3' '4' '5' '\r' '\n' '1' '2' '3' '4' '5' Strumień: 1 2 3 4 5 \r \n 1 2 3 4 5 EOF '1' '2' '3' '4' '5' '\n' '1' '2' '3' '4' '5' Rezultaty odczytu przy otwarciu w trybie tekstowym 0 1 2 3 4 5 6 7 8 9 10 11 \r \n Znacznik końca wiersza Rysunek 4 Konwersja znaczników końca wiersza w trybie tekstowym Konwersje takie nie zachodzą przy otwieraniu pliku w trybie binarnym. W drugim parametrze wywołania należy zatem użyć litery b, oznaczającejotwarcie w trybie binarnym. Zatem poprawiona wersja kodu, ujęta w ramy funkcji file_size może wyglądać następująco: long int file_size( char * fname ) FILE * fp; long int counter = 0; if( ( fp = fopen( fname, "rb" ) )!= NULL ) for( counter = 0; fgetc( fp )!= EOF; counter++ ) ; Wprowadzenie do programowania w języku C

Przetwarzanie plików return counter;... printf( "\nrozmiar pliku: %ld bajtow", file_size( "d.txt" ) ); 1.1.4 Inne przykłady przetwarzania plików znak po znaku Kopiowanie zawartości plików znak po znaku realizuje funkcja cpc_file_copy zaprezentowana poniżej. /*----------------------------------------------------------------- Funkcja cpc_file_copy realizuje kopiowanie zawartości źrodłowego pliku src do pliku docelowego dst. Wykorzystywane są znakowe operacje odczytu i zapisu. Funkcja nie zamyka strumieni src i dst. Prametry : Wskaźniki na prawidłowo otwarte strumienie binarne src, dst - odpowiednio dla pliku źrodłowego i docelowego Rezultat : TRUE jeżeli kopiowanie zakonczyło się poprawnie FALSE jeżeli wystapił błąd podczas kopiowania -----------------------------------------------------------------*/ int cpc_file_copy( FILE * dst, FILE * src ) int c; for( ; ( c = fgetc( src ) )!= EOF ; fputc( c, dst ) ) if( ferror( src ) ferror( dst ) ) return FALSE; return TRUE; Instrukcja for odczytuje znak (funkcja fgetc) z otwartego wcześniej strumienia źródłowego src. Dopóki nie jest to znak reprezentujący koniec pliku, odczytany znak zapisywany jest (funkcja fputc) do strumienia docelowego dst. Ciało iteracji for zawiera instrukcję warunkową if testującą, czy w trakcie odczytu i zapisu nie doszło do błędu. Ustawiany jest wtedy znacznik błędu strumienia, jego testowanie realizuje funkcja ferror. Jej rezultatem jest wartość różna od zera, jeżeli wskaźnik błędu jest ustawiony, zero w przeciwnym wypadku. Czasem zachodzi konieczność wyczyszczenia znacznika błędu strumienia, realizuje to funkcja clearerr: void clearerr( FILE * stream ); Funkcja realizuje zerowanie wskaźników: osiągnięcia końca pliku i błędu strumienia. Dopóki wskaźnik błędu jest ustawiony wszystkie operacje na strumieniu będą sygnalizowały błąd. Przykłady i ćwiczenia

9 Kolejny przykład prezentuje funkcję toupper_file_copy, realizującą m.in. zamianę małych liter na duże w trakcie kopiowania: /*----------------------------------------------------------------- Funkcja toupper_file_copy realizuje kopiowanie zawartości źrodłowego pliku src do pliku docelowego dst. W trakcie kopiowanie wszystkie litery małe są zamieniane na duże a tzw. biale spacje na znak myślnika '-' Wykorzystywane są znakowe operacje odczytu i zapisu. Funkcja nie zamyka strumieni src i dst. Parametry : Wskaźniki na prawidłowo otwarte strumienie binarne src, dst - odpowiednio dla pliku źrodlowego i docelowego Rezultat : TRUE jeżeli kopiowanie zakonczyło się poprawnie FALSE jeżeli wystapił błąd podczas kopiowania -----------------------------------------------------------------*/ int toupper_file_copy( FILE * dst, FILE * src ) int c; while( ( c = fgetc( src ) )!= EOF ) fputc( ( isspace( c ) )? '-' : toupper( c ), dst ); if( ferror( src ) ferror( dst ) ) return FALSE; return TRUE; W trakcie kopiowania można realizować filtrowanie znaków, np. kopiowanie tylko liter i cyfr: while( ( c = fgetc( src ) )!= EOF ) if( isalnum( c ) ) fputc( c, dst ); Kopiowanie wyłącznie dużych liter: while( ( c = fgetc( src ) )!= EOF ) if( isupper( c ) ) fputc( c, dst ); Następny przykład prezentuje wyświetlanie tych znaków pliku źródłowym języka C, które zapisane są w obrębie komentarza. Nazwa pliku podawana jest przez użytkownika. Program nie działa prawidłowo dla komentarzy zagnieżdżonych. Wprowadzenie do programowania w języku C

Przetwarzanie plików #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_LEN 255 enum BOOLEAN FALSE, TRUE ; char file_name[ MAX_LEN ]; FILE * ask_name_and_open( void ) puts( "\nwyswietlam znaki z komentarzy programu w jezyku C\n" ); printf( "Podaj nazwe pliku: " ); fgets( file_name, MAX_LEN, stdin ); /* Wyrzucenie niepotrzebnego \n na koncu nazwy pliku */ file_name[ strlen( file_name ) - 1 ] = '\0'; return fopen( file_name, "rt" ); void display_rem( FILE * file ) int in_rem = FALSE; /* Czy wewnątrz komentarza?*/ int c; while( ( c = fgetc( file ) )!= EOF ) if( c == '/' ) if( ( c = fgetc( file ) ) == '*' ) in_rem = TRUE; continue; if( c == '*' ) if( ( c = fgetc( file ) ) == '/' ) in_rem = FALSE; continue; if( in_rem ) /* Wyprowadzaj znaki jezeli w komentarzu*/ putchar( c ); Przykłady i ćwiczenia

11 int main() FILE * file; if( ( file = ask_name_and_open() ) == NULL ) puts( "\nblad otwarcia pliku." ); else display_rem( file ); fclose( file ); puts( "\nnacisnij Enter by zakonczyc..." ); ( void )getchar(); return EXIT_SUCCESS; Kolejna funkcja display_tags, wyprowadza do strumienia wyjściowego programu znaczniki HTML, zgodnie z poniższym opisem. /*----------------------------------------------------------------- Funkcja realizuje wyprowadzanie do stdout tekstów znaczników znalezionych w analizowanym pliku html. Są to zarówno znaczniki otwierające jak i zamykające a także komentarze. Prawdę mówiąc, wszystko co znajdze się pomiędzy znakami '<' i '>' jest traktowane jako znacznik. Parametry: FILE * fp - wskaźnik plikowy, otwartego uprzednio pliku. Rezultat: Brak -----------------------------------------------------------------*/ void display_tags( FILE * fp ) /* in_tag - zmienna opisujaca stan przeszukiwania: TRUE gdy jestesmy wewnatrz znacznika, FALSE gdy poza nim. */ int in_tag = FALSE; /* tag zmienna, w której umieszczony zostanie wyłuskany z pliku tekst pojedynczego znacznika. */ char tag[ MAX_TAG_LEN ] = ""; /* Zmienne pomocnicze: i - pozycja w tablicy tag, na której ma być dopisany znak odczytany z pliku. c bufor dla odczytanego z pliku, pojedynczego znaku. */ int i = 0, c; puts( "--Przeszukiwanie tekstu..." ); while( ( c = fgetc( fp ) )!= EOF ) /* Identyfikacja znaku wczytanego z pliku */ Wprowadzenie do programowania w języku C

Przetwarzanie plików switch( c ) /* Zaczyna się znacznik, zapamiętujemy to w zmiennej in_tag, począwszy od następnego znaku, aż do przyjścia znaku '>', będziemy dopisywać do bufora tekstu znacznika (zmienna tag) kolejne znaki, zawsze na pozycji określonej przez i. Teraz i zerujemy, żeby wypełnić bufor znacznika od początku. */ case '<' : in_tag = TRUE; i = 0; break; /* Kończy się znacznik, dopisujemy znak końca napisu w buforze tekstu znacznika (zmienna tag), zapamiętujemy fakt, ze znacznik się zakończył wpisując wartość FALSE do zmiennej in_tag. */ case '>' : if( in_tag ) tag[ i ] = '\0'; in_tag = FALSE; i = 0; break; /* Wykryto inny znak niz '<' i '>'. Jeżeli aktualnie jesteśmy wewnątrz znacznika (zmienna in_tag z wartością TRUE) to dopisujemy przeczytany z pliku znak do bufora tekstu znacznika (zmienna tag) na kolejnej, wolnej pozycji (zmienna i). Uwaga, dzieje się tak, gdy w buforze jest jeszcze miejsce. W przeciwnym wypadku przerywamy dopisywanie kolejnych znaków do bufora znacznika, obcinając go do maks. dlugości bufora. */ default: if( in_tag ) if( i < MAX_TAG_LEN - 1 ) tag[ i++ ] = c; else tag[ i ] = '\0'; break; /* switch */ /* Jeżeli jesteśmy poza znacznikiem a bufor znacznika nie jest pusty, to znaczy, ze właśnie opuściliśmy znacznik, trzeba wyprowadzić do stdout jego tekst i przygotować się na wyłuskanie następnego znacznika. */ if( in_tag == FALSE && tag[ 0 ]!= '\0' ) puts( tag ); tag[ 0 ] = '\0'; /* while */ Przykłady i ćwiczenia

13 puts( "--Zrobione" ); 1.2 Ćwiczenia w przetwarzaniu plików znak po znaku Ta grupa ćwiczeń poświęcona jest treningowi przetwarzania plików tekstowych znak po znaku. Programy do realizacji: 1. Napisać program wyznaczający ile w pliku tekstowym jest liter dużych, małych, cyfr oraz znaków interpunkcyjnych (znaki kropki, przecinka, średnika). Program pyta użytkownika o nazwę pliku, dla którego ma przeprowadzić powyższe statystyki. 2. Napisać program wyznaczający liczbę wierszy w pliku tekstowym. Program pyta użytkownika o nazwę pliku, dla którego ma przeprowadzić tą operację. Wskazówka każdy wiersz w pliku tekstowym (nawet pusty) kończony jest znakiem \n. 3. Napisać program wyznaczający współczynnik skomentowania programu w języku C, tzn. procentowy stosunek liczny znaków w komentarzach do ogólnej liczby znaków w programie. Program pyta użytkownika o nazwę pliku, dla którego ma przeprowadzić tą operację. W statystyce nie biorą udziału tzw. białe znaki, czyli znaki spacji, tabulacji i nowego wiersza. 4. Napisać program kopiowania zawartości jednego pliku do drugiego. Program powinien zapytać o nazwy pliku źródłowego i docelowego. W trakcie kopiowania każda duża litera ma być zamieniana na małą, a każda mała na dużą. 5. Napisać program wyznaczający liczbę słów zawartych w pliku tekstowym. Jako słowo traktowane ma być dowolny ciąg liter, cyfr i znaków podkreślenia, rozpoczynający się od litery. Program pyta użytkownika o nazwę pliku wejściowego. 1.3 Przetwarzanie plików liniami Pliki tekstowe reprezentowane są również jako strumienie bajtów. Można je jednak przetwarzać wierszami, od strony programu separatorem wierszy jest znak \n (z uwzględnieniem przedstawionych wcześniej uwag na temat reprezentowania plików tekstowych, zobacz Rozdz. 1.1). Przedstawiona niżej funkcja list_file prezentuje sposób przetwarzania pliku wiersz po wierszu. Funkcja wyprowadza do strumienia wyjściowego programy Wprowadzenie do programowania w języku C

Przetwarzanie plików zawartość kolejnych wierszy pliku tekstowego. Wykorzystuje do tego bufor tekstowy buffer, którego rozmiar określony jest wartością stałem MAX_LINE. /*----------------------------------------------------------------- Wyprowadza do stdout zawartość pliku o nazwie fname. Parametry: char * fname wskaźnik na tablicę zawierającą nazwę pliku. Rezultat: Brak. -----------------------------------------------------------------*/ #define MAX_LINE 256 void list_file( char * fname ) FILE * fp; char buffer[ MAX_LINE ]; if( ( fp = fopen( fname, "rt" ) )!= NULL ) while( fgets( buffer, MAX_LINE, fp )!= NULL ) printf( "%s", buffer ); Iteracją sterującą przetwarzaniem wiersz po wierszu zarządza funkcja fgets. Jej rezultatem będzie wartość NULL w momencie osiągnięcia końca pliku. Program wykorzystujący taką funkcję można np. wykorzystać do wylistowania pliku źródłowego, zawierającego program w języku C, zobacz Rysunek 5. Rysunek 5 Efekty działania funkcji list_file Na rysunku tym wszystko wygląda dobrze. Jednak w przypadku linii dłuższych niż szerokość okna konsoli, wyświetlane wiersze będą zawijane, zobacz Rysunek 6. Przykłady i ćwiczenia Rysunek 6 Zbyt długie wiersze będą zawijane Można zmodyfikować funkcję list_file tak, by nie łamała za długich wierszy a umieszczała na ich końcu symbol informujący, że wiersz jest dłuższy od szerokości ekranu (zobacz Rysunek 7). Realizuje to funkcja list_file_nowrap o następującej postaci:

15 #define MAX_LINE 256 #define MAX_CHARS_IN_LINE 80 void list_file_nowrap( char * fname ) FILE * fp; char buffer[ MAX_LINE ]; if( ( fp = fopen( fname, "rt" ) )!= NULL ) while( fgets( buffer, MAX_LINE, fp )!= NULL ) if( strlen( buffer ) > MAX_CHARS_IN_LINE ) buffer[ MAX_CHARS_IN_LINE - 1 ] = '>'; buffer[ MAX_CHARS_IN_LINE ] = '\0'; printf( "%s", buffer ); Stała MAX_CHARS_IN_LINE reprezentuje aktualną szerokość okna konsoli liczoną w znakach. Linia zapisana w tablicy buffer jest obcinana do tej liczby znaków poprzez wstawienie na pozycji MAX_CHARS_IN_LINE znacznika końca napisu. Na znaku poprzednim wstawiany jest symbol '>' sugerujący użytkownikowi, że linia jest dłuższa niż szerokość okna konsoli. Rysunek 7 Zbyt długie wiersze są obcinane funkcja list_file_nowrap w akcji Można dokonać kolejnych modyfikacji rozpatrywanej funkcji, przykładowo rozszerzając ją o numerowanie wierszy. Realizuje to funkcja nlist_file_nowrap, która dołącza na początku każdej linii jej numer. #define MAX_LINE 256 #define MAX_CHARS_IN_LINE 80 void nlist_file_nowrap( char * fname ) FILE * fp; int counter = 0; char buffer[ MAX_LINE ]; if( ( fp = fopen( fname, "rt" ) )!= NULL ) while( fgets( buffer, MAX_LINE, fp )!= NULL ) if( strlen( buffer ) > MAX_CHARS_IN_LINE - 5 ) Wprowadzenie do programowania w języku C

Przetwarzanie plików buffer[ MAX_CHARS_IN_LINE - 6 ] = '>'; buffer[ MAX_CHARS_IN_LINE - 5 ] = '\0'; printf( "%03d: %s", ++counter, buffer ); Numerowanie linii opiera się na zmiennej counter, dla numerów linii zarezerwowano domyślnie trzy cyfry. Łącznie ze znakiem dwukropka i jedną spacją daje to 5-ęć znaków. O tyle mnie znaków można zmieścić w linii tekstu, stąd inne niż poprzednio wartości przy obcinaniu zbyt długich linii. Efekt działania funkcji nlist_file_nowrap prezentuje Rysunek 8. Przykłady i ćwiczenia Rysunek 8 Efekt działania funkcji nlist_file_nowrap W trakcie takiego przetwarzania plików można realizować wiele różnorodnych operacji. Może to być np. poszukiwanie linii zawierających określony wzorzec tekstowy. Realizuje to przedstawiona dalej funkcja pattern_list_file. Otrzymuje ona w postaci drugiego parametru wywołania tablice znaków pattern, zawierającą poszukiwany wzorzec. #define MAX_LINE 256 #define MAX_CHARS_IN_LINE 80 void pattern_list_file( char * fname, char * pattern ) FILE * fp; int counter = 0; char buffer[ MAX_LINE ]; if( ( fp = fopen( fname, "rt" ) )!= NULL ) while( fgets( buffer, MAX_LINE - 1, fp )!= NULL ) ++counter; if( strstr( buffer, pattern )!= NULL ) if( strlen( buffer ) > MAX_CHARS_IN_LINE - 5 )

17 buffer[ MAX_CHARS_IN_LINE - 6 ] = '>'; buffer[ MAX_CHARS_IN_LINE - 5 ] = '\0'; printf( "%03ld: %s", counter, buffer ); Dla tej funkcji kluczowe jest wywołanie funkcji strstr, przeszukującej tablicę buffer w poszukiwaniu podciągu identycznego z pattern. W przypadku znalezienia takiego podciągu rezultatem funkcji jest wskaźnik na początek podciągu znalezionego w tablicy buffer. W naszym przypadku sam fakt znalezienia podciągu (a więc niezerowy rezultat funkcji strstr) wystarcza, by zakwalifikować linię do wyświetlenia. Wyniki działania funkcji pattern_list_file wywołanej z parametrem pattern zawierającym napis słowo kluczowe if prezentuje Rysunek 9. Rysunek 9 Filtrowanie linii zawierających wzorzec if. 1.4 Ćwiczenia w przetwarzaniu plików linia po linii Napisać program fview - przeglądacz plików tekstowych (odpowiednik dosowskiego zlecenia type). Zadaniem programu jest wyświetlanie na ekranie zawartości pliku tekstowego przekazanego parametrem wywołania programu. Jeżeli program zostanie wywołany w linii poleceń: fview ala.txt to ma wyświetlić zawartość pliku ala.txt lub sensownie zareagować na przypadek gdy taki plik nie istnieje. Program może być również wywoływany z opcjami: fview -p ala.txt gdzie p (od ang. page) ma powodować wyświetlanie ze stronicowaniem, tzn. z zatrzymaniem co 25 linii fview -c ala.txt gdzie c (od ang. cut) ma powodować obcinanie linii do 80 znaków, tzn. linie dłuższe niż 80 znaków nie będą zawijane do następnej linii ekranowej. fview -u ala.txt Wprowadzenie do programowania w języku C

Przetwarzanie plików gdzie u (od ang. upper) ma powodować wyświetlanie zawartości pliku dużymi literami. fview -l ala.txt gdzie l (od ang. lower) ma powodować wyświetlanie zawartości pliku małymi literami. Opcje mogą być łączone. tzn. jednocześnie -p i -u. Przykładowo wywołanie: fview upc ala.txt Ma spowodować wyświetlenie zawartości pliku tekstowego dużymi literami, ze stonnicowaniem i z obcinaniem zbyt długich linii. Oczywiście jednoczesne stosowanie opcji u i l się wyklucza. Wskazówka. Przykład obsługi parametrów wywołania programu, zobacz Suplement parametry funkcji main, str. 27. 1.5 Blokowe przetwarzanie plików Przedstawione wcześniej przykłady przetwarzania plików wykorzystywały funkcje odczytujące/zapisujące pojedyncze znaki lub ich ciągi zakończone znakiem końca linii. Taki styl przetwarzania plików dedykowany jest głównie do przetwarzania plików tekstowych. Często jednak pliki nie zawierają danych tekstowych. Przykładem są pliki graficzne, dźwiękowe czy multimedialne. Ich zawartość to najczęściej binarny obraz zawartości pamięci operacyjnej (np. ciąg bajtów opisujących kolory piksela) uzupełniony o dodatkowe informacje (np. nagłówek pliku BMP, czy dane EXIF). 1.5.1 Przykład blokowego zapisu i odczytu Do przetwarzania plików, których zawartość ma charakter binarny, wykorzystuje się najczęściej odczyt/zapis bloków. Pod pojęciem bloku rozumieć będziemy ciąg bajtów o określonej długości, nie zakładamy, że ciąg taki ma jakakolwiek strukturę. Rozmiar takiego bloku jest określony liczbą jego bajtów. Aby korzystać z blokowego odczytu i zapisu musimy spełnić dwa warunki. Po pierwsze, musimy oczywiście dysponować otwartym plikiem, zaleca się, aby plik taki otwarty był w trybie binarnym. Po drugie musimy w programie posiadać zmienną, która realizuje funkcję bufora, z którego będą pobierane dane do zapisu lub, do którego będą pobierane dane w przypadku odczytu. Przeanalizujmy to zagadnienie na konkretnym przykładzie. Załóżmy, że naszym zadaniem jest utworzenie nowego pliku binarnego, zapisanie do niego liczby typu float, zamknięcie pliku, powtórne jego otwarcie w trybie do Przykłady i ćwiczenia

19 odczytu i odczytanie zapisanej wcześniej liczby. Operacje te realizuje przedstawiony niej kod. #include <stdio.h> #include <stdlib.h> int main() FILE * fp; float num = 123.321; if( ( fp = fopen( "d.dat", "wb" ) )!= NULL ) printf( "\nzapis liczby: %g", num ); fwrite( &num, sizeof( num ), 1, fp ); num = 0; if( ( fp = fopen( "d.dat", "rb" ) )!= NULL ) fread( &num, sizeof( num ), 1, fp ); printf( "\nodczyt liczby: %g", num ); puts( "\n\nnacisnij Enter by zakonczyc..." ); ( void )getchar(); return EXIT_SUCCESS; Przebieg wykonania programu ilustruje Rysunek 10: Przyjrzyjmy się fragmentowi: Rysunek 10 Zapis/odczyt liczby float w postaci bloku fwrite( &num, sizeof( num ), 1, fp ); Funkcja fwrite realizuje zapis bloku do pliku fp. Blokiem owym jest zmienna num liczba rzeczywista. Pierwszym parametrem tej funkcji jest wskaźnik na obszar pamięci zawierający blok do zapisu. Zatem funkcja fwrite otrzymuje jako pierwszy parametr wyrażenie &num, czyli wskaźnik na zmienną num, która ma być zapisana do pliku fp. Zmienna ta, jest zatem buforem w pamięci operacyjnej, z którego pobierane są dane zapisywane do pliku. Drugi parametr funkcji fwrite to, liczony w bajtach, rozmiar zapisywanego bloku. W naszym przypadku jest on równy liczbie bajtów zajmowanych przez daną typu Wprowadzenie do programowania w języku C

Przetwarzanie plików float w pamięci operacyjnej określonej wyrażeniem sizeof( float ). Trzeci w kolejności parametr określa liczbę bloków do zapisania. W naszym przypadku to pojedyncza dana typu float, zatem wartość ta to 1. Szczegółowy opis funkcji fwrite jest następujący: size_t fwrite( void *ptr, size_t size, size_t n, FILE *stream ); Funkcja zapisuje dane z obszaru pamięci wskazywanego przez ptr do strumienia stream. Zapisuje n bloków o rozmiarze size. Łączna liczba zapisanych bajtów to n*size. Rezultatem funkcji jest liczba zapisanych bloków (nie bajtów!). W przypadku wystąpienia końca pliku lub błędu, rezultatem funkcji jest liczba, potencjalnie zerowa, bezbłędnie zapisanych bloków. Odczyt danych z pliku realizowany jest przez funkcję fread.przyjrzyjmy się fragmentowi: fread( &num, sizeof( num ), 1, fp ); Funkcja fread realizuje odczyt bloku bajtów z pliku fp. Odczytane dane zapisywane są w pamięci operacyjnej, w obszarze wskazywanym przez pierwszy parametr wywołania funkcji - funkcja fread otrzymuje zatem jako pierwszy parametr wyrażenie &num, czyli wskaźnik na zmienną num, do której mają być wstawione dane przeczytane z pliku fp. Odczytywany jest jeden blok o rozmiarze sizeof( num ). Szczegółowy opis funkcji fread jest następujący: size_t fread( void *ptr, size_t size, size_t n, FILE *stream ); Funkcja czyta dane ze strumienia stream do obszaru pamięci wskazywanego przez ptr. Odczytuje n bloków o rozmiarze size. Łączna liczba odczytanych bajtów to n*size. Rezultatem funkcji jest liczba przeczytanych bloków (nie bajtów!). W przypadku napotkania końca pliku lub błędu, rezultatem jest liczba bezbłędnie odczytanych znaków, która potencjalnie może być równa zero. Należy zwrócić uwagę na to, że zapisowi i odczytowi podlegają binarne reprezentacje danych. Zatem zapis do pliku liczby 123.321 z wykorzystaniem funkcji fwrite spowoduje zrzucenie do pliku binarnej reprezentacji tej liczby, zatem zazwyczaj czterech bajtów zorganizowanych zgodnie ze standardem kodowania liczb rzeczywistych (zazwyczaj zgodnych z odpowiednim standardem IEEE). Podgląd pliku d.dat w notatniku pokaże zawartość taką jak prezentuje to Rysunek 11. Rysunek 11 Zawartość pliku d.dat w Notatniku Przykłady i ćwiczenia

21 Z powyższych opisów wynika istotna informacja. Funkcje fread i fwrite pozwalają na kontrolę poprawności wykonywanych operacji odczytu i zapisu. Wystarczy kontrolować rezultat wywołania tych funkcji i porównywać z liczbą zadanych bloków. Ilustrują to przedstawione niżej fragmenty. Pierwszy ilustruje zapis do pliku z kontrolą jego poprawności. if( ( fp = fopen( "d.dat", "wb" ) )!= NULL ) printf( "\nzapis liczby: %g", num ); if( fwrite( &num, sizeof( num ), 1, fp )!= 1 ) printf( "\nblad zapisu!" ); else printf( "\nzapis wykonany" ); Drugi fragment prezentuje zapis z kontrolą jego poprawności. if( ( fp = fopen( "d.dat", "rb" ) )!= NULL ) if( fread( &num, sizeof( num ), 1, fp )!= 1 ) printf( "\nblad odczytu!" ); else printf( "\nodczyt liczby: %g", num ); 1.5.2 Zapis i odczyt ciągów danych Przedstawiony poprzednio przykład prezentował zapis/odczyt pojedynczej liczby rzeczywistej do/z pliku z wykorzystaniem funkcji fwrite i fread. Jak można zrealizować zapis do pliku ciągu wielu takich liczb? Załóżmy, że zapisujemy do pliku 12-cie liczb typu float reprezentujących dochody z kolejnych miesięcy roku podatkowego. Dane te są zapisane w dwunastoelementowej tablicy. Pierwszym narzucającym się rozwiązaniem jest zapisanie kolejno każdego elementu tablicy jako bloku. Prezentuje to przedstawiony dalej przykład, zawierający również odczyt danych. #include <stdio.h> #include <stdlib.h> #define LB_MIES 12 int main() FILE * fp; float d[ LB_MIES ]; int nr; /* Wstawiamy do tablicy przykładowe dane */ for( nr = 0; nr < LB_MIES; nr++ ) d[ nr ] = 1000 * ( nr + 1 ); Wprowadzenie do programowania w języku C

Przetwarzanie plików if( ( fp = fopen( "d.dat", "wb" ) )!= NULL ) for( nr = 0; nr < LB_MIES; nr++ ) if( fwrite( &d[ nr ], sizeof( d[ nr ] ), 1, fp )!= 1 ) printf( "\nblad zapisu!" ); else printf( "\nzapisano: %g", d[ nr ] ); /* Zerujemy tablice by stwierdzic czy odczyt dziala */ for( nr = 0; nr < LB_MIES; nr++ ) d[ nr ] = 0; if( ( fp = fopen( "d.dat", "rb" ) )!= NULL ) for( nr = 0; nr < LB_MIES; nr++ ) if( fread( &d[ nr ], sizeof( d[ nr ] ), 1, fp )!= 1 ) printf( "\nblad odczytu!" ); else printf( "\nodczytano: %g", d[ nr ] ); puts( "\n\nnacisnij Enter by zakonczyc..." ); ( void )getchar(); return EXIT_SUCCESS; Zaprezentowane rozwiązanie jest skuteczne. Można jednak uzyskać taki sam efekt znacznie prościej. Można bowiem zapisać całą tablicę d do pliku pojedynczym wywołaniem funkcji fwrite oraz odczytać pojedynczym wywołaniem funkcji fread. Ilustruje to przedstawiony niżej przykład. #include <stdio.h> #include <stdlib.h> #define LB_MIES 12 int main() FILE * fp; float d[ LB_MIES ]; int nr; /* Wstawiamy do tablicy przykładowe dane */ for( nr = 0; nr < LB_MIES; nr++ ) printf( "\nzapis: %g", d[ nr ] = 1000 * ( nr + 1 ) ); if( ( fp = fopen( "d.dat", "wb" ) )!= NULL ) if( fwrite( &d[0], sizeof( d[0] ), LB_MIES, fp )!= LB_MIES ) printf( "\nblad zapisu!" ); Przykłady i ćwiczenia

23 /* Zerujemy tablice by stwierdzic czy odczyt dziala */ for( nr = 0; nr < LB_MIES; nr++ ) d[ nr ] = 0; if( ( fp = fopen( "d.dat", "rb" ) )!= NULL ) if( fread( &d[0], sizeof( d[0] ), LB_MIES, fp )!= LB_MIES ) printf( "\nblad odczytu!" ); for( nr = 0; nr < LB_MIES; nr++ ) printf( "\nodczyt: %g", d[ nr ] ) ; puts( "\n\nnacisnij Enter by zakonczyc..." ); ( void )getchar(); return EXIT_SUCCESS; Zapis całej tablicy d do pliku identyfikowanego przez fp realizuje fragment: if( fwrite( &d[0], sizeof( d[0] ), LB_MIES, fp )!= LB_MIES ) printf( "\nblad zapisu!" ); Funkcja fwrite otrzymuje jako pierwszy parametr adres początku tablicy d, czyli &d[0]. Zapisywane są również bloki o rozmiarze sizeof( d[0] ), ale jest ich teraz tyle, ile wynosi wartość stałej LB_MIES, czyli 12. Rezultat funkcji porównywany jest z LB_MIES, bowiem tyle bloków powinno zostać z sukcesem zapisanych do pliku. Operacja odczytu wygląda analogicznie: if( fread( &d[0], sizeof( d[0] ), LB_MIES, fp )!= LB_MIES ) printf( "\nblad odczytu!" ); Funkcja fread otrzymuje wskaźnik na obszar pamięci, do którego ma wstawić odczytywane z pliku fp dane. Odczytowi podlegają oczywiście liczby rzeczywiste, ich liczność określa stała LB_MIES, wartość ta jest również wykorzystywana przy testowaniu poprawności odczytu. Należy zwrócić uwagę, że pierwszy parametr obu funkcji będzie zazwyczaj zapisywany inaczej w przypadku, gdy buforem danych w pamięci operacyjnej jest tablica. Nazwa tablicy jest bowiem traktowana jako wskaźnik na jej pierwszy element. Zatem zamiast pisać &d[0] napiszemy raczej d. Zatem wywołania funkcji fread i fwrite będą wyglądać następująco: fread( d, sizeof( d[0] ), LB_MIES, fp ) fwrite( d, sizeof( d[0] ), LB_MIES, fp ) Wprowadzenie do programowania w języku C

Przetwarzanie plików Wyjaśnienia wymaga również wyrażenie określające rozmiar bloku. Zamiast sizeof( d[0] ) można napisać sizeof( float ) jest to dozwolone i da dokładnie taki sam efekt. Dlaczego zatem w prezentowanych przykładach wykorzystuje się pierwszy sposób? Zwróćmy uwagę, że zapis taki stwierdza, że rozmiar bloku równy jest rozmiarowi pierwszego elementu tablicy. Jeżeli zmienimy typ elementów tablicy np. na double, nie trzeba modyfikować linii zawierającej wywołanie fwrite czy fread. W przypadku wersji drugiej, każde wystąpienie nazwy typu float należy zamienić na double, a przeoczenie nawet jednej zamiany może powodować trudne do wykrycia błędy. Testowanie rezultatu funkcji fread jest zwyczajowo wykorzystywane do sekwencyjnego odczytu nieznanej liczby bloków. Osiągnięcie pozycji końcowej w pliku w trakcie odczytu powoduje, że rezultatem funkcji jest zerowa liczba odczytanych bloków. Może to służyć do skonstruowania iteracji przetwarzającej sekwencyjnie plik, blok po bloku, co ilustruje przedstawiony niżej przykład. W przykładzie tym odczytuje się z pliku liczby określające kolejne dochody, przy czym liczba tych dochodów nie jest wcześniej znana. Następuje sumowanie dochodów i wyznaczenie ich liczby. Zerowy rezultat liczby odczytanych bloków jest sygnałem, że wszystkie dane zostały przeczytane z pliku. if( ( fp = fopen( "d.dat", "rb" ) )!= NULL ) int counter = 0; float num, suma = 0; while( fread( &num, sizeof( num ), 1, fp ) == 1 ) printf( "\nodczytano: %g", num ); suma += num; counter++; printf( "\nsuma przychodow: %g", suma ); printf( "\nliczba odczytanych przychodow: %d", counter ); 1.5.3 Kopiowanie zawartości plików blok po bloku Funkcje blokowego zapisu i odczytu mają najróżniejsze zastosowania. Jednym z nich może być kopiowanie zawartości plików z wykorzystaniem przydzielanego dynamicznie bufora pamięciowego. Ilustruje zamieszczona dalej funkcja bpb_file_copy. /*----------------------------------------------------------------- Funkcja bpb_file_copy realizuje kopiowanie zawartości źrodłowego pliku src do pliku docelowego dst. Wykorzystywane są blokowe operacje zapisu i odczytu. Funkcja nie zamyka strumieni src i dst Przykłady i ćwiczenia

25 Prametry : Wskaźniki na prawidłowo otwarte strumienie binarne src, dst - odpowiednio dla pliku źródlowego i docel. Rezultat : TRUE jeżeli kopiowanie zakończyło się poprawnie FALSE jeżeli wystapił błąd podczas kopiowania -----------------------------------------------------------------*/ int bpb_file_copy( FILE * dst, FILE * src ) char * copy_buff = NULL; /* Wskaźnik na bufor kopiowania */ size_t buff_size = 30 * 1024; /* Rozmiar bufora kopiowania */ size_t in = 0; /* Liczba przeczytanych bloków */ if( ( copy_buff = ( char * )malloc( buff_size ) ) == NULL ) return FALSE; while( ( in = fread( copy_buff, 1, buff_size, src ) )!= 0 ) if( fwrite( copy_buff, 1, in, dst )!= in ) return FALSE; free( copy_buff ); return TRUE; Funkcja malloc przydziela pamięć dla bloku o rozmiarze buff_size (30KB). Przed zakończeniem funkcji pamięć ta jest zwalniana. Algorytm kopiowania jest prosty, jednak zawiera pewne szczegóły wymagające skomentowania. Iteracja while wykorzystując funkcję fread odczytuje z pliku src bloki o rozmiarze buff_size. Każdy odczytany blok jest następnie zapisywany do pliku docelowego dst. Pojawia się jednak pewien problem istnieje niewielkie prawdopodobieństwo, że rozmiar pliku będzie wielokrotnością rozmiaru bloku. Zatem najprawdopodobniej ostatni odczytany blok będzie zawierał mniej bajtów niż pojemność bufora. Pojawia się pytanie jak dowiedzieć się, ile bajtów zawiera ostatni, potencjalnie niepełny blok. Funkcja fread, swoim rezultatem, informuje nas o liczbie przeczytanych bloków, nie bajtów. W naszym przypadku ta informacja nie jest zbyt użyteczna. Można jednak zastosować prostą sztuczkę. Można ustalić, odczytujemy bloki jednobajtowe. Wtedy jako liczbę odczytywanych bloków przekażemy nie jak do tej pory jeden a właśnie buff_size. W takim przypadku rezultatem funkcji jest liczba odczytanych bloków jednobajtowych a zatem po prostu liczba bajtów. Dla funkcji fread nie ma znaczenia czy odczytujemy bloki jednobajtowe czy o większej pojemności, rozmiar odczytywanego bloku jest iloczynem liczby bloków i rozmiaru pojedynczego bloku. Zatem konstrukcja: in = fread( copy_buff, 1, buff_size, src ) ) Wprowadzenie do programowania w języku C

Przetwarzanie plików powoduje odczytanie buff_size jednobajtowych bloków z pliku src do obszaru wskazywanego przez copy_buff. Rezultatem funkcji jest liczba rzeczywiście przeczytanych, jednobajtowych bloków, zapamiętujemy ją w zmiennej in. W przypadku odczytania mniejszej liczby bajtów (ostatni niepełny blok) w zmiennej tej będziemy mieli liczbę odczytanych bajtów. Po odczytaniu ostatniego bloku, kolejna operacja odczytu zakończy się i rezultatem funkcji fread będzie zero. Spowoduje to zakończenie iteracji while. Zapis danych do pliku dst wykorzystuje liczbę bajtów odczytanych przez funkcję fread a zapamiętanych w zmiennej in. Realizuje to wywołanie: fwrite( copy_buff, 1, in, dst ) Ponieważ rezultatem funkcji fwrite jest liczba z sukcesem zapisanych bloków, możemy sprawdzić, czy rezultat ten jest równy liczbie zapisywanych bloków (zawartość zmiennej in). Jeżeli tak nie jest, może to oznaczać błąd zapisu do pliku docelowego. Przerywamy wtedy kopiowanie i sygnalizujemy błąd. Realizuje to sekwencja: if( fwrite( copy_buff, 1, in, dst )!= in ) return FALSE; Innym przykładem wykorzystania odczytu blokowego może być funkcja hex_dump przedstawiona niżej. 1.5.4 Odczyt blokowy w szesnastkowym podglądzie plików Wyświetlanie HEX ASCII zastosowanie funkcji fread i fwrite. /*----------------------------------------------------------------- Funkcja hex_dump wyprowadza do stdout zawartość pliku wyświetlaną w postaci szesnastkowej oraz ASCII. Wygląda to podobnie jak "podgląd szesnastkowy" w NC czy programie DiskEditor. Parmetry : file - Wskaźnik na prawidłowo otwarty strumień binarny Uwaga funkcja nie zatrzymuje wyświetlania np. co 24 linie, w ramach ćwiczeń praktycznych można to poprawić. -----------------------------------------------------------------*/ void hex_dump( FILE * file ) #define BUFFER_LEN 19 /* Tyle znaków będzie w linii na ekranie */ unsigned char buffer[ BUFFER_LEN ]; /* Bufor na odczyt. znaki */ int i = 0; while( ( in_chars = fread( buffer, 1, BUFFER_LEN, file ) ) > 0 ) /* Wypisz : hex, dwie pozycje, wiodące zera, duże litery */ for( i = 0; i < in_chars; i++) printf( "%02X ", buffer[ i ] ); Przykłady i ćwiczenia

27 printf(" "); /* Separator części szesnastkowej od ASCII */ /* Wypisz bufor jako ASCII o ile można, jeśli nie to '.' */ for( i = 0; i < in_chars; i++ ) printf( "%c", isprint( buffer[ i ] )? buffer[ i ] : '.' ); putchar('\n'); Wyniki działania programu wykorzystującego funkcje hex_dump prezentuje Rysunek 12. Rysunek 12 Wyniki działania funkcji hex_dump 1.6 Ćwiczenia w blokowym przetwarzaniu plików Należy rozbudować program z ćwiczenia opisanego w rozdziale Ćwiczenia w przetwarzaniu plików linia po linii, str. 17. Proszę wprowadzić dodatkową opcję -h powodującą podgląd pliku w wersji szesnastkowej, wzorując się na funkcji hex_dump przedstawionej powyżej. 1.7 Suplement parametry funkcji main Przedstawiony niżej program prezentuje sposób wykorzystania parametrów funkcji main. Parametry te zawierają informacje o parametrach wywołania programu w linii poleceń. Załóżmy, że program ma otrzymać dwa parametry pierwszy to pojedyncza litera a, b, lub c poprzedzona myślnikiem, drugi to napis, np. nazwa pliku. Pierwszy parametr jest opcjonalny. Drugi musi być, jak go nie ma to program ma wyświetlić informację o sposobie wywołania programu. #include <stdio.h> #include <string.h> #include <stdlib.h> #include <ctype.h> Wprowadzenie do programowania w języku C

Przetwarzanie plików char err_mess[] = "\nblad wywolania programu\nwlasciwa postac to:" "\nmain1 -[a b c] nazwapliku\nnazwapliku"; int main( int argc, char * args[] ) int i; puts( "\n\n\n" ); /* Zmniejszam najpierw argc bo uwzględnia nazwę pliku programu */ if( --argc == 0 ) puts( err_mess ); else switch( argc ) case 1 : /* Jeden parametr, ma to być nazwa pliku, zatem nie może się zaczynać od '-', tzn. znak na zerowej pozycji napisu wskazywanego przez pierwszy parametr nie może być równy'-' */ if( args[ 1 ][ 0 ] == '-' ) puts( err_mess ); else printf( "\npojedynczy parametr: %s", args[ 1 ] ); break; case 2 : /* Jeżeli dwa parametry, to pierwszy ma musi się zaczynać od '-', tzn. znak na zerowej pozycji napisu wskazywanego przez pierwszy parametr musi być równy '-', drugi może być a lub b lub c */ if( strlen( args[1] ) == 2 && args[ 1 ][ 0 ] == '-' && ( tolower( args[ 1 ][ 1 ] ) == 'a' tolower( args[ 1 ][ 1 ] ) == 'b' tolower( args[ 1 ][ 1 ] ) == 'c' ) ) printf( "\npierwszy parametr OK %s", args[ 1 ] ); printf( "\ndrugi parametr: %s", args[ 2 ] ); else printf( "\nbledny pierwszy parametr: %s\n %s", args[ 1 ], err_mess ); break; default : printf( "\nbledna liczba parametrow\n %s", err_mess ); (void)getchar(); return EXIT_SUCCESS; Przykłady i ćwiczenia