Pliki i funkcje wejścia/wyjścia
Otwieranie i zamykanie pliku
Czytanie i zapisywanie do pliku
wymaga jego uprzedniego otwarcia.
Plik możemy otworzyć funkcją
fopen
, której musimy podać dwa argumenty:
- ścieżkę do pliku w postaci napisu
np.
"plik.txt"
,"/etc/fstab"
,"C:\CONFIG.SYS"
, - tryb otwarcia pliku — również napis:
"r"
— tylko do odczytu, znacznik na początku pliku,"r+"
— do odczytu i zapisu, znacznik na początku pliku,"w"
— tylko do zapisu, czyszczenie pliku (jeśli istniał), znacznik na początku pliku,"w+"
— do odczytu i zapisu, czyszczenie pliku (jeśli istniał), znacznik na początku pliku,"a"
— (tryb dopisywania — "appending") tylko do zapisu, znacznik na końcu pliku,"a+"
— do odczytu i zapisu, znacznik na końcu pliku.
Funkcja fopen
zwraca uchwyt do otwartego pliku,
jeśli czynność się powiodła. W przeciwnym razie zwracana jest wartość NULL
.
FILE * f; f = fopen("program.c", "r"); if(!f){ perror("fopen"); exit(1); }
Funkcja perror
wypisuje przyczynę wystąpienia błędu na standardowe wyjście błędów.
Gdy już zakończymy wszystkie działania na pliku,
należy go koniecznie zamknąć za pomocą funkcji fclose
.
fclose(f);
Standardowe wejście, wyjście i wyjście błędów
Najczęściej w programie w momencie uruchamiania
dostępne są trzy otwarte pliki:
stdin
, stdout
, stderr
.
Pierwszy do odczytu, dwa pozostałe do zapisu.
Można ich używać jak zwykłych plików, przy czym najczęściej są powiązane z wejściem z klawiatury oraz wyjściem na ekran.
Czytanie z pliku
Mając otwarty do odczytu plik, możemy z niego czytać dane.
Pamiętaj, że odczyt informacji z dysku jest wielokrotnie wolniejszy, niż odczyt z pamięci operacyjnej. Dlatego w miarę możliwości staraj się unikać np. dwukrotnego wczytywania zawartości pliku, jeśli możliwe jest jednokrotne wczytanie całego pliku do pamięci operacyjnej.
Funkcja fscanf
działa analogicznie do funkcji scanf
, przy czym różnica
polega na tym, że jawnie podajemy, z jakiego pliku ma się odbywać czytanie.
int zmienna, wczytano; wczytano = fscanf(f, "%d", &zmienna); if(!wczytano){ fprintf(stderr, "W pliku nie znaleziono liczby.\n"); } else if(wczytano == EOF) { fprintf(stderr, "Osiągnięto koniec pliku.\n"); } else { printf("Wczytana z pliku liczba to %d.", zmienna); }
Znanej nam już funkcji fgets
używamy do wczytywania całych linii tekstu.
char napis[100]; fgets(napis, 100, f); printf("Wczytano linię: '%s'", napis);
Do wczytywania pojedynczych znaków
możemy użyć funkcji fgetc
.
int znak; znak = fgetc(f); if(znak == EOF){ fprintf(stderr, "Został osiągnięty koniec pliku.\n"); } else { printf("Wczytano znak '%c'.\n", znak); }
Warto zwrócić uwagę, że zmienna znak
w powyższym
przykładzie jest typu int
.
Tylko wtedy porównywanie jej wartości do EOF
ma sens,
ponieważ wartość EOF
jest spoza zakresu typu char
.
Zapisywanie do pliku
Analogicznie do funkcji printf
działa funkcja fprintf
.
fprintf(f, "%d %d\n", a, b);
Funkcja fputs
zapisuje do pliku
zawartość napisu.
fputs(napis, f);
Aby zapisać do pliku pojedynczy znak,
możemy użyć funkcji fputc
.
fputc(znak, f);
Większy przykład
Poniższy program wypisuje na standardowe wyjście zawartość pliku podanego jako argument programu.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]){ if(argc < 2){ fprintf( stderr, "Podaj nazwę pliku jako argument programu.\n" ); exit(1); } FILE * f = fopen(argv[1], "r"); if(!f){ perror("fopen"); exit(2); } int znak; while((znak = fgetc(f)) != EOF) fputc(znak, stdout); fclose(f); return 0; } |
Zadania
-
Napisz program, który wypisze liczbę znaków w pliku, którego nazwa jest podana jako parametr wywołania programu.
-
Napisz program, który jest uruchamiany z dwoma parametrami — nazwami plików. Program ma kopiować pierwszy plik do drugiego.
-
Zmodyfikuj program z poprzedniego zadania tak, aby zawartość pierwszego pliku była dopisywana do drugiego.
-
Napisz program, który jest uruchamiany z dwoma parametrami — nazwami plików. Program ma porównywać zawartość obu plików leksykograficznie. Jeśli pierwszy plik jest wcześniejszy, program ma wypisać liczbę -1. Jeśli oba pliki są jednakowe, program ma wypisać liczbę 0. Jeśli drugi plik jest wcześniejszy, program ma wypisać liczbę 1.
-
Zdefiniuj typ strukturalny
struct Miasto
do przechowywania danych na temat miast: nazwy, powierzchni i ludności.Pobierz plik miasta.txt, w którym kolumny oznaczają odpowiednio: powierzchnię, ludność i nazwę miasta.
Zdefiniuj funkcję
struct Miasto *wczytane_miasto(FILE *plik)
, która wczyta z otwartego pliku dane na temat jednego miasta i zwróci je jako wskaźnik do dynamicznie zaalokowanej struktury typustruct Miasto
. Jeśli nie udało się wczytać kolejnego miasta z powodu napotkania końca pliku, funkcja powinna zwrócić wartośćNULL
.Zdefiniuj funkcję, która przyjmuje jako parametr uchwyt do otwartego pliku. Funkcja, używając funkcji
wczytane_miasto
, ma wypełnić dynamiczną tablicę wskaźników do wartości typustruct Miasto
. Nie znamy z góry liczby wierszy czyli miast w podanym pliku, więc funkcja powinna na bieżąco powiększać rozmiar tablicy za pomocą funkcjirealloc
, aby móc zapisać wskaźniki do kolejnych wczytywanych miast. Za wskaźnikiem do ostatniego wczytanego miasta w tablicy powinna się znaleźć wartośćNULL
jako znacznik końca tablicy. Funkcja ma zwrócić adres wspomnianej tablicy.Napisz funkcję, która otrzyma tablicę miast jak opisana powyżej i zwróci wskaźnik na miasto o największej gęstości zaludnienie.
Napisz funkcję, która wypisuje na standardowe wyjście informacje o mieście, które dostała jako parametr — wskaźnik na strukturę
struct Miasto
.Napisz program, który, używając powyższych funkcji, wypisze miasto o największej gęstości zaludnienia.