Rad sa datotekama u jeziku C

Lekcija 15 od 15 · C

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žimZnačenjeAko datoteka ne postojiAko postoji
"r"čitanjegreška (NULL)čita od početka
"w"pisanjepravi se novabriše se stari sadržaj
"a"dodavanje na krajpravi se novapiš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
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
Č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?