Do sada su podaci stizali sa tastature, a rezultati išli na ekran. Datoteke omogućavaju da program pročita podatke iz fajla i da rezultat sačuva – i posle završetka programa. Na nekim takmičenjima se ulaz čita iz datoteke, pa je ova lekcija i priprema za to.
Pre nego što počneš: potrebno predznanje – stringovi, strukture, petlje. Kako da učiš: svaki primer prekucaj i pokreni, a zadatke na kraju prvo reši sam/a; uputstvo i rešenje otvori tek posle toga. Svi programi su u jeziku C (C11) i provereno se prevode (gcc -Wall) i daju navedeni izlaz.
Otvaranje i zatvaranje: fopen i fclose
Rad sa datotekom ide u tri koraka: otvori (fopen), čitaj ili piši, zatvori (fclose). fopen vraća pokazivač tipa FILE * ili NULL ako otvaranje nije uspelo – to se uvek proverava.
Režim
Značenje
Ako datoteka ne postoji
Ako postoji
"r"
čitanje
greška (NULL)
čita od početka
"w"
pisanje
pravi se nova
briše se stari sadržaj
"a"
dodavanje na kraj
pravi se nova
piše se iza postojećeg sadržaja
#include <stdio.h>
int main(void) {
FILE *f = fopen("pozdrav.txt", "w"); /* otvori za pisanje */
if (f == NULL) {
printf("Ne mogu da otvorim datoteku.\n");
return 1;
}
fprintf(f, "Zdravo iz datoteke!\n"); /* kao printf, ali u datoteku */
for (int i = 1; i <= 3; i++)
fprintf(f, "%d. red\n", i);
fclose(f); /* obavezno zatvori */
printf("Upisano u pozdrav.txt\n");
return 0;
}
Datoteka pozdrav.txt nastaje u fascikli iz koje se program pokreće (u Code::Blocks-u to je fascikla projekta). Otvori je u Notepad-u i proveri sadržaj.
Čitanje brojeva: fscanf
fscanf(f, ...) radi kao scanf, ali čita iz datoteke. Vraća broj uspešno pročitanih vrednosti, a na kraju datoteke vraća EOF. Zato se nepoznat broj podataka čita petljom while (fscanf(...) == 1).
Datoteka brojevi.txt sadrži brojeve razdvojene razmacima ili novim redovima, npr. 4 8 15 16 23 42:
#include <stdio.h>
int main(void) {
FILE *f = fopen("brojevi.txt", "r");
if (f == NULL) {
printf("Nema datoteke brojevi.txt\n");
return 1;
}
int x, n = 0, zbir = 0, max = 0;
while (fscanf(f, "%d", &x) == 1) { /* čitaj dok ima brojeva */
if (n == 0 || x > max) max = x;
zbir += x;
n++;
}
fclose(f);
if (n > 0)
printf("Brojeva: %d, zbir: %d, prosek: %.2f, najveci: %d\n", n, zbir, (double)zbir / n, max);
return 0;
}
Čitanje red po red: fgets
fgets(tekst, velicina, f) čita jedan ceo red (sa razmacima) i vraća NULL na kraju datoteke. Na kraju pročitanog reda ostaje znak '\n', koji se obično uklanja.
#include <stdio.h>
#include <string.h>
int main(void) {
/* prvo napravimo datoteku sa nekoliko redova */
FILE *f = fopen("pesma.txt", "w");
if (f == NULL) return 1;
fprintf(f, "Programiranje je zabavno\nC je brz jezik\n\nKraj\n");
fclose(f);
/* zatim je čitamo red po red */
f = fopen("pesma.txt", "r");
if (f == NULL) return 1;
char red[200];
int broj = 0;
while (fgets(red, sizeof red, f) != NULL) {
red[strcspn(red, "\n")] = '\0'; /* ukloni '\n' sa kraja */
broj++;
printf("%d: %s (%d znakova)\n", broj, red, (int)strlen(red));
}
fclose(f);
return 0;
}
Ulaz iz jedne, izlaz u drugu datoteku
Tipičan zadatak: pročitati podatke iz ulaz.txt, obraditi ih i rezultat upisati u izlaz.txt. Istovremeno mogu biti otvorene dve datoteke.
U ulaz.txt je u prvom redu broj učenika n, a zatim za svakog ime i bodovi. U izlaz.txt treba upisati one koji imaju bar 50 bodova:
#include <stdio.h>
int main(void) {
FILE *ul = fopen("ulaz.txt", "r");
FILE *iz = fopen("izlaz.txt", "w");
if (ul == NULL || iz == NULL) {
printf("Greska pri otvaranju datoteka.\n");
return 1;
}
int n, bodovi, polozilo = 0;
char ime[30];
fscanf(ul, "%d", &n);
for (int i = 0; i < n; i++) {
fscanf(ul, "%29s %d", ime, &bodovi);
if (bodovi >= 50) {
fprintf(iz, "%s %d\n", ime, bodovi);
polozilo++;
}
}
fprintf(iz, "Polozilo: %d od %d\n", polozilo, n);
fclose(ul);
fclose(iz);
return 0;
}
ulaz.txt
4
Marko 45
Ana 92
Iva 50
Luka 30
izlaz.txt posle pokretanja
Ana 92
Iva 50
Polozilo: 2 od 4
Na ekranu se ništa ne ispisuje – ceo rezultat je u datoteci.
Dodavanje na kraj: režim "a"
#include <stdio.h>
/* svaki poziv dodaje jedan red na kraj dnevnika */
void zapisi(const char *poruka) {
FILE *f = fopen("dnevnik.txt", "a");
if (f == NULL) return;
fprintf(f, "%s\n", poruka);
fclose(f);
}
int main(void) {
remove("dnevnik.txt"); /* počni od prazne datoteke */
zapisi("Program pokrenut");
zapisi("Ucitani podaci");
zapisi("Kraj rada");
/* pročitaj ceo dnevnik znak po znak */
FILE *f = fopen("dnevnik.txt", "r");
if (f == NULL) return 1;
int c;
while ((c = fgetc(f)) != EOF)
putchar(c);
fclose(f);
return 0;
}
Da je u funkciji zapisi korišćen režim "w", u dnevniku bi ostao samo poslednji red – svako otvaranje bi obrisalo prethodni sadržaj. fgetc čita jedan znak; promenljiva c je tipa int da bi mogla da primi i vrednost EOF.
Takmičenja: freopen
Ako zadatak traži čitanje iz datoteke, a program je već napisan sa scanf/printf, dovoljno je na početku main preusmeriti standardni ulaz i izlaz:
#include <stdio.h>
int main(void) {
/* od ovog trenutka scanf čita iz ulaz.txt, a printf piše u izlaz.txt */
freopen("ulaz.txt", "r", stdin);
freopen("izlaz.txt", "w", stdout);
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", a + b);
return 0;
}
Pre predaje proveri tačna imena datoteka iz teksta zadatka. Kada vežbaš kod kuće, ova dva reda možeš da zakomentarišeš i unosiš podatke sa tastature.
Zadaci za vežbu
Za svaki zadatak postoji primer, uputstvo i rešenje. Rešenja su provereno tačna na primerima iz teksta.
ZADATAK 01Težina: lako
Najduži red
U datoteci ulaz.txt je tekst u više redova (svaki red ima najviše 200 znakova). U datoteku izlaz.txt upisati redni broj i sadržaj najdužeg reda (ako ih ima više, prvog od njih), u obliku broj: tekst.
U primeru je levo sadržaj ulaz.txt, a desno sadržaj izlaz.txt.
Primer
Ulaz
Kratak red
Ovo je najduzi red ovde
Srednje dug red
Izlaz
2: Ovo je najduzi red ovde
Uputstvo
Čitaj sa fgets, ukloni '\n', pamti najveću dužinu i kopiraj taj red sa strcpy.
Rešenje (C)
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *ul = fopen("ulaz.txt", "r");
FILE *iz = fopen("izlaz.txt", "w");
if (ul == NULL || iz == NULL) return 1;
char red[205], najduzi[205] = "";
int broj = 0, najBroj = 0, najDuzina = -1;
while (fgets(red, sizeof red, ul) != NULL) {
red[strcspn(red, "\n")] = '\0';
broj++;
int d = (int)strlen(red);
if (d > najDuzina) { /* strogo veće: ostaje prvi najduži */
najDuzina = d;
najBroj = broj;
strcpy(najduzi, red);
}
}
if (najBroj > 0)
fprintf(iz, "%d: %s\n", najBroj, najduzi);
fclose(ul);
fclose(iz);
return 0;
}
ZADATAK 02Težina: srednje
Statistika ocena
U datoteci ulaz.txt su ocene (celi brojevi od 1 do 5) razdvojene razmacima i novim redovima; njihov broj nije unapred poznat. U izlaz.txt upisati koliko je bilo kojih ocena (od 5 ka 1, u obliku ocena: broj) i prosek na dve decimale.
Primer
Ulaz
5 4 5 3
2 5 4
5 4
Izlaz
5: 4
4: 3
3: 1
2: 1
1: 0
Prosek: 4.11
Uputstvo
Niz brojača int koliko[6] = {0}; svaka pročitana ocena x povećava koliko[x]. Čitaj sa while (fscanf(ul, "%d", &x) == 1).
Rešenje (C)
#include <stdio.h>
int main(void) {
FILE *ul = fopen("ulaz.txt", "r");
FILE *iz = fopen("izlaz.txt", "w");
if (ul == NULL || iz == NULL) return 1;
int koliko[6] = {0}, x, n = 0, zbir = 0;
while (fscanf(ul, "%d", &x) == 1) {
if (x >= 1 && x <= 5) {
koliko[x]++;
zbir += x;
n++;
}
}
for (int o = 5; o >= 1; o--)
fprintf(iz, "%d: %d\n", o, koliko[o]);
if (n > 0)
fprintf(iz, "Prosek: %.2f\n", (double)zbir / n);
fclose(ul);
fclose(iz);
return 0;
}
Česte greške i saveti
Uvek proveri da li je fopen vratio NULL – najčešći razlog je pogrešno ime datoteke ili pogrešna fascikla.
Režim "w" briše postojeći sadržaj datoteke čim se otvori; za dopisivanje koristi "a".
Ne zaboravi fclose: dok datoteka nije zatvorena, deo upisanog teksta može ostati u memoriji i ne stići na disk.
Petlja while (!feof(f)) je česta greška – poslednji podatak se obradi dva puta. Proveravaj povratnu vrednost fscanf/fgets.
U Windows putanji obrnuta kosa crta piše se dvostruko: "C:\\podaci\\ulaz.txt", ili se koristi obična /.
Proveri znanje
Otvori kviz
Datoteke
1. Šta vraća fopen ako datoteka za čitanje ne postoji?
2. Šta radi režim "w" sa postojećom datotekom?
3. Kada se završava petlja while (fscanf(f, "%d", &x) == 1)?
4. Koja funkcija čita ceo red teksta sa razmacima?
Podešavanja kolačića
Koristimo kolačiće da bismo vam pružili najbolje moguće iskustvo. Takođe nam omogućavaju da analiziramo ponašanje korisnika kako bismo stalno poboljšavali veb lokaciju za vas.