Sunteți pe pagina 1din 25

Capitolul 1

Functii

O funcţie reprezintă o secvenţă de instrucţiuni care poate fi identificată şi apelată prin intermediul unui
nume. Funcţiile sunt un element foarte important în orice limbaj de programare, deoarece uşurează foarte
mult munca programatorului, eliminând necesitatea scrierii aceluiaşi cod de mai multe ori.

De asemenea, prin folosirea funcţiilor programele pot fi structurate în blocuri, implicând o mai bună
depanare şi modularizare a programelor respective. Utilizarea unei funcţii presupune două elemente
distincte:

- definirea funcţiei;

- apelul funcţiei;

Definirea unei funcţii reprezintă precizarea tipului returnat de funcţia respectivă, a argumentelor funcţiei
şi a tipurilor acestora şi scrierea corpului funcţiei (instrucţiunile care vor fi executate când va fi apelată
funcţia). Forma generală a unei funcţii este următoarea:

tip_returnat nume_functie (tip_date1 arg1, tip_date2 arg2, ...,

tip_dateN argN );

. . .

Corpul functiei ;

. . .

Apelarea unei funcţii constă în folosirea propriu-zisă a funcţiei, într-o altă funcţie. Apelarea se poate face
atât în funcţia principală (main), cât şi într-o altă funcţie.

Returnarea unei valori de către o funcţie se face folosind instrucţiunea return înainte de încheierea funcţiei
respective. De asemenea, instrucţiunea return se poate folosi şi pentru încheierea forţată a execuţiei unei
funcţii. O funcţie poate returna orice valoare dintr-un tip de bază, cu excepţia unui tablou. Dacă funcţia nu
returnează nici o valoare, atunci se consideră că returnează void (tipul vid). Atenţie! dacă nu se specifică
nimic la tipul returnat de funcţie, compilatorul C consideră că funcţia respectivă returnează o valoare de
tip întreg (va fi semnalat un avertisment (warning) – Function should return a value)

Exemplu: definirea şi apelarea unei funcţii care returnează o valoare întreagă:


int calcul (int a, int b)

int c;

c = a + b;

return c;

void main (void)

int x, y, z;

x = calcul (y, z); // apelarea funcţiei

printf (“Rezultatul este %d”, x);

}Sa se scrie un program care calculeaza valoarea expresiei S=1-2+3-4+....+, folosind functii proprii

#include <stdio.h>

int suma1(int n) //cu instructiunea FOR


{
int s=0, i=1, semn=1;
for(; i<=n; i++)
{
s+=semn*i; //s=s+semn*i
semn=-semn;
}
return s;
}

int suma2(int n) //cu instructiunea WHILE


{
int s=0, i=1, semn=1;
while (i<=n)
{
s+=semn*i; //s=s+semn*i
semn=-semn;
i++;
}
return s;
}
int suma3(int n) //cu instructiunea DO WHILE
{
int s=0, i=1, semn=1;
do
{
s+=semn*i; //s=s+semn*i
semn=-semn;
i++;
}
while (i<=n);
return s;
}

void main()
{
int n;
printf("Introduceti n:");
scanf("%d", &n);

printf("\n\tSuma calculata cu FOR are valoarea: %d" , suma1(n));


printf("\n\tSuma calculata cu WHILE are valoarea: %d" , suma2(n));
printf("\n\tSuma calculata cu DO WHILE are valoarea: %d" , suma3(n));

getchar();
int k;
scanf("%d", k);
}

Capitolul 2

Tablouri si Pointeri

Notiunea de pointer

Pointerii au fost introdusi in limbajele de programare pentru a putea rezolva mai eficient anumite
probleme sau pentru a da mai multa claritate anumitor programe.

O prima definitie poate fi urmatoarea:

Pointerul este o variabila ce contine adresa unui obiect.

Obiectul a carei adresa este retinuta de pointer poate fi:

· variabila

· functie
Fie urmatorul exemplu:

int x;

int *px;

Am definit o variabila de tip intreg x si o variabila pointer, care poate contine adresa unei variabile de tip
intreg. Simbolul * ce apare in stanga variabilei px arata ca px este o variabila pointer.

Prin atribuirea

px=&x;

pointerul va avea ca valoare adresa de memorie alocata variabilei x (vezi laboratorul nr.1, definitia
variabilei). Operatorul unar & este utilizat pentru a se obtine adresa variabilei x (operator unar=are un
singur operand)

Acum putem sa lucram cu continutul variabilei x (i.e cu valoarea acesteia) prin intermediul pointerului
px, deci indirect, fara sa mai folosim variabila x. La prima vedere, aceasta modalitate de lucru poate parea
dificila si nu tocmai utila. Necesitatea utilizarii pointerilor va apare cu mai multa claritate in sectiunea
dedicata sirurilor de caractere si functiilor.

Exemplul 1. Fie programul urmator:

#include <iostream.h>

void main()

In programul de mai sus am introdus valorile variabilelor intregi x si y, am definit un pointer la variabila x
si am atribuit acestuia adresa de memorie alocat variabilei x. Sa analizam atent linia:

cout<<'x are valoarea '<<*px;

Prin *px se intelege valoarea aflata in zona de memorie a carei adresa este memorata in pointerul px.
Valoarea afisata va fi chiar valoarea introdusa pentru x deoarece, inainte de afisare, pointerul px a primit
ca valoare adresa variabilei x, adresa la care se afla valoarea acesteia (valoare dobandita prin utilizarea
functiei cin).

Atribuirea *px=y; va modifica valolarea care se afla la adresa memorata de px, valoare care va fi valoarea
introdusa de utilizator pentru variabila y. Astfel va fi modificata chiar valoarea pe care o are variabila x.

Fireste ca era mai simplu sa folosim atribuirea x=y; care are acelasi efect si ne scuteste de de-a mai folosi
pointeri, insa exemplul este pur didactic.

Operatorul unar * este folosit sub forma *variabila_pointer, valoarea acestei expresii fiind valoarea care
se gaseste in memorie la adresa memorata de pointerul ce apare ca operand. In concluzie, prin px avem
acces la adresa variabilei x, iar prin *px la valoarea variabilei x.
Vom spune ca un pointer “refera” indirect un obiect sau ca “pointeaza”(arata) la obiectul respectiv.
Variabilele pointer pot fi incadrate ca fiind de tip referinta.

Exemplul 2. Sa se calculeze suma a doua numere reale folosind pointeri.

#include <iostream.h>

void main()

2. Pointeri si tablouri

In limbajul C, exista o foarte stransa legatura intre pointeri si tablouri, astfel ca pointerii si tablourile sunt
tratate la fel. Orice program in care apar tablouri poate fi modificat astfel incat sa foloseasca poiteri in
locul tablourilor. In aceasta sectiune vom discuta despre legatura dintre pointeri si vectori (tablouri
unidimensionale).

Fie urmatoarele declaratii:

int a[20];

int *pa;

Am declarat o variabila a , care este un vector cu maxim 20 elemente intregi si un pointer la o variabila de
tip intreg. Dupa cum se stie, o valoare int are nevoie de 16 biti pentru a fi memorata, adica 2 bytes ( o
variabila int poate retine numere intregi intre -32768 si 32767, vezi curs Bazele Informaticii). Pentru
tabloul a vor fi alocati 2· 20=40 bytes consecutivi in memorie adica, pentru primul element a[0] sunt
alocati primii 2 bytes, pentru a[1] urmatorii 2 bytes,…, pentru a[19] ultimii 2 bytes din cei 40 alocati.

Fie atribuirea:

pa=&a[0];

Dupa aceasta atribuire, pointerul pa contine adresa primului element al vectorului, adica pa pointeaza la
inceputul vectorului a.

Daca scriem pa=&a[3]; atunci pa va referi elementul al 4-lea din vectorul a, iar *pa va contine valoarea
sa.

Operatiile care se pot realiza cu pointeri sunt:

· comparatia

· adunarea unui pointer cu un intreg

· scaderea unui intreg dintr-un pointer


Doi pointeri pot fi comparati folosind operatori relationali. In comparatia:

if(p1==p2) cout<<”Adrese identice”;

else cout<<”Adrese diferite”;

se verifica daca adresa memorata de p1 este aceeasi cu adresa retinuta de p2, unde p1 si p2 sunt pointeri
de acelasi tip.

Se poate compara un pointer cu valoarea NULL (sau 0). Un pointer are valoarea NULL (valoare
nedefinita) daca nu refera nici un obiect.

Adunarea unui pointer cu un intreg este definita numai atunci cand pointerul refera un tablou (un element
al tabloului). Scaderea este definita in acelasi caz.

Exemplul 3. Sa se citeasca elementele unui vector si sa se afiseze acestea utilizand pointeri.

#include <iostream.h>

void main()

//afisarea vectorului folosind pointeri

pa=&a[0];

for(i=0;i<n;i++)

Prima pate a programului nu contine elemente noi, doar a doua parte meritand atentie. Mai intai
initializam pointerul pa cu valoarea primului element al vectorului a. Ciclul for contine urmatoarele
prelucrari:

· afiseaza valoarea aflata la adresa indicata de pointer;

· aduna pointerul pa cu 1

Incrementarea pointerului pa are ca efect modificarea adresei memorate in pa. Noua adresa este adresa
zonei de memorie corespunzatoare elementului urmator, o adresa cu 2 bytes mai mare decat precedenta.
Observam ca marirea pointerului cu o unitate inseamna de fapt trecerea la urmatorul element din vector.

Daca vom introduce pentru n o valoare mai mare decat 20 (numarul maxim de elemente ale vectorului,
asa cum reiese din declaratie) atunci pointerul pa va depasi zona de memorie alocata vectorului si va
referi o adresa la care se pot afla date importante pentru program.

1 Să se citească un şir de la tastatură şi apoi să se afişeze pe ecran caracter cu caracter. Elementele


şirului vor fi accesate prin indecşi şi prin aritmetica pointerilor.
#include<stdio.h>

#include<string.h>

#include<conio.h>

void afiseaza_prin_indici(char *sir){

int k;

printf("\n sirul afisat utilizandu-se tablou ci indice: ");

for(k=0; sir[k]; ++k)

putchar(sir[k]);

void afiseaza_prin_pointer(char *s){

printf("\n sirul afisat utilizandu-se pointeri: ");

while(*s)

putchar(*s++);

void main(void){

char sirdat[20];

printf("\n dati un sir de max 20 caractere:");

gets(sirdat);

/* apelarea celor doua functii pentru afisarea folosind indici si pointeri */

afiseaza_prin_indici(sirdat);

afiseaza_prin_pointer(sirdat);

getch();

}
Capitolul 3

Structuri si uniuni

O structură este o colecĠie de variabile la care se face referire utilizându-se un singur nume. Datorită
faptului că permit gruparea mai multor variabile (între care există o anumită relaĠie într-un context dat)
úi tratarea lor în mod unitar úi nu ca entităĠi separate, structurile sunt cu succes utilizate în organizarea
datelor complexe .
Variabilele din interiorul structurii se numesc membri sau câmpuri úi pot avea tipuri diferite. Fiecare
membru are la rândul său un nume, utilizat pentru a-l selecta din structură.
Pentru definirea unei structuri se foloseúte cuvântul cheie struct într-o instrucĠiune a cărei formă generală
este : struct nume_structura{

tip 1

var11,

var12;

tip 2

var2;

tip n varn;

} lista_variabile;

Unde:

-nume_structura are caracter optional si este o etichetă prin intermediul căreia tipul de dată tocmai definit
poate fi folosit în continuare pentru declararea unor variabile;

-var11, var12, var2, …, varn sunt membrii structurii;

-tip1, tip2, …, tipn reprezintă tipurile membrilor structurii;

-variabilele de acelasi tip pot fi definite într-o singură instructiune úi vor fi separate prin virgulă (,);

-definirea variabilelor de tipuri diferite se face în instructiuni diferite, separate prin punct si virgulă (;);

-variabilele membru sunt declarate într-un bloc delimitat de acolade ({});

-lista_variabile are de asemenea caracter optional si este o listă care declară una sau mai multe variabile
-definirea structurii se încheie cu punct si virgulă (;), această definire fiind, în ansamblu, o instructiune;

-desi atât nume_structura, cât si lista_variabile au caracter optional, ele nu pot lipsi în acelaúi timp; cu alte
cuvinte măcar una dintre ele trebuie să apară pentru că altfel definirea structurii n-ar avea sens (de vreme
ce nici nu s-au declarat variabile, nici nu s-a dat un nume structurii pentru a putea fi folosită ulterior).

Un prim exemplu, foarte simplu, de structură este reprezentat de coordonatele unui punct în plan.
Structura poate fi definită astfel:

struct punct{

int x;

int y;

};

sau:

struct punct{

int x, y;

};

Uniunea

O uniune este o zonă de memorie folosită de două sau mai multe variabile (de obicei de tipuri diferite) la
momente diferite de timp. Se utilizează frecvent atunci când un element are la un moment dat unul ‫܈‬i
numai unul din mai multe formate. Definirea ei este similară cu cea a unei structuri, dar folosete cuvântul
cheie union:

union nume_uniune{

tip 1 var11, var12;

tip 2 var2;

tip n varn;

} lista_variable;

Uniunile pot contine structuri si tablouri si, de asemenea, pot apărea în structuri si în tablouri. Dacă o
variabilă membru a unui astfel de element imbricat trebuie accesată, notatia este identică cu cea utilizată
la structuri imbricate. Următorul exemplu declară un vector de elemente imbricate; fiecare locatie a
vectorului e o structură care contine câteva variabile, inclusiv o uniune.
Se va scrie un program C ce va evidenţia diferenţele între folosirea unei structuri şi a unei
uniuni.
a) se va folosi o structură de înregistrări:
#include<stdio.h>
struct exemplu
{
long int a;
char* b;
char* c;
};
void main()
{
exemplu x;
printf("Spatiul ocupat de o variabila la structura: %d\n",sizeof(x));
x.a=10;
x.b="ABCD";
x.c="EFG";
printf("%ld\n",x.a);
printf("%s\n",x.b);
printf("%s\n",x.c);

getchar();
int k;
scanf("%d", &k);
}

b) se va folosi o structură tip uniune:


#include<stdio.h>
union exemplu
{
long int a;
char* b;
char* c;
};
void main()
{
exemplu x;
printf("Spatiul ocupat de o variabila la uniune: %d\n",sizeof(x));
x.a=10;
x.b="ABCD";
x.c="EFG";

printf("%ld\n",x.a);
printf("%s\n",x.b);
printf("%s\n",x.c);

getchar();
int k;
scanf("%d", &k);
}
Capitolul 4

Programarea prin rafinare

Un mediu integrat de dezvoltare a programelor oferă tehnici şi instrumente care asistă programatorul în
toate fazele elaborării programului: editare, codificare, compilare, testare etc. Într-un astfel de caz se
poate aprecia că o parte din tehnologia care urmează să fie aplicată este intrinsecă mediului de
dezvoltare.

Principalele elemente care evidenţiază stilul de programare sunt următoarele:

 aspectul general al programului;

 claritatea şi lizibilitatea programului;

 structurarea şi modularizarea programului în conformitate cu funcţiile şi operaţiile care se


implementează;

 robusteţea programului: calitatea sa de a continua chiar şi la apariţia unor erori;

 întreţinerea programului: uşurinţa cu care poate fi modificat şi îmbunătăţit ulterior. Pentru a atinge
aceste obiective programatorii au la dispoziţie mai multe pârghii prin intermediul cărora pot acţiona atât
la fazele de proiectare cât şi la cele de programare, testare etc.

Acestea sunt:

- Organizarea programului: definirea subprogramelor în conformitate cu princi-palele activităţi şi


operaţii desprinse din problema care urmează a fi rezolvată.

- Organizarea datelor: trebuie să concorde cu structura obiectelor din realitatea problemei care se
rezolvă.

- Comunicarea între subprograme: se recomandă să se realizeze exclusiv prin intermediul parametrilor.


Astfel se acordă subprogramului un mai mare grad de generalitate şi efectele sale se limitează la textul
subprogramului: sunt mai uşor de urmărit la citirea programului. În cazul în care comunicarea între
subprograme se face şi prin intemediul variabilelor globale, apar aşa numitele efectele laterale care
îngreunează urmărirea şi înţelegerea programelor.

- Testarea prin program a unor categorii de erori, de exemplu cele de intrare/ ieşire. Unele limbaje,
inclusiv C, permit astfel de operaţii ceea ce determină îmbunătăţirea robusteşii programelor.

- Alegerea numelor simbolice (identificatorilor) din program astfel încât să se facă o legătură directă cu
semnificaţia utilităţii respective în problema de rezolvat. În acest fel se poate uşura urmărirea şi
înţelegerea programului. -Utilizarea comentariilor: reprezintă cea mai simplă metodă de documentare a
programelor, utilă chiar şi autorilor programului, dacă îl parcurg după un anumit timp. În practica
programării se utilizează linii de comentarii distincte prin care se descriu funcţiile programului, funcţia
fiecărui subprogram, datele de intrare şi cele de ieşire, indicaţii de utilizare a programului etc. De
asemenea, se obişnuieşte ca prelucrările mai importante sau mai dificile din program să fie însoţite de
comentarii explicative.

-Formatul liber de redactare al liniilor sursă. Această facilitate, prezentă în majoritatea limbajelor de
programare moderne, pune în concordanţă textul programului cu organizarea şi semnificaţia sa. În acest
fel creşte lizibilitatea şi claritatea programului

Elaborarea programului prin metoda detalierii în paşi succesivi

Cu cât problema este mai complexă, trecerea de la enunţul problemei la program se face mai anevoios.
În aceste situaţii este util ca atât elaborarea algoritmului cât şi scrierea programului să se facă treptat,
într-un proces de detaliere succesivă a rezolvării problemei. Stilul de lucru corespunzător s-a concretizat
într-o metodă de proiectare-programare descendentă (top-down) numită metoda detalierilor în paşi
succesivi (step wise refirement). În această metodă se porneşte de la general, elaborând mai întâi
programul principal (pasul 1). Astfel se proiectează şi se descriu, în limbajul de implementare ales,
principalele date ale programului (cele de la nivelul programului principal), iar funcţiile apelate din
programul principal sunt doar enunţate: numele şi lista de parametrii formali. În continuare, prin
detalieri (rafinări) succesive (paşii 2, 3, …) se dezvoltă funcţiile apelate direct din programul principal
(pasul 2), apoi funcţiile apelate din acestea din urmă (pasul 3) ş.a.m.d., până la elaborarea în amănunt a
întregului program. Caracteristica esenţială a acestui mod de rafinare a programului este următoarea: în
fiecare pas se dezvoltă funcţiile care au apărut (fiind doar enunţate) în pasul anterior; dacă operaţiile
implementate în pasul curent sunt suficient de complexe, este posibil să fie enunţate noi funcţii care vor
fi dezvoltate (detaliate) în pasul următor.
Capitolul 5

Programarea avansata

Un program C este compus dintr-o ierarhie de funcţii, orice program trebuind să conţină cel puţin
funcţia main, prima care se execută la lansarea programului C. Funcţiile pot face parte dintr-un singur
fişier sursă sau din mai multe fişiere sursă. Un fişier sursă C este un fişier text care conţine o succesiune
de funcţii şi, eventual, declaraţii de variabile.

Structura unui program C este, în principiu, următoarea:

-directive preprocesor

-definiţii de tipuri

-prototipuri de funcţii

- tipul funcţiei şi tipurile parametrilor funcţiei

-definiţii de variabile externe

-definiţii de funcţii

Limbajul C impune următoarele reguli asupra identificatorilor:

-Un identificator este o secvenţă de caractere, de o lungime maximă ce depinde de compilator (în
general are maxim 255 de caractere). Secvenţa poate conţine litere mici şi mari(a-z, A-Z), cifre (0-9) şi
simbolul '_' (underscore, liniuţa de subliniere).

-Primul caracter trebuie să fie o literă sau '_'. Pentru că multe nume rezervate de compilator, invizibile
programatorului, încep cu '_', este indicat a nu utiliza '_' pentru începutul numelor utilizator.

-Un identificator nu poate fi un cuvânt cheie (int, double, if, else, for).

-Deoarece limbajul C este case-sensitive identificatorii sunt şi ei la rândul lor case-sensitive. Astfel, suma
nu este Suma şi nici SUMA.

-Identificatorii sunt atomi lexicali, în cadrul lor nu e permisă utilizarea spaţiilor albe şi a altor caractere
speciale (ca +, -, *, /, @, &, virgulă, etc.) putem însă folosi caracterul '_'.

Tipurile de date fundamentale sunt:

-caracter (char – 1 octet)

-întreg (int – 4 octeţi)

-virgulă mobilă (float – 4 octeţi)


-virgulă mobilă dublă precizie (double – 8 octeţi)

-nedefinit (void)

Tipurile de date derivate sunt:

-tipuri structurate (tablouri, structuri)

-tipul pointer (4 octeţi)

1 Program C pt. afisarea Tabelului codurilor ASCII;

#include <stdio.h>

void main(){
unsigned short c;
for(c=0;c<=255;c++)
switch(c){
case 7 : printf("b%3uł",c);break; // beep
case 8 : printf("B%3uł",c);break; // back space
case 9 : printf("T%3uł",c);break; // tab
case 10 : printf("L%3uł",c);break; // line feed
case 13 : printf("R%3uł",c);break; // return
case 27 : printf("E%3uł",c);break; // escape
default : printf("%c%3uł",c,c); // caractere afisabile
};
}
Capitolul 6

Fisiere

1. Deschiderea unui fisier

Pentru a deschide un fisier la acest nivel de prelucrare a fisierelor se utilizeaza functia fopen. Ea
returneaza un pointer spre tipul FILE (tipul fisierului) sau pointerul nul in caz de eroare. Prototipul functiei
este urmatorul:

 FILE *fopen(const char * calea, const char * mod);

unde: - calea – are aceeasi semnificatie ca si in cazul functiei open adica este un pointer spre un sir de
caractere care defineste calea spre fisierul care se deschide;

- mod – este un pointer spre un sir de caractere care defineste modul de prelucrare al fisierului
dupa deschidere, acest sir de caractere se defineste astfel:

 “r” – deschidere pentru citire;

 “w” – deschidere pentru scriere;

 “a” – deschidere pentru adaugare;

 “r+” – deschidere pentru modificare citire-scriere;

 “rb” – deschidere pentru citire binara;

 “wb” – deschidere pentru scriere binara;

 “r+b” – deschidere pentru citire-scriere binara.


Cu ajutorul functiei fopen se poate deschide un fisier inexistent in modul w sau a. In acest caz,
fisierul respectiv se considera in creare.

Daca se deschide un fisier existent in modul w, atunci se va crea din nou fisierul respectiv si vechiul
continut al sau se va pierde.

Deschiderea unui fisier in modul a permite adaugarea de inregistrari dupa ultima inregistrare
existenta in fisier.

2. Prelucrarea pe caractere a unui fisier

Fisierele pot fi scrise si citite caracter cu caracter folosind doua functii simple

 putc –pentru scriere;

 getc –pentru citire.

Functia putc are prototipul:

 int putc(int c, FILE *fp);

unde: - c – codul ASCII al caracterului care se scrie in fisier;

- fp – un pointer spre tipul FILE a carui valoare a fost returnata de functia fopen la deschiderea
fisierului in care se face scrierea; In particular fp poate fi (stdont, stderr, stdprn, stdaux).

Functia putc returneaza valoare lui c sau –1 la eroare.

Functia getc are prototipul:

 int getc(FILE *fp);

unde: - fp – un pointer spre tipul FILE a carui valoare a fost definita la apelul functiei fopen. In
particular fp poate fi (stdin, stdoux).

Functia putc returneaza codul ASCII al caracterului citit din fisier.

3. Inchiderea unui fisier

Dupa terminarea prelucrarii unui fisier, acesta urmeaza a fi inchis. Inchiderea unui fisier se
realizeaza cu ajutorul functiei fclose de prototip:

 int fclose(FILE *fp);


unde: - fp –este pointerul spre tipul FILE. Valoarea lui a fost definita prin functia fopen la deschiderea
fisierului.

Functia returneaza valorile:

- 0 –la inchidere normala;

- -1 –la eroare.

4. Intrari/iesiri de siruri de caractere

Biblioteca standard a limbajului C contine functiile fgets si fputs care permit citirea, respectiv
scrierea, inregistrarilor care sunt siruri de caractere.

Functia fgets are prototipul:

 char *fgets(char *s, int n, FILE *fp);

unde: - s –este pointerul spre zona in care se pastreaza caracterele citite din fisier;

- n –este dimensiunea in octeti a zonei in care se citesc caracterele din fisier;

- fp –este pointerul spre tipul FILE a carui valoare s-a definit la deschiderea fisierului.

Citirea caracterelor se intrerupe la intalnirea caracterului “n” sau dupa citirea a cel mult n-1
caractere. In zona spre care pointeaza s se pastreaza caracterul “n” daca acesta a fost citit din fisier, iar apoi
se memoreaza caracterul nul (“ 0”). In mod normal, functia returneaza valoarea pointerului s. La intalnirea
sfarsitului de fisier functia returneaza valoarea 0.

Functia fputs scrie un sir de caractere intr-un fisier. Ea are prototipul:

 int fputs (const char *s, FILE *fp);

unde: - s – este pointerul de la inceputul zonei de memorie care contine sirul de caractere care se scrie in
fisier.

- fp – este pointerul spre tipul FILE a carui valoare a fost definita la deschiderea fisierului prin
apelul fopen.

Functia fputs returneaza codul ASCII al ultimului caracter scris in fisier sau –1 la eroare.

Aceste doua functii sunt similare cu functiile gets si puts.

5. Intrari iesiri cu format (I/O)

Biblioteca standard a limbajului C contine functii care permit realizarea operatiilor de I/O cu format.
In acest scop se pot utiliza functiile fscanf, fprintf.
Acestea sunt similare cu functiile sscanf, respectiv sprintf. Diferenta dintre ele consta in faptul
ca fscanf si fprintfau ca prim parametru un pointer spre tipul FILE, iar sscanf si sprintf au ca prim parametru
un pointer spre o zona in care se pastreaza caractere. Astfel fscanf citeste date dintr-un fisier si le
converteste, in timp ce sscanf realizeaza acelasi lucru dar utilizand date din memorie.
Functia fprintf converteste date din format intern in format extern si apoi le scrie intr-un fisier, spre
deosebire de functia sprintf care realizeaza acelasi lucru dar rezultatele se pastreaza in memorie.

Prototipul functiei fscanf:

 int fscanf(FILE *fp, const char *format, );

unde: - fp – este pointer spre tipul FILE a carui valoare a fost definita prin apelul functiei fopen.

Prototipul functiei fprintf:

 int fprintf(FILE *fp, const char *format, );

unde: - fp – este pointer spre tipul FILE.

6. Pozitionarea intr-un fisier

Limbajul C contine functia fseek care permite deplasarea capului de citire-scriere al discului in vederea
prelucrarii inregistrarilor fisierului intr-o ordine diferita de cea secventiala.

Prototipul functiei fseek:

 int fseek(FILE *fp, long deplasament, int origine);

unde: - fp – este pointer spre tipul FILE care defineste fisierul in care se face pozitionarea capului de citire-
scriere.

7. Stergerea unui fisier

Un fisier poate fi sters apeland functia unlink. Aceasta are prototipul:

 int unlink(const char *calea);

unde: - calea – este pointer spre un sir de caractere identic cu cel utilizat la crearea fisierului in
functia fopen.

1.Sa se scrie un program care afiseaza lungimea celei mai lungi linii din fisierul text
TEST:
/*------------------------------------------------*/
/* */
/* afiseaza lungimea lg_max a celei mai lungi */
/* linii a fisierului */
/* */
/*------------------------------------------------*/
#include <stdio.h>

void main(void)
{
FILE *f;
char c;
int lg_max, lg_curenta;

lg_max=lg_curenta=0;

if (!(f=fopen("TEST", "r")))
{
puts("Fisierul TEST nu poate fi deschis");
return;
}

while ((c=getc(f))!=EOF)
if (c=='\n')
{
if (lg_max<lg_curenta)
lg_max = lg_curenta;
lg_curenta = 0;
}
else
lg_curenta++;

fclose(f);
printf ("\nLinia cea mai lunga are lungimea %d", lg_max);
}

Capitolul 7

Recursivitate

Recursivitatea este un mecanism general de scriere a problemelor. Un algoritm recursiv se


caracterizează prin proprietatea că se autoapelează, adică din interiorul lui se apelează pe el însuşi.
Atunci când se scrie un algoritm recursiv este suficient să se gândească ce se întâmplă la un anumit
nivel, pentru că la orice nivel se întâmplă exact acelaşi lucru. Într-un algoritm recursiv, pentru a realiza
un anumit calcul sunt necesare două elemente:

1. o formulă de recurenţă;

2. o valoare iniţială cunoscută.

Un exemplu de recursivitate este în definirea formală a numerelor naturale din cadrul teoriei mulțimilor:
- baza recursiei este faptul că 1 este număr natural;

- în plus, orice număr natural are un succesor, care este de asemenea un număr natural.

Un alt exemplu ar fi definirea conceptului de strămoş al unei persoane:

- un părinte este strămoșul copilului (“baza”);


- părinții unui strămoș sunt și ei strămoși (“pasul de recursie”).

• recurenţă = repetiţie, revenire;

• formulă de recurenţă = formulă care exprimă orice termen dintr-un şir, în funcţie de termenii
precedenţi;

Se numeşte funcţie recursivă o funcţie care din corpul ei se apelează pe ea însăţi. Un subprogram se
numeşte recursiv dacă se autoapelează. Orice funcţie recursivă trebuie să îndeplinească două condinţii:
1. să se poată executa cel puţin o dată fără să se autoapeleze;

2. toate apelurile să se producă astfel încât să se tindă spre îndeplinirea condiţiei de execuţie fără
apeluri.

Competenţe specifice

• utilizarea corectă a subprogramelor predefinite şi a celor definite de utilizator

• construirea unor subprograme pentru rezolvarea subproblemelor unei probleme

• aplicarea mecanismului recursivităţii prin crearea unor subprograme recursive (definite de utilizator)

• compararea dintre implementarea recursivă şi cea iterativă a aceluiaşi algoritm

• analiza problemei în scopul identificării subproblemelor acesteia

• descrierea metodei de rezolvare a unei probleme în termeni recursivi

Din afara funcţiei recursive, se face un prim apel la funcţia recursivă, după care funcţia se autoapeleză
de un anumit număr de ori. Pentru orice algoritm iterativ există un algoritm recursiv echivalent (rezolvă
acceaşi problemă) şi invers, pentru orice algoritm recursiv există unul iterativ echivalent

1 Să se scrie un program C, pentru rezolvarea cmmdc-ului dintre două numere întregi fără semn (pentru
determinarea cmmdc-ului vom folosi algritmul lui Euclid prin scăderi). Varianta recursive

#include <stdio.h>
#include <conio.h>

unsigned int cmmdc(unsigned int a, unsigned int b)


{
if(a==b) return a;
else
if(a>b) return cmmdc(a-b,b);
else return cmmdc(a,b-a);
}
void main()
{
unsigned int x,y;
printf("Introduceti x: ");
scanf("%u",&x);
printf("Introduceti y: ");
scanf("%u",&y);

if(!x || !y) //daca x=0 sau y=0


printf("cmmdc(%u,%u) = 1\n",x,y);
else
printf("cmmdc(%u,%u) = %u\n",x,y,cmmdc(x,y));

getch();
}

Capitolul 8

Metode generale

Elaborarea algoritmilor

Prin elaborarea (proiectarea) unui algoritm intelegem intreaga activitate depusa de la enuntarea
problemei pana la realizarea algoritmului corespunzator rezolvarii acestei probleme.

In elaborarea unui algoritm deosebim urmatoarele activitati importante:

- specificarea problemei;

- descrierea metodei alese pentru rezolvarea problemei;

- proiectarea propriu-zisa. Ea consta in descompunerea problemei in subprobleme, obtinerea algoritmului


principal si a tuturor subalgoritmilor apelati, conform metodelor prezentate in sectiunile urmatoare. Ea se
termina cu descrierea algoritmului principal si a subalgoritmilor mentionati, dar si cu precizarea denumirilor si
semnificatiilor variabilelor folosite;

- verificarea algoritmului obtinut.

Proiectarea ascendenta si proiectarea descendenta

Exista doua metode generale de proiectare a algoritmilor, a caror denumire provine din modul de
abordare a rezolvarii problemelor: metoda descendenta si metoda ascendenta. Proiectarea
descendenta (top-down) porneste de la problema de rezolvat, pe care o descompune in parti rezolvabile
separat. De obicei aceste parti sunt subprobleme independente, care la randul lor pot fi descompuse in
subprobleme. La prima descompunere accentul trebuie pus pe algoritmul (modulul) principal nu asupra
subproblemelor. La acest nivel nu ne intereseaza amanunte legate de rezolvarea subproblemelor, presupunem
ca le stim rezolva, eventual ca avem deja scrisi subalgoritmi pentru rezolvarea lor. Urmeaza sa consideram pe
rand fiecare subproblema in parte si sa proiectam (in acelasi mod) un subalgoritm pentru rezolvarea ei. In final,
se va descrie subalgoritmul de rezolvare al fiecarei subprobleme, dar si interactiunile dintre acesti subalgoritmi si
ordinea in care ei sunt folositi.

Notiunea de modul va fi definita in sectiunea urmatoare. Deocamdata intelegem prin modul orice
subalgoritm sau algoritmul principal. Legatura dintre module se prezinta cel mai bine sub forma unei diagrame
numita arbore de programare. Fiecarui modul ii corespunde in arborele de programare un nod, ai carui
descendenti sunt toate modulele apelate direct. Nodul corespunzator algoritmului principal este chiar nodul
radacina.

Arbore de programare

+---------+

| PRINC |

+---------+

+-------+ | +-------+

| | |

+-------+ +-------+ +-------+

|CITDATE| |CALCULE| |TIPREZ |

+-------+ +-------+ +-------+

+------+ | +---+

+----+ +----+ +----+

| M1 | | M2 | | M3 |

+----+ +----+ +----+

In multe carti metoda top-down este intalnita si sub denumirea stepwise-refinement, adica rafinare
in pasi succesivi. Este vorba de un proces de detaliere pas cu pas a specificatiei, denumit proiectare
descendenta. Algoritmul apare in diferite versiuni succesive, fiecare fiind o detaliere a versiunii precedente.

Scopul urmarit este acelasi: concentrarea atentiei asupra partilor importante ale momentului si amanarea
detaliilor pentru mai tarziu. Daca ar fi necesar sa le deosebim am spune ca metoda top-down se refera la nivelul
macro iar metoda rafinarii succesive la nivel micro. La nivel macro se doreste descompunerea unei probleme
complexe in subprobleme. La nivel micro se doreste obtinerea unui modul in versiune finala. I
Programarea structurata este un stil de programare aparut in urma experientei primilor ani de activitate. Ea
cere respectarea unei discipline de programare si folosirea riguroasa a catorva structuri de calcul. Ca rezultat se
va ajunge la un algoritm usor de urmarit, clar si corect.

Termenul programare, folosit in titlul acestei sectiuni si consacrat in literatura de specialitate, este folosit
aici in sens larg si nu este identic cu cel de programare propriu-zisa. Este vorba de intreaga activitate depusa
pentru obtinerea unui program, deci atat proiectarea algoritmului cat si traducerea acestuia in limbajul de
programare ales.

Bohm si Jacopini au demonstrat ca orice algoritm poate fi compus din numai trei structuri de calcul:

- structura secventiala;

- structura alternativa;

- structura repetitiva.

Knuth considera programarea structurata ca fiind un mijloc de a face produsele program mai usor de citit. De
asemenea, programarea structurata este definita ca fiind programarea in care abordarea este top-down,
organizarea muncii este facuta pe principiul echipei programatorului sef, iar in proiectarea algoritmilor se
folosesc cele trei structuri de calcul definite de Bohm-Jacopini.

Alti autori considera programarea structurata nu ca o simpla metoda de programare ci ansamblul


tuturor metodelor de programare cunoscute. Dar programarea modulara, programarea top-down, sau bottom-
up (ascendenta sau descendenta) au aparut inaintea programarii structurate. Important este faptul ca
programarea structurata presupune o disciplina in activitatea de programare.

Consideram ca programarea structurata se poate intalni:

- la nivel micro, privind elaborarea unui subalgoritm;

- la nivel macro, privind dezvoltarea intregului produs informatic (algoritm).

La nivel micro programarea structurata este cea in care autorul este atent la structura fiecarui modul in
parte, cerand claritate si ordine in scriere si respectarea structurilor de calcul definite mai sus.

La nivel macro programarea structurata presupune practicarea proiectarii top-down, a programarii


modulare si a celorlalte metode de programare, cerand ordine in intreaga activitate si existenta unei structuri
clare a intregii aplicatii, precizata prin diagrama de structura a aplicatiei.

In acest scop am definit limbajul Pseudocod, care are structurile de calcul mentionate. Schemele logice
obtinute dintr-o descriere in Pseudocod a unui algoritm, conform semanticii propozitiilor Pseudocod, se
numesc D-scheme (de la Dijkstra) sau scheme logice structurate.

1 Un program muzical

#include <stdio.h>
#include <dos.h>
#include <conio.h>
main(){ /* Do do# Re re# Mi Fa fa# sOl sol# La la# Si */
int octava[]={65 , 69 , 73 , 78 , 82 , 87 , 92 , 98 , 104 , 110 , 116 , 123};
int i,j,nr_octava,i_nota,timp=500;
float masura,durata,durata_masura;
char *linia="42$2R2R4M4F2O2L1R2R2S2S4L4O2O2"; //$4D2D4$3S4L2";

do{
masura=(float)(linia[0]-'0')/(linia[1]-'0');durata_masura=0;
for(i=2;linia[i]!='\0';i++){
if (i%2==0){
switch(linia[i]){
case '$' : {nr_octava=1;for(j=linia[++i]-'0';j>0;j--)nr_octava*=2;}
break;
case 'D' : i_nota=0;break;
case 'd' : i_nota=1;break;
case 'R' : i_nota=2;break;
case 'r' : i_nota=3;break;
case 'M' : i_nota=4;break;
case 'F' : i_nota=5;break;
case 'f' : i_nota=6;break;
case 'O' : i_nota=7;break;
case 'o' : i_nota=8;break;
case 'L' : i_nota=9;break;
case 'l' : i_nota=10;break;
case 'S' : i_nota=11;break;
}
} else {
if (linia[i]=='6') durata=1/16; else durata=1/(float)(linia[i]-'0');
durata_masura+=durata;
if (durata_masura>masura) { nosound();durata_masura=0;}
sound(nr_octava*octava[i_nota]);
delay(durata*timp);
} /* else */
} /* for */
} /* do */
while (!kbhit());
nosound();
}

S-ar putea să vă placă și