10. Datoteke

Podeli svoje utiske o poglavlju na anketi https://docs.google.com/forms/d/1xdwvmDBf-dHSrliFcBA9KL1egNNWD9tYxEjdQRzcqSg

Preduslovi za rad:

Poznavanje funkcija
Poznavanje struktura
Poznavanje rada sa standardnim ulazom/izlazom
Poznavanje argumenata komandne linije

Moguće je čuvati podatke programa nakon što se program završi koristeći datoteke. Na ovom predmetu će se koristiti tekstualne datoteke, čije ime se završava ekstenzijom .txt. Možemo napraviti novu datoteku i popuniti je kroz Visual Studio Code:

code primer.txt

Sadržaj datoteke ćemo učitati u program i obraditi ga. Kao primer uzmimo podatke o državama Južne Amerike:

Argentina 45 2.796
Brazil 216 8.51
Urugvaj 3 0.173
Peru 34 1.285
Kolumbija 52 1.142

Prva reč u svakom redu predstavlja ime države, potom je naveden broj stanovnika (u milionima) i površina države. Podatke ćemo učitati u niz struktura.

10.1. Funkcije za rad sa datotekom

Da bismo radili sa datotekom, potrebno je otvoriti i zatvoriti je unutar programa. Koristićemo funkcije fopen i fclose. Sve funkcije sem fopen će raditi sa pokazivačem na datoteku FILE *, sem fopen koji vraća adresu otvorene datoteke.

Kao argument funkciji fopen prosleđujemo string sa imenom datoteke koju želimo da otvorimo, a kao povratnu vrednost dobijamo pokazivač na datoteku FILE *, kojim ćemo raditi sa datotekom. Kada završimo rad, prosledićemo taj pokazivač funkciji fclose.

Datoteke će zarad jednostavnosti biti smeštene u istom direktorijumu u kom se nalazi program. Pored imena datoteke, funkcija fopen zahteva string koji sadrži režim otvaranja datoteke - r u slučaju da želimo da čitamo iz datoteke, w u slučaju da želimo da pišemo. Iako su samo po jedan karakter, režimi se prosleđuju kao string.

Ako pišemo u datoteku (otvorimo sa režimom w), automatski će se napraviti nova datoteka ako datoteka sa unetim imenom ne postoji. To se neće desiti pri čitanju (ne može čitanje iz nepostojeće datoteke), pa će funkcija fopen vratiti NULL, što može izazvati grešku pri izvršavanju programa. Zato je potrebno proveriti da li je datoteka uspešno otvorena, što možemo preko funkcije za otvaranje datoteka sa proverom:

FILE *safe_fopen(char *ime, char *rezim, int kod_greske)
{
    FILE *pf = fopen(ime, rezim);
    
    if (pf == NULL)
    {
        printf("Nije uspesno pronadjena datoteka.\n");
        exit(kod_greske);
    }
    
    return pf;
}

Ako datoteka nije pronađena, potrebno je završiti program i ispisati grešku. Funkcija exit služi za završavanje programa iz bilo koje funkcije, prosleđujemo joj kod greške. Završavanje programa u slučaju da se ne otvori datoteka je samo jedna od strategija za obradu ovog izuzetka, koju smo mi odlučili da koristimo.

Za čitanje i pisanje u datoteku koriste se fscanf i fprintf, kao i fgets i fputs. Pozivi fscanf i fprintf su slični pozivima scanf i printf, s tim što se prvo navodi pokazivač na datu datoteku. Učitavanje se odvija red po red, sve dok nije učitan poslednji red, u kom slučaju će funkcija fscanf vratiti vrednost EOF, koja označava kraj datoteke.

10.2. Primer programa

Primer programa koji učitava države, i ispisuje gustinu stanovništva za svaku državu u novu datoteku pod imenom gustina.txt:

 1#include <stdio.h>
 2#include <stdlib.h>
 3
 4#define MAX_IME 31
 5#define MAX_DRZAVA 10
 6
 7typedef struct drzava_st
 8{
 9    char drzava[MAX_IME];
10    int stanovnistvo;
11    float povrsina;
12} DRZAVA;
13
14FILE *safe_fopen(char *, char *, int);
15void ucitavanje(FILE *, DRZAVA *, int *);
16void ispisi_gustine(FILE *, DRZAVA *, int);
17
18int main()
19{
20    DRZAVA drzave[MAX_DRZAVA];
21    int n;
22    
23    FILE *ulazna, *izlazna;
24    ulazna = safe_fopen("primer.txt", "r", 1);
25    ucitavanje(ulazna, drzave, &n);
26    fclose(ulazna);
27    
28    izlazna = safe_fopen("gustina.txt", "w", 2);
29    ispisi_gustine(izlazna, drzave, n);
30    fclose(izlazna);
31
32    return 0;
33}
34
35FILE *safe_fopen(char *ime, char *rezim, int kod_greske)
36{
37    FILE *pf = fopen(ime, rezim);
38    
39    if (pf == NULL)
40    {
41        printf("Nije uspesno pronadjena datoteka.\n");
42        exit(kod_greske);
43    }
44    
45    return pf;
46}
47
48void ucitavanje(FILE *ulazna, DRZAVA *d, int *pn)
49{
50    int i = 0;
51    
52    while(fscanf(ulazna, "%s %d %f", d[i].drzava, &d[i].stanovnistvo, &d[i].povrsina) != EOF)
53    {
54        i++;
55    }
56    
57    *pn = i;
58}
59
60void ispisi_gustine(FILE *izlazna, DRZAVA *d, int n)
61{
62    int i;
63    for(i = 0; i < n; i++)
64    {
65        fprintf(izlazna, "%s %.2lf\n", d[i].drzava, d[i].stanovnistvo / d[i].povrsina);  
66    }
67}

Nakon izvršavanja, pojaviće se nova datoteka gustina.txt sa sledećim sadržajem:

Argentina 16.09
Brazil 25.38
Urugvaj 17.34
Peru 26.46
Kolumbija 45.53