Pojęcie pliku Na początku dobrze by było, abyś dowiedział się, czym jest plik. Odpowiedni artykuł dostępny jest w Wikipedii. Najprościej mówiąc, plik to pewne dane zapisane na dysku.

Typy metody obsługi plików Istnieją dwie metody obsługi czytania i pisania do plików: wysokopoziomowa niskopoziomowa

Należy pamiętać, że nie wolno nam używać funkcji z obu tych grup jednocześnie w stosunku do jednego, otwartego pliku, tzn. nie można najpierw otworzyć pliku za pomocą fopen(), a następnie odczytywać danych z tego samego pliku za pomocą read().

Czym różnią się oba podejścia do obsługi plików? Otóż metoda wysokopoziomowa ma swój własny bufor, w którym znajdują się dane po odczytaniu z dysku a przed wysłaniem ich do programu użytkownika. W przypadku funkcji niskopoziomowych dane kopiowane są bezpośrednio z pliku do pamięci programu. W praktyce używanie funkcji wysokopoziomowych jest prostsze a przy czytaniu danych małymi porcjami również często szybsze i właśnie ten model zostanie tutaj zaprezentowany. Identyfikacja pliku Każdy z nas, korzystając na co dzień z komputera przyzwyczaił się do tego, że plik ma określoną nazwę. Jednak, w pisaniu programu, posługiwanie się całą nazwą niosłoby ze sobą co najmniej dwa problemy: duże zużycie pamięci - przechowywanie całej nazwy pliku zajmuje niepotrzebnie pamięć, ryzyko błędów (zostały szerzej omówione w rozdziale Napisy). Programiści korzystają z identyfikatora pliku, który jest pojedynczą liczbą całkowitą. Dzięki temu kod programu jest czytelniejszy i nie trzeba korzystać ciągle z pełnej nazwy pliku. Jednak sam plik nadal jest identyfikowany po swojej nazwie. Aby "przetworzyć" nazwę pliku na odpowiednią liczbę korzystamy z funkcji open lub fopen. Różnica wyjaśniona została poniżej.

Identyfikator pliku w niskopoziomowej metodzie : podstawowym identyfikatorem pliku jest liczba całkowita, która jednoznacznie identyfikuje dany plik w systemie operacyjnym. Liczba ta w systemach typu UNIX jest nazywana deskryptorem pliku. w wysokopoziomowej metodzie: wskaźnik na strukturę typu FILE

Niskopoziomowa obsługa plików

Niskopoziomowa metoda obsługi plików = obsługa na poziomie deskryptoraJakub Narębski :Zapis wyników do pliku w C, C++ i Javie

Znajdują się tu funkcje do wykonywania niskopoziomowych operacji wejścia/wyjścia na deskryptorach plików gnu libc manua: Low-Level Input/Output prymitywy dla funkcji we/wy wyższego poziomu opisanych w sekcji Wejście/wyjście w strumieniach funkcje do wykonywania operacji sterowania niskiego poziomu, dla których nie ma odpowiedników w strumieniach

Nazwy funkcji są typu read(), open(), write() i close().

We/wy na poziomie strumienia jest bardziej elastyczne i zwykle wygodniejsze; dlatego programiści na ogół używają funkcji na poziomie deskryptora tylko wtedy, gdy jest to konieczne. Oto niektóre z typowych powodów: do odczytu plików binarnych w dużych porcjach do wczytywania całego pliku do rdzenia przed jego analizowaniem. do wykonywania operacji innych niż przesyłanie danych, które można wykonać tylko za pomocą deskryptora. (Możesz użyć fileno, aby uzyskać deskryptor odpowiadający strumieniowi.) Aby przekazać deskryptory do procesu potomnego. (Proces potomny może utworzyć własny strumień, aby używać deskryptora, który dziedziczy, ale nie może bezpośrednio dziedziczyć strumienia)

Podstawowym identyfikatorem pliku jest liczba całkowita, która jednoznacznie identyfikuje dany plik w systemie operacyjnym. Liczba ta w systemach typu UNIX jest nazywana deskryptorem pliku.

Wysokopoziomowa obsługa plików Nazwy funkcji z tej grupy zaczynają się od litery "f" (np. fopen(), fread(), fclose()), a identyfikatorem pliku jest wskaźnik na strukturę typu FILE. Owa struktura to pewna grupa zmiennych, która przechowuje dane o pliku - jak na przykład aktualną pozycję w nim. Szczegółami nie musisz się przejmować, funkcje biblioteki standardowej same zajmują się wykorzystaniem struktury FILE. Programista może więc zapomnieć, czym tak naprawdę jest struktura FILE i traktować taką zmienną jako "uchwyt", identyfikator pliku.

Nazwa pliku

Możemy tworzyć unikalne nazwy plików programowo:stackoverflow question: how-to-create-custom-filenames-in-c ?

for(i = 0; i < 100; i++) { char filename[sizeof "file100.txt"];

sprintf(filename, "file%03d.txt", i); fp = fopen(filename,"w"); }

Formatowanie %03d dopełnia ciąg do 3 cyfr z początkowymi zerami. Dzieki temu pliki będą poprawnie posortowane.

Dane znakowe Skupimy się teraz na najprostszym z możliwych zagadnień - zapisie i odczycie pojedynczych znaków oraz całych łańcuchów.

Napiszmy zatem nasz pierwszy program, który stworzy plik "test.txt" i umieści w nim tekst "Hello world":

#include #include

int main () { FILE *fp; /* używamy metody wysokopoziomowej - musimy mieć zatem identyfikator pliku, uwaga na gwiazdkę! */ char tekst[] = "Hello world"; if ((fp=fopen("test.txt", "w"))==NULL) { printf ("Nie mogę otworzyć pliku test.txt do zapisu!\n"); exit(1);

fprintf (fp, "%s", tekst); /* zapisz nasz łańcuch w pliku */ fclose (fp); /* zamknij plik */ return 0;

Teraz omówimy najważniejsze elementy programu. do identyfikacji pliku używa się wskaźnika na strukturę FILE (czyli FILE *). Funkcja fopen zwraca ów wskaźnik w przypadku poprawnego otwarcia pliku, bądź też NULL, gdy plik nie może zostać otwarty. Pierwszy argument funkcji to nazwa pliku, natomiast drugi to 'tryb dostępu - w oznacza "write" (pisanie). Zwrócony "uchwyt" do pliku będzie mógł być wykorzystany jedynie w funkcjach zapisujących dane. I odwrotnie, gdy otworzymy plik podając tryb r ("read", czytanie), będzie można z niego jedynie czytać dane. Funkcja fopen została dokładniej opisana w odpowiedniej części rozdziału o bibliotece standardowej.

Jak uprościć nazwę typu FILE*? Używając typedef: typedef FILE* plik; plik fp;

Po zakończeniu korzystania z pliku należy plik zamknąć. Robi się to za pomocą funkcji fclose. Jeśli zapomnimy o zamknięciu pliku, wszystkie dokonane w nim zmiany zostaną utracone!

Pliki a strumienie Można zauważyć, że do zapisu do pliku używamy funkcji fprintf, która wygląda bardzo podobnie do printf - jedyną różnicą jest to, że w fprintf musimy jako pierwszy argument podać identyfikator pliku. Nie jest to przypadek - obie funkcje tak naprawdę robią to samo. Używana do wczytywania danych z klawiatury funkcja scanf też ma swój odpowiednik wśród funkcji operujących na plikach - jak nietrudno zgadnąć, nosi ona nazwę fscanf.

W rzeczywistości język C traktuje tak samo klawiaturę i plik - są to źródła danych, podobnie jak ekran i plik, do których można dane kierować. Jest to myślenie typowe dla systemów typu UNIX, jednak dla użytkowników przyzwyczajonych do systemu Windows albo języków typu Pascal może być to co najmniej dziwne. Nie da się ukryć, że między klawiaturą i plikiem na dysku zachodzą podstawowe różnice i dostęp do nich odbywa się inaczej - jednak funkcje języka C pozwalają nam o tym zapomnieć i same zajmują się szczegółami technicznymi. Z punktu widzenia programisty, urządzenia te sprowadzają się do nadanego im identyfikatora. Uogólnione pliki nazywa się w C strumieniami.

Każdy program w momencie uruchomienia "otrzymuje" od razu trzy otwarte standardowe strumienie ( ang. Standard Streams )gnu software: libc manual -Standard-Streams: stdin (wejście) = odczytywanie danych wpisywanych przez użytkownika stdout (wyjście) = wyprowadzania informacji dla użytkownika stderr (wyjście błędów) = powiadamiania o błędach

Warunki korzystania ze standardowych strumieni : dołączyć plik nagłówkowy stdio.h nie musimy otwierać ani zamykać strumieni standardowych ( tak jak w przypadku niestandardowych plików : fopen i fclose )

Warto tutaj zauważyć, że konstrukcja: fprintf (stdout, "Hej, ja działam!"); jest równoważna konstrukcji: printf ("Hej, ja działam!"); Podobnie jest z funkcją scanf(). fscanf (stdin, "%d", &zmienna); działa tak samo jak: scanf("%d", &zmienna);

Obsługa błędów Jeśli nastąpił błąd, możemy się dowiedzieć o jego przyczynie na podstawie zmiennej errno zadeklarowanej w pliku nagłówkowym errno.h. Możliwe jest też wydrukowanie komunikatu o błędzie za pomocą funkcji perror. Na przykład używając: fp = fopen ("tego pliku nie ma", "r"); if( fp == NULL )

perror("błąd otwarcia pliku"); exit(-10);

dostaniemy komunikat: błąd otwarcia pliku: No such file or directory

Inny sposób :stackoverflow question: what-is-the-reason-for-fopens-failure-to-open-a-file

#include #include

int main() { errno = 0; FILE *fb = fopen("/home/jeegar/filename","r"); if(fb==NULL) printf("its null"); else printf("working");

printf("Error %d \n", errno);

Zaawansowane operacje Pora na kolejny, tym razem bardziej złożony przykład. Oto krótki program, który swoje wejście zapisuje do pliku o nazwie podanej w linii poleceń: #include #include /* program udający bardzo prymitywną wersję programu "tee" */

int main (int argc, char **argv) { FILE *fp; int c; if (argc < 2)

fprintf (stderr, "Uzycie: %s nazwa_pliku\n", argv); exit(-1);

fp = fopen (argv, "w"); if (!fp)

fprintf (stderr, "Nie moge otworzyc pliku %s\n", argv); exit(-1);

printf("Wcisnij Ctrl+D+Enter lub Ctrl+Z+Enter aby zakonczyc\n"); while ((c = fgetc(stdin)) != EOF)

fputc(c, stdout); fputc(c, fp);

fclose(fp); return 0; } Tym razem skorzystaliśmy już z dużo większego repertuaru funkcji. Między innymi można zauważyć tutaj funkcję fputc(), która umieszcza pojedynczy znak w pliku. Ponadto w wyżej zaprezentowanym programie została użyta stała EOF, która reprezentuje koniec pliku (ang. End Of File). Powyższy program otwiera plik, którego nazwa przekazywana jest jako pierwszy argument programu, a następnie kopiuje dane z wejścia programu (stdin) na wyjście (stdout) oraz do utworzonego pliku (identyfikowanego za pomocą fp). Program robi to tak długo, aż naciśniemy kombinację klawiszy Ctrl+D (w systemach Unixowych) lub Ctrl+Z(w Windows), która wyśle do programu informację, że skończyliśmy wpisywać dane. Program wyjdzie wtedy z pętli i zamknie utworzony plik.

Rozmiar pliku Dzięki standardowym funkcjom języka C możemy m.in. określić długość pliku. Do tego celu służą funkcje fsetpos, fgetpos oraz fseek. Ponieważ przy każdym odczycie/zapisie z/do pliku wskaźnik niejako "przesuwa" się o liczbę przeczytanych/zapisanych bajtów. Możemy jednak ustawić wskaźnik w dowolnie wybranym miejscu. Do tego właśnie służą wyżej wymienione funkcje. Aby odczytać rozmiar pliku powinniśmy ustawić nasz wskaźnik na koniec pliku, po czym odczytać ile bajtów od początku pliku się znajdujemy. Użyjemy do tego tylko dwóch funkcji: fseek oraz fgetpos. Pierwsza służy do ustawiania wskaźnika na odpowiedniej pozycji w pliku, a druga do odczytywania na którym bajcie pliku znajduje się wskaźnik. Kod, który określa rozmiar pliku znajduje się tutaj: #include

int main (int argc, char **argv) { FILE *fp = NULL; fpos_t dlugosc; if (argc != 2) { printf ("Użycie: %s \n", argv); return 1;

if ((fp=fopen(argv, "rb"))==NULL) { printf ("Błąd otwarcia pliku: %s!\n", argv); return 1;

fseek (fp, 0, SEEK_END); /* ustawiamy wskaźnik na koniec pliku */ fgetpos (fp, &dlugosc); printf ("Rozmiar pliku: %d\n", dlugosc); fclose (fp); return 0; } Znajomość rozmiaru pliku przydaje się w wielu różnych sytuacjach, więc dobrze przeanalizuj przykład!

Przykład - pliki graficzne Plik graficzny tworzymy : bezpośrednio w C ( fprintf / fwrite ) pośrednio za pomocą strumieni i potoku, wtedy : zamiast komend zapisu do pliku ( np. fprintf ) używamy komend wysyłających do standardowego wyjścia ( np. fprint, putchar)Przykład programu wykorzystującego potok zamiast przykładowej komendy : ./a.out używamy : ./a.out > anti.ppm Ultimate Anti-Buddhabrot by Claude Heiland-Allen

rastrowy

dostęp sekwencyjny

Najprostszym przykładem rastrowego pliku graficznego jest plik PPM. Poniższy program pokazuje jak utworzyć plik w katalogu roboczym programu. Do zapisu:is a collection of C routines for creating and reading Portable Bit Map files (PBM).