You are on page 1of 18

PRELEGERE XIII

PROGRAMAREA CALCULATOARELOR ŞI LIMBAJE DE


PROGRAMARE

Noţiunea de funcţie - continuare

VI. Funcţii recursive


O funcţie se poate activa nemijlocit pe ea însăşi de un număr nedeterminat de ori. În
această situaţie, funcţia se numeşte recursivă. O condiţie de oprire a acestui proces
trebuie prevăzută şi programată. În caz contrar, spaţiul afectat în stivă pentru depozitarea
adresei de retur, a valorilor parametrilor transmişi prin valoare, a informaţiilor despre
parametrii transmişi prin adresă, a rezultatelor intermediare şi al apelului de funcţie va
creşte rapid, riscînd depăşirea acestuia.
Etapele care apar în desfăşurarea fiecărui proces de activare a unei funcţii
recursive sînt acelaşi ca la activarea unei funcţii obişnuite, chiar dacă apelul de funcţie
este situat în definiţia unui bloc de funcţie şi nu în funcţia apelantă.
Aici, funcţia fact() realizează calculul factorialului conform formulei
n! = nx(n-1)!, care permite organizarea acesteia ca o funcţie recursivă .
/* Functie recursiva */
int fact(int n)
{ if (n == 0) return 1; else return n*fact(n-1); }
Funcţia 12.6.1
De fapt, determinarea expresiei n! cu ajutorul unei funcţii recursive fact() se face
pe baza formulei n! = fact(n)*fact(n-1)* … fact(1)*fact(0). Se observă că, sînt necesare
n apeluri ale funcţiei fact(), urmate de secvenţele corespunzătoare de reveniri.
Terminarea apelurilor recursive are loc atunci cînd se întîlneşte apelul fact(0). Calculul
factorialilor se face acum de la dreapta către stînga.
Dacă n este foarte mare, atunci este posibil depăşirea spaţiului din stivă cu
semnalarea unei erori corespunzătoare şi oprirea programului.
În consecinţă, simplitatea şi eleganţa funcţiilor recursive nu sînt motivaţii suficiente în
recomandarea utilizării lor în rezolvarea unor probleme.
Şi funcţia predefinită main() poate primi atributul de recursiv, după cum se
observă în exemplu de mai jos:
int main(void)
{ printf("\n Acest mesaj se tipareste la infinit");
main();
return 0; }
Funcţia 12.6.2

1
VI. Funcţii predefinite

Biblioteca limbajului oferă un sortiment bogat de funcţii predefinite, care sînt un


instrument eficace la doispoziţia utilizatorului în scrierea unor programe rapide,
elegante, uşor de urmărit şi de întreţinut. Acestea sînt destinate în utilizarea funcţiilor
matematice, prelucrarea valorilor ordinale, tratarea şirurilor de caractere, definirea,
actualizarea şi exploatarea fişierelor, folosirea pointerilor, alocarea dinamică a memoriei,
eficientizarea folosirii display-ului, dezvoltarea unei grafici diverse, etc.
În această secţiune vom prezenta contextul general de apelare a unor funcţii predefinite
care rezolvă operaţii privitoare la şirurile de caractere, la alocarea dinamică a memoriei,
la construirea de funcţii cu număr variabil de parametri şi la descrierea modulului
principal de program.

1) Cîteva funcţii din string.h


Declaraţiile de date împreună cu macrodefiniţiile şi prototipurile funcţiilor cu referire la
şiruri de caractere sînt grupate în fişierul antet string.h (extensia h provine de la cuvîntul
header) existent în directorul include. La începutul programelor, care utilizează funcţiile
enumerate mai jos şi nu numai, trebuie să se prevadă directiva #include <string.h>.
a) Întoarcerea numărului de caractere (lungimea) dintr-un şir de caractere se obţine
prin apelarea funcţiei predefinite strlen(). Antetul acestei funcţii are următorul format
general: unsigned long strlen(const char *sir). De exemplu, lungimea şirului evidenţiat
în figura 12.6.1.1 este 3, adică, strlen(şir)=3.
abc
sir
Figura 12.6.1.1
b) Duplicarea unui şir de caractere se realizează prin intermediul funcţiei
predefinite strdup(), al cărui antet de definiţie este următorul:
char *strdup(const char *sir1).
Atribuirea sir2 = strdup(sir1); poate fi reprezentată schematic ca în figura
12.6.1.2:
aabb11cc
sir1

sir2 aabb11c
Figura 12.6.1.2 c
c) Concatenarea (suma) a două şiruri de caractere este efectul apelului funcţiei
predefinite strat(). Linia de definiţie a acestei funcţii este următoarea:
char *strat(char *sir1, const char *sir2).
Şirul al doilea de caractere se memorează în spaţiul alocat lui sir1, după valoarea
acestuia, aşa cum se prezintă în figura 12.6.1.3.

sir1 sir1 sir2

sir2 sir2
Figura 12.6.1.3

2
d) Compararea a două şiruri de caractere se rezolvă, prin apelul fucţiei
predefinite strcmp(), după schema:
- dacă sir1 < sir2, atunci funcţia întoarce –1;
- dacă sir1 > sir2, atunci funcţia întoarce 1; în caz contrar se întoarce 0.
Antetul de definiţie al funcţiei strcmp() este următorul:
int strcmp(const char *sir1, const char *sir2).
e) Prin apelul funcţiei predefinită strcpy(), care are linia de definiţie
char *strcpy(char *sir1, const char *sir2), se realizează copierea şirului al doilea de
caractere în zona de memorie afectată lui sir1. Operaţia de copiere poate fi urmărită în
reprezentarea grafică din figura 12.6.1.2, schimbînd doar sensul săgeţii verticale.
f) Funcţia strchr() caută într-un şir de caractere prima apariţie a unui caracter dat şi
întoarce pointer la acea apariţie, după cum se observă în figura 12.6.1.4. Sintaxa generală
a antetului de definiţie este următoarea:
char *strchr(const char *sir1, int c).

c c
sir1

Figura 12.6.1.4
În programul 12.6.1.1 intervin funcţiile strlen() şi strcat(). Prin apelul funcţiei
strlen() se află lungimea tuturor şirurilor de caractere manipulate în program, iar
rezultatul returnat prin apelarea funcţiei strcat() este sir3, care reprezintă concatenarea
şirurilor de caractere sir1 cu sir2.
/*Utilizarea functiilor strlen() si strcat() */
#include <stdio.h>
#include <conio.h>
#include <string.h>
void main(void)
{ clrscr();
char *sir1 = “Programare”;
char *sir2 = “ in Turbo C++”;
char *sir3;
printf("\n Sirul %s are lungimea de %d caractere ", sir1, strlen(sir1));
printf("\n Sirul %s are lungimea de %d caractere ", sir2, strlen(sir2));
sir3 = strcat(sir1, sir2);
printf("\n Sirul %s are lungimea de %d caractere ", sir3, strlen(sir3));
getch(); }
Sirul Programare are 10 caractere
Sirul in Turbo C++ are 13 caractere
Sirul Programare in Turbo C++ are 23 caractere
Programul 12.6.1.1
Pentru a stîrni mai mult curiozitatea cititorilor, textul definiţiilor funcţiilor
strlen() şi strcat() se prezintă în cele ce urmează.

/* Functia strlen() */
unsigned long strlen(const char * sir)

3
{ register int n;
for(n=0; *sir != ‘\0’; ++sir) ++n; return n; }
Funcţia 12.6.1.1
După cum se observă, se utilizează registrele de memorie pentru alocarea
numărătorului de paşi, în vederea optimizării procesării structurii repetitive for. De cîte
ori se găseşte un caracter diferit de caracterul terminator de şir de caractere (\0) se
incrementează valoarea contorului n.
/* Functia strcat() */
char*strcat(char * sir1, const char *sir2)
{ register char *p = sir1;
while(*p) ++p;
while(*p++ = *sir2++); return sir1; }
Funcţia 12.6.1.2
Cu prima instrucţiune while se localizează terminatorul de sfîrşit de şir de
caractere pentru sir1, apoi cu a doua instrucţiune while se memorează, în continuare,
caracter după caracter, conţinutul lui sir2. Zona de memorie afectată lui sir2 nu suportă
nici o modificare.

2. Funcţii de alocare dinamică


Alocarea dinamică a variabililor, care nu au dimensiunea cunoscută sau la care
dimensiunea variază înte limite largi, prin intermediul funcţiilor predefinite din fişierul
antet stdlib.h.
a) Cele mai des folosite funcţii predefinite de alocare dinamică în limbajul C++ sînt
malloc() şi free(), care se aplică variabililor de tip static şi automatic. Prin apelul
funcţiei malloc(), necesarul de memorie pentru o astfel de variabilă se afectează în
momentul execuţiei programului, în zona Heap, după care se eliberează prin funcţia
free(), în vederea realocării lui în alt scop.
Formatul general al antetului de definiţie pentru funcţia malloc() este următorul: void
*malloc(unsigned numar_octeti);. Argumentul numar_octeti indică dimensiunea în
octeţi rezervată în zona de memorie Heap.
Rezultatul funcţiei este un pointer de tip void, care conţine adresa primului octet din
zona Heap afectată. În situaţia cînd memoria este insuficientă , funcţia întoarce un
rezultat NULL. Se reaminteşte că un pointer de tip void poate fi asignat oricărui alt tip
de pointer.
De exemplu, pentru alocare dinamică de memorie unui tablou unidimensional, raţional în
simplă precizie şi cu dimensiune precizată se pot utiliza următoarele două linii de
program: float *x;
x = (float ) malloc(5*sizeof(float));
x[0], x[1], x[2], x[3] şi x[4] sînt componentele tabloului x, care vor fi memorate în zona
Heap la adesele x, x+1, x+2, x+3 şi respectiv x+4. Precizarea spaţiului de memorie
pentru un anume tip de dată prin operatorul sizeof() şi utilizarea operatorului cast pentru
conversie de tip la atribuire
sînt recomandate în asigurarea portabilităţii programelor.
Dezafectarea unei alocării dinamice de memorie se face prin apelul funcţiei
free(), care are următorul format general: void free(void * nume_pointer);.

4
După prelucrarea tabloului x, din exemplul de mai sus, se impune eliberarea
memorie afectate acestuia, prin free(x);.
Prin programul 12.6.2.1 se citeşte, de la tastatura computerului, un număr
nedeterminat de valori reale nenule şi diferite între ele, a căror sumă se află şi se tipăreşte
pe ecranul monitorului în modulul principal de program.
/* Functii de alocare dinamica */
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
float *cit_nr(int );
void main(void)
{ clrscr();
int k, i = 0 ;
float suma, *t;
printf("\n Introduceti numarul valorilor intregi : ");
scanf("%d", &k);
t = cit_nr(k);
for ( suma =0., i = 0; i < k; ++i) suma +=*t+i; //Aflarea sumei
printf("\nSuma =%f", suma); //Tiparirea sumei
free(t); //Eliberarea memoriei
getch();
}
/* Functia de citire numere intregi */
float *cit_nr(int k)
{ int i; float *p;
if(!(p = (float *) malloc(k*sizeof(float)))) //Alocare dinamica
{ puts("memorie insuficienta");
return NULL; }
printf("\n Nr. sint : ");
for (i = 0; i < k; ++i) //Citirea sirului de valori
{ scanf("%f", p+i); printf(" \n ");}
return p; }
Introduceti numarul valorilor intregi : 2
Nr. sint: 4
5
Suma = 9.000000
Programul 12.6.2.1
Absenţa funcţiei free(t); în modulul principal are ca efect neeliberarea variabilei
dinamice t şi în consecinţă, zona de memorie Heap rămîne alocată şi neadresată pînă la
încheierea execuţiei programului. Absenţa argumentului local p în cadrul instrucţiunii
return are de asemenea efecte nedorite. Domeniul de vizibilitate al lui p este blocul
funcţiei cit_nr() şi dispariţia ei nu implică eliberarea zonei de memorie Heap alocate
dinamic şi adresată dinamic pînă la terminarea execuţiei acesteia. În modulul principal,
variabila dinamică t nu mai poate fi accesată deşi ea există în memorie şi nici eliberată
pentru că pointerul p dispare odată cu revenirea în funcţia main().

5
b) În plus, pentru limbajul C++ există mai multe variante de organizare a
spaţiului de memorie afectat variabililor dinamice, numite modele de memorie. Acestea
necesită cunoştinţe de programare avansată.

3. Funcţii cu număr variabil de parametri


Funcţiile cu un număr variabil de parametri ridică probleme legate atît de cunoaşterea
dimensiunii zonei de memorie afectată şi de tipul de dată utilizat în fixarea modului de
reprezentare a valorii de memorare, cît şi de referirea valorilor parametrilor actuali care
se transferă parametrilor actuali, în absenţa indentificatorilor acestora. Dintre funcţiile
predefinite cu un număr variabil de parametri des utilizate în programele prezentate pînă
în prezent se amintesc scanf() şi printf
Fişierul antet stdarg.h oferă un set de funcţii predefinite, care permite scrierea şi
folosirea funcţiilor utilizator cu un număr variabil de parametri. Astfel, cu funcţiile
va_start(), va_arg() şi va_end() se pot prelucra liste de parametri formali, pentru care nu
se cunoaşte numărul şi tipurile acestora.
Funcţia va_start() iniţializează variabila nume_var cu adresa primului parametru
formal din sublista variabilă. Formatul general al acestei funcţii este următorul:
void va_start(va_list nume_var, par_ultim_fix);, unde va_list este un tip de
pointer predefinit şi precizat în fişierul antet stdarg.h, către lista variabilă de parametri
formali, nume_var este numele simbolic al variabilei prin intermediul căreia se
adresează parametri formali, iar par_ultim_fix este indentificatorul ultimului parametru
din sublista fixă.
Funcţia va_arg() returnează valoarea parametrului indicat de variabila nume_var
şi atribuie acesteia adresa următorului parametru formal din sublista variabilă. Sintaxa
generală a funcţiei va_arg() este următoarea:
tip_par va_arg(va_list nume_var, tip_var);, unde tip_par este tipul
următorului parametru formal din sublista variabilă, care va preciza dimensiunea zonei de
memorie alocată variabilei nume_var. Valorile transferate sînt extinse după caz la
tipurile int şi double.
Funcţia va_end() încheie operaţia de extragere a parametrilor din sublista variabilă.
Formatul ei general este următorul: void va_end(va_list nume_var);.
Orice reluare a operaţiei de extragere a valorilor parametrilor din lista de variabile
respectivă trebuie să recurgă din nou, în ordinea precizată mai sus, la cele trei funcţii.
În programul 12.6.3.1 intervine funcţia suma(), care află suma unui număr
variabil de valori cu tipul double cu ajutorul funcţiilor va_start(), va_arg() şi va_end().
Lista de valori poate să conţină numai constante reale, variabile şi elemente de tablou, cu
restricţia că ultimul element al listei este zero. Deci, expresiile chiar cu rezultat de tip
double nu sînt admise în lista de valori. Prima neconcordanţă de tip, fără să fie
semnalată, generează oprirea prelucrărilor cu afişarea unui rezultat greşit .
/* Functii cu numar variabil de parametri */
#include <stdarg.h>
#include <stdio.h>
typedef double dbl;
void suma(char *msg, ...)
{ dbl arg, total =0.;
va_list v;

6
va_start(v, msg); /*initializarea variabilei v */
while(( arg = va_arg(v, dbl)) != 0.) {total += arg;}
printf (msg, total); va_end(v); }
/*incheie citirea parametrilor */
void main(void)
{ dbl x = 7., t[] ={4,5, 6,9, 0};
suma("suma =%lf\n", t[1], 4., t[3], 1., 3., 3., x, t[2], 0.) }
Suma = 38.000000
Programul 12.6.3.1

4. Funcţia main()
Funcţia main() determină modulul principal de program. Deci, cu ea se începe şi se
încheie execuţia oricărui program.
Prin funcţia main() se pot transmite informaţii necesare lansării în execuţie a
programului, cum ar fi: indentificatorul de recunoaştere al programului, opţiuni de
compilare şi date iniţiale. Aceste informaţii se numesc argumente din linia de comandă,
deoarece sînt editate de sistemul de operare în cadrul comenzii de lansare în execuţie a
programului respectiv.
Funcţia main() are următoarea structură:
[tip_de_dată] main([int argc, char *argv[]],[char *env[]])
{…………………..}
Atît tipul funcţiei cît şi lista de argumente sînt opţionale. În general, tipul de date
care precede cuvîntul rezervat main este void. Dacă funcţia main() întoarce un rezultat,
prin utilizarea instrucţiunii return, către programul apelant (adică, sistemul de operare),
atunci acesta este 0, care indică terminarea normală a execuţiei funcţiei, sau un cod de
eroare în caz de eşec.
Argumentele argv[] şi env[] sînt tablouri de pointeri. Parametrul argc fixează
dimensiunea tabloului argv[] şi trebuie să verifice condiţia argc≥1.
Linia de comandă cuprinde şiruri de caractere delimitate de spaţiu sau Tab.
Adresele acestora se memorează, în ordine, în tablourile de pointeri argv[] şi env[]. La
adresa argv[0] se înscrie numele programului. Numărul de adrese conţinute de tabloul
argv[] depinde de valoarea argumentului argc. În figura 12.6.4.1 se reprezintă grafic
conţinutul următoarei linii de comandă:
nume_program o lista de sase argumente, unde argc = 6.
“nume_progra
argv[0] m”
argv[1] “o”

argv[2] “lista“

argv[3] “de“

“sase“
argv[4]
“argumente

7
argv[5]
Figura 12.6.4.1
Următoarele şiruri de caractere vor fi înregistrate la adrese succesive în tabloul de
pointeri env[]. Aceste şiruri de caractere pot constitui valori pentru unii parametri ai
sistemului de operare, cum ar fi tipul prompt-ului tipărit la începutul unei linii de
comandă sau căi implicite de căutare în directoare. La ultima adresă din env[] se înscrie
valoarea NULL, care marchează sfîrşitul listei de opţiuni.
Programul următor vizualizează numele autorului în conformitate cu informaţiile
luate din linia de comandă: C:> Test Popescu I. Gheorghe <Enter>
/* Functia main()
#include <stdio.h>
#include <conio.h>
int main(argc, char* argv[]) { int i; printf("\nAutor :");
for (i = 1; i < argc; i++) printf(" %s ", argv[i]); return 0; getch(); }
Autor:
Popescu I. Ghoerghe
Programul 12.6.4.1

II. Noţiunea de structură

Definirea şi prelucrarea unui grup organizat de date neomogen se realizează prin


intermediul noţiunii de structură. Acelaşi lucru se denumeşte în alte limbaje de
programare cu articol sau înregistrare logică.
Unitatea elementară de informaţie din cadrul unei structuri se numeşte cîmp sau membru.
Atît natura cît şi ordinea unui cîmp într-o structură se fixează, de către utilizator, după
necesităţi. Numărul de cîmpuri care intră în componenţa unei structuri este nedeterminat.
Declararea şi iniţializarea structurilor şi tablourilor de structuri vor fi prezentate în
următoarele două secţiuni, iar diverse tipuri de structuri recursive, inclusiv exemple
adecvate, vor constitui esenţa secţiuni 3.7.5.

1. Declararea şi iniţializarea unei structuri


Un ansamblu format dintr-un număr nedeterminat de cîmpuri cu diverse tipuri defineşte o
structură. Referirea structurii se face printr-un nume simbolic stabilit de programator.
Sintaxa generală de declarare a unei structuri este următoarea:
struct nume_tip_structura
{ tip_cîmp_1 nume_cîmp_1;
tip_cîmp_2 nume_cîmp_2;
……………………………..
tip_cîmp_n nume_cîmp_n;
} lista_nume_var_tip_struct;
unde, struct este cuvînt cheie. Nume_tip_structura şi nume_cîmp_i, cu i= 1, n , sînt
identificatorii de recunoaştere pentru structură şi cîmpurilor componente structurii.
Identificatorii de cîmpuri trebuie să fie unici numai în tipul de structură în care au fost
definiţi. Tip_cîmp_i, cu i= 1, n , sînt tipuri de date, din cele descrise pînă în prezent, în
concordanţă cu natura cîmpurilor corespunzătoare. La rândul lui, tipul unui cîmp poate

8
să fie un tip de structură descris anterior, existînd astfel posibilitatea reprezentării şi
organizării a unor structuri de date complexe şi imbricate.
Lista de variabile de tip structură încheie construcţia sintactică de declarare a unei
structuri. Ca separator de variabile în cadrul listei se admite doar virgula..
Este posibil să absenteze, fie numele simbolic al structurii, fie lista de variabile. Prima
situaţie este mai rară, permiţînd declararea unei structuri anonime. Lista de variabile de
tip structură poate fi precizată ulterior, numai cînd identificatorul de recunoaştere al
structurii este cunoscut, printr-o construcţie de forma:
struct nume_tip_structura lista_var_tip_struct; .
O declarare mai simplă a variabililor de tip structură se poate obţine prin utilizarea
specificatorului typedef (vezi, secţiunea 2.4.2).
Referirea unui cîmp dintr-o variabilă de tip structură se realizează, prin calificare,
cu operatorul de selectare punct (.), astfel:
nume_variabila . nume_cîmp_i, unde i= 1, n . O astfel de referire poate să
intervină ca operand în expresii de atribuire sau ca parametru actual în funcţii de
citire/scriere.
Menţionăm doar că operatorul utilizat în aces caz este cel de selectare indirectă,
notat prin săgeată ().
Iniţializarea unei variabile de tip structură se obţine prin indicarea, în ordine,
pentru fiecare cîmp component cîte o valoare corespunzătoare, după cum urmează:
struct nume_tip_structura nume_variabila = (lista_valori);.
Separatorul de valori în lista_valori este tot virgula. Ulterior, aceste valori pot fi afişate,
modificate sau utilizate în diverse operaţii matematice.
În programul de mai jos se descrie o structură, de tip catalog şcolar, cu
următoarele cîmpuri componente: nume_prenume, adresa, discipline şi medie. A doua
linie din programul principal precizează valorea iniţială pentru variabila student de tip
structură catalog. Ulterior, o parte din aceste valori sînt schimbate prin instrucţiuni de
atribuire, şi anume notele de la a treia disciplină şi a patra şi media. Tipărirea valorii
variabilei student se face prin intermediul primelor trei funcţii de scriere printf() şi
succede un text de tip comentariu. Alte amănunte despre structura programului sînt date
la sfîrşitul secţiunii.
/* Declarare structura catalog */
#include <stdio.h>
#include <conio.h>
struct catalog
{ char nume_prenume[25];
char adresa[35];
int discipline[5];
float medie; };
void main(void)
{ clrscr();
struct catalog student={"Popescu Ioan","M Eminescu 30 Iasi",
10, 9, 9, 8, 10, 9.2};
student.discipline[2] = 10; student.discipline[3] = 10;student.medie=9.80;
printf("\n Variabila student este: ");
printf("\n%s %s", student.nume_prenume, student.adresa);

9
printf("\n %d %d %d %d %d %5.2f\n", student.discipline[0],
student.discipline[1], student.discipline[2], student.discipline[3],
student.discipline[4], student.medie);
struct catalog elev;
elev = student;
printf("\n Introduceti nume elev: \n");
gets(elev.nume_prenume);
elev.discipline[1]=10;
elev.medie=(elev.discipline[0]+elev.discipline[1]+elev.discipline[2]
+ elev.discipline[3]+elev.discipline[4])/5.;
printf("\n Variabila elev este: ");
printf("\n%s %s",elev.nume_prenume,elev.adresa);
printf("\n %d %d %d %d %d %5.2f", elev.discipline[0],
elev.discipline[1], elev.discipline[2], elev.discipline[3],
elev.discipline[4], elev.medie); getch(); }
Variabila student este:
Popescu Ioan M Eminescu 30 Iasi
10 9 10 10 10 9.80
Introduceti nume elev:
Ionescu Mihai
Variabila elev este:
Ionescu Mihai M Eminescu 30 Iasi
10 10 10 10 10 10.00
Programul 13.3.1.1
Variabilele, cu tipul structură identic, pot să apară în expresii de atribuire.
Asignările se realizează în ordinea apariţiei cîmpurilor din componenţa structurii
respective. În cazul programului de mai sus, atribuirea student = elev; este corectă şi se
întinde asupra tuturor cîmpurilor componente, adică: nume şi prenume, adresă, discipline
şi medie aritmetică. Numele şi prenumele elevului s-au citit de la tastatura calculatorului
cu ajutorul funcţiei gets(). După schimbarea notei la a doua disciplină, se recalculează
media aritmetică generală. Ultimele trei funcţii de scriere printf() afişează valoarea
variabilei elev.

III. FIŞIERE ŞI OPERAŢII DE INTRARE/IEŞIRE


1. Consideraţii generale
Transferul informaţiilor între calculator şi utilizator se face prin intermediul
dispozitivelor de intrare/ieşire. Printre acestea se pot enumera tastatura, monitorul,
imprimanta şi diverse unităţi de disc. De aceea, noţiunea de fişier trebuie discutată din
două puncte de vedere: fizic şi logic.
Pentru simplitate, dispozitivele fizice se denumesc prin vocabula fişier.
Modul de organizare, de consultare şi de tratare al fişierelor logice depinde de limbajul
de programare utilizat, de volumul informaţiilor prelucrate şi nu în ultimul rînd de
configuraţia sistemului de calcul. În limbajul C++ s-au gîndit două categorii de fişiere
logice (dispozitive de intrare/ieşire) logice: predefinite şi de tip utilizator.

10
Conţinutul logic al fişierelor de tip utilizator este determinat de noţiunea de structură, în
care se precizează numărul şi tipurile cîmpurilor componente structurii. Astfel, fişierul
logic de tip utilizator este un ansamblu organizat de un număr nedeterminat de structuri.
De fapt, fişierul logic de tip utilizator poate fi considerat un tablou multidimensional de
lungime variabilă, în care coloanele au diferite tipuri de date.
O situaţie specială o constituie fişierele predefinite (dispozitivele logice predefinite), care
sînt gestionate automat la lansarea în execuţie a programului de tip C. Fişierele
predefinite asociate claviaturii stdin şi stderr permit numai operaţii de citire, iar fişierele
predefinite asociate ecranului şi imprimantei stdout şi respectiv stdprn permit numai
operaţii de scriere. Fişierul predefinit auxiliar stdaux este de intrare/ieşire. Cele 5 cuvinte
cheie sînt constante pointer de tip file declarate în stdio.h.
Operaţiile de intrare/ieşire suportate de un fişier se efectuează cu ajutorul funcţiilor
predefinite care formează conţinutul fişierelor antet stdio.h şi conio.h. Aceste fişiere nu
fac parte integrantă din structura limbajelor de tip C, aşa cum se întîmplă cu toate
celelalte fişere cu extensia .h. De aceea, este necesar să se prevadă introducerea lor, în
antetul programului C++, prin directiva #include <nume_fişier_antet>.
Cînd apare o cerere de citire/scriere, sistemul de operare iniţiază o procedură de
control şi execuţie cu privire la transferul de informaţii, între dispozitivul fizic de
intrare/ieşire (ecran, tastatură, imprimantă, unitate de disc) şi dispozitivul logic de intrare/
ieşire similar, numit stream (flux).
Deschiderea stream-ului asociat unui anumit dispozitiv logic de intrare/ieşire
presupune, pe de o parte crearea indicatorilor standard de stare (variabile de tip File)
corespunzători şi poziţionarea acestora pe valorile iniţiale iar pe de alta, alocarea
necesarului de memorie zonei tampon (buffer-ului) prin intermediul căreia se realizează
efectiv transferul de informaţie. Rezultatul controlului transferului de informaţie de către
sistemul de operare se va reflecta în valorile actualizate ale indicatorilor de stare, care pot
fi aflate printr-o serie de funcţii standard, aşa cum vom vedea mai tîrziu. Închiderea
stream-ului va permite eliberarea memoriei afectate zonei tampon după golirea acesteia.
Stream-urile pot fi de tip text sau de tip binar.
Un stream de tip text transferă şiruri de texte organizate în linii de lungime
variabilă, în concordanţă cu numărul maxim de caractere gestionat de dispozitivul logic
de intrare/ieşire. Separarea liniilor se face prin caracterele de control sfîrşit de linie (0DH)
şi salt la linia următoare (0AH). Un astfel de stream se poate asocia cu stdin, stderr şi
stdout, din care numai stdin necesită o zonă tampon de memorie, cu dimensiunea egală
cu lungimea maximă a linei de text transferată.
Un stream de tip binar transferă o secvenţă de cifre binare fără o structură anume.
Completarea zonei tampon se face automat dacă şirul binar este mai scurt, avînd în
vedere că dimensiunea standard a zonei tampon este de 256 octeţi. Un stream de tip binar
se poate asocia cu stdprn şi stdaux.
În paragraful următor se prezintă prototipul funcţiilor predefinite care permit
operaţii de citire/scriere direct legate de dispozitivele logice predefinite de intrare/ieşire şi
care sînt mai des utilizate în structura programelor de tip C.

2. Funcţii predefinite pentru dispozitivele standard de intrare/ieşire


Funcţiile predefinite pentru tastatură permit realizarea operaţiei de citire a unor caractere
sub anumite condiţii: cu sau fără afişarea caracterelor cititite pe ecran şi cu sau fără

11
formatare lor. Funcţiile getch(), getche(), getchar() şi gets() permit o citire cu afişare, cu
excepţia lui getche() şi fără formatare. Atît tipărirea pe ecran, cît şi formatarea datelor
citite se realizează cu funcţia scanf().
Corespunzător, pentru operaţia de scriere fără formatare pe ecranul monitorului s-
au conceput funcţiile putchar() şi puts(), iar pentru cea cu formatare printf().
Lungimea şirului de intrare/ieşire diferă la ambele categorii de funcţii astfel
getch(), getche(), getchar() şi putchar() prelucrează un singur caracter şi nu are
importanţă în rest.

2.1 Operaţia de citire fără formatare


Introducerea unor caractere de la tastatură (dispozitivul de intrare standard stdin) poate fi
cu sau fără ecou (cu sau fără afişare pe ecran), iar funcţiile predefinite uzuale folosite în
acest scop se prezintă după cum urmează:
- int getch(void); Această funcţie aşteaptă apăsarea unei taste, după care afişează
pe ecran caracterul citit. Prototipul ei se află în fişierul conio.h. Aproape în toate
programele C prezentate în capitolele anterioare s-a utilizat această funcţie ca un
subterfugiu pentru obţinerea accesului nelimitat de către programator la pagina cu
rezultatele programului (pagina de ecran utilizator).
- int getche(void); Funcţia getche() este analoagă cu getch(), atît ca localizare,
cît şi ca efect, doar că returnarea caracterului furnizat de la tastatură este fără ecou;
- int getchar(void); Această funcţie transformă caracterul citit în întreg fără semn
numai după apăsarea tastei Enter (CR). În caz de eroare sau condiţie de sfîrşit de fişier
(EOF) furnizează valoarea –1 (deoarece caracterul EOF are valoarea în hexazecimal 0xff
= -1);
- char *gets(char *s); Cu funcţia gets() se poate introduce de la claviatura
calculatorului un şir de caractere de lungime variabilă. Caracterele tastate succesiv sînt
memorate, pe rînd, în tabloul de la adresa s, iar după apăsarea tastei Enter se adaugă
terminatorul de şir de caractere ‘\0’. În caz de eroare funcţia gets() întoarce NULL, altfel
adresa şirului de caractere.
Exemplul 1. ……………
char sir[15];
gets(sir);
puts(sir);
………………..
Cu gets() se citeşte din stdin un şir de caractere de lungime maximă 15, care se
tipăreşte apoi la stdout prin intermediul funcţiei predefinite puts().

2.2 Operaţia de citire cu formatare


Operaţia de citire cu formatare permite pe lîngă introducerea de date de diverse tipuri de
la suportul de intrare, conversia acestora de la reprezentarea externă la reprezentarea
binară, cu respectarea restricţiilor impuse (numărul total de caractere alocate, numărul de
zecimale, caractere de spaţiere, etc.).
Prototipul funcţiei scanf() de citire cu formatare este următorul:
int scanf(const char *şir_de_formatare [, lista_adrese_variabile]);
Lista cu adresele variabililor de intrare este opţională. Există o corespondenţă
strictă de tip, de număr şi de poziţie între descriptorii de format (specificatorii de format)

12
din şirul de formatare şi variabilile a căror adrese sînt precizate în lista_adrese_variabile.
Orice neconcordanţă va fi sancţionată cu un mesaj de eroare.
Şirul de formatare poate să conţină:
- descriptori de format, care sînt şiruri de caractere precedate de ‘%’ conforme cu
tipurile de date atribuite variabililor a căror adrese intervin în lista_adrese_variabile. Lista
acestora se precizează în figura 5.2.2.1.
- secvenţele escape ‘\n’ (linie nouă) şi ‘\t’ (tab);
- blancul (spaţiul liber);
- orice alt caracter ASCII.
Funcţia scanf() citeşte în ordine caracterele tastate de la claviatura calculatorului,
care constituie valoarea unei variabile de intrare, le interpretează conform precizărilor
furnizate de descriptorul de format corespunzător şi le depune la adresa alocată în
memorie variabilei respective. După satisfacerea întregii liste de variabile, funcţia
returnează numărul de valori citite. Dacă lista de adrese de variabile este vidă, atunci
rezultatul întors este nul. Ultimile două categorii de caractere din şirul de formatare
declanşează citirea fără memorare a caracterului citit din stdin, dacă există identitate între
ele.
Sintaxa generală a unui descriptor de format se prezintă astfel:
%[*][n1[.n2]][c1][c2]indentificator_descriptor, unde singurile argumente obligatorii sînt
% şi indentificator_descriptor. Lista descriptorilor de fomat se prezintă în tabelul 5.2.2.1.
Celelelalte argumente sînt cu următoarele semnificaţii:
- * precizează că următoarea valoare citită nu se mai atribuie variabilei care
urmează în lista de adrese;
- n1 reprezintă numărul maxim de caractere alocate unei valori;
- n2 este numărul de cifre situate după punctul zecimal în cazul unei valori
raţionale;
- c1 reprezintă un modificator de adresă pentru variabila de intrare
corespunzătoare, şi anume: F pentru far (modelul depărtat) sau N pentru near (modelul
apropiat);
- c2 reprezintă un modificator de tip pentru argumentul de intrare respectiv, după
cum urmează:
h (short int) pentru descriptorii de format d, i, o, u şi x;
b) l (long int) pentru descriptorii de format d, i, o, u şi x sau în situaţia tipului double
pentru descriptorii de format e, f şi g;
c) L (long double) pentru descriptorii de format e, f şi g.
Dacă n1 este precizat în componenţa descriptorului de format, atunci citirea valorii
respective se termină după preluarea din stdin a unui număr de caractere egal cu n1 sau la
întîlnirea unui caracter incompatibil cu formatul precizat. Dacă n1 este absent, atunci
citirea valorii respective se încheie după întîlnirea unui blanc, a unui tab, a unui caracter
incompatibil cu formatul precizat sau după tastarea unui sfîrşit de linie (Enter).
Indentificator Valoarea de intrare Tip variabilă
descriptor *
c caracter char
s şir de caractere char
d întreg zecimal int
D întreg zecimal int

13
u întreg zecimal fără semn unsigned
U întreg zecimal fără semn unsigned
e, f, g, E, G număr raţional long
o întreg octal float
O întreg octal int
x întreg hexazecimal fără semn long
X întreg hexazecimal fără semn int
i întreg (d, o sau x) long
I întreg (D, O sau X) int
n - long
p adresa în hexazecimal: int
far: YYYY:ZZZZ pointer
near: ZZZZ
Tabelul 13.2.2.1
Se precizează că descriptorul de format n specifică lungimea şirului de intrare în
citirea curentă.
Dacă şirul de intrare este prea lung, ultimele caractere sînt ignorate la citirea
curentă, dar rămînînd în dispozitivul de intrare pot afecta următoarea citire. Cu
funcţia predefinită fflush() se înlătură, prin program, balastul rămas în dispozitivul de
intrare. Dacă lista adreselor de variabile nu se satisface complet, şirul de intrare fiind prea
scurt, atunci computerul intră într-o stare de aşteptare. Indiferent de lungimea şirului de
intrare, execuţia funcţiei scanf() se încheie la citirea unui caracter incompatibil cu
specificaţia de format interpretată, cu afişarea unui mesaj de eroare corespunzător.
Cîteva exemple. 1. …………….
char c1,s1;
int i;
long j;
float x;
double y;
……………
scanf (“%c%s”, c1, s1);
scanf(“%3d%4D%f%lf”, &i, &j, &x, &y);
……………………………………………..
Pentru şirul de intrare aABCD12312347.5 -16.35 se produc următoarele atribuiri:
c1 = ’a’, s1 = “ABCD”, i = 123, j = 1234, x = 7.5 şi y = -16.35 şi funcţiile scanf se
termină normal.
2. …………….
float z[10];
int i;
for ( i = 0; i<10; ++i)
scanf(“%f”, z[i]);
fflush(stdin);
…………………………..
În situaţia şirului de intrare 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 1.10 1.11 1.12
instrucţiunea for se încheie normal, dar este necesar să se prevadă funcţia fflush() pentru

14
golirea dispozitivului de intrare, altfel ultimile două valori din şir pot afecta citirea
următoarea din program.
3. …………..
int a, b;
scanf (“%d%d”, a, b);
…………………………..
Funcţia scanf() se termină cu eroare în cazul şirului de intrare 15 1x3y, deoarece a
doua valoare conţine litere în locul a două cifre zecimale.

2.3 Operaţia de scriere fără formatare


Scrierea unui caracter pe ecranul monitorului se realizează prin intermediul funcţiei
predefinite putchar(). Prototipul acestei funcţii este următorul:
int putchar(int c);. În situaţia cînd operaţia de scriere decurge normal, funcţia
putchar() întoarce ca rezultat caracterul afişat pe ecran. Altfel, rezultatul ei se consideră
EOF (-1).
Cu funcţia predefinită puts() se poate tipări la stdout un şir de caractere. Aceasta
are următorul prototip: int puts(const char *s);.
Funcţia puts() scrie pe ecran şirul de caractere memorat la adresa s şi sare la linie
nouă. În caz de eroare rezultatul ei este EOF (adică, -1).

2.4 Operaţia de scriere cu formatare


Operaţia de scriere cu formatare presupune conversia unei secvenţe binare din
reprezentarea internă într-un şir de caractere ASCII şi afişarea acestui şir la stdout,
conform specificaţiilor de formatare precizate în componenţa funcţiei predefinită printf.
Formatul general al prototipului funcţiei printf() este următorul:
int printf(const char * şir_de_formatare, listă_valori_ieşire);.
Şirul de formatare cuprinde:
- descriptori de format, care sînt şiruri de caractere precedate de ‘%’ conforme cu
tipurile valorilor de ieşire. Lista descriptorilor de format se precizează în figura 5.2.4.1;
- caractere ASCII, care se tipăresc ca atare la dispozitivul de ieşire;
- secvenţe escape de spaţiere de tipul ‘\n’ (linie nouă) şi ‘\t’ (tab);
- blancul.
Indentificator Format de scriere Tip variabilă
descriptor
c caracter char
s şir de caractere char *
d, i întreg zecimal cu semn int
u întreg zecimal fără semn int
f [-]n1.dddddd float
e [-]n1.dddde[+/-]ddd float
E [-]n1.ddddE[+/-]ddd float
g similar cu f sau e, în funcţie de float
precizie
G similar cu E float
o întreg octal fără semn int
x ( sau X) întreg hexazecimal fără semn int

15
cu a, …, f (sau A, …, F)
% caracterul % -
n la adresa indicată afişează int *
numărul de caractere transferate
curent
p adresa în hexazecimal: pointer
far: YYYY:ZZZZ
near: ZZZZ
Tabelul 5.2.4.1
În această situaţie, spre deosebire de operaţia de citire cu formatare, structura
descriptorilor de format poate fi completată cu o serie de precizări privitoare la alinierea
valorilor în limitele spaţiului alocat, la semn, la punctul zecimal sau la prefixul datelor
octale şi hexazecimale, după cum urmează:
%[a][n1[.n2]][c1][c2]indentificator_descriptor, unde singurile argumente obligatorii,
după cum se ştie, sînt % şi indentificator_descriptor. Majoritatea argumentelor sînt cu
semnificaţii similare celor prezentate în paragraful 5.2.2. Urmează să discutăm doar
semnificaţia lui a. Alinierea valorilor în spaţiul precizat prin descriptorul de format se
face ţinînd cont de regulile de mai jos:
- alinierea implicită (în absenţa lui a) a valorilor numerice se face la dreapta, iar a
şirurilor de caractere la stînga şi completare cu spaţii sau 0 în rest. Precizia implicită
pentru valorilor de tip raţional este de 6 cifre zecimale (adică, după punctul zecimal se
afişează 6 cifre, eventual cu adăugare de zerouri);
- dacă a este –, atunci alinierea valorilor numerice se face la stînga şi completare cu spaţii
în dreapta;
- dacă a este +, atunci valorilor numerice sînt precedate de + sau -;
- dacă a este spaţiu liber, numai valorile negative sînte precedate de semnul minus;
- dacă a este #, atunci se produc inserări de caractere distinctive, astfel:
se adaugă prefixul 0 la valorile octale (pentru o);
2) se adaugă prefixul 0x sau 0X la valorile hexazecimale (pentru x sau X);
3) se înscrie punctul zecimal pentru e, E, f, g sau G, iar în cazul descriptorilor g şi G nu se
elimină digiţii 0 finali.
Cîteva exemple. 1. În tabelul 13.2.4.2 se descriu cîteva formate de scriere pentru
tipuri de date întregi şi raţionale în simplă precizie:
Tip de dată Descriptor de format Format de scriere
întregi
153 %d 153
153 %3d 153
153 %2d (se ignoră) 153
153 %5d 153
raţoinale
7.52 %f 7.520000
7.52 %f4.2 7.52
7.5 %f4.2 7.50
7.5 %f6.2 7.50
17.52 %f3.1 (se ignoră) 17.52
Tabelul 13.2.4.2

16
…………….
int k = 14;
double z = -335.3;
printf(“\nk=%3d\tz=%9.2lf”, k, z);
Şirul de formatare cuprinde secvenţe de evitare, caractere ASCII şi descriptorii de
format d şi lf. Valorile variabililor k şi z vor fi tipărite în clar, la linie nouă, iar spaţiul
liber între ele este egal cu o tabulare standard.

3 Operaţii aplicabile fişierelor utilizator


Fişierele de tip utilizator pot suporta operaţiile de creare, exploatare şi actualizare.
Operaţia de creare constă în scrierea structurilor componente una după alta pe suportul
magnetic afectat fişierului sau în directorul precizat în calea fişierului. Indicatorul care
fixează poziţia curentă în fişier se iniţializează cu 0. Valoarea acestuia va fi actualizată la
fiecare transfer. Fişierul se încheie cu un caracter special EOF (end of file - sfîrşit de
fişier). Operaţia de exploatare permite citirea parţială sau totală a unui fişier în vederea
inventarierii, prelucrării sau tipăririi informaţiilor conţinute de către acesta. Operaţia de
actualizare constă în modificare de cîmpuri componente unor structuri, în adăugare de noi
structuri, nu neapărat la sfîrşitul fişierului, sau în şteregere de structuri cu un conţinut
perimat. Actualizare presupune în anumite situaţii crearea unui nou fişier de acelaşi tip
sau nu cu cel iniţial, cu luare de măsuri referitoare la indentificarea corectă a fişierului
nou (în general, se utilizează un nume simblic diferit de cel al fişierului iniţial).
Indiferent de operaţia de intrare/ieşire executată asupra fişierului, progresarea în
fişier se face prin funcţii predefinite de citire/scriere de un tip corespunzător. Accesul la
informaţiile unui fişier poate fi secvenţial sau aleator (direct). Accesul secvenţial
presupune citirea/scrierea structurilor una după alta, începînd cu prima şi terminînd la
ultima. Valoarea variabilei poziţie în fişier se incrementează automat cu un anumit număr
de octeţi după fiecare operaţie scanf()/printf(). Pe baza informaţiilor furnizate prin
program, accesul aleator permite direct depistarea unei anumite structuri din componenţa
fişierului prin intermediul funcţiei predefinită fseek(), fără a se mai trece prin toate
structurile dinaintea acesteia. Cu alte cuvinte, valorea indicatorului poziţie în fişier este
gestionată prin program, realizîndu-se astfel operaţii de intrare/ieşire în diverse zone ale
fişierului. Este util de remarcat că în fişierele memorate pe disc se pot combina cele două
tipuri de accese, în sensul că o anumită porţiune din fişier poate fi procesată în totalitate.
Orice intervenţie într-un fişier este precedată de operaţia de deschidere a fişierului
prin funcţia predefinită fopen(), care pregăteşte fişierul pentru acest lucru şi succedată de
operaţia de închidere a fişierului prin fclose(). Aşa cum s-a mai spus, nu acelaşi lucru se
întîmplă în cazul fişierelor predefinite, unde cele două operaţii se realizează automat.
Indentificarea unui fişier se face printr-un şir de caractere ce include numele
unităţii de disc, calea, numele simbolic al fişierului şi extensia acestuia. Şirul de caractere
poate să conţină litere, cifre, liniuţa de subliniere, blancul, :, şi \ (backslash-ul se
reprezintă prin secvenţa \\). Sistemul de operare nu face distincţie înte majuscule şi
minuscule. Se recomandă ca fixarea numelui simbolic al fişierului să se facă în aşa fel
încît să sugereze funcţia lui, deoarece acesta rămîne o caracteristică permanentă şi
independentă de programul care îl va utiliza de acum înainte. Dacă numele unităţii de
disc absentează, atunci se consideră unitatea de disc implicită. Fişierul va fi căutat în
directorul de lucru în cazul cînd lipseşte calea.

17
În concluzie, pentru a se lucra corect cu fişiere utilizator trebuie să se parcurgă
următoarele etape:
- să se precizeze numele formal al fişierului printr-o variabilă de tip FILE în
partea de declaraţii a unităţii de program corespunzătoare. Trebuie să existe concordanţă
de nume între fişierul formal (declarat ca o variabilă FILE) şi numele unui fişier ce există
sau va exista pe un suprt magnetic;
- să se deschidă fişierul prin fopen(), cu precizarea tipurilor operaţiei de
citire/scriere şi stream-ului (text sau binar) executate;
- să se scrie/citească în fişier cu funcţia predefinită fwrite()/fread();
- să se închidă fişierul cu fclose().
Indentificatorii declaraţi în domeniul fişierului cu caracter global au, în mod
implicit, legătură de tip extern. Cu specificatorul static ultimul atribut poate fi schimbat în
unul de tip intern. Indentificatorii neglobali au legătură identică cu cea rezultată din
declaraţia indentificatorilor globali, dacă există şi este cu vizibilitate. În mod similar,
specificatorul static poate determina o legătură internă, iar specificatorul extern una
externă.
Urmărirea reperelor anunţate mai sus se poate face în exemplu prezentat în ultima
secţiune.

18

You might also like