Professional Documents
Culture Documents
Kao što je već spomenuto, datoteke se primarno koriste za trajno pamćenje podataka.
Osnovne operacije koje se obavljaju pomoću datoteka su spremanje i učitavanje podataka.
Spremanje podataka je postupak pri kojem program podatke iz svojih varijabli (tj. iz radne
memorije) zapisuje u datoteku. Stoga se spremanje podataka još zove i zapisivanje podataka.
Pri učitavanju (tj. čitanju) podataka, program podatke prebacuje iz datoteke u svoje varijable.
Kaže se da program učitava (ili čita) iz datoteke.
2. Nakon uspješnog otvaranja datoteke, slijedi obavljanje operacija nad njom. Program
zapisuje podatke iz memorije u datoteku ili učitava podatke iz datoteke u memoriju. Pri
zapisivanju i učitavanju, ostavljena je velika sloboda na koji način se to izvodi. Drugim
riječima, programer sam odlučuje koje će podatke program zapisivati (ili učitavati).
Posebno, pri učitavanju, nije nužno učitati cjelokupni sadržaj datoteke u radnu memoriju
(tj. u varijable programa).
3. Nakon što su svi podaci zapisani ili učitani potrebno je datoteku zatvoriti.
Ovo su bili osnovni koraci pri upotrebi datoteka. Datoteke se mogu upotrebljavati na razne
načine, ali osnovni princip rada ostaje isti: otvaranje datoteke, zapisivanje ili učitavanje
podataka, zatvaranje datoteke. Datoteka služi samo kao trajno "skladište" podataka. Da bi
program izveo bilo koje operacije nad podacima koji se nalaze u datoteci, on mora prethodno
te podatke učitati u varijable u radnu memoriju.
OTVARANJE DATOTEKE
Kao što je već spomenuto, otvaranje datoteke je pridruživanje konkretne datoteke objektu
određene klase. Sve ostale operacije nad datotekom se obavljaju preko te datoteke. Klasa čiji
se objekt pridružuje datoteci ovisi o tome za što će se datoteka upotrebljavati. Razlikujemo
dva slučaja: otvaranje datoteke za pisanje (tzv. izlazne datoteke) te otvaranje datoteke za
čitanje (tzv. ulazne datoteke). Datoteku otvaramo za pisanje kada želimo u nju nešto zapisati.
Slično, datoteka se otvara za čitanje ako želimo iz nje nešto pročitati. Bez obzira na način
upotrebe, ukoliko želimo koristiti datoteke u programu, potrebno je navesti slijedeću liniju na
početak programa:
#include <fstream.h>
ofstream fout("IZLAZ.TXT");
Način otvaranja datoteke za čitanje je sličan prethodnom načinu. Osnovna razlika je u tome
što se upotrebljava klasa ifstream:
ifstream fin("ULAZ.TXT")
Ovom naredbom je datoteka IZLAZ.TXT otvorena za čitanje i povezana sa objektom fin. Pri
otvaranju datoteke za čitanje, vrijede malo drugačija pravila:
1. Otvara se datoteka koja ima zadani naziv i nalazi se u mapi u kojoj se nalazi i
program. Ukoliko datoteka sa zadanim nazivom ne postoji, datoteka se neće otvoriti.
ZATVARANJE DATOTEKE
Datoteka se zatvara pozivom metode close objekta koji je povezan sa datotekom. Metoda ne
prima argumente. Npr. prethodno otvorene datoteke moguće je zatvoriti na slijedeći način:
fin.close();
fout.close();
Nakon obavljanja potrebnih operacija nad datotekama (čitanje ili pisanje), datoteke je uvijek
potrebno zatvoriti.
Moguće su situacije kada nakon gornjih naredbi datoteka neće biti uspješno otvorena. Pri
otvaranju datoteke za čitanje, to se može dogoditi ukoliko datoteka sa takvim nazivom ne
postoji. Pri otvaranju datoteke za pisanje mogući razlozi su pokušaj stvaranja datoteke na
zaštićenom disku (ili u zaštićenoj mapi), pokušaj stvaranja datoteke na disku na kojem nema
slobodnog prostora itd. Vrlo je važno provjeriti prije same upotrebe datoteke je li ona uspješno
otvorena. To se može napraviti na slijedeći način:
ifstream fin("ULAZ.TXT");
Važno je uočiti da se unutar uvjeta if naredbe nalazi samo !fin, pri čemu je fin naziv objekta
koji je pridružen datoteci ULAZ.TXT. Ovo je moguće zato što klasa ifstream (isto vrijedi i za
klasu ofstream) ima ugrađen operator konverzije u cijeli broj. Taj operator se brine da vrati
vrijednost takvu da ovako napisan if uvjet bude ispunjen samo ako datoteka nije uspješno
otvorena. Provjera se obavlja na isti način za izlazne datoteke.
Kao što je već ranije spomenuto, osnovne operacije (praktički i jedine) koje se mogu izvoditi
nad datotekama su čitanje i pisanje. U nastavku će biti opisano izvođenje tih operacija.
#include <fstream.h>
main()
{
cout << "Unesite broj: ";
int x;
cin >> x;
ofstream fout("BROJ.TXT");
if (!fout)
cout << "Datoteka nije uspjesno otvorena!\n";
else
{
fout << x << endl; // pisanje u datoteku
fout.close();
}
return 0;
}
Sadržaj datoteke je moguće provjeriti pomoću bilo kojeg tekstualnog editora (npr. Notepad).
Upotrebom operatora << nad objektom koji je pridružen datoteci, moguće je pisati u datoteku
na identičan način kao što se zapisuje na zaslon. Čak će i sam sadržaj "izgledati" kao što bi
izgledao da se na isti način zapisivao na zaslon.
Važno je napomenuti da se ovakvo zapisivanje može obaviti samo pomoću objekta tipa
ofstream (tip za izlazne datoteke). Sam objekt može imati bilo koji naziv (naravno, potrebno je
poštivati pravila o nazivu varijabli u jeziku C++). Posebno je zgodno nazvati objekt fout zbog
očite sličnosti sa objektom cout.
#include <fstream.h>
main()
{
ifstream fin("BROJ.TXT");
int x;
if (!fin)
cout << "Datoteka ne postoji!\n";
else
{
fin >> x; // učitavanje iz datoteke u varijablu
return 0;
}
Ovakvo učitavanje moguće je jedino sa objektima tipa ifstream. Kod ovakvih datoteka je zbog
sličnosti sa cin objektom zgodno odgovarajući objekt nazvati fin. Važno je uočiti da u ovom
programu korisnik ništa ne unosi: podatak se čita iz datoteke!
Rad prethodna dva programa možete provjeriti tako da prvo pokrenete program koji zapisuje
u datoteku, a zatim program koji učitava iz (te iste) datoteke.
UPOTREBA DATOTEKA U KONKRETNIM PROGRAMIMA
float ocjene[MAX];
int nocjena;
Također se pretpostavlja da je u main funkciji već napisan dio koda koji popunjava to polje
(ugrađene su opcije dodaj, promijeni i sl.). Slijedi funkcija koja sprema sadržaj u datoteku
OCJENE.TXT:
void spremi()
{
ofstream fout("OCJENE.TXT");
if (!fout)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
fout << nocjena << endl; // zapisivanje broja elemenata
fout.close();
}
}
Pretpostavimo da je funkcija spremi pozvana u trenutku kada se u polju nalaze tri ocjene: 1,2
i 3. Izgled datoteke OCJENE.TXT bi bio slijedeći:
3
1
2
3
Prvo se nalazi broj 3 (čime se označava da slijede tri broja). Nakon toga slijede sami elementi
polja (tj. ocjene 1, 2 i 3).
Uočite da nije slučajno pređeno u novi red svaki put nakon što se zapiše neki podatak. Kada
ne bi bilo tako izgled datoteke za prethodni primjer bi bio:
3123
Program bi pri učitavanju pogrešno pročitao ovakav podatak kao 3123 (umjesto 3, 1, 2, 3).
Učitavanje ovakve datoteke je sada lagano obaviti. Prvo je potrebno učitati broj ocjena n, te
zatim n ocjena:
void ucitaj()
{
ifstream fin("OCJENE.TXT");
if (!fin)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
fin >> nocjena;
// ucitavanje n elemenata
for (int i = 0;i < nocjena;i++)
fin >> ocjene[i];
fin.close();
}
}
Prilikom rada sa datotekama (posebno prilikom čitanja), potrebno je znati da postoji nešto što
se zove trenutna pozicija. Prilikom čitanja, trenutna pozicija (u datoteci) je zapravo mjesto
otkuda će slijedeća naredba čitanja krenuti sa čitanjem podatka. Podatak se čita sve dok se
ne dođe do znaka razmaka, tabulatora ili prelaska u novi red. Pri otvaranju datoteke, trenutna
pozicija je na samom početku datoteke. Nakon svakog čitanja, ona se pomiče prema kraju
datoteke. Zahvaljujući tome, funkcija spremi radi točno onako kako bi i trebala. Nakon što se
pročita broj nocjena, trenutna pozicija u datoteci se pomiče na slijedeći podatak (tj. prvu
ocjenu), nakon toga na drugi podatak itd.
Nakon ovakvog učitavanja, prethodni sadržaj polja je izgubljen tj. podaci iz radne memorije su
zamijenjeni podacima iz datoteke. Naravno, da bi ovakvo učitavanje radilo datoteka
OCJENE.TXT mora postojati, a njen sadržaj mora biti zapisan na način na koji radi funkcija
spremi. Još jednom je važno istaknuti da programer sam određuje na koji način će podaci biti
zapisani pri čemu mora voditi računa o tome da na taj način i učitava podatke iz datoteke.
Posebno je važno pri spremanju podataka paziti da se iza svakog podatka zapiše jedan od
znakova koji označavaju kraj podatka (novi red, razmak ili tabulator tj. "\n", " " ili "\t").
Pokušaj učitavanja iza kraja datoteke (tj. nakon što su pročitani svi podaci) je moguć. Takav
pokušaj će učitati podata ("učitani" podatak će naravno biti neispravan jer se ne nalazi u
datoteci) i program "neće shvatiti" da nešto nije u redu. Za provjeru je li učitani podatak zaista
u datoteci, može se upotrijebiti metoda eof koja se nalazi u klasi ifstream. Metoda eof vraća
vrijednost različitu od nule (tj. logička istina u jezicima C i C++) ukoliko je pokušano čitanje iza
kraja datoteke. Inače je povratna vrijednost jednaka nuli. Upotrebom te metode, moguće je
prepraviti način zapisivanja podataka u datoteku. Sada više nije potrebno zapisivati broj
elemenata polja, već je dovoljno zapisati samo elemente. Funkcija koja učitava će čitati jedan
po jedan podatak sve dok ne dođe do kraja datoteke (provjeru će obaviti pozivom metode
eof). Slijede funkcije koje na ovaj način zapisuju i učitavaju polje ocjena:
void spremi()
{
ofstream fout("OCJENE.TXT");
if (!fout)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
// zapisivanje svakog elementa
for (int i = 0;i < nocjena;i++)
fout << ocjene[i] << endl;
fout.close();
}
}
void ucitaj()
{
ifstream fin("OCJENE.TXT");
if (!fin)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
nocjena = 0; // početni broj ocjena
fin.close();
}
}
Nova verzija učitavanja će ispravno učitati podatke iz datoteke samo onda kada su oni
zapisani na način kako je to napravljeno u novoj verziji funkcije spremi.
SPREMANJE I UČITAVANJE OBJEKATA
Spremanje i učitavanje upotrebom operatora << i >> radi za jednostavne tipove podataka (int,
float, stringovi, ...). Ukoliko želimo spremiti ili učitati cijeli objekt tada je potrebno svaki član
objekta zapisati ili učitati posebno. Slijedeći primjer ilustrira spremanje i učitavanje jednog
objekta. Pretpostavimo da je deklariran slijedeći objekt:
class Radnik
{
private:
float placa;
char ime[20], prezime[20];
public:
float get_placa() const;
void set_placa(float n_placa);
if (!fout)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
fout << rad.get_placa() << " ";
fout << rad.get_ime() << " ";
fout << rad.get_prezime() << endl;
fout.close();
}
}
if (!fin)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
char ime[20],prezime[20];
float placa;
fin.close();
rad.set_placa(placa);
rad.set_ime(ime);
rad.set_prezime(prezime);
}
}
Funkcija spremi zapisuje podatke u datoteku član po član. Pri tome se iza prva dva člana
zapisuje znak razmaka, a iza trećeg znak za prelazak u novi red. Ovo je napravljeno iz čisto
"estetskih" razloga (radi lakšeg čitanja datoteke u tekstualnom editoru). Ne bi bilo razlike (za
funkciju ucitaj) kada bi se iza svakog podatka (tj. člana) zapisao prelazak u novi red.
Funkcija ucitaj mora imati tri privremene lokalne varijable u koje će učitati podatke iz datoteke.
Nakon toga se pozivom pripadnih set metoda, podaci premještaju u objekt. Ovdje je argument
funkcije deklariran kao referenca, kako bi se promjena u objektu (tj. smještanje učitanih
podataka u objekt) "vidjele" i u pozivnoj funkciji.
Sada je vrlo jednostavno napraviti i spremanje čitavog polja objekata. Funkcije će raditi sa
poljem objekata ranije deklarirane klase Radnik. Pretpostavlja se da su deklarirane globalne
varijable:
Radnik radnici[MAX];
int nradnik;
void spremi()
{
ofstream fout("RADNICI.TXT");
if (!fout)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
// zapisivanje svakog objekta
for (int i = 0;i < nradnik;i++)
{
fout << radnici[i].get_placa() << " ";
fout << radnici[i].get_ime() << " ";
fout << radnici[i].get_prezime() << endl;
}
fout.close();
}
}
void ucitaj()
{
ifstream fin("RADNICI.TXT");
if (!fin)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
nradnik = 0; // početni broj radnika
// učitavanje svih elemenata iz datoteke
while(1)
{
char ime[20],prezime[20];
float placa;
nradnik++;
}
}
fin.close();
}
}
Dosadašnji programi koji su zapisivali podatke u datoteku su prilikom pisanja uvijek prebrisali
prethodni sadržaj datoteke. Podsjetimo se, prilikom otvaranja datoteke za pisanje, prethodni
sadržaj datoteke se automatski prebriše. Prilikom otvaranja datoteke za pisanje, moguće je
posebno navesti da se prethodni sadržaj datoteke ne prebriše. Ovo se radi na slijedeći način:
ofstream fout("IZLAZ.TXT",ios::app);
Konstruktoru objekta se šalje još jedan argument (ios::app), čime se određuje da prethodni
sadržaj datoteke neće biti obrisan. Kaže se da se datoteka otvara u tzv. append (dodaj)
modu. Pri tome vrijede slijedeća pravila:
BINARNE DATOTEKE
Dosada opisane datoteke su tzv. tekstualne datoteke. Najčešće se koriste kada je u njih
potrebno zapisati podatke na način razumljiv čovjeku. Nedostatak je u tome što veličina
zapisanog podatka ovisi o samom podatku koji se zapisuje. Pretpostavimo da se u neku
tekstualnu datoteku zapisuju samo cijeli brojevi. Recimo da su zapisana 3 broja: 5, 10 i 123.
Izgled datoteke je slijedeći:
5
10
123
Očito je da veličina jednog zapisa (broja) ovisi o samom podatku. Ovo se može pokazati kao
veliki nedostatak. Recimo da se u datoteci nalazi puno brojeva i mi prilikom čitanja želimo
pročitati samo jedan, točno određen broj (npr. 100. po redu u datoteci). Nas dakle zanima
samo 100. broj u datoteci. Da bi smo pročitali taj podatak, moramo prvo pročitati prethodnih
99 brojeva, što je ilustrirano u slijedećoj funkciji:
return x;
}
}
Ovo često može predstavljati problem. Ukoliko program u datoteci čuva veću količinu
podataka, ovo rješenje se pokazuje sporim. Zbog ovakvog načina pristupa, tekstualne
datoteke se često nazivaju slijedne ili sekvencijalne datoteke.
Ovaj nedostatak je moguće riješiti upotrebom tzv. binarnih datoteka. U binarnu datoteku,
podatak se doslovno zapisuje onako kako ga računalo "vidi" u radnoj memoriji. Npr. tip
unsigned short int zauzima u memoriji dva byte-a. Prilikom zapisivanja jednog broja tipa
unsigned short int, zapisati će se uvijek točno dva byte-a. Drugim riječima, veličina podatka u
datoteci ovisi o tipu tog podatka, a ne o njegovom sadržaju. Sadržaj binarne datoteke više
nije razumljiv čovjeku (tj. ne može se provjeriti pomoću tekstualnog editora), a podaci su
unutra zapisani točno onako kako "izgledaju" u radnoj memoriji.
Slijedeći program ilustrira osnovni način kako se može zapisati jedan podatak u binarnu
datoteku:
main()
{
ofstream fout("BROJ.DAT",ios::binary + ios::out);
if (!fout)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
int x = 256;
fout.close();
}
return 0;
}
Načelni princip rada ostaje isti: otvaranje, operacije nad datotekom, zatvaranje. Prva razlika je
pri samom otvaranju. Potrebno je poslati dodatni argument konstruktoru čime se navodi da se
datoteka otvara kao binarna i to za pisanje (ios::binary + ios::out). Uočite da se zastavica
ios::out može proslijediti kao argument samo ako je riječ o ofstream klasi.
Osnovna razlika je ipak u samom zapisivanju. Naime, kod binarnih datoteka zapisivanje se
obavlja pomoću metode write. Prvi argument je adresa podatka (tj. varijable) koja se
zapisuje. U našem primjeru adresa podatka je naravno &x. Prije same adrese se uvijek
navodi operator konverzije (const char *). Drugi argument je veličina podatka u byte-ovima.
Taj podatak se može dobiti pomoću operatora sizeof. Naime, sizeof prima kao argument tip
podatka, a vraća veličinu tog tipa u byte-ovima tj. koliko varijabla tog tipa zauzima byte-ova u
memoriji.
Ovdje je važno primjetiti da prvi argument uvijek mora biti adresa varijable. Ukoliko želimo
zapisati neki točno određen broj, moramo ga prvo zapisati u privremenu varijablu, a zatim tu
varijablu zapisati u datoteku (gornji program radi točno tako).
Osnovna pravila ostaju ista: ukoliko datoteka s tim nazivom ne postoji, stvoriti će se nova
datoteka, u suprotnom, sadržaj postojoće datoteke će biti prebrisan.
main()
{
ifstream fin("BROJ.DAT",ios::binary + ios::in);
if (!fin)
cout << "Ne mogu otvoriti datoteku!";
else
{
int x;
fin.read((char *) &x,sizeof(int));
fin.close();
return 0;
}
Rad prethodna dva programa možete provjeriti tako da prvo pokrenete program koji piše u
datoteku, a zatim program koji čita iz datoteke.
ZAPISIVANJE POLJA U BINARNU DATOTEKU
float ocjene[MAX];
int nocjena;
Također se pretpostavlja da je u main funkciji već napisan dio koda koji popunjava to polje
(ugrađene su opcije dodaj, promijeni i sl.). Funkcija koja obavlja zapisivanje ima slijedeći
oblik:
void pisi()
{
ofstream fout("OCJENE.DAT",ios::binary + ios::out);
if (!fout)
cout << "Ne mogu otvoriti datoteku!\n";
else
{
for (int i = 0;i < nocjena;i++)
fout.write((const char *) &ocjene[i], sizeof(float));
fout.close();
}
}
Važno je uočiti prilikom pisanja, da se šalje adresa i-tog elementa polja (&ocjene[i]). Drugi
argument je sizeof(float) jer su elementi polja tipa float.
Na sličan način se može napraviti i čitanje polja iz binarne datoteke. Potrebno je u petlji čitati
podatak po podatak sve dok se ne dođe do kraja datoteke. Metoda eof radi isto kao i kod
tekstualnih datoteka:
void citaj()
{
ifstream fin("OCJENE.DAT",ios::binary + ios::in);
if (!fin)
cout << "Ne mogu otvoriti datoteku!";
else
{
nocjena = 0;
while(1)
{
fin.read((char *) &ocjene[nocjena],sizeof(float));
if (fin.eof() != 0)
break;
else
nocjena++;
}
fin.close();
}
}
Princip je očito isti kao i kod čitanja polja iz tekstualne datoteke. Čita se jedan po jedan
podatak, pri čemu se nakon svakog podatka provjerava je li ispravno pročitan (tj. jesmo li
došli do kraja datoteke).
Uočite da je prvi argument adresa objekta klase Radnik, dok je drugi argument veličina
jednog objekta te klase.
Slično se može obaviti i čitanje polja objekata klase Radnik iz ovako zapisane datoteke:
nradnik = 0;
while(1)
{
fin.read((char *) &radnici[nracnik],sizeof(Radnik));
if (fin.eof() != 0)
break;
else
nradnik++;
Važno je napomenuti da je ovakvo pisanje i čitanje objekta moguće samo ako objekti ne
sadrže pokazivačke članove ili reference.
Sada se možemo pozabaviti ključnom prednosti binarnih datoteka. Već je ranije spomenuto
da je nedostatak tekstualnih datoteka u tome što ne omogućava direktan pristup podatku. U
binarnim datotekama je to moguće jer su svi podaci jednake veličine. Npr. ukoliko u datoteku
upisujemo objekte klase Radnik, tada je svaki podataka veličine sizeof(Radnik). Drugim
riječima moguće je odrediti za određeni podatak koliko byte-ova je u datoteci zapisano prije
njega. Npr. prije 100. objekta klase Radnik je u datoteku zapisano 99 objekata što je zapravo
99 * sizeof(Radnik) byteova. Općenito se može reći da je prije i-tog podatka zapisano (i - 1) *
sizeof(tip) byteova. Pri tome tip označava tip podatka koji se zapisuje u datoteku. Ovdje je
pretpostavljeno brojanje od jedan (podatak na početku datoteke je 1. element a ne 0.).
Zahvaljujući tom strogo matematičkom odnosu, moguće je lako direktno pristupiti točno
određenom podatku.
SEEKG METODA
Ovom metodom moguće je promijeniti trenutnu poziciju datoteke. Podsjetimo se, trenutna
pozicija je ona pozicija otkuda će krenuti slijedeća naredba čitanja. Metoda prima jedan
argument - novu poziciju (broj byte-ova od početka datoteke). Slijedeća funkcija učitava 100.
objekt iz datoteke RADNICI.DAT u koju su zapisani objekti klase Radnik:
void citaj_100()
{
ifstream fin("RADNICI.DAT",ios::binary + ios::in);
if (!fin)
cout << "Ne mogu otvoriti datoteku!";
else
{
Radnik x;
fin.read((char *) &x,sizeof(Radnik));
fin.close();
Uočite kako se do 100. objekta dolazi u dvije naredbe. Prva naredba je "skok" na 100.
poziciju, nakon čega slijedi učitavanje samo jednog (100.) objekta.
Što se događa ukoliko pokušamo postaviti trenutnu poziciju iza kraja datoteke? Pozicija se
neće promijeniti, ali nećemo dobiti dojavu o pogrešci. Ovo je moguće zaobići ako se uvede
kontrola pozicije. Prije same promjene pozicije, potrebno je vidjeti je li nova pozicija ispravna
(tj. nalazi li se nova pozicija u datoteci). Npr. za datoteku RADNICI.DAT možemo odrediti
koliko objekata je zapisano u njoj, te paziti da nikad ne mijenjamo poziciju iza zadnjeg
objekta. Da bi odredili broj objekata u datoteci, moramo odrediti veličinu datoteke te taj broj
podijeliti sa veličinom jednog podatka (tj. sa veličinom jednog objekta).
Veličina datoteke se određuje u dva koraka. Prvo, potrebno je postaviti trenutnu poziciju na
sam kraj datoteke, što se radi posebnim pozivom metode seekg:
fin.seekg(0,ios::end);
Nakon ove naredbe, trenutna pozicija je na samom kraju datoteke. Sada se može pozvati
tellg metoda. Ta metoda vraća trenutnu poziciju (broj byte-ova od početka datoteke):
Ovime smo dobili veličinu datoteke u byte-ovima. Još je jedino potrebno izračunati koliko je
objekata zapisano u datoteci:
long br_radnika()
{
ifstream fin("RADNICI.DAT",ios::binary + ios::in);
return rez;
}
}
Upotrebom binarnih datoteka, posebno tehnike direktnog pristupa, moguće je smanjiti utrošak
radne memorije. Pretpostavimo da radimo program za evidenciju zaposlenih u nekoj firmi.
Ukoliko je riječ o velikoj firmi, broj radnika može biti prilično velik te je moguće da jednostavno
nemamo dovoljno prostora u radnoj memoriji za tako veliko polje (podsjetimo se da je prostor
radne memorije relativno mali). Moguće rješenje je upotreba datoteke za čuvanje svih
podataka. Svi objekti (u ovom slučaju radnici) se nalaze u datoteci, a u radnoj memoriji
čuvamo samo podatke o radniku čije podatke trenutno obrađujemo (npr. pri ispisu, promjeni ili
sl.). U slijedećim koracima će biti objašnjeno kako se može realizirati ovakav program.
ORGANIZACIJA PODATAKA
Podaci će biti organizirani u objektima klase Radnik. Svi podaci će biti pohranjeni u datoteci
RADNICI.DAT, a u radnoj memoriji će se uvijek nalaziti najviše jedan objekt (onaj koji se
trenutno obrađuje). Slijedi deklaracija klase radnik:
class Radnik
{
private:
float placa;
char ime[20], prezime[20];
public:
float get_placa() const {return placa;};
void set_placa(float n_pl) {placa = n_pl;};
Ovo je pojednostavljeni oblik klase radnik. Pri tome je u klasu radi jednostavnosti ugrađena
metoda ispisi koja na zaslon ispisuje sve podatkovne članove.
Dodavanje novog člana je vrlo jednostavno za realizirati. Potrebno je prvo učitati podatke sa
tipkovnice te popuniti jedan objekt klase Radnik. Nakon toga taj objekt je potrebno dodati na
kraj datoteke. Zadnji korak se vrlo jednostavno može napraviti ukoliko se datoteka otvori za
dodavanje. Sjetimo se, pri otvaranju datoteke za dodavanje, ukoliko ta datoteka ne postoji,
stvoriti će se nova datoteka. Ukoliko ta datoteka već postoji, njen sadržaj će ostati očuvan, a
svaki novi upis u datoteku će biti dodan na kraj datoteke:
void dodaj_novi()
{
char ime[20],prezime[20];
float pl;
Radnik pom;
pom.set_placa(pl);
pom.set_ime(ime);
pom.set_prezime(prezime);
if (!fout)
cout << "Greska! Ne mogu otvoriti datoteku!\n";
else
{
// zapisi novi objekt na kraj datoteke
fout.write((const char *) &pom, sizeof(Radnik));
fout.close();
Prilikom otvaranja, drugi argument je ios::binary + ios::out + ios::app. Ovo se čita na slijedeći
način: otvori datoteku za pisanje kao binarnu, novi podaci će se dodati na kraj datoteke (klasa
naravno mora biti ofstream).
Ovo je već ranije obrađeno. Dovoljno je čitati jedan po jedan objekt iz datoteke sve dok se ne
dođe do samog kraja:
void ispisi_sve()
{
cout << "\n\n\n";
if (!fin)
cout << "Greska! Ne mogu otvoriti datoteku!\n";
else
{
while(1)
{
Radnik pom;
if (fin.eof() != 0)
break;
else
pom.Ispisi();
}
fin.close();
}
}
Broj objekata u datoteci se računa prema ranije objašnjenom principu. Prvo je potrebno
"skočiti" na kraj datoteke, pročitati trenutnu poziciju (tj. veličinu datoteke u byte-ovima) te
podijeliti sa veličinom jednog zapisa (objekta klase Radnik):
if (!fin)
return 0;
else
{
// skoci na kraj
fin.seekg(0,ios::end);
fin.close();
return rez;
}
}
ISPIS ODREĐENOG ELEMENTA
void ispisi_jednog()
{
unsigned long n;
unsigned long x;
if (x > n)
cout << "Nema toliko radnika u bazi!\n";
else if (x < 1)
cout << "Pogresan unos!\n";
else
{
ifstream fin("RADNICI.DAT", ios::binary + ios::in);
if (!fin)
cout << "Greska! Ne mogu otvoriti datoteku!\n";
else
{
Radnik pom;
// procitaj podatak
fin.read((char *) &pom,sizeof(Radnik));
fin.close();
}
}
}
Prije obavljanja promjene, potrebno je usvojiti još par novih tehnika rada sa datotekama. Ideja
same promjene je da se prvo pročita podatak koji se mijenja, nakon toga se ispišu trenutne
vrijednosti za taj podatak (u ovom primjeru se ispisuju trenutni podaci radnika), korisnik unosi
nove podatke te se novi podaci prepisuju preko starih. Datoteku je potrebno istovremeno
otvoriti za čitanje i za pisanje. U tu svrhu se koristi klasa fstream:
Drugi argument se čita na slijedeći način: datoteka se otvara kao binarna, za čitanje i pisanje,
ukoliko datoteka ne postoji neće se stvoriti nova datoteka, a ukoliko datoteka postoji njen
sadržaj neće biti prebrisan. Sada je moguće obavljati i operacije čitanja i operacije pisanja
nad datotekom. Načelno se postupak promjene podataka svodi na slijedeće korake:
void promjena_radnika()
{
unsigned long n;
n = br_obj();
unsigned long x;
cin >> x;
if (x > n)
cout << "Nema toliko radnika u bazi!\n";
else if (x < 1)
cout << "Pogresan unos!\n";
else
{
fstream f("RADNICI.DAT", ios::binary + ios::in +
ios::out + ios::nocreate);
if (!f)
cout << "Greska! Ne mogu otvoriti datoteku!\n";
else
{
// citanje podataka
Radnik pom;
f.seekg((x - 1) * sizeof(Radnik));
f.read((char *) &pom,sizeof(Radnik));
pom.set_placa(pl);
pom.set_ime(ime);
pom.set_prezime(prezime);
f.close();
}
}
}
GLAVNI PROGRAM
Sada je moguće napraviti jednostavni glavni program koji povezuje ove funkcije u cjelinu:
void nacrtaj_izbornik()
{
cout << "\n\n\n";
cout << "1. Dodavanje novog elementa\n";
cout << "2. Ispis svih elemenata\n";
cout << "3. Ispis tocno jednog radnika\n";
cout << "4. Promjena podataka radnika\n";
cout << "X. Kraj rada\n\n";
cout << "Odabir: ";
}
main()
{
while(1)
{
nacrtaj_izbornik();
char ch;
cin >> ch;
if (ch == 'X' || ch == 'x')
break;
switch(ch)
{
case '1':
dodaj_novi();
break;
case '2':
ispisi_sve();
break;
case '3':
ispisi_jednog();
break;
case '4':
promjena_radnika();
break;
}
}
return 0;
}
DATOTEKE.............................................................................................................................................1
OSNOVE RADA S DATOTEKAMA............................................................................................1
OTVARANJE DATOTEKE..................................................................................................2
DATOTEKE ZA PISANJE (IZLAZNE DATOTEKE)............................................2
DATOTEKE ZA ČITANJE (ULAZNE DATOTEKE)............................................2
ZATVARANJE DATOTEKE...................................................................................2
PROVJERA JE LI DATOTEKA USPJEŠNO OTVORENA..................................3
OBAVLJANJE OSNOVNIH OPERACIJA NAD DATOTEKAMA.............................................3
JEDNOSTAVNO PISANJE U DATOTEKU........................................................................3
JEDNOSTAVNO ČITANJE IZ DATOTEKE........................................................................4
UPOTREBA DATOTEKA U KONKRETNIM PROGRAMIMA.................................................5
POSEBNO ZAPISIVANJE BROJA ELEMENATA POLJA..................................................5
ZAPISIVANJE POLJA BEZ ZAPISIVANJA BROJA ELEMENATA....................................6
SPREMANJE I UČITAVANJE OBJEKATA..................................................................................8
SPREMANJE I UČITAVANJE POLJA OBJEKATA............................................................9
DODAVANJE NA KRAJ DATOTEKE........................................................................................10
BINARNE DATOTEKE...............................................................................................................10
JEDNOSTAVNO ZAPISIVANJE U BINARNU DATOTEKU.............................................11
JEDNOSTAVNO ČITANJE IZ BINARNE DATOTEKE....................................................12
ZAPISIVANJE POLJA U BINARNU DATOTEKU...........................................................13
ČITANJE POLJA IZ BINARNE DATOTEKE....................................................................13
PISANJE I ČITANJE OBJEKATA U BINARNE DATOTEKE...........................................14
DIREKTAN PRISTUP PODACIMA...........................................................................................14
SEEKG METODA.............................................................................................................15
ODREĐIVANJE VELIČINE DATOTEKE.........................................................................15
ČUVANJE PODATAKA UZ MINIMALNI UTROŠAK RADNE MEMORIJE.........................16
ORGANIZACIJA PODATAKA..........................................................................................16
DODAVANJE NOVOG ELEMENTA.................................................................................17
ISPIS SVIH ELEMENATA U DATOTECI.........................................................................17
RAČUNANJE BROJA OBJEKATA U DATOTECI............................................................18
ISPIS ODREĐENOG ELEMENTA...................................................................................19
PROMJENA PODATAKA ZA JEDAN ELEMENT............................................................19
GLAVNI PROGRAM.........................................................................................................21