nav_dugme codeBlog codeBlog
  • početna Početna stranica
  • Sačuvani članci Sačuvani članci
  • Članci
     (spisak)
  • Kontakt
Povratak na vrh stranice

Info & povezani članci Info o članku - dugme

Info

trejler_sat Datum objave: 08.01.2020.

trejler_olovka Poslednja izmena: 14.05.2021.

trejler_dokument Jezici: C
Python

trejler_teg_narandzasti Težina: 8/10

C
Python
datoteke
obrada teksta
niske
teorija
saveti
zanimljivosti

Povezani članci

Šablonske niske u programskim jezicimaOperacije sa bitovima u programskom jeziku CRegularni izrazi - napredna pretraga tekstaArgumenti komandne linijeStrukture podatakaKako napraviti dobru lozinkuUvod u PythonIzbor prvog programskog jezikaASCII, Unicode i UTF - Predstavljanje znakova na računarimaKako napraviti syntax highlighter
Svi članci
In C++ it’s harder to shoot yourself in the foot, but when you do, you blow off your whole leg.
Bjarne Stroustrup

Operacije sa tekstualnim datotekama u programskim jezicima C i Python

Facebook LinkedIn Twitter Viber WhatsApp E-mail
zoom_plus zoom_minus bookmark
početna > Članci > Teorija

Uvod

U informacionim sistemima, termin "datoteka" označava imenovanu kolekciju srodnih podataka koji se trajno čuvaju u računarskoj memoriji (i pri tom takva kolekcija podataka ima praktičan značaj za korisnike računara, i može se otvarati i obrađivati u različitim programima).

Datoteke predstavljaju važnu i zanimljivu pojavu, a sposobnost obavljanja operacija sa datotekama - pre svega sa tekstualnim datotekama - predstavlja prilično važan deo osnovne pismenosti svakog programera.

Da bismo se što bolje upoznali sa operacijama čitanja iz tekstualnih datoteka i pisanja u tekstualne datoteke, u nastavku ćemo razmotriti osnovnu teoriju i nekoliko primera iz programskih jezika C i Python - dva popularna jezika koji se koriste u fazi učenja.

Programski kodovi iz C-a (koji se tiču učitavanja datoteka i obrade podataka), ni u kom slučaju ne deluju "skroz jednostavno" iz perspektive mlađih programera koji se sa takvim kodovima prvi put sreću (ali verujemo i nadamo se da neće biti ni teški za razumevanje :)), dok, sa druge strane, primeri iz Python-a (kao što je i inače svojstveno ovom popularnom skriptnom jeziku), naizgled deluju jednostavnije ....

Operacije sa tekstualnim datotekama u C-u

Za početak ćemo se usmeriti na teži zadatak, * tj. sagledaćemo nekoliko primera iz programskog jezika C, ali, što je još važnije, upoznaćemo se sa opštim principima za pristup tekstualnim ** datotekama (koji važe u gotovo svim programskim jezicima).

* Postoje (naravno) dobri razlozi zašto smo izabrali baš takav redosled. :)

** Operacije sa binarnim datotekama biće tema za neki drugi članak, pri čemu (u praktičnom smislu), poznavanje operacija sa tekstualnim datotekama prestavlja neophodan preduslov za upoznavanje sa obradom binarnih datoteka.

Drugim rečima, binarne datoteke nisu komplikovanije sam po sebi, već se problematika svodi na to da je format podataka 'komplikovaniji sam po sebi'.

U tekstualnim datotekama koje su formatirane po ASCII standardu (a upravo takvim datotekama ćemo se baviti u nastavku), svaki bajt predstavlja jedan znak.

U binarnim ('netekstualnim') datotekama, grupe bajtova predstavljaju različite funkcionalne elemente (primer: u grafičkim formatima, prvih nekoliko grupa bajtova tipično predstavlja 'zaglavlje' u kome su navedene osnovne informacije o slici (dimenzije u pikselima, 8 ili 16 bita po RGB kanalu i sl), a 'grupe bajtova' u nastavku predstavljaju podatke o pojedinačnim pikselima).

Dakle, u oba slučaja (da se nadovežemo na konstatacije iz uvodnog poglavlja), u pitanju su kolekcije srodnih podataka koji se trajno čuvaju u računarskoj memoriji (praktično: na hard diskovima, SSD drajvovima i sl), u oba slučaja format podataka je od značaja za korisnike računara, i pri tom se podaci mogu učitavati u različite programe i obrađivati na prikladan način.

Čitanje tekstualne datoteke (osnovni primer)

Najjednostavniji primer otvaranja tekstualne datoteke i čitanja sadržaja znak-po-znak, uz ispis na standardni izlaz (tj. 'konzolu' ili 'terminal'), podrazumeva sledeće korake:

  • otvaranje datoteke u režimu čitanja (preko funkcije fopen)
  • if grananje preko koga se ispituje da li je otvaranje datoteke proteklo na predviđeni način
  • do-while petlju preko koje se znaci čitaju iz datoteke i ispisuju na ekran
  • zatvaranje datoteke
		
#include<stdio.h>
#include<stdlib.h>

void main()
{
	int i, n;
	FILE *f;

	/* otvaranje datoteke */
	f = fopen("datoteka_1.txt", "r");

	/* provera pristupa */
	if (f == NULL) {
		printf("Greška pri čitanju!\n");
		return;
	}

	/* čitanje postojećih znakova iz datoteke */
	do {
        c = fgetc(f);
        printf("%c", c);

    } while (c != EOF);

	/* zatvaranje datoteke */
	fclose(f);
}
		
	
Slika 1. - Program za čitanje sadržaja datoteke bez prethodnog učitavanja u memoriju (implementiran u C-u).

Po želji, primere možete kopirati u odgovarajuće razvojno okruženje (i potom kompajlirati i pokretati programe).

Iako primer ne deluje komplikovano, osvrnućemo se na određene delove; pre svega, na samu funkciju za otvaranje fopen (uz komentare u kodu):

		
f = fopen("datoteka_1.txt", "r");

/*
	Argumenti funkcije fopen su:
	ime datoteke koju treba otvoriti
	i režim pristupa, koji može biti:

	"r" - čitanje (read)
	"w" - upisivanje (write)
	"a" - dodavanje na postojeći
	      sadržaj (append)
*/
		
	
Slika 2. - Funkcija fopen (ukoliko je pristup navedenoj datoteci moguć, funkcija vraća pokazivač na datoteku).

Dodatna napomena: u gotovo svim jezicima, u funkciji za učitavanje datoteke postoji parametar tj. argument preko koga se bira režim pristupa datoteci (još jednostavnije, kada ubuduće, u naredbama za rad sa datotekama, vidite niske "r", "w" i "a" - znaćete o čemu se radi). :)

Ukoliko otvaranje datoteke nije proteklo u skladu sa očekivanjima, bitno je odreagovati na odgovarajući način (što je u gornjem primeru rešeno preko jednostavnog if grananja):

		
if (f == NULL)

/*
	Ukoliko datoteka nije
	uspešno kreirana ili otvorena
	(u zavisnosti od toga koju operaciju
	pokrećemo), pokazivač na datoteku će
	imati sistemsku vrednost NULL,
	a u primeru koji koristimo (budući da
	izvršavanje programa zavisi od sadržaja
	datoteke), prekinućemo izvršavanje
	programa
*/
		
	
Slika 3. - Provera pokazivača f (ukoliko ne možemo da pristupimo datoteci, prekinućemo izvršavanje, a u nekom drugom slučaju mogli bismo preduzeti i dodatne korake).

Sama operacija čitanja datoteke (onako kako je prikazano u primeru) .....

		
do {
	c = fgetc(f);
    printf("%c", c);

} while (c != EOF);
		
	
Slika 4. - Čitanje sadržaja datoteke, znak-po-znak, preko funkcije fgetc.

.... vrlo je jednostavna, ali, sa sobom povlači i pitanje: kako (inače) postupiti ako sadržaj datoteke treba sačuvati u memoriju i obraditi na određeni način (a ne samo "ispisati u konzoli")?!

Odgovor sledi u narednom odeljku.

Na kraju, veoma (!) je bitno da ne zaboravimo da zatvorimo datoteku:

		
fclose(f);
		
	
Slika 5. - Za zatvaranje datoteke koristi se funkcija fclose.

U suprotnom, može doći do grešaka u izvršavanju programa koji pišemo, ili do grešaka usled pokušaja drugih programa da naknadno pristupe datoteci koja je otvorena.

Složeniji primer: učitavanje tekstualne datoteke u RAM

Primer koji smo prethodno videli, sasvim uspešno prikazuje osnovnu funkcionalnost mehanizma za čitanje podataka iz datoteke, ali, ako je predviđeno da se tekst (nadalje) obrađuje u programu, prvo je potrebno kopirati podatke iz datoteke - u operativnu memoriju.

Neki zadaci mogu se (naravno) obaviti jednostavnim čitanjem "znak-po-znak" i upisom u (drugu) izlaznu datoteku, ali, tipično se sadržaj datoteke prvo mora učitati u memoriju.

Manje datoteke (čija krajnja veličina ne prelazi nekoliko desetina redova i sl), mogu se učitavati preko statičkih nizova ....

		
char sadrzaj_male_datoteke[1000];
		
	
Slika 6. - Inicijalizacija statičkog niza znakova (rešenje koje se praktično može koristiti samo za baferovanje manjih datoteka).

.... i pogotovo je čest slučaj da se manje pomoćne niske čuvaju kao statički nizovi znakova ....

		
char pomocni_bafer[20];
		
	
Slika 7. - Inicijalizacija statičkog niza znakova je vrlo uobičajen način za čuvanje manjih pomoćnih niski.

.... međutim, za čuvanje sadržaja iole većih datoteka, u praksi se gotovo uvek koristi dinamička alokacija memorije, najčešće preko funkcije malloc ("memory allocation"), uz prethodno očitavanje veličine datoteke.

Funkcija main, u implementaciji koju ćemo razmotriti, ima sledeći sadržaj:

		
// Priprema
char *naziv  = "datoteka_1.txt";
FILE *f      = fopen(naziv, "r");
int velicina = ocitavanje_velicine_datoteke(f);
char *buffer = ucitavanje_datoteke(f, velicina);

// Obrada
printf("%s", buffer);

// Zatvaranje
fclose(f);
free(buffer);
		
	
Slika 8. - Implementacija programa za učitavanje sadržaja datoteke u RAM (implementacija pojedinačnih funkcija sledi u nastavku).

U prikazanom kodu, posebnu pažnju treba obratiti na sledeće detalje:

  • veličina datoteke očitava se preko zasebne funkcije (radije nego da takav kod bude zapisan unutar funkcije main)
  • za smeštaj podataka potrebno je obezbediti "bafer" (blok memorije, odgovarajuće veličine, kome se pristupa preko pokazivača)
  • kreiranje bafera takođe se obavlja preko zasebne funkcije (u kojoj se poziva funkcija malloc)

Obrada ponovo podrazumeva prost ispis sadržaja datoteke (ali, u primeru koji razmatramo, najvažnija operacija je učitavanje, i stoga nećemo nepotrebno skretati pažnju čitalaca komplikovanijim primerima obrade). *

Na kraju, potrebno je (ponovo) obratiti posebnu pažnju na oslobađanje pokazivača koji je korišćen za datoteku (f), i takođe je potrebno obratiti pažnju na oslobađanje pokazivača koji je vezan za tekstualni bafer koji je alociran preko funkcije malloc (promenljiva buffer).

* Naravno, sa učitanim baferom mogu se obavljati i mnogo zanimljivije operacije.

Takođe, bitno je znati da bafer koji nastaje kao rezultat izvršavanja funkcije malloc nije inicijalizovan nulama, i stoga (kao što ćemo videti uskoro u primeru), potrebno je da niske koje se upisuju u bafer budu terminisane znakom '\0' ("null znak") - i to je nešto što moramo sami uraditi ("neće se desiti automatski").

Doduše, postoji i drugačiji ('automatizovani') metod terminisanja niski, međutim, uglavnom se koristi pristup koji je opisan u prethodnom pasusu (više o svemu, u narednim poglavljima).

Preostaje da implementiramo funkcije za očitavanje veličine datoteke i alokaciju bafera.

Funkcija ocitavanje_velicine_datoteke vraća:

  • veličinu datoteke u bajtovima - u slučaju da je predat pokazivač koji uredno pokazuje na datoteku (ili)
  • vrednost -1 - ukoliko se preda pokazivač sa vrednošću NULL

(Objašnjenja su u komentarima.)

		
int ocitavanje_velicine_datoteke(FILE *f)
{
	// na početku ("za svaki slučaj"),
	// proverava se da li je pokazivač
	// na datoteku uredno prosleđen.
	if (f == NULL) return -1;

	// Ukoliko je pokazivač na datoteku
	// uredno prosleđen, "prošetaćemo" ga
	// do kraja datoteke.
	fseek(f, 0L, SEEK_END);

	// Pozicija pokazivača predstavlja
	// veličinu datoteke (u bajtovima).
	int velicina = ftell(f);

	// Da bi pokazivač (f), i dalje mogao
	// da se koristi (u drugim funkcijama),
	// vratićemo ga na početak.
	fseek(f, 0L, SEEK_SET);

	return velicina;
}
		
	
Slika 9. - Implementacija funkcije za utvrđivanje veličine datoteke.

Funkcija ucitavanje_datoteke vraća:

  • pokazivač na blok memorije u koji je učitan sadržaj datoteke - ukoliko nema "ispada" (ili)
  • sistemsku vrednost NULL - ukoliko dođe do greške
		
char *ucitavanje_datoteke(FILE *f, int velicina)
{
	// Ukoliko nije prosleđen ispravan
	// FILE pokazivač, prekida se izvršavanje.
	if (f == NULL) return NULL;

	// Pri kreiranju bafera, potrebno je dodati
	// jedan bajt (+1), zarad terminišućeg znaka '\0'.
	char *buffer = (char*) malloc(velicina + 1);

	// Ukoliko funkcija malloc ne vrati pokazivač,
	// takođe je potrebno prekinuti izvršavanje.
	if (buffer == NULL) return NULL;

	// Ostatak je sličan prethodnom primeru učitavanja,
	// s tim što se ovoga puta znakovi šalju u bafer
	// (odnosno, ne šalju se "na ekran").

	int  i = 0;
	char c;

	do {
		c = fgetc(f);
		*(buffer + i) = c;
		++i;

	} while (c != EOF);

	// Takođe, potrebno je terminisati nisku
	// (pre povratka u pozivajuću funkciju):
	*(buffer + i - 1) = 0;

	return buffer;
}
		
	
Slika 10. - Implementacija funkcije za učitavanje datoteke (sa baferovanjem).

Promena veličine bafera (realloc)

Pri radu sa tekstom neretko se javlja potreba za promenom veličine postojećeg bafera i, ukoliko bafer (koji je nastao izvršavanjem funkcija malloc, calloc * ili realloc**) - treba proširiti (što je najtipičniji slučaj), ili ako bafer treba suziti, najčešće se koristi funkcija realloc ("re-allocation"):

		
buffer = (char*) realloc(buffer, nova_velicina_u_bajtovima);
		
	
Slika 11. - Osnovni poziv funkcije realloc, bez naknadne provere bafera.

Kao što vidimo, ideja je jednostavna (sintaksa takođe), međutim, budući da operacija promene veličine bafera može proći neuspešno (bar ponekad), potrebno je postupati vrlo pažljivo.

Ukoliko se naredba koju smo videli na prethodnoj slici ne izvrši uspešno - pokazivač buffer će dobiti vrednost NULL, ali - bafer će zapravo biti očuvan u memoriji (neće biti oslobođen).

Situacija je, u najmanju ruku .... 'čudna': bafer ostaje u memoriji, mi ostajemo bez mogućnosti pristupa baferu - i jasno je da je u pitanju nepovoljan ishod. :)

* Funkcija calloc (kojoj će biti posvećen zaseban odeljak u nastavku), idejno je slična funkciji malloc.

** Funkcija realloc može se pozivati proizvoljan broj puta, to jest, postoje situacije u kojima je postojeći bafer mogao nastati i 'prethodnim proširivanjem' (preko funkcije realloc).

Vraćamo se na primer ....

Nepovoljna situacija (koju smo opisali pre napomene), može se izbeći uz 'razmišljanje unapred':

		
char *buffer_p = (char*) realloc(buffer, nova_velicina);

if (buffer_p != NULL) {
	buffer = buffer_p;
}
else {
	// obrada greške:
	// potencijalno zaustavljanje programa
	// (čije je izvršavanje zavisilo od
	// uspešne realokacije bafera) i sl.
}
		
	
Slika 12. - Korišćenje funkcije realloc - uz proveru novog bafera.

Za početak (kao što vidimo u gornjem primeru), uputno je da se povratna vrednost funkcije realloc za svaki slučaj poveže sa (novim) pomoćnim pokazivačem:

  • ako sve 'prođe po planu', "novi bafer" će zapravo biti proširena verzija dotadašnjeg bafera (posle čega se novi bafer lako može povezati sa starim/prvobitnim pokazivačem) i - u praksi - ukoliko postoji dovoljno slobodne memorije, funkcija realloc gotovo uvek uredno vraća ('novi') bafer
  • ukoliko dođe do greške, pokazivač buffer_p će dobiti vrednost NULL, ali (što je važnije), stari bafer (i sav sadržaj) - biće očuvan(i) - što ostavlja mogućnost da se pravilno odreaguje, u skladu sa okolnostima

Bez daljeg zalaženja u 'hipotetičke situacije', primetićemo samo da se najčešće dešava da dalje izvršavanje programa (ipak) zavisi od 'uredno uvećanog bafera', što praktično znači da ukoliko pomoćni pokazivač dobije vrednost NULL - treba zaustaviti dalje izvršavanje programa.

Sa druge strane, ponekad, stari/'dotadašnji' bafer (koji je očuvan), može biti iskorišćen.

U svakom slučaju, planirali smo unapred (stvorili smo 'opcije za nastavak') - i možemo reagovati u skladu sa okolnostima.

Kao što vidimo, programski jezik C nije nimalo sklon tome da od programera sakrije šta se dešava "ispod haube". :)

Upis u tekstualnu datoteku

Upis u datoteku podrazumeva (za početak), otvaranje datoteke uz argumente: "w" ili "a" ("write" - prepisivanje preko postojeće datoteke; "append" - dodavanje sadržaja na kraj postojeće datoteke).

		
#include<stdio.h>
#include<stdlib.h>

void main()
{
	int i, n;
	FILE *f;
	f = fopen("rezultat.txt", "w");

	if (f == NULL) {
		printf("Greška!\n");
		return;
	}

	printf("N = ");
	scanf("%d", &n);

	for (i = 0; i <= n; i++) {
		fprintf(f, "%d\n", rand());
	}

	printf("Rezultat je upisan u datoteku rezultat.txt");
	fclose(f);
}
		
	
Slika 13. - Program za generisanje n pseudoslučajnih brojeva koji se smeštaju u datoteku "rezultat.txt".

Bitni "sigurnosni elementi" iz prethodnih primera:

  • grananje preko koga se proverava da li je datoteka uspešno otvorena
  • obavezno zatvaranje datoteke nakon obavljanja operacija

.... i dalje su prisutni.

Sam upis u datoteku obavlja se preko funkcije fprintf ("file printf"), koja, u odnosu na "običnu" funkciju printf, ima dodatni parametar (prvi po redu), koji predstavlja pokazivač na datoteku.

Što se tiče sadržaja koji se upisuje u datoteku (u konkretnom primeru) ....

		
printf("N = ");
scanf("%d", &n);

for (i = 0; i <= n; i++) {
	fprintf(f, "%d\n", rand());
}
		
	
Slika 14. - Deo koda (sa prethodne slike), koji se tiče generisanja sadržaja koji se upisuje u datoteku (sadržaj je n slučajnih brojeva).

.... u pitanju je n nasumično generisanih celobrojnih vrednosti.

Ostale funkcije za obavljanje operacija sa memorijskim blokovima

Osim funkcija malloc i realloc (sa kojima ćete se sretati u najvećoj meri), u programskom jeziku C koristi se još i nekolicina drugih funkcija za rad sa memorijskim blokovima (najčešće zarad obrade teksta, ali i u mnogim drugim okolnostima).

U nastavku, osvrnućemo se na neke od takvih funkcija.

Funkcija calloc

Ukoliko je za određeni 'programerski zahvat' potreban bafer koji je inicijalizovan nulama, može se koristiti funkcija calloc ('contiguous allocation'), koja se od funkcije malloc razlikuje po sledećim svojstvima:

  • svaki bajt je inicijalizovan vrednošću 0 (što nije slučaj sa baferom koji je inicijalizovan preko funkcije malloc)
  • pri inicijalizaciji se predaju dva argumenta - broj elemenata (strukture zarad koje se rezerviše memorijski prostor), i veličina pojedinačnog elementa

U praksi, bafer (koji će biti korišćen za tekst), inicijalizuje se preko funkcije calloc na sledeći način:

		
char *buffer = (char*) calloc(broj_znakova, sizeof(char));
		
	
Slika 15. - Alokacija bafera preko funkcije calloc.

Međutim, iako naizgled deluje kao 'idealna zamena za funkciju malloc', može se reći da funkcija calloc nije toliko zastupljena u programskim kodovima kao funkcija malloc, iz sledećih razloga:

  • ako nije obavezno inicijalizovati ceo bafer nulama, postavlja se pitanje u vezi sa vremenom koje se nepotrebno troši na upisivanje nula u memorijske adrese *
  • ako jeste potrebno inicijalizovati ceo bafer nulama, '(ne)pisana pravila programiranja u C-u', nalažu da inicijalizaciju programer treba da obavi sam ('ručno')

* Prosto upisivanje vrednosti u memorijske adrese, svakako je nezahtevna operacija (po pitanju vremenskog zauzeća procesora), ali, jasno je takođe da se alokacija bez naknadne inicijalizacije (malloc), ipak obavlja brže od alokacije sa naknadnom inicijalizacijom (calloc).

Funkcija memcpy

Ukoliko je potrebno kopirati određeni broj bajtova iz jednog bafera u drugi, tipično * se koristi funkcija memcpy:

		
memcpy(pokazivac_2, pokazivac_1, broj_pozicija);

// pokazivac 1   - memorijska lokacija, unutar
//                 niske #1, od koje počinje kopiranje
// pokazivac 2   - memorijska lokacija, unutar
//                 niske #2, od koje počinje upis
// broj_pozicija - broj bajtova koje je
//                 potrebno kopirati
// niska #1 - niska čiji sadržaj se kopira
// niska #2 - niska u koju se upisuje
//            (delimičan ili potpun)
//            sadržaj niske #1
		
	
Slika 16. - Šema izvršavanja funkcije memcpy.

* Kada kažemo "tipično", mislimo na to da se zadatak takođe može rešiti i upotrebom specijalizovanih funkcija iz "domaće radinosti" i sl, ali - "tipično" - kopiranje bajtova iz jednog bafera u drugi, obavlja se preko funkcije memcpy.

Pre svega, funkcija memcpy često se koristi za inicijalizaciju memorijskih blokova koji su definisani preko funkcija malloc ili calloc ....

		
char *buffer = (char*) calloc(32, sizeof(char));
char *niska  = "You shall not pass!";

memcpy(buffer, niska, strlen(niska));
		
	
Slika 17. - Primer inicijalizacija bafera preko funkcije memcpy.

.... a ako se pitate zašto se ne upotrebljava samo središnja naredba ....

		
char *niska = "You shall not pass!";
		
	
Slika 18. - Primer inicijalizacije string konstante (što nije način za inicijalizaciju bafera).

.... podsetićemo se na to da je u pitanju inicijalizacija 'string konstante' (to jest, u pitanju je niska koja se ne može naknadno menjati).

Pogledajmo još jedan primer:

		
char *niska_1 = "matematikom";
char *niska_2 = "Ko se to bavi metafizikom?!";

char *buffer_1 = calloc(32, sizeof(char));
char *buffer_2 = calloc(32, sizeof(char));
memcpy(buffer_1, niska_1, strlen(niska_1));
memcpy(buffer_2, niska_2, strlen(niska_2));

char *pokazivac_1 = buffer_1;
char *pokazivac_2 = buffer_2 + 15;

memcpy(pokazivac_2, pokazivac_1, strlen(niska_1));
		
	
Slika 19. - Primer izvršavanja funkcije memcpy.

Preko prvog argumenta, pokazivac_2, određeno je da upis u nisku, koja se referencira preko pokazivača buffer_2, počinje od 16. znaka.

		
buffer_2: "Ko se to bavi metafizikom?!";
                         ^
                         char *pokazivac_2;
                         // buffer_2 + 15
		
	
Slika 20. - Šematski prikaz pozicije, unutar niske "buffer_2", na koju pokazuje pokazivač "pokazivac_2".

Za pravilan prikaz programskog koda (u gornjem odeljku i narednim odeljcima sa programskim kodom), potrebno je da rezolucija ekrana omogućava prikaz 45 ili više znakova u jednom redu (ili, 'u prevodu', najbolje bi bilo da članak posmatrate na desktop monitoru). :)

Drugi argument, pokazivac_1, određuje da kopiranje znakova iz niske, koja se referencira preko pokazivača buffer_1, počinje (praktično) od 1. znaka.

		
buffer_1 = "matematikom";
            ^
            char *pokazivac_1;
		
	
Slika 21. - Šematski prikaz pozicije, unutar niske "buffer_1", na koju pokazuje pokazivač "pokazivac_1".

Treći argument, strlen(niska_1), određuje da će ceo sadržaj prve niske biti kopiran u drugu nisku. *

Posle izvršavanja, niska #2 ima sledeći sadržaj: "Ko se to bavi matematikom?!".

* Naravno, da ne bi došlo do prekoračenja memorijskog prostora koji je dodeljen drugoj niski, u gornjem primeru smo udesili da niska #1 i poslednja reč u drugoj niski imaju istu dužinu.

Funkcija memset

Ukoliko je potrebno velikom brzinom upisati niz bajtova u susedne memorijske lokacije (unutar određenog bafera), tipično se koristi funkcija memset:

		
memset(adresa, vrednost_za_upis, broj_pozicija)
		
	
Slika 22. - Šema izvršavanja funkcije memset.

Da pojasnimo parametre funkcije:

  • adresa - pokazivač na memorijsku lokaciju od koje počinje upis (očekuje se da pripada uredno inicijalizovanom baferu)
  • vrednost - celobrojna promenljiva, koja praktično predstavlja znak koji će biti upisan u svaku memorijsku adresu
  • broj_pozicija - ukupan broj bajtova u koje će (počevši od lokacije koja je definisana preko prvog parametra, tj. argumenta), biti upisan znak (koji je definisan kao drugi argument)

U sledećem primeru ....

		
char *buffer_1    = calloc(32, sizeof(char));
char *niska_1     = "Funkcija memset je sjajna!";
memcpy(buffer_1, niska_1, strlen(niska_1));
char *pokazivac_1 = buffer_1 + 9;
int broj_pozicija = strlen("memset");
char novi_znak    = '*';

memset(pokazivac_1, novi_znak, broj_pozicija);
		
	
Slika 23. - Primer izvršavanja funkcije memset.

.... počevši od 10. pozicije ....

		
buffer_1: "Funkcija memset je sjajna!";
                    ^
                    char *pokazivac_1
                    // buffer_1 + 9
		
	
Slika 24. - Šematski prikaz pozicije, unutar niske "buffer_1", na koju pokazuje pokazivac "pokazivac_1".

.... u sledećih 6 pozicija (dužina niske "memset"), upisuje se znak '*'.

Rezultat izvršavanja je niska: "Funkcija ****** je sjajna".

Nažalost, ne znamo više koja je to funkcija - "sjajna" (ali sami smo krivi). :)

Funkcija memset često se koristi (kao što ste verovatno i sami naslutili), za 'brzinsko' resetovanje bafera.

Sledeći poziv:

		
memset(buffer, '\0', velicina_bafera);
		
	
Slika 25. - Resetovanje bafera preko funkcije memset.

.... upisaće vrednost '\0' u sve bajtove (u okviru bafera).

Operacije sa tekstualnim datotekama u Python-u

Ako je programski jezik C bio sklon da prikaže "sve" * što se dešava prilikom učitavanja datoteka i upisa u datoteke, sa Pythonom to nije slučaj, i može se reći da je obavljanje operacija sa datotekama znatno jednostavnije.

* U praksi, nije u pitanju "baš sve", odnosno, potrebno je razumeti da je proces učitavanja datoteka nezanemarljivo komplikovaniji čak i od onoga što se da videti u gornjim primerima (programski kod u C-u svakako nije previše 'udaljen' od asemblerskih tj. mašinskih naredbi, ali, nije ipak u pitanju "poklapanje 1:1").

Čitanje tekstualne datoteke

U Python-u, proces učitavanja datoteke je automatizovan (o detaljima implementacije brine interpretator), i stoga - u praktičnom smislu - nekoliko desetina linija koda i komentara koje smo videli u odeljku o učitavanju datoteka u C-u, staje u svega nekoliko linija koda:

		
# ----------------------------------------- #
f = open("ulaz.txt", "r")  # otvaranje
s = f.read()               # čitanje
f.close()                  # zatvaranje
# ----------------------------------------- #
		
	
Slika 26. - Primer Python skripte za učitavanje sadržaja datoteke.

Upis u tekstualnu datoteku

Programski kod koji se tiče upisa u datoteku, takođe je vrlo jednostavan:

		
# ----------------------------------------- #
s = "Tekstualni sadrzaj"
f = open("izlaz.txt", "w")  # otvaranje
f.write(s)                  # upis
f.close()                   # zatvaranje
# ----------------------------------------- #
		
	
Slika 27. - Primer Python skripte za upis tekstualnog sadržaja u datoteku.

Kao što verovatno i sami pretpostavljate, namerno nismo dodatno komentarisali primere iz Python-a, budući da su u pitanju isti principi sa kojima smo se prethodno susreli u C-u, a sam programski kod (kao što vidimo) - znatno je jednostavniji.

Za kraj ....

Poslednje poglavlje (u kome su opisane operacije sa datotekama u Python-u), predstavlja svojevrstan uvod u detaljniji osvrt na dva različita pristupa:

  • Python je pogodan za "brzinsko kreiranje" (sasvim funkcionalnih) programa i skripti
  • pisanje kompleksnijih primera u C-u, omogućava da se nauči mnogo više o tome kako programi i računari zapravo obrađuju podatke

Diskusijama na temu "C vs Python" bavićemo se u budućnosti (prva sledeća prilika biće uvodni članak o Python-u, a spremamo i članak o izboru prvog programskog jezika), međutim, već na osnovu prvog ("ne-baš-preterano-obimnog") članka o tekstualnim datotekama, mogu se doneti određeni zaključci.

Na primer, ako se neko prvo usmeri na komplikovaniji jezik (C) i komplikovanije primere, a tek posle (!) na skriptne jezike sa pojednostavljenom sintaksom (Python, JavaScript, Lua i sl), * mnogo više će naučiti i mnogo bolje će razumeti programske kodove kojima se bavi (bez obzira na to koji jezik će izabrati u nekom kasnijem trenutku).

* Sa druge strane (kao što smo nagovestili još na početku članka), 'obrnuti pristup' ("prvo Python, pa onda C") - nikako ne preporučujemo. :)

U svakom slučaju, sada možete (uz malo truda), čitati podatke iz datoteka i upisivati podatke u datoteke, a to su veštine koje veoma dobro dođu ....

Autor članka Nikola Vukićević Za web portal codeblog.rs
Napomena: Tekstovi, slike, web aplikacije i svi ostali sadržaji na sajtu codeblog.rs (osim u slučajevima gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog pismenog odobrenja autora.
© 2020-2026. Sva prava zadržana.
Facebook LinkedIn Twitter Viber WhatsApp E-mail
početna > Članci > Operacije sa tekstualnim datotekama u programskim jezicima C i Python
codeBlog codeBlog
Sajt posvećen popularizaciji kulture i veštine programiranja.
Napomena: Tekstovi i slike na sajtu codeblog.rs (osim u slučajevima, gde je drugačije navedeno) predstavljaju intelektualnu svojinu autora sajta codeblog.rs i zabranjeno je njihovo korišćenje na drugim sajtovima i štampanim medijima, kao i bilo kakvo drugo korišćenje u komercijalne svrhe, bez eksplicitnog odobrenja autora.
© 2020-2026. Sva prava zadržana.
Facebook - logo
Instagram - logo
LinkedIn - logo
Twitter - logo
E-mail
Naslovna
   •
Uslovi korišćenja
   •
Obaveštenja
   •
FAQ
   •
Kontakt