Documente Academic
Documente Profesional
Documente Cultură
Curs 1
Data
Prin dată se întelege un număr, mărime, relaţie etc., reprezentarea unui fenomen, lucru
sau fapt, fiind susceptibilă de a fi memorată, transformată sau vehiculată.
Alte definiţii pentru dată (N. Pelin, 2015):
date = materii prime de informaţii;
date = unele detalii (fapte, idei), care pot fi înregistrate, transmise, prelucrate şi
afişate. Datele nu dispun neapărat de sens în contextul problemei date.
date = un set de fapte obiective, eterogene despre un proces sau un eveniment care
au o utilitate redusă dacă nu sunt transformate în informaţii.
Informaţie
În sens larg, informaţia desemnează un element nou, necunoscut anterior. În sens
restrâns, informaţia poate fi definită ca o măsură a incertitudinii înlăturate prin realizarea unui
eveniment dintr-un set de evenimente posibile.
Alte definiţii pentru informaţie (N. Pelin, 2015):
informaţii = date care după prelucrare, au obţinut sens în contextul problemei
date.
informaţii = date înzestrate cu relevanţă şi scop.
Cunoştinţe
Putem defini cunoştinţele ca fiind informaţii înregistrate şi verificate în practică şi care
de mai multe ori pot fi utilizate în luarea deciziilor.
Alte definiţii pentru cunoştinţe (N. Pelin, 2015):
cunoştinţe = informaţii citite, auzite sau văzute şi înţelese.
cunoştinţe = o combinaţie fluidă de experienţe, valori, informaţii contextuale şi
intuiţie care oferă cadrul pentru evaluare şi încorporarea unor noi experienţe şi
informaţii.
Calculatorul este sistemul fizic care prelucrează datele introduse într-o formă prestabilită
şi furnizează rezultate fie într-o formă accesibilă utilizatorului, fie ca semnale destinate acţionării
unor echipamente. În functie de modul de reprezentare a datelor există:
· calculatoare numerice – datele sunt codificate numeric;
· calculatoare analogice – pentru codificare se utilizează elemente de tip continuu;
· calculatoare hibride – se îmbină elemente de tip numeric cu elemente de tip continuu.
În acest curs ne referim la calculatoarele numerice.
1
Fundamentele programării 2020-2021
Curs 1
Informatica este ştiinţa pluridisciplinară având ca scop proiectarea, dezvoltarea şi
exploatarea unor tehnici şi sisteme, pentru organizarea, memorarea şi distribuirea mai eficientă a
informaţiei.
1. Analiza problemei. În această primă etapă, se are în vedere stabilirea modelului logico-
matematic al problemei. Se identifică funcţia (sau funcţiile) problemei, se stabilesc datele de
ieşire şi datele de intrare. Această etapă are ca scop înţelegerea cât mai exactă a problemei
pe care dorim să o rezolvăm.
2. Etapa de proiectare a algoritmului are ca scop stabilirea algoritmului de rezolvare a
problemei. Este etapa în care se precizează operaţiile pe care trebuie să le execute un
calculator, pentru a rezolva problema. În cazul problemelor complexe este recomandabilă
descompunerea problemei în subprobleme care pot fi rezolvate mai uşor. Această tehnică,
numită top-down (de sus în jos) va avea ca efect final obţinerea unui sistem de module
conectate între ele după logica impusă de rezolvarea problemei. Practic, într-o primă fază se
obţine o schiţă generală de rezolvare care se rafinează succesiv până la obţinerea comenzilor
necesare execuţiei pe calculator. Împărţirea problemei în subprobleme prezintă avantajul
lucrului eficient în echipă - subproblemele pot fi rezolvate simultan de către membrii
echipei. De asemenea, obţinerea modulelor program prin implementarea fiecărei
subprobleme uşurează munca de depanare (depistarea erorilor) şi de întreţinere a
programului.
3. Descrierea algoritmului. Pe măsură ce creşte complexitatea problemelor de rezolvat, creşte
şi dificultatea de a descrie algoritmii cât mai exact, fără ambiguităţi, utilizând un limbaj
natural. Din acest motiv s-au imaginat diferite forme de descriere (diagrama de structură,
scheme logice, pseudocod etc.) care, pe de o parte permit reprezentarea corectă a
algoritmilor într-o manieră prietenoasă, naturală, iar pe de altă parte facilitează codificarea
cu uşurinţă într-un limbaj de programare.
4. Codificarea algoritmului. Se codifică algoritmul într-un limbaj de programare. Practic, în
această etapă, se obţine programul care rezolvă problema. În etapa de codificare, o
2
Fundamentele programării 2020-2021
Curs 1
importanţă deosebită trebuie acordată stilului de programare. În general, se consideră că
stilul de programare este bun dacă îndeplineşte condiţii cum ar fi:
programul este însoţit de documentaţia de analiză şi proiectare care trebuie să conţină
elementele minimale - descrierea funcţiilor programului (ce face programul), diagrama
de structură (schiţa generală a programului), tabele de descriere a variabilelor de
intrare şi ieşire etc.
există procedee de validare a datelor. Se pot utiliza diverse procedee (sume de control,
coduri autocorectoare, intervale de apartenenţă, scrierea cu ecou, verificarea unor
relaţii existente între variabilele de intrare etc.)
programul este lizibil. Acest lucru se poate realiza prin folosirea scrierii indentate (în
fierăstrău) a liniilor de program, prin alegerea unor nume sugestive pentru
identificatori, prin folosirea (neabuzivă) a comentariilor etc.
5. Etapa de testare este etapa în care se elimină erorile programului. Erorile pot fi de natură
sintactică sau de natură logică. Eliminarea erorilor de natură sintactică se face în urma
listelor de erori afişate de compilatoare. Stabilirea corectitudinii din punct de vedere logic a
programului se poate face prin demonstraţie matematică sau prin testarea programului
folosind date de test.
Deoarece demonstrarea matematică a corectitudinii este dificilă, chiar şi pentru programe
mici, metoda uzuală este metoda datelor de test. Datele de test trebuie alese cu grijă, astfel
încât să se poată valida toate ramurile programului. Dacă sunt erori se încearcă localizarea
acestora folosind diverse metode. O metodă rapidă constă în afişarea unor mesaje din loc în
loc, prin analiza cărora se poate stabili zona unde se află eroarea. Mediile actuale de
programare, care asistă programatorul în punerea la punct a programului oferă şi alte
facilităţi de depanare, cum ar fi: ferestre de observare în timpul execuţiei a unor variabile şi
expresii, execuţia pas cu pas a liniilor de program, puncte de oprire a execuţiei etc.
6. Execuţia programului. O dată îndepărtate erorile de sintaxă şi de logică, programul poate fi
executat. Rezultatele obţinute sunt analizate, şi în urma interpretării lor se iau deciziile
corespunzătoare.
7. Întreţinerea programului. În mod normal, programele sunt proiectate spre a fi rulate
(executate) la diverse intervale de timp, pe seturi de date diferite. Aplicaţia informatică
obţinută din unul sau mai multe programe trebuie privită şi ca un produs care suferă în timp
un proces de uzură morală. Activitatea de întreţinere a programului constă în modificări ale
programelor ori de câte ori este nevoie, făcute tocmai în scopul de a menţine aplicaţia la zi.
Depanarea şi întreţinerea programelor sunt două din activităţile care pun în lumină, în cel
mai înalt grad, importanţa unui stil bun de programare. Este evident că aceste activităţi vor fi
îngreunate dacă programele nu sunt suficient documentate, dacă lipsesc comentariile,
scrierea este neindentată, etc.
3
Fundamentele programării 2020-2021
Curs 1
Paradigme de programare
Limbajele de programare pot fi clasificate în funcţie de paradigma de programare
utilizată.
Paradigma este o idee, un set de reguli care precizează modul în care se construiește un
program într-un anume limbaj de programare
În articolul “The Paradigms of Programming”, R. Floyd defineşte noţiunea de paradigmă
de programare ca fiind o metodă de conceptualizare a modului de execuţie al calculelor într-un
calculator, precum şi a modului de structurare şi organizare al taskurilor responsabile cu execuţia
calculelor. O noţiune des utilizată în locul celei de paradigmă de programare este cea de stil de
programare.
Principalele paradigme de programare, descrise pe scurt, sunt (A. Vancea, 2018):
Programare procedurală si structurală - un program este privit ca o mulţime ierarhică
de blocuri şi proceduri. Bloc = Instrucţiune Compusă + Declaraţii Locale (Variabile)
- Limbaje de programare: Pascal, Modula, Delphi, C, C++, etc.
Programare orientată pe obiecte - un program este constituit dintr-o colecţie de
obiecte care interacţionează, fiecare obiect fiind înzestrat cu un comportament propriu şi
bine definit.
- Limbaje de programare: C++, Java, C#, Python, etc.
Programare bazată pe evenimente (event-driven) - fluxul de execuţie este bazat pe
apariţia unor evenimente externe programului şi pe reacţia programului la acestea.
- Limbaje de programare: C#, Java etc.
Programare imperativă – utilizarea unor instrucţiuni ce modifică starea unui program.
La fiecare moment de timp programul are o altă stare în funcție de instrucțiunile care sunt
executate.
- Limbaje de programare: C, C#, Pascal, Fortran, etc.
Programare funcţională - un program este descris pe baza unor funcţii matematice, a
căror stare nu se schimbă, utilizate de obicei recursiv.
- Limbaje de programare: Lisp, Miranda, Haskell, Erlang etc.
Programare logică - un program este un set de propoziții, într-o formă logică, în care se
exprimă fapte și reguli despre o problemă. Astfel se descrie ce anume este o soluție
pentru acea problemă, nu modul în care se ajunge la ea. Soluția este căutată în mulțimea
de fapte, cu ajutorul setului de reguli.
- Limbaje de programare: Prolog, ALF, Godel, etc.
Programare paralelă – execuţia unui program este constituită din acţiuni multiple
posibil a fi executate în paralel pe una sau mai multe maşini. Execuţia acestor acţiuni
poate fi independentă (execuţie pur paralelă) sau acţiunile pot depinde una de alta
(execuţie concurentă).
- Extensii paralele concurente şi distribuite ale limbajelor imperative:
Concurrent C, Parallel C, Super Pascal
- PVM (Parallel Virtual Machine) - suport pentru C, Fortran, Java
Programare generică - presupune definirea unor modele (şabloane, template-uri) care
ajută la reutilizarea codului. Practic, se scrie cod fără a lua în considerare tipul datelor
pentru care se va utiliza apoi acel cod. Tipul datelor se specifică în momentul utilizării
codului respectiv.
- Limbaje de programare: C++, C#, Java, Python etc.
4
Fundamentele programării 2020-2021
Curs 1
Trebuie subliniat că majoritatea limbajelor de programare sunt multi-paradigmă. Foarte
puţine limbaje de programare sunt “pure” în ceea ce priveşte paradigma folosită. De exemplu,
limbajul C++ permite utilizarea mai multor paradigme de programare: procedurală, orientată pe
obiecte, funcţională, generică.
În figura următoare, preluată de pe TIOBE Index se poate observa că, deşi popularitatea
unui limbaj poate varia semnificativ în timp, anumite limbaje se menţin totuşi în topul
preferinţelor pieţei, de exemplu Java sau C.
5
Fundamentele programării 2020-2021
Curs 1
Ceva interesant. Primul pas tradiţional în învăţarea unui nou limbaj de programare este
acela în care pe ecran apare textul “Hello World”. Primul program Hello World este considerat
cel introdus în cartea despre C (Kernighan & Ritchie, prima editie ‘The C Programming
Language’, 1978).
http://www.mycplus.com/featured-articles/hello-world-programs-in-300-programming-
languages/ - cum se afişează “Hello World!” în peste 300 de limbaje de programare.
6
Fundamentele programării 2020-2021
Curs 2
Capitolul 2. Algoritmi
1
Fundamentele programării 2020-2021
Curs 2
Limbajul natural nu este cea mai potrivită metodă de reprezentare a unui algoritm, polisemia
unor termeni creând ambiguitate.
De multe ori, descrierea unui algoritm nu se face direct într-un limbaj de programare, ci se
utilizează anumite convenţii de reprezentare (schema logică şi pseudocodul). Acestea au
avantajul lipsei de formalism, fiind mai intuitive, şi permit reprezentarea cu claritate a
structurilor algoritmice fundamentale.
Schema logică
Schema logică este un mod grafic de reprezentare a algoritmului, ce foloseşte simboluri
grafice pentru desemnarea operaţiilor ce vor fi executate, fluxul datelor fiind specificat prin
săgeţi.
Schema logică utilizează simbolurile grafice prezentate în tabelul următor:
Pseudocodul
Pseudocodul este un mod narativ de reprezentare a algoritmului, ce utilizează, pentru
specificarea instrucţiunilor, cuvinte cheie, denumite coduri. Acestea permit reprezentarea cu
claritate a structurilor algoritmice fundamentale. În raport cu schema logică, pseudocodul
prezintă avantajul existenţei unor cuvinte cheie şi pentru structurile de iteraţie.
2
Fundamentele programării 2020-2021
Curs 2
Schemă logică Pseudocod
var expr
unde var este o variabilă, iar expr este o expresie. Se citeşte: “variabilei var i se atribuie
valoarea expresiei expr”, sau “var primeşte valoarea lui expr”.
Decizia este reprezentată în schemă logică şi pseudocod în figura 2.1., unde condiţie
este o expresie logică, iar secvenţă1 şi secvenţă2 sunt două secvenţe de instrucţiuni.
Figura 2.1.
Efectul este următorul: dacă este îndeplinită condiţia condiţie, se execută secvenţa de
instrucţiuni secvenţă1, apoi se trece la următoarea instrucţiune, iar dacă nu, se execută secvenţa
secvenţă2, apoi se trece la instrucţiunea următoare.
3
Fundamentele programării 2020-2021
Curs 2
Iteraţia cu test iniţial este prezentată în schemă logică şi în pseudocod în figura 2.2., unde
condiţie este o expresie logică, ce se evaluează la valoarea adevărat sau fals, iar secvenţă
este o secvenţă de instrucţiuni, numită corpul ciclului.
Figura 2.2.
secvenţă
NU DA
condiţie secvenţă
Efectul este următorul: se execută secvenţa în mod repetat cât timp expresia condiţie se
evaluează la valoarea adevărat. Din figura 2.2. se observă că testul de continuare a repetării se
află situat înaintea secvenţei ce urmează a fi repetată. De aceea numărul minim de repetări ale
acesteia este zero.
Observaţii.
dacă din start condiţie ia valoarea Fals, secvenţa nu se execută niciodată;
secventa trebuie să conţină enunţuri care să conducă la modificarea valorii de adevăr a
condiţiei după un număr finit de paşi (condiţia trebuie sa ia valoarea Fals la un moment dat
pentru a se ieşi din ciclu, în caz contrar se produce o buclă eternă sau un ciclu infinit).
Decizia cu ramură vidă este reprezentată în schemă logică şi în pseudocod în figura 2.3.,
unde condiţie este o expresie logică, iar secvenţă este o secvenţă de instrucţiuni.
Efectul este următorul: dacă este îndeplinită condiţia condiţie, se execută instrucţiunile
din secvenţă, iar dacă nu, se trece la instrucţiunea următoare.
4
Fundamentele programării 2020-2021
Curs 2
Figura 2.3.
Schemă logică Pseudocod
NU conditie
DA DACĂ condiţie ATUNCI secvenţă
secvenţă
Decizia generalizată permite alegerea (selecţia) unei alternative din mai multe posibile.
Forma generală a selecţiei este următoarea:
Figura 2.4.
Schemă logică Pseudocod
5
Fundamentele programării 2020-2021
Curs 2
De regulă, în limbajele de programare se implementează alături de iteraţia cu test iniţial şi
următoarele două forme echivalente: iteraţia cu contor (structura Pentru) şi iteraţia cu test
final (structura Repetă .. Cât Timp).
Iteraţia cu contor este un caz particular al iteraţiei cu test iniţial. Rolul central îl ocupă o
variabilă specială, numită variabilă contor, care poate fi iniţializată, actualizată şi poate
controla valoarea logică a condiţiei. Actualizarea înseamnă modificarea cu o valoare
constantă (numită raţie sau pas) a variabilei contor. Reprezentarea în schemă logică şi în
pseudocod a iteraţiei cu contor este prezentată mai jos:
Figura 2.5.
iv1
NU DA
i v2
Observaţie. Situaţia descrisă în schema logică de mai sus se referă la cazul vi vf şi raţia
r 0. Când vi vf şi r 0 condiţia de test se schimbă în v vf.
Structura de control Pentru se foloseşte când se cunoaşte numărul de iteraţii.
Observaţiile făcute la structura Cât timp rămân valabile şi aici.
Iteraţia cu test final este prezentată în schemă logică în figura 2.6., unde condiţie este o
expresie logică, iar secvenţă este o secvenţă de instrucţiuni, numită corpul ciclului.
Figura 2.6.
DA
condiţie
NU
6
Fundamentele programării 2020-2021
Curs 2
Efectul este următorul: se execută secvenţa în mod repetat cât timp valoarea expresiei
condiţie este adevărat. Din figura 2.5. se observă că testul de oprire a repetărilor este plasat după
secvenţa ce trebuie repetată şi de aceea numărul minim de repetări este unu.
START
Citeşte a, b, c START
CITEŞTE a,b,c
E (a+b+c) / 3
E(a+b+c)/3
SCRIE E
Scrie E
STOP
STOP
7
Fundamentele programării 2020-2021
Curs 2
Aplicaţia 2: Să se reprezinte în schemă logică şi în pseudocod, algoritmul pentru rezolvarea
ecuaţiei de gradul I.
ax b 0; a,b R
Rezolvare:
Datele problemei sunt prezentate în tabelul 1.2.
Tabelul 1.2. Datele pentru rezolvarea ecuaţiei de gradul I
START
START
CITEŞTE a,b
Citeşte a, b
DACĂ a0 ATUNCI
NU
a0
DA x-b/a
SCRIE x
x - b/a
NU DA ALTFEL
b0
Scrie x
DACĂ b0 ATUNCI
STOP
STOP
8
Fundamentele programării 2020-2021
Curs 2
Aplicaţia 3: Să se realizeze schema logică şi pseudocodul pentru calculul factorialului unui
număr întreg n. (n!=1·2·3·...·n)
Rezolvare: Datele problemei sunt prezentate în tabelul 1.3.
Tabelul 1.3. Datele pentru calculul factorialului lui n
START
Citeşte n
START
p1 CITEŞTE n
i1 p 1
ii+1 pp*i
NU DA SCRIE p
in
STOP
Scrie p
STOP
Problema se poate rezolva folosind şi un alt tip de iteraţie (cu test iniţial sau cu test final),
dar întrucât se ştie de la început numărul de repetări ale actualizării produsului p, cea mai
avantajoasă structură este cea de iteraţie cu contor.
9
Fundamentele programării 2020-2021
Curs 3
C a fost inventat şi implementat prima dată în anii ’70 de către Dennis Ritchie,
programator de sistem la Bell Laboratories. C îşi are originea în limbajul BCPL (Basic
Computer Programming Language) care, prin perfecţionări şi dezvoltări succesive a
devenit limbajul B şi în final limbajul C.
Răspândirea iniţială a limbajului C se datorează folosirii sale în scrierea sistemului de
operare UNIX versiunea 2, în anul 1972.
Una din versiunile remarcabile ale limbajului C este cea furnizată împreună cu
versiunea a 5 a sistemului de operare UNIX. Această versiune este descrisă prima oară
în 1978, în cartea lui Brian Kernighan şi Dennis Ritchie intitulată The C
Programming Language. Cartea este cunoscută ca un punct de referinţă în evoluţia
limbajului, fiind asimilată cu un adevărat standard.
Prima standardizare ISO a limbajului C s-a făcut în anul 1990 (standard cunoscut cu
numele de ISO C). Standarde ulterioare au apărut în anii 1999, 2011, 2018. Fiecare
nouă versiune a venit cu câteva noi facilităţi, astfel încât de-a lungul anilor, limbajul C
şi-a păstrat popularitatea, fiind şi astăzi unul dintre cele mai cunoscute şi utilizate
limbaje de programare.
Anul Standard C
1972 Birth
1978 K&R C
1989/1990 ANSI C şi ISO C
1999 C99
2011 C11
2018 C18
1
Fundamentele programării 2020-2021
Curs 3
Prima categorie cuprinde limbajul cod-maşină şi limbajul de asamblare. Ambele sunt
specifice tipului de maşină de calcul pe care sunt implementate. Limbajul cod maşină este
limbajul alcătuit din acele instrucţiuni elementare care sunt înţelese şi executate de un anumit tip
de calculator. Limbajul de asamblare foloseşte în locul codurilor numerice reprezentări
simbolice, numite şi mnemonice, care uşurează munca de programare. Operaţiile limbajului de
asamblare sunt operaţii de bază ale calculatorului. El nu acceptă structuri de control şi date
structurate, dar permite adresarea simbolică a locaţiilor de memorie. Din aceste motive
programele în limbaj de asamblare sunt lungi şi se scriu anevoios, dar sunt performante din punct
de vedere al vitezei de execuţie şi al posibilităţilor de acces la resursele hardware.
A doua categorie, cea a limbajelor de nivel înalt, include limbaje precum Fortran,
Cobol, Visual Basic, Pascal, Python, Java, C# etc. O parte din trăsăturile lor comune se referă la
posibilitatea de a folosi structuri de control, date structurate, de a elabora cu uşurinţă programe
portabile (care se pot adapta uşor la implementarea pe diverse categorii de sisteme de calcul).
Limbajele din această categorie pierd însă calitatea esenţială a limbajelor de nivel coborât, aceea
de a exploata eficient resursele maşinii de calcul pe care sunt implementate.
Categoria limbajelor de nivel mediu îmbină trăsăturile principale ale limbajelor de nivel
înalt cu cele ale limbajelor de nivel coborât. Limbajul C este un limbaj de nivel mediu.
Limbajul C oferă posibilitatea organizării programelor în module şi permite
implementarea unor structuri de control şi tipuri de date care facilitează programarea structurată.
Ca şi limbajele de nivel înalt este uşor de învăţat şi de folosit, iar în plus are un număr foarte mic
de cuvinte cheie. Portabilitatea specifică limbajelor de nivel înalt este accentuată în C prin
folosirea funcţiilor de bibliotecă în realizarea operaţiilor de intrare/ieşire şi de prelucrare a
fişierelor. Numărul mic de cuvinte cheie şi prezenţa unei bogate familii de operatori permit
realizarea unor programe concise, cu un cod sursă relativ mic.
Compilatorul C este mai puţin sever în comparaţie cu majoritatea compilatoarelor
limbajelor de nivel înalt. Dacă la aceste trăsături adăugăm şi posibilitatea de a oferi facilităţi ale
limbajelor de nivel coborât (lucru cu adrese de memorie, accesarea regiştrilor, incrementări,
decrementări, apelul unor funcţii ale sistemului de operare) obţinem imaginea unui limbaj
puternic şi flexibil preferat în special de programatorii profesionişti.
Precizări. Faza de analiză a unei probleme evidenţiază uzual o funcţie principală şi mai
multe funcţii secundare ale acesteia. Rezultatul acestei faze îl constituie o reprezentare modulară,
care reflectă interdependenţa dintre funcţiile problemei. În principiu, orice program C este o
secvenţă de funcţii aflate la acelaşi nivel.
2
Fundamentele programării 2020-2021
Curs 3
directive preprocesor
declaratii globale
tip main(lista de parametri)
{
declaratii locale
instructiuni
}
3
Fundamentele programării 2020-2021
Curs 3
O funcţie C este alcătuită din antet şi un bloc de declaraţii şi instrucţiuni delimitat de
acoladele { şi }, numit şi corpul funcţiei. Antetul conţine numele funcţiei, tipul valorii returnate
(întreg, real etc.) şi o listă de parametri formali care poate fi eventual vidă.
O funcţie este definită dacă este prezentată complet, adică are forma:
antet
corpul funcţiei
Dacă se prezintă doar antetul funcţiei, se spune că funcţia este declarată. Declaraţia unei
funcţii poartă numele de prototip.
funcţie funcţie
apelantă apelată
apel functie
4
Fundamentele programării 2020-2021
Curs 3
Funcţiile standard ale limbajului se pot clasifica în: funcţii de intrare/ieşire, funcţii pentru
prelucrarea caracterelor, funcţii pentru prelucrarea şirurilor de caractere etc. În mod
corespunzător prototipurile acestor funcţii sunt grupate în fişiere speciale numite fişiere antet
sau header (au extensia <.h>). De exemplu, funcţiile matematice sunt grupate în fişierul antet
”math.h”, funcţiile de manipulare a şirurilor de caractere în ”string.h” etc. Pentru a utiliza o
funcţie standard în program trebuie cunoscut prototipul ei. Acest lucru este posibil prin
includerea fişierului antet în program utilizând directiva #include.
Un program C poate avea funcţiile editate într-un singur fişier (programe monofişier)
sau în mai multe fişiere (programe multifişier).
Observaţii:
Programul este alcătuit dintr-o singură funcţie, funcţia main() şi foloseşte funcţia
standard de scriere (ieşire) printf()cu prototipul în biblioteca standard de intrare -
ieşire <stdio.h>.
Valoarea returnată de funcţia main() (o valoare întreagă, de tip int) arată cum s-a
încheiat execuţia programului. Încheierea normală a execuţiei este reprezentată de
valoarea 0 returnată de funcţie. Dacă execuţia întâmpină probleme, procesul se va
încheia cu returnarea unei valori diferite de zero.
5
Fundamentele programării 2020-2021
Curs 4
Așa cum se întâmplă cu orice limbaj artificial, temelia pe care se clădesc programele C este
alcătuită din alfabet și vocabular (atomi lexicali). Combinând atomii lexicali după regulile
specifice de sintaxă se construiesc linii valide de program și, în final, programul.
Alfabetul limbajului este alcătuit dintr-o mulțime de simboluri care se pot clasifica în
simboluri afișabile și neafișabile.
Setul minim de simboluri afișabile (care pot fi reprezentate grafic) este alcătuit din:
litere mari ale alfabetului englez:
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
cifre zecimale: 0 1 2 3 4 5 6 7 8 9
liniuță de subliniere: _
semne de punctuație și semne speciale:
, . ; : ? ’ ( ) [ ] < > ” ! | \ / ~ # &
^ * - = + { } %
1
Fundamentele programării 2020-2021
Curs 4
Simbolurile neafișabile (fără echivalent grafic) sunt reprezentate prin secvențe escape
(de evitare) sau coduri backslash-caracter. Aceste simboluri reprezintă coduri ASCII care nu pot
fi citite de la tastatură. Folosirea lor în programe în locul echivalentelor ASCII este recomandată
din rațiuni de portabilitate.
Codurile backslash ale limbajului C sunt prezentate în Tabelul 4.2.
Observații:
Tab orizontal înseamnă saltul cursorului cu un număr de coloane, iar tab vertical saltul
cursorului cu un număr de linii.
Notația octală folosește cifre în baza 8 (adică 0,1,2,3,4,5,6,7), iar notația
hexazecimală cifre în baza 16 (adică 0,1,2,3,4,5, 6,7,8,9,A,B,C,D,E,F). Se
observă că literele A,B,C,D,E,F corespund respectiv numerelor 10,11,12,13,14,15.
Secvențele ”\ddd” permit scrierea oricărui caracter din setul ASCII
ca un număr octal format din trei cifre, iar secvențele ”\xdd” ca un număr hexazecimal format
din două cifre. De exemplu, caracterul backspace poate fi scris ca ”\010” sau ”\x08”.
2
Fundamentele programării 2020-2021
Curs 4
4.2. Vocabularul limbajului
Vocabularul limbajului este alcătuit din atomi lexicali. Aceștia reprezintă grupuri de
simboluri afișabile care primesc în timpul procesului de compilare o anumită semnificație.
Prezentăm mai jos următorii atomi lexicali:
identificatori (nume);
constante;
operatori;
semne de punctuație;
simboluri speciale;
IDENTIFICATORI
Un identificator reprezintă o secvență de litere, cifre, liniuțe de subliniere, primul
caracter din secvență fiind obligatoriu o literă sau liniuță de subliniere.
De exemplu, Cod_mat,cod_mat,y_1,ax,_ol sunt identificatori, în timp ce
x...1, a&b, 3xy nu sunt. În legătură cu identificatorii facem următoarele precizări:
În C se face deosebirea între literele mari și mici ale alfabetului. De exemplu, Cod_mat și
cod_mat reprezintă nume diferite;
Deși sunt permiși, este recomandabil ca identificatorii care încep cu liniuță de subliniere să
fie evitați. Ei pot coincide cu nume rezervate, invizibile programatorului, provocând erori;
Standardul C nu limitează numărul de caractere (lungimea) unui identificator. Un compilator
C va ignora însă caracterele aflate pe poziții mai mari decât un număr prestabilit.
Cuvintele cheie sunt cuvinte rezervate C care au o destinație prestabilită (nu pot fi
folosite ca nume de funcție sau variabilă). Standardul ANSI C (anul 1989) avea 32 de cuvinte
cheie, din care 27 au fost definite de varianta originală a limbajului C (standardul
Kernighan/Ritchie). În standardul C99 au mai fost adăugate încă 5 cuvinte cheie, iar altele 7 în
standardul C11. În cel mai nou standard la momentul actual, C18, nu au fost adăugate facilități
noi, ci doar unele corecții tehnice pentru C11.
În total, standardul C18 are 44 cuvinte cheie, prezentate în tabelul 4.3.
Tabelul 4.3 Cuvinte cheie în limbajul C (standardul C18)
Cuvinte cheie după standardul Kernighan / Ritchie:
auto break case char continue
default do double else extern
float for goto if int
3
Fundamentele programării 2020-2021
Curs 4
long register return short sizeof
static struct switch typedef union
unsigned while
Cuvinte cheie adăugate de standardul ANSI C:
const enum signed void volatile
Cuvinte cheie adăugate de standardul C99:
_Bool _Complex _Imaginary inline restrict
Cuvinte cheie adăugate de standardul C11:
_Alignas _Alignof _Atomic _Generic _Noreturn
_Static_assert _Thread_local
După cum se poate observa cuvintele cheie din C se scriu cu litere mici. Pe lângă
cuvintele cheie rezervate de standardul C, diverse tipuri de compilatoare C includ și cuvinte
cheie folosite în exploatarea eficientă a mediului de operare specific (facilități privind
programarea interlimbaje, accesarea întreruperilor etc.), numite cuvinte cheie extinse.
Cuvintele cheie extinse folosite cel mai des sunt:
asm cdecl far huge interrupt near
CONSTANTE
Constantele pot fi numere, caractere, șiruri de caractere; valoarea lor nu se schimbă în
timpul execuției unui program. În C există patru tipuri de constante: întreg, real, caracter, șir.
O constantă întreagă este un număr zecimal, octal sau hexazecimal care reprezintă o
valoare întreagă pozitivă. Dacă se doresc și reprezentări ale unor numere întregi negative se
adaugă semnul minus în fața constantei respective.
Constantele întregi zecimale sunt numere întregi scrise în baza 10 (de exemplu:
759,+38,6496), constantele octale sunt numere în baza 8 care încep, pentru identificare, cu
cifra zero (de exemplu: 012 sau 0765), iar constantele hexazecimale sunt numere în baza 16
care încep pentru identificare cu caracterele 0x (de exemplu: 0xA3, 0xBC1, 0x7E31).
Constantele reale sunt numere reale pozitive. Pentru a reprezenta valori reale negative se
plasează semnul minus în fața constantei. Semnul minus este tratat ca operator aritmetic. Există
două modalități de reprezentare o constantelor reale: în format F (cu punct zecimal) și în
format exponențial (formă științifică).
Reprezentarea în format F este reprezentarea uzuală pentru numere reale. De exemplu,
constantele 32.753,0.591,-4296.823, .69 sunt valori reale reprezentate în format F.
4
Fundamentele programării 2020-2021
Curs 4
Se observă că partea întreagă a reprezentării poate să lipsească atunci când este egală cu zero
(.69 este tot una cu 0.69).
O constantă reală în format exponențial are forma generală:
numar simbexp valexp
unde:
numar este o constantă întreagă sau o constantă reală în format F
simbexp este E sau e
valexp este o constantă întreagă pozitivă precedată sau nu de semnele + sau-
grupul simbexp valexp se interpretează ca fiind egal cu 10valexp.
Remarcă. În realitate între numar, simbexp, valexp nu apar spații. De exemplu,
numerele reale: 1.6*103, -2.6*10-4, 0.32*106, 423*104 se scriu, respectiv:
1.6E+3,-2.6E-4, 0.32e+6,423E+04.
O constantă caracter este o literă, cifră, semn de punctuație sau secvență escape
cuprinse între două apostrofuri. Exemplu de constante caracter: ’a’,’\n’,’\’’,’7’
reprezentând respectiv a, newline, apostrof, 7.
O constantă șir este o secvență de litere, cifre și simboluri incluse între ghilimele.
Exemple de constante șir: ”\n Acesta e un sir”, ”Str. Cameliei, nr.3” etc.
Constantele șir se memorează în octeți consecutivi (un octet pentru fiecare caracter). Sfârșitul
șirului este marcat de un octet nul (care conține ’\0’) ce se adaugă automat. Din acest motiv un
șir cu n caractere ocupă n+1 octeți consecutivi.
OPERATORI
Operatorii reprezintă combinații de semne speciale care arată modalitatea de prelucrare
sau atribuire a valorilor. Limbajul C posedă o bogată familie de operatori, fapt ce permite
elaborarea unor programe compacte.
În Tabelul 4.4 prezentăm lista operatorilor C și semnificația lor.
5
Fundamentele programării 2020-2021
Curs 4
6
Fundamentele programării 2020-2021
Curs 4
Observație. Caracterul op de la atribuirea compusă poate fi unul din semnele * / % +
- >> << & ^ rezultând corespunzător operatorii compuși *= /= %= += -= >>= <<=
= &= ^=.
Între semnele care alcătuiesc un operator compus nu trebuie să existe spațiu. De exemplu,
operatorul >= nu poate fi utilizat sub forma > = . De la regula de mai sus face excepție
operatorul condițional (?:) care are altă interpretare.
SEMNE DE PUNCTUA|IE
Semnele de punctuație folosite în C sunt: ... # : {}
SIMBOLURI SPECIALE
Orice simbol care nu aparține alfabetului C este considerat atom lexical. Astfel de semne
sunt: @ și $. Ele pot fi folosite în construcția constantelor caracter și a șirurilor. Ca exemplu,
considerăm constanta șir ”Am folosit semnul @”.
Atomii lexicali sunt separați în cadrul programului prin simboluri ale alfabetului cu rol de
separator. Separatorii sunt: spațiul, tabul (orizontal și vertical), sfârșit de linie, sfârșit de pagină,
comentariu.
Prin comentariu se înțelege orice succesiune de simboluri cuprinse între /* și */. De
asemenea, un comentariu poate fi scris după semnul // (linia ce începe cu // va fi tratată ca un
comentariu. Acest tip de comentariu nu necesită și marcarea sfârșitului său).
Comentariile nu sunt luate în considerare de compilator, ele servesc la documentarea
programului ușurând înțelegerea și depanarea lui. Un comentariu poate fi plasat oriunde în
program și se poate întinde pe unul sau mai multe rânduri.
Exemplu:
/*
Acest program calculeaza produsul a doua matrici. Matricile
de intrare A(4x3) si B(3x2) se citesc de la tastatura, iar
matricea produs rezultata este C(4x2).
*/
Comentariile nu pot fi imbricate (incluse unul în altul). De exemplu, următoarea
construcție este eronată:
/* Un comentariu care include
/* un comentariu inclus */
*/
Modul în care sunt aranjați atomii lexicali într-un program este impus de specificul
problemei și de regulile de sintaxă ale limbajului.
7
Fundamentele programării 2020-2021
Curs 5
Expresiile sunt combinații valide sintactic de date și operatori. Aici, prin date, înțelegem
deopotrivă constante și variabile. Spre deosebire de constante care sunt valori fixe, variabilele
semnifică valori care se pot modifica prin program.
În C, ca și în alte limbaje, datele sunt clasificate în tipuri de date. Există tipuri de date
fundamentale (numite și predefinite, simple sau de bază) și tipuri de date derivate. Tipurile
derivate (tablouri, pointeri, structuri, uniuni, enumerări și orice tip definit de programator) se
bazează pe tipurile fundamentale.
1
Fundamentele programării 2020-2021
Curs 5
Dacă această zonă o destinăm memorării doar a întregilor pozitivi, printr-un calcul simplu
se poate vedea că plaja de reprezentare este [0, 2N-1]. Dacă zona este destinată memorării
atât a întregilor cu semn cât și fără semn, bitul N-1 va fi folosit pentru reprezentarea semnului (0
pentru numere pozitive, 1 pentru numere negative), iar plaja de reprezentare va fi
[-2N-1,2N-1-1].
Având în vedere aceste considerații , de exemplu, o variabilă de tip signed int va avea un
domeniu de valori cuprins între -32768 și 32767, iar una de tip unsigned int va lua valori
între 0 și 65535.
Observații:
• Pentru tipul întreg de date (char, int, short, long) reprezentarea implicită este signed.
• Specificarea unui modificator fără tip înseamnă considerarea implicită a tipului int.
În tabelul 5.1. sunt prezentate tipurile de date întregi în limbajul C, împreună cu plaja de
reprezentare. Încă o dată, precizăm că dimensiunea unui tip de date depinde de varianta de
implementare a limbajului și de tipul de procesor folosit.
2
Fundamentele programării 2020-2021
Curs 5
Se pot obţine informaţii privind zona de memorie alocată unui tip de date cu ajutorul
operatorului sizeof, utilizat sub forma sizeof(tip). De asemenea, pentru a vedea exact
intervalul de valori corespunzător unui tip de date, se pot folosi constantele definite în
bibliotecile limits.h (pentru tipuri întregi) și float.h (pentru tipuri reale).
int main() {
3
Fundamentele programării 2020-2021
Curs 5
Exemplul 5.2. Tipuri de date reale
#include <stdio.h>
#include <float.h>
int main()
{
printf("Dimensiune pentru tipul float (octeti): %d \n", sizeof(float));
printf("Precizie float: %d\n", FLT_DIG );
printf("FLT_MAX : %g\n", FLT_MAX);
printf("FLT_MIN : %g\n", FLT_MIN);
printf("DBL_MAX : %g\n", DBL_MAX);
printf("DBL_MIN : %g\n", DBL_MIN);
return 0;
}
Începând cu standardul C99, a fost adăugat în limbaj un tip de date pentru valori logice
(adevărat şi fals). Tipul de date se numeşte _Bool. Adiţional, în biblioteca <stdbool.h> s-a
definit aliasul bool pentru acest tip de date, împreună cu definiţiile macro true şi false.
Se practică însă în continuare, destul de mult considerarea variabilelor logice ca fiind de tip
int, cu utilizarea convenţiei: orice expresie diferită de zero are valoarea adevărat, iar dacă e
egală cu zero, valoarea fals.
tip lista_de_variabile;
unde:
tip poate fi orice tip de date recunoscut în C, iar lista_de_variabile conține
unul sau mai mulți identificatori despărțiți prin virgulă. În exemplele de mai jos vom folosi doar
tipurile fundamentale.
float x,y;
int a,b1;
short a_x,b_y;
double z;
Orice variabilă folosită în program trebuie mai întâi declarată. Dacă pe linia de declarare
variabila este inițializată se spune că are loc o definire a variabilei.
4
Fundamentele programării 2020-2021
Curs 5
Exemple de declaraţii și definiții de variabile:
float x=38.981,I;
int ab=-453;
char ch=’A’,z;
este crearea constantei simbolice nume_variabila care poate fi utilizată, dar nu poate fi
modificată prin program. Dacă tip lipsește se consideră implicit că tipul este int. În exemplul
de mai jos se definesc două constante, constanta pi și constanta de tip int, ore_zi.
Exemplu:
const double pi=3.1415926536;
const ore_zi=24;
5
Fundamentele programării 2020-2021
Curs 5
5.4. Funcții uzuale de intrare/ieșire pentru consolă. Descriptori de format
Prezentăm în continuare funcțiile folosite frecvent pentru transferul de date de la tastatură
în memoria calculatorului (funcții de intrare) și din memoria calculatorului pe ecran (funcții de
ieșire); cu această ocazie introducem și descriptorii de format cei mai folosiți. Deosebim trei
categorii de funcții de intrare/ieșire pentru consolă:
int printf(sir_format,lista_de_argumente);
unde:
6
Fundamentele programării 2020-2021
Curs 5
%c caracter
%f numere reale în notație uzuală
%e sau %E numere reale în notație științifică (e sau E)
%x sau %X hexazecimal fără semn (litere mici sau majuscule)
%o octal fără semn
%s șir de caractere
%g sau %G se alege reprezentarea cu numărul cel mai mic de
caractere dintre cea în notație uzuală și cea în notație
științifică (de tip e sau E)
%p valoare pointer
7
Fundamentele programării 2020-2021
Curs 5
Exemplul 5.3. Afişarea valorilor şi sumei a două numere întregi, sub forma:
x=valoare y=valoare
suma=valoare
# include <stdio.h>
int main()
{
int x=10, y=-43;
printf ("\n\tx=%d\t\y=%d\n\t suma=%i", x,y, x+y);
return 0;
}
Exemplul 5.4. Afişarea unei constante întregi şi a valorilor sale în octal şi hexazecimal pe câte
un rând
#include <stdio.h>
int main()
{
const x=4529;
printf("\n numarul este=%d",x);
printf("\n valoarea in octal este=%o",x);
printf("\n valoarea in hexazecimal este=%x\n",x);
return 0;
}
Exemplul 5.5. Afişarea unui caracter şi a codului său ASCII; afişarea se va termina cu un semnal
sonor.
#include <stdio.h>
int main()
{
char a='Q';
printf("\n Caracterul %c are codul ASCII=%i\a",a,a);
return 0;
}
Exemplul 5.6. Afişarea unor valori folosind diverşi descriptori de format; comentariile arată
efectul execuţiei funcţiei printf().
#include <stdio.h>
int main()
{
char ch;
short k;
8
Fundamentele programării 2020-2021
Curs 5
int i;
long int j;
float x;
ch='A';
printf("\n Caracterul %c are codul ASCII = %i",ch,ch);
/* Caracterul A are codul ASCII = 65 */
k=250;
printf("\n k=%hu",k); /* k=250 */
i=4567;
printf("\n i=%i",i); /* i=4567 */
printf("\n i=%u",i); /* i=4567 */
printf("\n -i=%i",-i); /* -i=-4567 */
printf("\n i=%i",i); /* i=4567 */
printf(" are valoarea hexazecimala %x",i);
/* are valoarea hexazecimala 11d7 */
printf(" sau echivalent, %X",i);
/* sau echivalent, 11D7 */
printf("\n i=%i",i); /* i=4567 */
printf(" are valoarea octala %o",i);
/* are valoarea octala 10727 */
j=123456;
printf("\n j=%li",j); /* j=123456 */
x=76.5432;
printf("\n x=%f",x); /* x=76.543198 */
printf("\n x=%e",x); /* x=7.65320e+01 */
printf("\n x=%E",x); /* x=7.65320E+01 */
printf("\n x=%g",x); /* x=76.543200 */
printf("\n x=%G",x); /* x=76.543200 */
x=-0.123456789;
printf("\n x=%f",x); /* x=-0.123457 */
printf("\n x=%e",x); /* x=-1.234568e-01 */
printf("\n x=%E",x); /* x=-1.234568E-01 */
printf("\n x=%g",x); /* x=-0.123457 */
printf("\n x=%G",x); /* x=-0.123457 */
9
Fundamentele programării 2020-2021
Curs 5
completare să fie făcută cu cifra 0 în loc de spații, atunci, înainte de întregul care specifică
dimensiunea de afișare, se pune cifra 0.
De exemplu, descriptorul %7f semnifică afișarea unui număr real pe minim 7 coloane și,
completarea eventualelor coloane libere cu spații, iar %07f impune același număr minim de
coloane pentru afișare, însă completarea coloanelor libere se va face cu cifra 0.
Utilitatea precizării dimensiunii minime de afișare apare mai ales la afișarea tablourilor în care
alinierea se face pe coloane.
Programul următor ilustrează efectul precizării dimensiunii câmpului de afișare:
Exemplul 5.7. Afişarea unor valori folosind descriptori de format cu precizarea dimensiunii
câmpului de afişare
#include <stdio.h>
int main()
{
int i;
float x;
i=4567;
printf("\n i=%4i",i); /* i=4567 */
printf("\n i=%6i",i); /* i= 4567 */
printf("\n i=%3i",i); /* i=4567 */
printf("\n i=%06i",i); /* i=004567 */
x=76.123001;
printf("\n x=%10f",x); /* x= 76.123001 */
printf("\n x=%010f",x); /* x=076.123001 */
De exemplu, descriptorul %7.3f indică afișarea unui număr real pe minim 7 coloane și cu
3 cifre zecimale după virgulă. Lucrurile se petrec asemănător dacă în loc de %f se folosește %e
sau %E. Dacă se folosește unul din descriptorii %g sau %G specificatorul de precizie arată
minimul de cifre semnificative.
10
Fundamentele programării 2020-2021
Curs 5
Aplicat unui întreg, specificatorul de precizie arată numărul minim de cifre cu care va
apare afișat întregul respectiv (dacă întregul nu are suficiente cifre atunci se completează la
început cu numărul necesar de cifre 0).
Se poate impune alinierea la stânga a datelor plasând semnul – (minus) imediat după
semnul % în cadrul descriptorului.
#include <stdio.h>
int main()
{
int i;
double x;
x=76.123401;
printf("\n x=%10.3f",x); /* x= 76.123 */
printf("\n x=%-10.3f",x); /* x=76.123 */
printf("\n x=%3.7f",x); /* x=76.1234010 */
printf("\n x=%10.2e",x); /* x= 7.61e+01 */
printf("\n x=%-10.1E",x); /* x=7.6E+01 */
printf("\n x=%10.3g",x); /* x= 76.1 */
printf("\n x=%-10.4G",x); /* x=76.12 */
printf("\n %.4s","testare"); /* test */
printf("\n %10.4s","testare"); /* test */
printf("\n %-10.4s","testare");/* test */
printf("\n %-1.10s","testare");/* testare */
return 0;
}
int scanf(sir_format,lista_de_argumente);
unde:
• sir_format poate conține descriptori de format, caractere de spațiere albe, alte caractere;
• lista_de_argumente este de forma: &var1,&var2,...,&varn. Prin &v se înțelege
adresa variabilei v.
11
Fundamentele programării 2020-2021
Curs 5
Funcția întoarce numărul de argumente cărora li s-a atribuit o valoare sau constanta EOF
(egală de obicei cu –1) în caz de insucces.
Precizări:
• În marea lor majoritate descriptorii de format folosiţi la funcţia scanf() sunt identici
cu cei de la funcţia printf(); practic, din tabelul prezentat anterior obţinem o listă
validă pentru scanf(), îndepărtând %E,%X,%G. Folosiți cu scanf(), descriptorii
%f,%e,%g sunt echivalenţi.
• Lista de argumente este citită de la stânga la dreapta și asociată în această ordine
cu lista de descriptori. Fiecare descriptor arată funcţiei scanf() tipul valorii care se va
citi: întreg, real, șir, pointer etc.. Să observăm că aceste valori sunt transferate
variabilelor v1,v2,...vn prin intermediul adreselor &v1,&v2,...&vn.
• Este de menționat faptul că în cazul citirii unui şir, deoarece însuși numele şirului
reprezintă o adresă, operatorul de luare a adresei & nu va mai preceda obligatoriu
numele şirului.
• Ca şi în cazul funcţiei printf() descriptorii de format pot avea şi un modificator de
lungime maximă a şirului de caractere care va fi citit. De exemplu, apelul:
scanf(”%15s”,sir);
are drept consecinţă citirea a maximum 15 caractere din şirul de intrare şi atribuirea
şirului format variabilei sir. Dacă şirul de intrare are mai mult de 15 caractere,
caracterele în plus se ignoră; la un nou apel al funcţiei scanf() explorarea şirului de
intrare începe cu aceste caractere anterior ignorate.
• Caracterele albe de spaţiere în şirurile de intrare pot fi blankurile (spaţiile albe),
taburile (spaţii tab), sau caracterul linie nouă (tasta enter). Aceste caractere albe de
spaţiere sunt ignorate dacă în şirul format avem corespunzător între descriptori cel
puţin un spaţiu. De asemenea, orice alt caracter poate fi folosit ca separator în fluxul
de intrare, cu condiţia ca el să fie plasat corespunzător şi între descriptorii de format
din sir_format. Dacă această condiţie nu e îndeplinită, la prima neconcordanţă (de la
stânga la dreapta) între separatorii din fluxul de intrare şi cei din sir_format execuţia
funcţiei scanf() se încheie. De exemplu, apelul funcţiei scanf():
scanf(”%d,%f,%s”,&x,&y,sir);
realizează o atribuire corectă a datelor de intrare dacă ele sunt despărţite prin virgulă.
Pentru a atribui variabilei x valoarea 32, lui y valoarea 10.75 și variabilei sir valoarea
anI, în fluxul de intrare trebuie să avem 32,10.75,anI.
12
Fundamentele programării 2020-2021
Curs 5
Exemplul 5.9. Citirea numerelor reale x şi X de la tastatură, calculul produsului x*X şi afişarea
lui în format exponenţial.
#include <stdio.h>
int main()
{
float x,X;
printf("\n Tastati doua numere separate prin spatiu ");
scanf("%f %f",&x,&X);
X=X*x;
printf("\n Produsul X*x este = %e \n", X);
return 0;
}
Observații:
• X şi x sunt variabile diferite;
• X=X*x; este o expresie de atribuire care se poate scrie mai scurt sub forma X*=x; cu
ajutorul operatorului compus *=.
int getchar();
și întoarce următorul caracter care va fi citit. Dacă s-a atins sfrșitul șirului sau se produce o
eroare se întoarce EOF.
int getch(void);
int getche(void);
13
Fundamentele programării 2020-2021
Curs 5
Spre deosebire de funcția getchar() unde caracterul tastat este citit numai dacă se
apasă în continuare tasta Enter, funcțiile getch() și getche() preiau caracterul imediat după
ce a fost tastat (fără a mai apăsa Enter). De asemenea, funcția getch() preia caracterul de la
tastatură fără a-l afișa pe ecran, în timp ce getche() afișează pe ecran caracterul citit (citire cu
ecou).
Funcția pentru afișare putchar() are forma generală
Funcția întoarce în caz de succes caracterul scris, iar în caz contrar EOF.
Exemplul 5.10. Citirea si afişarea unui caracter folosind funcţiile speciale getche()şi
putch()
#include <stdio.h>
#include <conio.h>
int main()
{
char x;
printf("\n Tastati o litera! ");
x=getche();
printf("\n Multumesc! Ati tastat litera ");
putch(x);
printf("\n Mai tastati o litera pentru inchidere! ");
getch();
return 0;
}
14
Fundamentele programării 2020-2021
Curs 5
gets(sir_destinatie);
se citește un șir de la tastatură în sir_destinatie, iar apelul
puts(sir);
are ca efect afișarea șirului sir pe ecran.
Singurul operator ternar este operatorul condițional ?:. Operanzii săi sunt plasați după
schema operand1 ? operand2 : operand3.
CLASE DE PRECEDENŢĂ
Prioritățile operatorilor impun ordinea de evaluare a expresiilor. Ca și în calculele
algebrice obișnuite ordinea de evaluare poate fi modificată cu ajutorul parantezelor rotunde.
15
Fundamentele programării 2020-2021
Curs 5
Operatorii care au priorități egale, aparțin aceleiași clase de precedență. Lista
operatorilor grupați după clase de precedență este dată în Tabelul 5.3.
Tabelul 5.3 Clase de precedență
Clasa Operatori
1 (paranteze, op. de selecție) () [] -> .
2 (op.unari) ++ -- ! ~ - + & * sizeof cast
3 (op. multiplicativi) %/*
4 (op. aditivi) +-
5 (op. shift) << >>
6 (op. relaționali) < <= > >=
7 (op. relaționali) == !=
8 (“SI” pe bit) &
9 (“SAU” exclusiv bit cu bit) ^
10 (“SAU” bit cu bit) |
11 (“SI” logic) &&
12 (“SAU” logic) ||
13 (operator condițional) ?:
14 (atribuire) = += -= *= etc.
15 (secvențiere) ,
v=e;
unde v este un nume de variabilă, iar e este o expresie. În C, membrul stâng și membrul drept al
unei atribuiri se mai numesc valoare stângă (lvalue), respectiv valoare dreaptă (rvalue). Spre
deosebire de alte limbaje (Fortran, Pascal etc.) în C, operatorul de atribuire poate apare și în
interiorul unei expresii, fapt ce permite o compactare a codului sursă. De exemplu, două atribuiri
succesive de genul:
A=pi*r*r;
V=A*h;
pot fi scrise compact sub forma: V=(A=pi*r*r)*h;
Practic, ce am scris mai sus este o instrucțiune expresie. Rezultatul evaluării expresiei
(A=pi*r*r) este pi*r*r; după cum se vede, acest rezultat se poate folosi mai departe în
16
Fundamentele programării 2020-2021
Curs 5
calcule. Atribuirea valorii pi*r*r variabilei A apare ca un efect secundar al instrucțiunii
expresie A=pi*r*r;.
v=e;
v şi e să aibă tipuri diferite. În această situație au loc conversii de tip. Regula de conversie este
următoarea: valoarea membrului drept (valoarea lui e) se convertește la tipul membrului stâng
(tipul lui v). Deoarece sizeof (int) <= sizeof (float) <= sizeof (double), se spune că int este mai
„slab” decât float, care este la rândul său mai „slab” decât double. Dacă membrul stâng este de
un tip mai „slab” decât tipul membrului drept pot avea loc pierderi de informație (prin
trunchiere) sau depășirea posibilităților de reprezentare. De exemplu, în secvența:
int x,y;
float a,b;
.........
x=a;
b=y;
variabila x va primi partea fără fracție a valorii a sau un rezultat imprevizibil dacă se depășesc
posibilitățile de reprezentare, iar valoarea întreagă y va fi convertită la o valoare reprezentată în
virgulă mobilă.
x=y=z=s=0;
Efectul este atribuirea valorii 0 variabilelor x,y,z,s.
variabila operator=expresie;
De exemplu, expresia x=x+2; are același efect cu x+=2;
17
Fundamentele programării 2020-2021
Curs 5
Dacă v e un operand oarecare cazurile uzuale de atribuire v=v+1 și v=v-1 se pot scrie
simplificat sub forma v++ și respectiv v--. Operatorul ++ se numește operator de
incrementare, iar -- operator de decrementare. Funcție de poziția lor față de operand ei pot fi
operatori prefix sau postfix.
De exemplu:
Dacă operandul nu apare în cadrul unei expresii, după cum se vede din exemplul de mai
sus, nu are importanță dacă operatorii sunt prefix sau postfix. Când operandul apare în cadrul
unei expresii, dacă este precedat de operatorul ++ sau -- se execută întâi incrementarea sau
decrementarea operandului și apoi este folosit în expresie; dacă este însă urmat de operatorul ++
sau -- incrementarea sau decrementarea se va face după folosirea sa în expresie.
De exemplu,
iar
expresia y=x++; e echivalentă cu secvența y=x; x=x+1;
Să observăm că cele două secvențe vor produce aceeași valoare pentru x și valori diferite
pentru y.
int x=7,y=3,z,w;
. . . . .
z=(w=x<y,x+y);
18
Fundamentele programării 2020-2021
Curs 5
z va primi valoarea 10. Explicația este următoarea: se evaluează mai întâi expresia w=x<y, apoi
expresia x+y. Rezultatul evaluării expresiei x+y este 10, iar întreaga expresie (w=x<y,x+y)
va primi această valoare care se va atribui lui z.
Practic, operatorul virgulă oferă o modalitate elegantă de a scrie mai multe expresii în
secvență, sub forma compactă a unei singure expresii. În exemplul de mai sus, expresia
z=(w=x<y,x+y);
înlocuiește secvența
w=x<y;
z=x+y;
OPERATORI ARITMETICI
Limbajul C are următorii operatori aritmetici: -,+,*,/,%,--,++.
Semnificația lor rezultă din Tabelul 5.3 și din prezentarea făcută anterior operatorilor ++
și --. În plus vom face următoarele observații:
• aplicat unor operanzi întregi operatorul / va produce doar partea întreagă a împărțirii;
de exemplu, secvența:
. . .
int x,y;
x=7;
y=x/2;
. . .
va produce valoarea 3 pentru y.
• operatorul % aplicat unor operanzi întregi furnizează restul împărțirii acelor întregi;
de exemplu, secvența:
. . .
int x,y
x=7;
y=x%2;
. . .
produce pentru y valoarea y=1, adică restul împărțirii lui 7 la 2.
19
Fundamentele programării 2020-2021
Curs 5
OPERATORI RELAŢIONALI ŞI LOGICI
Operatorii relaționali din limbajul C sunt: <, >, <=, >=, ==, !=. Semnificația lor
rezultă din Tabelul 5.3. În urma evaluării unei expresii în care intervin operatori relaționali
rezultă valoarea 0 pentru fals și 1 pentru adevarat. Programul de mai jos afișează valorile
anunțate în comentariile alăturate:
Exemplul 5.11. Afişarea valorilor unor expresii în care intervin operatori relaţionali
#include <stdio.h>
int main()
{
float x=0, y=2.3;
printf("\n x<y are valoarea %d",x<y) ; /*1*/
printf("\n x<=y are valoarea %d",x<=y); /*1*/
printf("\n x>y are valoarea %d",x>y) ; /*0*/
printf("\n x>=y are valoarea %d",x>=y); /*0*/
printf("\n x==y are valoarea %d",x==y); /*0*/
printf("\n x!=y are valoarea %d",x!=y); /*1*/
return 0;
}
&& SI logic
|| SAU logic
Modul de acțiune al acestor operatori este prezentat în Tabelul 5.4.
Observații:
Din Tabelul 5.3 rezultă că operatorii logici && și || au o prioritate mai mică decât
operatorii relaționali. Din acest motiv:
20
Fundamentele programării 2020-2021
Curs 5
expresia a<=b||c>d e echivalentă cu (a<=b)||(c>d)
Dacă într-o expresie formată din operanzi legați prin operatorul || , iar valoarea
primului operand este 1, valoarea expresiei este 1 și ceilalți operanzi nu se mai evaluează. Dacă
într-o expresie formată din operanzi legați prin operatorul &&, primul operand ia valoarea 0,
valoarea expresiei este 0 iar ceilalți operanzi nu se mai evaluează.
a=c<d||d>=c&&a<3;
& SI pe bit
| SAU pe bit
^ SAU exclusiv pe bit
~ NU pe bit (complement față de 1)
>> deplasare dreapta (shift dreapta)
<< deplasare stânga (shift stânga)
Modul de acțiune al primilor 4 operatori pe bit e rezumat
în Tabelul 5.5.
21
Fundamentele programării 2020-2021
Curs 5
Tabelul 5.5 Tabla valorilor de adevăr pentru operatorii pe bit &,|,^ și ~
a b a&b a|b a^b ~a
0 0 0 0 0 1
0 1 0 1 1 1
1 0 0 1 1 0
1 1 1 1 0 0
Asemănarea dintre Tabelul 5.4 și Tabelul 5.5 nu trebuie să ne inducă în eroare. Operatorii
logici tratează valorile 0 și 1 ale operanzilor ca pe valori logice, iar operatorii pe bit tratează
operanzii ca succesiuni de biți.
Să considerăm două valori x=5 şi y=36 și reprezentările lor în baza 2, adică x=0000
0101 și y=0010 0100. Avem:
0000 0101
0010 0100 0000 0101
x^y= 0010 0001 ~x= 1111 1010
Observaţii:
• Operatorul & reprezintă o modalitate de a elimina un bit (de a-l face egal cu 0) sau
de a reţine biţii care ne interesează (operaţie de mascare).
• În operaţiile de transmitere a datelor, bitul superior (cel mai din stânga) al unui octet
este considerat de cele mai multe ori bit de paritate. Printr-o operaţie de setare la 1 a
acestui bit sau de anulare a sa, se poate obţine un număr par sau impar de biţi în octetul
respectiv funcţie de convenţia de paritate acceptată (paritatea poate fi pară sau impară).
Utilizarea parităţii, permite verificarea corectitudinii octetului transmis. Arătăm mai jos
cum se poate modifica bitul de paritate:
Exemplu:
1. Anularea bitului de paritate al unui octet.
1000 0101 &
0111 1111
0000 0101
22
Fundamentele programării 2020-2021
Curs 5
2. Setarea la valoarea 1 a bitului de paritate a unui octet.
0000 0101 |
1000 0000
1000 0101
Observăm că anularea bitului de paritate s-a făcut prin “înmulţire” cu numărul 127
(0111 1111 în baza 2), iar setarea la valoarea 1 a aceluiaşi bit prin ”adunare” cu
numărul 128 (1000 0000 în baza 2).
• Datorită posibilităţii de a modifica valorile biţilor, operatorii pe bit se folosesc mai ales în
proiectarea programelor de interfaţă cu dispozitive periferice.
• O altă aplicaţie interesantă se referă la codificarea şi decodificarea unui fişier. O modalitate
simplă este folosirea operatorului ~, pornind de la observaţia că ~(~x)=x, pentru x întreg
arbitrar. Deci, dacă există un program de codificare, la prima sa rulare toţi biţii nuli devin 1,
iar toţi biţii de 1 devin nuli (codificarea). La o nouă rulare este evident că se obţine fişierul
iniţial (decodificarea).
• O posibilitate mai puternică de codificare şi decodificare este operatorul ^. În acest caz
pentru aceste operaţii se foloseşte o cheie.
Reluând exemplul cu valorile
0000 0101
0010 0100
0010 0001
0010 0100
și
23
Fundamentele programării 2020-2021
Curs 5
Aici, numar_intreg se referă la numărul de poziţii cu care vor fi deplasaţi spre stânga,
respectiv spre dreapta biţii variabilelor.
Deplasarea biţilor spre un capăt sau altul poate produce pierderea unui număr de biți de la
respectiva extremitate. Cu ce se completează biții rămași liberi?
Dacă deplasarea este spre stânga, biții liberi din dreapta se completează cu 0.
Dacă deplasarea este spre dreapta, biții liberi din stânga se completează automat cu 0
numai dacă numărul este fără semn. Dacă numărul este negativ, din neccesitatea de a conserva
semnul (reprezentat în bitul cel mai semnificativ cu 1), biții liberi din stânga se completează cu 1.
Exemplu. Să considerăm x=-9. Reprezentarea sa în binar se face folosind codul
complementar față de 2. Acesta se obține adunând la complementul față de 1 al numărului
valoarea 1. Deci, valoarea -9 se va obține ca ~9+1. Suita de operații în binar este următoarea:
Exemplu:
Dacă x=7 atunci:
24
Fundamentele programării 2020-2021
Curs 5
Conversii de tip implicite
Limbajul C oferă posibilitatea de a construi expresii cu date de tipuri diferite. Din acest
motiv există un set de reguli de conversie a operanzilor la tipul operandului cel mai “tare”.
Aceste reguli sunt cunoscute sub numele de avansare de tip (type promotion). Iată setul de
reguli:
(float)i/2;
unde i a fost definit prin
int i=3;
este 1.5. Dacă în aceleași condiții se evalua expresia i/2; rezultatul ar fi fost trunchiat la 1 (se
împărțeau doi întregi).
25
Fundamentele programării 2020-2021
Curs 6
expresie;
unde expresie are efect lateral (conţine o atribuire sau reprezintă un apel de funcţie).
. . . . . . .
x=(a+b)*c;
x+=2;
p++;
6.2.1. Instrucțiunea if
if(conditie)
instructiune_1;
else
instructiune_2;
1
Fundamentele programării 2020-2021
Curs 6
Efectul instrucţiunii este următorul: dacă expresia conditie este adevărată (diferită
de zero) se execută instructiune_1 în caz contrar (conditie este egala cu zero) se
execută instructiune_2. Figura 6.1 ilustrează modul de execuţie al instrucţiunii if.
#include <stdio.h>
#include <math.h>
int main()
{
float x,y;
printf("Introduceti x=");
scanf("%f",&x);
if (x<0)
printf("\nCalcul imposibil");
else
{
y=sqrt(x);
printf("\nRadical din %.2f = %f",x,y);
}
return 0;
}
else;
sau şi mai simplu
if(expresie)
instructiune;
2
Fundamentele programării 2020-2021
Curs 6
formă cunoscută sub numele de if cu ramură vidă. Instrucţiunea instructiune se execută numai
dacă expresia este adevărată, adică se execută condiţionat.
#include <stdio.h>
int main()
{
float x,y,max;
printf("x=");
scanf("%f",&x);
printf("y=");
scanf("%f",&y);
max=x;
if (max<y)
max=y;
printf("Maximul dintre x=%.2f si y=%.2f este = %.2f",x,y,max);
return 0;
}
3
Fundamentele programării 2020-2021
Curs 6
Pentru n mare, folosind o aliniere strictă, se ajunge la o structură cu adâncime mare (mult
if(expr1)
instructiune1;
else if(expr2)
instructiune2;
else if(expr3)
instructiune3;
. . . . . .
else
instructiunen+1;
numită şi scara if-else-if
Exemplul 6.3. Programul care citeşte coordonatele unui punct şi stabileşte în ce cadran se află
acesta.
#include <stdio.h>
int main()
{
float x,y;
printf("\n abscisa x=");
scanf("%f",&x);
printf("\n ordonata y=");
scanf("%f",&y);
if (x>=0 && y>=0)
printf("\n Punctul apartine cadranului I");
else if (x<0 && y>=0)
printf("\n Punctul apartine cadranului II");
else if(x<0 && y<0)
printf("\n Punctul apartine cadranului III");
else
printf("\n Punctul apartine cadranului IV");
return 0;
}
Ţinând cont că în limbajul C orice expresie diferită de zero este adevărată, o secvenţă de genul
if(expr!=0)
instructiune;
4
Fundamentele programării 2020-2021
Curs 6
este echivalentă cu
if(expr)
instructiune;
Cuvântul else se asociază întotdeauna cu cel mai apropiat if incomplet care nu este deja
asociat cu un else şi care este în acelaşi bloc cu el.
De exemplu, în secvenţa
if(x)
if(y)
printf(”\n x si y nenuli”);
else
printf(”\n x nul”);
alinierea şi mesajele sugereză asocierea lui else cu primul if. În realitate, else se asociază cu cel
de-al doilea if, fiind adecvată alinierea
if(x)
if(y)
printf(”\n x si y nenuli”);
else
printf(”\n x nul”);
Dacă totuşi vrem să punem în practică prima intenţie putem folosi una din formele echivalente:
a) if(x)
{
if(y)
printf(”\n x si y nenuli”);
else
printf(”\n x nul”);
5
Fundamentele programării 2020-2021
Curs 6
b) if(x)
if(y)
printf(”\n x si y nenuli”);
else;
else
printf(”\n x nul”);
c) if(x&&y)
printf(”\n x si y nenuli”);
else
if(!x)
printf(”\n x nul”);
else
Construcţiile “dense” trebuie facute însă cu grijă, deoarece este posibil să avem surprize
neplăcute. Secvenţa de mai jos
x=y=7;
a=5;b=6;
if((x=a)(y=b))
printf(”\nx=%i si y=%i”,x,y);
va produce x=5, y=7 şi nu x=5, y=6 cum ne-am fi aşteptat. Pentru ca expresia
(x=a) (y=b)
6
Fundamentele programării 2020-2021
Curs 6
să fi adevărată este suficient ca numai una din expresiile (x=a) sau (y=b) să fie adevărată.
Cu alte cuvinte, se execută atribuirea x=5, expresia (x=5) ia valoarea adevărat, valoarea
expresiei (y=b) nu mai are importanţă şi deci atribuirea y=6 nu mai are loc.
expr1?expr2:expr3;
Efectul execuţiei unei astfel de secvenţe este echivalent cu efectul execuţiei secvenţei:
if expr1
expr2;
else
expr3;
Exemplul 6.4. Programul care afişează maximul dintre două numere a şi b citite de la tastatură.
#include <stdio.h>
int main()
{
int a,b;
printf("\n a=");
scanf("%i",&a);
printf("\n b=");
scanf("%i",&b);
printf("\n Maximul dintre a=%i si b=%i este %i\n",a,b,a<b?b:a);
return 0;
}
În plus, expresia expr1 ? expr2 : expr3 va lua valoarea expr2 sau expr3
după cum expr1 este adevărată sau nu.
7
Fundamentele programării 2020-2021
Curs 6
Instrucţiunea switch permite selecţia unei alternative din mai multe posibile într-o formă
comodă şi elegantă.
. . . . . . . .
default:secventa de instructiunin+1;break;
}
unde:
* expresie este expresia selectoare care trebuie să fie de tip întreg;
* c1,c2,...,cn sunt constante de tip întreg distincte între ele;
* default este o etichetă opţională;
8
Fundamentele programării 2020-2021
Curs 6
Exemplul 6.5. Programul citeşte una din literele a,A,m,M,p,P de la tastatură şi afişează o
listă de nume care încep cu una din aceste litere, fără să ţină cont dacă litera este mare sau mică.
Dacă se tastează alt caracter se afişează un mesaj de eroare.
#include <stdio.h>
int main()
{
printf("\n Tastati una din literele: a,A,m,M,p,P ");
switch(getch())
{
case 'a':
case 'A':printf("\n Aurel,Ana,Andrei"); break;
case 'm':
case 'M':printf("\n Maria,Mihai,Marin"); break;
case 'p':
case 'P':printf("\n Paula,Petre,Pavel"); break;
default :printf("\n Ati tastat gresit !");
}
return 0;
}
Observaţie. În programul de mai sus, indiferent dacă s-a tastat a sau A se afişează
aceeaşi listă de nume: Aurel, Ana, Andrei. Explicaţia este următoarea. Dacă se tastează
a se intră în switch, prin case 'a'. Secvenţa de prelucrări corespunzătoare fiind vidă şi
neântâlnindu-se nicio instrucţiune break se trece şi se execută secvenţa de prelucrări
corespunzătoare constantei case 'A' (adică afişarea listei). Deoarece secvenţa se încheie cu
break se iese din switch. Analog se întâmplă şi cu grupurile de litere m,M şi p,P.
Instrucţiunile iterative (repetitive sau de ciclare) permit ca una sau mai multe
instrucţiuni să fie repetate. Numărul de iteraţii depinde de îndeplinirea unei condiţii. Dacă testul
asupra condiţiei se face înaintea instrucţiunilor care se repetă, se spune că iteraţia e cu test iniţial;
în caz contrar iteraţia e cu test final.
În limbajul C există două instrucţiuni cu test iniţial, while şi for şi o instrucţiune cu test
final, do - while.
9
Fundamentele programării 2020-2021
Curs 6
while(conditie)
instructiune;
unde:
conditie poate fi orice expresie;
instructiune poate fi o instrucţiune simplă, vidă sau o instrucţiune compusă
(numită şi corpul ciclului).
Efectul instrucţiunii este următorul: se execută instructiune cât timp conditie e adevărată
(diferită de zero). Atunci când conditie devine falsă (egală cu zero), execuţia programului
continuă cu instrucţiunea imediat următoare. Organigrama de mai jos ilustrează sugestiv modul
de lucru al instrucţiunii while.
instructiune
NU DA
condiţie instructiune
Observaţii:
Dacă din start condiţia este falsă instructiune nu se execută niciodată.
Ieşirea din ciclu se poate face normal (ca efect al prelucrărilor din instructiune, după un
număr de paşi, conditie devine falsă), anormal (printr-o instrucţiune de salt care transferă
execuţia programului din interiorul ciclului în afara lui) sau niciodată (conditie rămâne mereu
adevărată - se obţine aşa zisa buclă eternă).
Prezentăm mai jos un exemplu de program unde apare foarte naturală prezenţa testului
iniţial în buclă şi deci folosirea instrucţiunii while.
10
Fundamentele programării 2020-2021
Curs 6
Exemplul 6.6. Calculul lungimii unui şir de caractere citit de la tastatură; sfârşitul şirului este
marcat de tasta Enter (caracterul ’\r’).
#include <stdio.h>
int main()
{
int i=0;
printf("\n Tastati un sir:\n");
while (getche()!='\r')
i++;
printf("\n Lungimea sirului =%d",i);
return 0;
}
Spre deosebire de alte limbaje. în limbajul C, instrucţiunea for are o implementare mai
flexibilă. Ea depăşeşte cadrul tradiţional în care este plasată de obicei: instrucţiune cu contor
(variabilă de control) recomandată spre a fi folosită ori de câte ori se cunoaşte numărul de
iteraţii.
for(initializare;conditie;actualizare)
instructiune;
initializare
while(conditie)
{
instructiune
actualizare
}
11
Fundamentele programării 2020-2021
Curs 6
De aici rezultă şi efectul execuţiei sale: se execută blocul instructiune–actualizare cât timp
conditia este îndeplinită.
#include <stdio.h>
int main()
{
float s=0,x;
int i,n;
printf("\n n=");
scanf("%i",&n);
for (i=0;i<n;i++)
{
printf("\n x[%i]=",i);
scanf("%f",&x);
s+=x;
}
printf("\n Suma este =%f",s);
return 0;
}
Observaţii:
Componenta initializare a instrucţiunii for poate conţine atribuiri care nu se
referă neapărat la variabila contor. În exemplul de mai sus iniţializarea variabilei s se poate
face în componenta initializare a ciclului for cu ajutorul operatorului virgulă, astfel:
for(s=0,i=0;i<n;++i)
{
. . . . . .
}
. . . . . .
Componenta actualizare a instrucţiunii for poate să conţină mai multe variabile contor.
Variabila contor nu trebuie să fie prezentă obligatoriu în componenta conditie a unei
instrucţiuni for.
Exemplul 6.8. Afişarea termenilor şirului lui Fibonacci mai mari ca 1 şi mai mici ca un
număr dat m. Şirul are forma 1, 1, 2, 3, 5, 8, …, adică primii doi termeni sunt egali cu 1, iar
orice alt termen se obţine ca sumă a celor doi termeni care-l preced.
12
Fundamentele programării 2020-2021
Curs 6
#include <stdio.h>
int main()
{
int a,b,c,m,i;
printf("\n Limita de afisare =");
scanf("%i",&m);
printf("\n Termenii sirului Fibonacci < %i\n",m);
printf("\n 1 1 ");
for (i=3,a=b=1,c=2;c<m;i++)
{
printf("%i ",c);
a=b;b=c;
c=a+b;
}
return 0;
}
Observaţii:
Dacă în programul precedent renunţăm la ideea de a utiliza numărul de ordine al
termenilor putem folosi secvenţa:
for(a=b=1,c=2;c<m;)
{
printf(”\n Termenul =%i”,c)
a=b;b=c;
c=a+b;
}
Secvenţa de program:
int i=50000;
for(;i;i--);
are ca efect introducerea unei temporizări egală cu timpul necesar decrementării contorului
i de la voloare 50000 la 0. Aici se observă absenţa componentei initializare şi totodată
ciclarea unei instrucţiunii vide.
13
Fundamentele programării 2020-2021
Curs 6
for(;;);
creează un ciclu infinit sau o buclă eternă.
do
{
instructiuni;
}
while(conditie);
Când corpul ciclului este format dintr-o singură instructiune acoladele pot să lipsească.
Totuşi, este recomandabil să se păstreze chiar şi în această situaţie pentru a distinge mai uşor o
instrucţiune while care începe, de o instrucţiune do-while care se termină.
Efectul instrucţiunii este următorul: se execută secvenţa de instructiuni cât timp expresia
condiţională conditie este adevărată. Organigrama de mai jos ilustrează cu claritate modul de
execuţie al ciclului do-while.
instructiune
REPETĂ instructiune CÂT TIMP condiţie
DA
condiţie
NU
14
Fundamentele programării 2020-2021
Curs 6
Observaţii:
Datorită aşezării testului de condiţie după corpul ciclului, este evident că grupul de
instructiuni se execută cel puţin o dată.
Dacă în corpul ciclului nu se află instrucţiuni care să conducă la o condiţie falsă după
un număr finit de ciclări (ieşire normală din ciclu) sau instrucţiuni de salt din
interiorul ciclului (ieşire anormală) se obţine un ciclu infinit (buclă eternă).
Exemplul 6.9. Programul care citeşte două numere a şi b de la tastatură şi afişează suma lor.
Procesul continuă cât timp la întrebarea ”Continuati?” se apasă literele d sau D.
#include <stdio.h>
int main()
{
float a,b;
char c;
do
{
printf("\n a=");
scanf("%f",&a);
printf("\n b=");
scanf("%f",&b);
printf("\n Suma a+b=%.2f",a+b);
printf("\n Continuati? (d/n) ");
c=getch();
}
while (c == 'D' || c == 'd');
return 0;
}
Exemplul 6.10. Programul implementează un meniu simplu. Pentru două numere citite de la
tastatură se efectuează una din următorele operaţii: suma, diferenţa produsul sau împărţirea
(atunci când este posibil) sau se iese din meniu.
#include <stdio.h>
int main()
{
float a,b;
char ch;
printf("\n a=");scanf("%f",&a);
printf("\n b=");scanf("%f",&b);
printf("\n + Adunare");
printf("\n - Scadere");
printf("\n * Inmultire");
15
Fundamentele programării 2020-2021
Curs 6
printf("\n / Impartire");
printf("\n r Renunta!");
printf("\n Introduceti optiunea dvs:");
do
{
ch=getchar();
switch(ch)
{
case '+' :printf("\n a+b=%.2f",a+b); break;
case '-' :printf("\n a-b=%.2f",a-b); break;
case '*' :printf("\n a*b=%.2f",a*b); break;
case '/' :if (b)
printf("\n a/b=%.2f",a/b);
else
printf("\n Impartire imposibila!");
break;
case 'r' :
case 'R' :exit(0);
}
}
while (ch != '+' && ch != '-' && ch != '*' && ch != '/');
return 0;
}
Observaţie. Funcţia exit() provoacă întreruperea programului şi revenirea în sistemul de
operare. Funcţia are forma generală:
void exit(int cod_retur);
De obicei, cod_retur are valoarea 0 la ieşire normală din program şi o valoare
diferită de 0 în caz contrar.
Exemplul 6.11. Programul care calculează şi afişează radicalul dintr-un număr. Noutatea faţă de
Exemplul 6.1 este apariţia unei bucle iterative de validare: ieşirea din buclă urmată de calculul
radicalului se face doar atunci când numărul citit de la tastatură este pozitiv.
#include <stdio.h>
#include <math.h>
int main()
{
float x;
do
{
printf("\n x=");
scanf("\n %f",&x);
}
while (x<0);
16
Fundamentele programării 2020-2021
Curs 6
printf("\n Radical din x=%.2f este y=%.4f",x,sqrt(x));
return 0;
}
#include <stdio.h>
int main()
{
int i,j;
for (i=1;i<=5;i++)
{
for (j=1;j<=i;j++)
printf("%2d",j);
printf("\n");
};
return 0;
}
Evident, o structură de cicluri incluse poate să conţină oricare din instrucţiunile de ciclare
permise în C. Iată un exemplu de acest gen:
Exemplul 6.13. Programul rezolvă următoarea problemă: cât timp se citeşte de la tastatură o
valoare x diferită de zero se afişează şirul de numere x,x+1,x+2,x+3,x+4. Acest proces continuă
sau se întrerupe la cerere.
#include <stdio.h>
int main()
{
17
Fundamentele programării 2020-2021
Curs 6
int x,i;
char ch;
do
{
printf("\n x=");
scanf("%i",&x);
while (x)
{
for (i=0;i<5;i++)
printf("%5d",x+i);
printf("\n x=");
scanf("%i",&x);
}
printf("\n Continuati ? (d/n)");
ch=getch();
}
while (ch == 'd' || ch == 'D');
return 0;
}
După cum s-a văzut la prezentarea instrucţiunii switch, instrucţiunea break poate încheia
execuţia unei prelucrări case şi forţa trecerea execuţiei la prima instrucţiune aflată după switch.
O altă situaţie unde instrucţiunea break îşi dovedeşte utilitatea este atunci când se doreşte
încheierea forţată a unui ciclu. În acest caz întâlnirea instrucţiunii break în corpul ciclului
determină întreruperea ciclării şi trecerea controlului la prima instrucţiune care urmează după
ciclu.
Exemplul 6.14. Programul “extrage” şi afişează cel mult 20 de numere aleatoare folosind funcţia
rand(). Dacă s-a ”extras” numărul 10 programul nu mai efectuează restul „extragerilor”.
18
Fundamentele programării 2020-2021
Curs 6
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i,x;
srand(time(0));
for (i=0;i<20;i++)
{
x=rand()%20+1;
printf("\n %i. S-a extras numarul %i",i+1,x);
if (x==10) break;
}
return 0;
}
Precizări. O instrucţiune break are efect numai asupra ciclului cel mai interior în care se
află. Aceeaşi precizare e valabilă şi la instrucţiuni switch imbricate sau incluse în cicluri:
instrucţiunea break va afecta numai prima instrucţiune switch in care se află, nu şi eventualele
instrucţiuni switch sau de ciclare în care ar putea fi inclusă prima.
Când continue este întâlnită într-un ciclu for se continuă cu execuţia componentei de
actualizare şi apoi a testului condiţional.
Când este întâlnită într-un while sau do-while, se trece la execuţia expresiei condiţionale.
Exemplul 6.15. Programul citeşte de la tastatură 10 numere întregi însumându-le doar pe cele
strict pozitive.
#include <stdio.h>
int main()
{
int i,x,s;
for (i=s=0;i<10;i++)
{
printf("\n x=");
19
Fundamentele programării 2020-2021
Curs 6
scanf("%i",&x);
if (x<=0) continue;
s+=x;
}
printf("\n Suma este=%d",s);
return 0;
}
goto eticheta;
.
.
.
eticheta:instructiune;
#include <stdio.h>
int main()
{
float a,b;
char ch;
for(;;)
{
printf("\n a=");
scanf("%f",&a);
printf("\n b=");
scanf("%f",&b);
printf("\n + Adunare");
printf("\n - Scadere");
20
Fundamentele programării 2020-2021
Curs 6
printf("\n * Inmultire");
printf("\n / Impartire");
printf("\n r Renunta");
printf("\n Introduceti optiunea dvs:");
ch=getche();
switch (ch)
{
case '+' :printf("\n a+b=%.2f",a+b); break;
case '-' :printf("\n a-b=%.2f",a-b); break;
case '*' :printf("\n a*b=%.2f",a*b); break;
case '/' :if (b)
printf("\n a/b=%.2f", a/b);
else
printf("\n Impartire imposibila");
break;
case 'r' :
case 'R' :goto STOP;
}
}
STOP:printf("\n Am renuntat!");
}
Instrucţiunea return realizează întoarcerea din funcţia apelată în funcţia care a făcut
apelul (funcţia apelantă). O discuţie detaliată despre instrucţiunea return se va face în capitolul
dedicat funcţiilor C.
21
Fundamentele programării 2020-2021
Curs 7
caz cele şase variabile a,b,c,d,e,f au fost redenumite x1,x2,...,x6; ele pot fi privite
astfel drept primele 6 componente ale vectorului x.
tip nume_tablou[dim];
unde:
• tip reprezintă tipul de bază al elementelor indexate;
• dim reprezintă numărul de elemente ale tabloului;
• nume_tablou este un identificator reprezentând numele tabloului.
int a[100];
se vor aloca vectorului a, 100*4=400 de octeţi într-o zonă continuă de memorie (în condițiile
în care tipul int memorează pe 4 octeți).
Observaţii:
• alocarea se face în timpul compilării;
• odată făcută alocarea, în scopul măririi vitezei de execuţie nu se mai face nicio verificare a
dimensiunii; gestionarea corectă a zonei de memorie alocate cade, deci, exclusiv în sarcina
programatorului.
Referirea la un element al tabloului unidimensional se face precizând numele tabloului
urmat de o expresie întreagă între paranteze drepte, adică:
nume_tablou[expr]
1
Fundamentele programării 2020-2021
Curs 7
Expresia expr poate lua valori întregi cuprinse în intervalul[0,dim-1]. Astfel, în urma
declaraţiei float y[10]; vectorului y va avea 10 componente reale şi anume:
y[0] componenta_1
y[1] componenta_2
........
y[9] componenta_10
Intenţia de a referi ultimul element al unui tablou sub forma nume_tablou[dim] este
o greşeală cu atât mai gravă, cu cât, ţinând cont de observaţiile anterioare, ea nu este depistată de
compilator. De obicei expresiile întregi folosite pentru a accesa componentele unui tablou sunt
constante sau variabile simple (indici).
#include <stdio.h>
int main()
{
int n,x[50],i;
// citirea elementelor vectorului
printf("\n Nr. elemente n=");
scanf("%d",&n);
for (i=0;i<n;i++)
{
printf("\n x[%d]=",i);
scanf("%d",&x[i]);
}
// afisarea elementelor vectorului
printf("\nVectorul x: ");
for (i=0;i<n;i++)
printf("%d ",x[i]);
return 0;
}
2
Fundamentele programării 2020-2021
Curs 7
Generalizând, un tablou multidimensional poate fi considerat ca un tablou
unidimensional care are ca elemente un tablou cu restul de dimensiuni; declaraţia unui tablou cu
dimensiunile dim1,dim2,...,dimn are forma generală:
tip nume_tablou[dim1][dim2]...[dimn];
iar referirea la un element al tabloului se face prin:
nume_tablou[indice1][indice2]...[indicen],
unde indicei ia valori întregi în intervalul [0,dimi-1], pentru i=1,2,...,n.
Observaţii:
• Referirea clasică la elementele unui tablou prin separarea indicilor prin virgulă este
incorectă în C şi are semnificaţia rezultată din folosirea operatorului de secvenţiere. De
exemplu, considerând declaraţia de mai sus, referirea x[i,j] este echivalentă cu x[j].
• Dimensiunea zonei continue de memorie alocate este dată (în octeţi) de valoarea
dim1*dim2*dim3*dimn*sizeof(tip).
#include <stdio.h>
int main()
{
int n,m,a[10][10],i,j;
// citire matrice
printf("Nr. linii n=");
scanf("%d",&n);
printf("Nr. coloane m=");
scanf("%d",&m);
for (i=0;i<n;i++)
for(j=0;j<m;j++)
{
printf("a[%d][%d]=",i,j);
scanf("%i",&a[i][j]);
}
// afisare matrice
printf("\nMatricea a:\n");
for (i=0;i<n;i++)
{
for(j=0;j<m;j++)
printf("%5d",a[i][j]);
printf("\n");
}
return 0;
}
3
Fundamentele programării 2020-2021
Curs 7
7.3. Inițializarea tablourilor
Exemplele anterioare ne-au arătat cum se poate iniţializa un tablou prin valori date de la
tastatură. Există însă posibilitatea iniţializării unui tablou printr-o definiţie de forma:
float a[3][2]={1,2,3,4,5,6};
int b[3][2][2]={1,2,3,4,5,6,7,8,9,10,11,12};
În cel de-al doilea caz a fost iniţializată o matrice. Cum se vor completa elementele
matricii? Răspunsul este simplu: ”pe linii”, adică se completează linia 0, apoi linia 1 şi în sfârşit
linia 2. Matricea a va arăta astfel:
1 2
3 4
5 6
int a[3][2]= {
{1, 2},
{3, 4},
{5, 6},
};
În general, iniţializarea elementelor unui tablou multidimensional se face după regula
<ultimul indice variază cel mai rapid>, care este o generalizare a regulii de memorare <pe linii>.
Astfel, cele douăsprezece componente ale tabloului b[3][2][2] vor fi iniţializate astfel:
b[0][0][0]=1
b[0][0][1]=2
b[0][1][0]=3
b[0][1][1]=4
b[1][0][0]=5
b[1][0][1]=6
b[1][1][0]=7
4
Fundamentele programării 2020-2021
Curs 7
b[1][1][1]=8
b[2][0][0]=9
b[2][0][1]=10
b[2][1][0]=11
b[2][1][1]=12
Aceste reguli decurg din modul de liniarizare a unui tablou in C: poziţiei i1*i2*...*in
din tablou îi corespunde în forma sa liniarizată (formă sub care i se va aloca memoria) poziţia k
dată de formula:
k=i1*dim2*...*dimn+i2*dim3*dimn+...+in-1*dimn+in.
De exemplu, în cazul unei matrici a[m][n] liniarizarea se face după formula k=i*n+j.
• Dacă dim=nval_in iniţializarea tabloului decurge normal după regula de mai sus.
• Dacă dim<nval_in se va produce o eroare, iar dacă dim>nval_in restul de elemente
sunt iniţializate cu zero.
int vector[]={-7,-2,95,21,5,-23};
float a[][2]={1,2,3,4,5,6};
int b[][2][2]={1,2,3,4,5,6,7,8,9,10,11,12};
Declaraţia unui şir cu numele nume_sir de lungime maxim dim-1 caractere se face sub
forma:
char nume_sir[dim];
Sfârşitul unui şir este marcat de caracterul ’\0’; din acest motiv, în declaratia şirului,
dimensiunea tabloului trebuie să fie cel puţin cu o unitate mai mare decât numărul de caractere al
şirului pe care vrem să-l memorăm.
5
Fundamentele programării 2020-2021
Curs 7
De exemplu:
char sr[10];
conţine declaraţia şirului sr. Iniţializarea se poate face printr-o definiţie a şirului sub forma:
char sr[10]=”aAbBcCdD”;
Dacă numărul elementelor din şir este mai mare decât dimensiunea şirului, caracterele în
plus sunt ignorate, în caz contrar restul elementelor este iniţializat cu zero (caracterul nul).
char sr[]=”aAbBcCdD”;
care este echivalentă cu iniţializarea tabloului sr cu un şir de caractere, adică
char sr[]={’a’,’A’,’b’,’B’,’c’,’C’,’d’,’D’,\0’};
Se observă prezenţa explicită a terminatorului de şir ’\0’.
Ultimele două variante rezervă şirului un număr de locaţii egal cu numărul elementelor din
şir plus o unitate (corespunzătoare terminatorului ’\0’) şi este mai comodă, deoarece nu
precizează o limită maximă pentru numărul de caractere, permiţând programatorului să evite
numărarea elementelor din şir.
Exemplul 7.3.
#include <stdio.h>
int main()
{
int i;
char sir[]="aAbBcCdD";
printf("Sirul este: %s",sir);
printf("\nLiterele selectate: ");
for (i=0;sir[i];i+=2)
printf("%c",sir[i]);
return 0;
}
6
Fundamentele programării 2020-2021
Curs 7
7.5. Funcții pentru prelucrarea șirurilor de caractere
Pentru operaţiile de intrare/ieşire cu şiruri se pot folosi funcţiile scanf() respectiv printf()
cu descriptorul de format %s.
Exemplul 7.4. Citirea a două şiruri sub forma nume prenume şi afişarea lor sub forma prenume
nume.
#include <stdio.h>
int main()
{
char nume[25],prenume[25];
printf("\n Numele=");
scanf("%s",nume);
printf("\n Prenumele=");
scanf("%s",prenume);
printf("\n %s %s",prenume,nume);
return 0;
}
Pentru citirea unui şir de la tastatură se poate folosi funcţia gets() cu forma generală:
gets(sir_destinatie);
iar pentru afişarea unui şir pe ecran funcţia puts() cu forma generală:
puts(sir).
Observaţie. Funcţia gets() transferă în sir_destinatie toate caracterele până la apăsarea
tastei Enter.
7
Fundamentele programării 2020-2021
Curs 7
▪ <0 dacă sir1<sir2;
▪ =0 dacă sir1=sir2;
▪ >0 dacă sir1>sir2.
#include <stdio.h>
#include <string.h>
int main()
{
char min[20],x[20];
printf("\nIntroduceti siruri de caractere separate prin Enter! ");
printf("\nLa sfarsit tastati STOP \n");
strcpy(min,gets(x));
while (strcmp(x,"stop"))
{
strcmp(min,x)<0 ? min : strcpy(min,x);
gets(x);
}
printf("\n Sirul minim este "); puts(min);
printf("\n si are lungimea %i",strlen(min));
return 0;
}
8
Fundamentele programării 2020-2021
Curs 8
Capitolul 8. Pointeri
1
Fundamentele programării 2020-2021
Curs 8
int x,*p;
p=&x;
este corectă şi are înţelesul că adresa variabilei x este memorată în p.
Să considerăm secvenţa:
int x,*p;
x=10;
p=&x;
Deoarece în pointerul p se va memora adresa lui x, variabila *p va avea conţinutul
variabilei x, adică *p=10. Din modul de definire, se observă că operatorii & şi * sunt
complementari, adică x=*(&x).
Să observăm, de asemenea, că o secvenţă de genul:
int *p;
*p=31;
este greşită deoarece simpla declaraţie a lui p ca pointer nu presupune alocare de memorie.
Corect ar fi, de exemplu, o secvenţă de forma
int x,*p;
p=&x;
*p=31;
care implică şi iniţializarea variabilei x cu valoarea 31. Procedeul de a ne referi la valoarea unei
variabile in mod indirect, folosind un pointer este numit indirectare, de unde şi numele
operatorului *. În atribuirea *p=31, *p ţine locul variabilei x, adică are valoare stîngă (left
value). În general *p poate apare oriunde poate apare variabila x. Este necesar ca tipul
pointerului să fie acelaşi cu cel al variabilei la care se referă pentru a asigura întotdeauna o
folosire corectă a indirectărilor. Astfel, secvenţa
short int *p;
float x,y;
x=62549.21;
p=&x;
y=*p;
este greşită deoarece se încearcă folosirea pointerului către întregi scurte p spre a transfera în y
valoarea reală conţinută în x. În această situaţie doar doi octeţi din x vor fi transferaţi în y.
2
Fundamentele programării 2020-2021
Curs 8
Sunt posibile şi abateri de la regula de mai sus (folosind pointeri către void sau conversii
explicite cast) şi în aceste cazuri este evident că programatorul trebuie să-şi asume
responsabilitatea efectuării corecte a operaţiilor. Deşi, de obicei, nu suntem interesaţi în
cunoaşterea valorii concrete a unui pointer, totuşi, se poate obţine această valoare ( în
hexazecimal) cu ajutorul descriptorului %p, ca în exemplul următor.
Exemplul 8.1. Afișarea unui pointer
#include <stdio.h>
int main()
{
int x,*x_ptr;
x=10;
x_ptr=&x;
printf("\n Adresa lui x este=%p",&x);
printf("\n Valoarea lui x este=%i",x);
printf("\n Valoarea lui x_ptr=%p",x_ptr);
printf("\n Valoarea lui x=%i",*x_ptr);
return 0;
}
Este admisă folosirea pointerului către tipul void cu înţelesul de pointer care nu are
asociat un tip de date precis (pointer către “orice”). Declaraţiile void * se folosesc în situaţiile în
care nu trebuie precizat în mod expres tipul pointerilor tocmai pentru a permite scrierea unor
funcţii cât mai generale din punct de vedere al tipurilor pe care le manevrează.
Valoarea NULL (declarată în fişierele antet ”stdio.h”, ”stdlib.h” etc.) are înţelesul de
pointer zero şi poate fi atribuită oricărui pointer cu semnificaţia că nu indică nimic (nu conţine
nicio adresă).
Aşa cum s-a vazut, un pointer poate fi iniţializat cu adresa unei variabile statice. Există
însă şi posibilitatea de a atribui pointerului considerat adresa unui bloc de memorie din zona
dinamică, numită heap. Acest lucru se poate face utilizând una din funcţiile speciale,
malloc(), calloc(), realloc(). Funţiile malloc() şi calloc() au prototipul în
fişierul stdlib.h, iar realloc() are prototipul în fişierul malloc.h. Zonele alocate
rămân ocupate până la dealocarea explicită cu ajutorul funcţiei free() (prototipul funcţiei se
află în stdlib.h).
Drept exemplu, considerăm secvenţa de program:
int *p;
. . . . . . .
3
Fundamentele programării 2020-2021
Curs 8
/* se aloca o zona de memorie de 30*sizeof(int) octeti la o
adresa memorata in p*/
p=malloc(30*sizeof (int));
*p=70;
. . . . . . .
/* se dealoca (elibereaza) zona de memorie alocata*/
free(p);
Observaţii:
Funcţia malloc() are forma generală
void *malloc(unsigned int nr_octeti);
Ea întoarce un pointer de tip void şi din acest motiv poate fi atribuit oricărui tip de pointer (vezi
secvenţa program de mai sus).
Dacă spaţiul de alocare este suficient, pointerul întors de funcţia malloc() conţine adresa
primului octet al zonei de memorie alocate. În caz contrar (spaţiu insuficient), funcţia întoarce
valoarea NULL. De aceea, un mod riguros de alocare se poate face sub forma:
if(!(p=malloc(50*sizeof(int))))
{
printf(”\n Spatiu insuficient”);
exit(1);
}
Funcţia free() se aplică unui pointer care indică o zonă de memorie alocată anterior cu
funcţia malloc().
4
Fundamentele programării 2020-2021
Curs 8
int t1[5],t2[5];
t1=t2;
deoarece ea constituie o încercare de a modifica pointerul constant t1.
5
Fundamentele programării 2020-2021
Curs 8
int *t[10];
ar fi de asemenea corectă, dar ar avea o altă semnificaţie şi anume: t este un tablou de pointeri
către întregi.
Pentru a înţelege semnificaţia pointerilor către tablouri, să considerăm secvenţa de
program:
int x[10],*p=x;
p++;
Pointerul p va conţine, de asemenea, adresa primului element al tabloului x, dar p++ va
conţine adresa p=&x+sizeof(int) (adică adresa variabilei x[1]).
Se observă că diferenţa dintre cele două cazuri apare la aritmetica pointerilor:
când se lucrează cu pointeri către tablouri de dimensiune dim componente de tip tip,
aritmetica folosită are unitatea egală cu dim*sizeof(tip).
când se lucrează cu pointeri către tip aritmetica folosită are unitatea egală cu
sizeof(tip).
Pointerii către tablouri unidimensionale sunt extremi de utili
mai ales în înţelegerea şi folosirea tablourilor cu mai multe dimensiuni în C.
8.5. Operații aritmetice cu pointeri
Au sens operaţiile de adunare (scădere) ale unui pointer cu un întreg.
Semnificaţia expresiilor p+n,p-n, unde p este pointer de un anumit tip, iar n este un
număr întreg pozitiv este următoarea: p+n va conţine adresa p+n*sizeof(tip), iar p-n
adresa p-n*sizeof(tip), unde tip este tipul pointerului.
În secvenţa următoare se consideră că tipul short int se memorează pe 2 octeţi, iar tipul
float pe 4 octeţi.
short int *p1,*p2;
float *p;
. . . . . .
p2=p1+3;/* p2 contine adresa p1+3*2 */
. . . . . .
p-=5;/* p contine adresa p-5*4 */
De obicei, programatorul este mai puţin interesat de adresa concretă a zonei de memorie
unde se află variabilele. De aceea, operaţiile asupra pointerilor au semnificaţie practică dacă
deplasările se fac în interiorul unui tablou.
6
Fundamentele programării 2020-2021
Curs 8
Dacă x[n] este un tablou cu n componente, atunci pentru orice i=0,1,…,n-1
x+i e echivalent cu &x[i],
iar
*(x+i) e echivalent cu x[i]
Exemplul 8.2. Comutarea elementelor egal departate de capetele unui vector şi afişarea
vectorului obţinut.
#include <stdio.h>
int main()
{
int x[7]={1,2,3,4,5,6,7},i,*p,y;
// afisarea vectorului initial
printf("\n Vectorul initial este:");
for (i=0;i<7;i++)
printf("\nx[%d]=%d",i,x[i]);
// comutarea elementelor vectorului x
for (i=0;i<3;i++)
{
y=*(x+i);
*(x+i)=*(x+6-i);
*(x+6-i)=y;
}
// afisarea vectorului comutat
printf("\n Vectorul comutat este:");
for (i=0,p=x;i<7;++i)
{
printf("\nx[%d]=%d",i,*p);
p++;
}
return 0;
}
7
Fundamentele programării 2020-2021
Curs 8
for (i=0;i<7;i++)
printf("\nx[%d]=%d",i,x[i]);
/* comutarea elementelor vectorului x */
p=x;
q=&x[6];
do
{
y=*p;
*p=*q;
*q=y;
p++;
q--;
}
while (p<q);
/* afisarea vectorului comutat */
printf("\n Vectorul comutat este:");
for (i=0,p=x;i<7;++i)
{
printf("\nx[%d]=%d",i,*p);
p++;
}
return 0;
}
Scăderea a doi pointeri p1,p2 are semnificaţie în cazul în care p1,p2 au acelaşi tip de
bază şi se referă la elementele aceluiaşi tablou.
Dacă t este numele tabloului şi
p1=&t[i1];
p2=&t[i2];
atunci
p1-p2 semnifică adresa i1-i2 din tabloul t.
Sunt posibile combinaţii de pointeri cu ajutorul operatorilor &,*,++ şi --, însă ele trebuie
facute cu multă grijă. Următorul program ilustrează câteva din efectele acestor combinaţii
(rezultatele afişării sunt date în comentarii).
8
Fundamentele programării 2020-2021
Curs 8
Exemplul 8.4. Afişarea valorilor unui pointer p combinat în expresii cu operatorii * şi ++.
#include <stdio.h>
int main()
{
int x[]={5,4,3,2,1},*p;
p=x;
printf("\n Valoarea=%i",*p++); /* val=5 */
printf("\n Valoarea=%i",*p); /* val=4 */
p=x;
printf("\n Valoarea=%i",(*p)++); /* val=5 */
printf("\n Valoarea=%i",*p); /* val=6 */
p=x;
printf("\n Valoarea=%i",*++p); /* val=4 */
p=x;
printf("\n Valoarea=%i",++*p); /* val=7 */
return 0;
}
Pointer Pointer
Adresă Adresă Valoare
Figura 8.1 Adresare indirectă (pointer dublu)
Exemplu:
int **p,*q,x;
x=10;
q=&x;
p=&q;
9
Fundamentele programării 2020-2021
Curs 8
Se observă că pointerului q i se atribuie adresa variabilei x în timp ce pointerului p i se
atribuie adresa pointerului q. Dacă la această secvenţă adăugăm
**p=7;
efectul este că valoarea lui x va deveni 7. Deci **p permite accesul indirect (indirectare dublă)
la conţinutul variabilei x. Limbajul permite folosirea pointerilor tripli şi în general a pointerilor
multipli, dar apar dificultăţi la manevrarea corectă a unor astfel de pointeri. Pointerii dubli şi
tablourile de pointeri permit extinderea corectă a aritmeticii pointerilor prezentaţi şi la cazul
tablourilor multidimensionale. Se porneşte de la observaţia că un tablou multidimensional este
un tablou cu o singură dimensiune care are drept elemente un tablou cu restul de dimensiuni.
Pentru uşurinţa expunerii să considerăm cazul unui tablou bidimensional (matrice), ca în
exemplul de mai jos.
Fie declaraţia:
int x[3][5];
Conform observaţiei făcute, tabloul x[3][5] poate fi privit ca un tablou cu trei variabile
pointer x[0],x[1],x[2] care arată respectiv către tablourile unidimensionale:
x[0][0] x[0][1] ... x[0][4],
x[1][0] x[1][1] ... x[1][4],
şi
x[2][0] x[2][1] ... x[2][4].
Dar x[0], x[1], x[2], se pot scrie echivalent *x,*(x+1) şi *(x+2), respectiv.
Deci numele tabloului x poate fi văzut ca un pointer dublu către tablouri de câte cinci
locaţii de tip int. Din acest motiv, avem echivalenţele:
x[i][j]<=>(*(x+i))[j]<=>*(*(x+i)+j),
unde expresia x+i înseamnă x+i* sizeof(int)*5 (conform proprietăţii pointerilor către
tablouri unidimensionale). Alte echivalenţe evidente sunt
x[i][j]<=>*(x[i]+j)<=>*(&x[0][0]+5*i+j)
Observaţie:
Ultima expresie din echivalenţele de mai sus conţine funcţia de alocare a tabloului
bidimensional x[3][5].
10
Fundamentele programării 2020-2021
Curs 9
9.1. Structuri
Structurile reprezintă colecţii de date neomogene grupate sub acelaşi nume, într-o zonă
de memorie compactă. Declaraţia unei structuri are forma generală:
struct nume_structura
{
tip nume_camp1;
tip nume_camp2;
tip nume_camp3;
.............
tip nume_campn;
}lista_variabile_structura;
unde:
nume_camp1,nume_camp2,…,nume_campn sunt nume de câmpuri, membre ale
structurii;
tip reprezintă tipul câmpului şi poate fi orice tip simplu sau derivat admis în C;
nume_structura reprezintă numele structurii;
lista_variabile_structura este o listă de variabile al cărei tip este struct
nume_structura.
nume_structura şi lista variabile_structura sunt opţionale, dar nu pot
lipsi simultan: declaraţia unei structuri anonime, care în plus are şi
lista_variabile_structura vidă este absolut inutilă.
Iată un exemplu de structură care reflectă un rând din cartea de telefoane.
struct agenda
{
char nume[15];
char prenume[20];
char adresa[50];
unsigned long int telefon;
}pers1,pers2;
1
Fundamentele programării 2020-2021
Curs 9
Aici, pers1 şi pers2 sunt nume de variabile care vor avea tipul struct agenda. Această
declaraţie este echivalentă cu:
struct agenda
{
char nume[15];
char prenume[20];
char adresa[50];
unsigned long int telefon;
};
struct agenda pers1,pers2;
Selectarea unui câmp din structură se face utilizând operatorul . sub forma:
nume_variabila_structura.nume_camp
De exemplu, accesul la informaţia conţinută în câmpul adresa al variabilei pers1 se face
utilizând construcţia
pers1.adresa
Asupra unui câmp selectat se pot aplica toate operaţiile care se pot aplica unei variabile
de acelaşi tip cu câmpul. Are sens, de exemplu, o secvenţă ca aceasta
. . . . . .
scanf(”%d”,&pers1.telefon);
printf(”\n Numarul de telefon este %d”,pers1.telefon);
. . . . . .
Dacă o variabilă este un pointer către o structură, accesul la un câmp al structurii se face
utilizând operatorul -> (alcătuit din semnul - şi semnul >) sub forma:
nume_pointer_structura->nume_camp
pers3->nume
Remarcăm faptul că operatorul -> este o sinteză a operatorilor * (indirectare) şi .
(selecţie). Aşadar, expresia
pers3->nume
este echivalentă cu expresia
2
Fundamentele programării 2020-2021
Curs 9
(*pers3).nume
Mărimea unei structuri se calculează utilizând operatorul sizeof. De exemplu, mărimea
structurii agenda este dată de sizeof(struct agenda). Este recomandată folosirea
acestui operator şi nu calculul manual al mărimii structurii (însumând octeţii ocupaţi de fiecare
câmp) atât din motive de portabilitate cât şi din raţiuni de corectitudine: în unele situaţii,
compilatorul, prin alinierile pe care le face, obţine un necesar de memorie mai mare decât
dimensiunea obţinută manual.
declaratie structura={lista_valori_initiale};
Copierea informaţiei bit cu bit dintr-o structură în altă structură de acelaşi tip este posibilă
printr-o singură instrucţiune de atribuire. Astfel, atribuirea
pers1=pers2;
este echivalentă cu
strcpy(pers1.nume,pers2.nume);
strcpy(pers1.prenume,pers2.prenume);
strcpy(pers1.adresa,pers2.adresa);
pers1.telefon=pers2.telefon;
O structură poate fi inclusă în altă structură obţinându-se structuri imbricate.
3
Fundamentele programării 2020-2021
Curs 9
În interiorul unei structuri numele câmpurilor trebuie să fie diferite, însă două structuri
diferite pot avea nume de câmpuri comune. De asemenea, atât numele structurii, cât şi numele
unui câmp al structurii pot coincide cu numele unei variabile. De exemplu, declaraţiile:
struct pers
{
int i;
int j;
}p;
int pers;
float i;
. . . . .
sunt perfect valide.
Un tablou poate fi câmp al unei structuri aşa cum se vede în exemplul următor:
struct anonima
{
float x[5][2];
int y;
}a;
a.x[i][j],0<=i<=4,0<=j<=1.
De asemenea, componentele unui masiv de date pot fi de tip structură. În exemplul care
urmează se prezintă un program care memorează o listă cu informaţii despre notele obţinute de o
grupă de studenţi într-un vector de maxim 30 componente.
struct catalog
{
char npr[30];
short nota;
}stud[30];
După memorare, lista este aranjată în ordinea alfabetică a numelor folosind un algoritm
de sortare cunoscut (sortarea prin selecţie).
4
Fundamentele programării 2020-2021
Curs 9
Exemplul 9.1. Ordonarea unei liste folosind algoritmul de sortare prin selecţie.
#include <stdio.h>
#include <string.h>
struct catalog
{
char npr[30];
short nota;
}stud[30],aux;
int main()
{
short i,j,l,n;
/* introducerea datelor */
printf("\n n=");
scanf("%d",&n);
for (i=0;i<n;i++)
{
printf("\n Nume=");
scanf("%s",stud[i].npr);
printf("\n Nota=");
scanf("%d",&stud[i].nota);
}
/* ordonarea alfabetica a catalogului */
for (i=0;i<n-1;i++)
{
l=i;
for (j=i+1;j<n;j++)
strcmp(stud[j].npr,stud[l].npr)<0 ? l=j : l;
aux=stud[i];
stud[i]=stud[l];
stud[l]=aux;
}
/* afisarea catalogului ordonat */
printf("\n Catalogul ordonat este:\n");
for (i=0;i<n;i++)
printf("\n %15s %4d",stud[i].npr,stud[i].nota);
return 0;
}
Câmpurile unei structuri pot fi pointeri către structura din care fac parte. Acest lucru
permite construirea unor structuri recursive (autoreferite) foarte utile în implementarea
dinamică a cozilor, stivelor, listelor, arborilor, grafurilor. Iată un exemplu de program care
construieşte o listă şi apoi afişează elementele sale. Lista va fi construită element cu element de
la sfârşit spre început.
5
Fundamentele programării 2020-2021
Curs 9
Exemplul 9.2. Construirea unei liste simplu înlănţuite şi afişarea elementelor sale.
#include <stdio.h>
#include <malloc.h>
struct nod
{
int data;
struct nod *leg;
}*p,*q;
int main()
{
int x;
q=NULL;
printf("\nIntroduceti elementele listei\n\a");
printf("\nx=");
scanf("%i",&x);
while (x)
{
p=(struct nod *)malloc(sizeof(struct nod));
p->data=x;
p->leg=q;
q=p;
printf("\nx=");
scanf("%i",&x);
}
6
Fundamentele programării 2020-2021
Curs 9
9.2. Câmpuri de biţi
În C există posibilitatea să accesăm un bit din memorie prin intermediul câmpurilor de
biţi. Forma generală a declaraţiei unui câmp de biţi este:
struct nume_structura
{
tip nume_camp1:lungime;
tip nume_camp2:lungime;
.
.
.
tip nume_campn:lungime;
}lista_variabile;
unde lungime reprezintă numărul de biţi ai câmpului, iar tip poate fi unsigned sau signed.
După cum se observă, un câmp de biţi este de fapt membrul unei structuri căruia i se
precizează lungimea (numărul de biţi). Din acest motiv, accesul la informaţia conţinută într-un
câmp se face folosind operatorii consacraţi . sau ->.
struct stare_disp
{
unsigned comp1:1;
unsigned comp2:1;
unsigned comp3:1;
unsigned comp4:1;
unsigned comp5:1;
unsigned comp6:1;
}stare;
7
Fundamentele programării 2020-2021
Curs 9
Starea componentei 4, de pildă, va fi furnizată de stare.comp4. Avantajul folosirii
unei astfel de structuri este evident: în loc să se folosească 6 octeţi pentru codificare, se foloseşte
doar un octet.
Dacă anumiţi membri ai unui câmp de biţi nu ne interesează, pot fi săriţi fără a le da
nume. De exemplu, dacă ne interesează numai bitul 4, se poate folosi următoarea formă
simplificată:
struct stare_disp
{
unsigned:3;
unsigned comp4:1;
}stare;
9.3. Uniuni
O uniune este o structură de date care permite folosirea în comun a aceleiaşi zone de
memorie de două sau mai multe variabile diferite, la momente de timp diferite. Forma generală
de declarare a unei uniuni este:
union nume_uniune
{
tip nume_camp1;
tip nume_camp2;
tip nume_camp3;
. . . . . . .
tip nume_campn;
}lista variabile_uniune;
După cum se poate constata, forma generală de declarare a unei uniuni este asemănătoare
cu cea a unei structuri. Precizările făcute la uniuni referitoare la relaţiile dintre numele uniunii,
8
Fundamentele programării 2020-2021
Curs 9
numele câmpurilor, numele variabilelor_uniune şi numele oricărei variabile rămân valabile şi
aici. Pentru selectarea câmpurilor putem folosi, de asemenea, operatorii . şi ->.
Operatorul sizeof aplicat tipului de date union, adică sizeof(union
nume_uniune) va furniza lungimea uniunii. Aceasta este mai mare sau egală cu lungimea
celui mai mare câmp al uniunii.
Deosebirea fundamentală dintre o uniune şi o structură constă însă în felul în care
câmpurile folosesc memoria. La o structură, zonele de memorie rezervate câmpurilor sunt
diferite pentru câmpuri diferite. La o uniune, toate câmpurile din uniune împart aceeaşi zonă de
memorie. Din acest motiv, datele memorate într-o uniune pot fi referite diferit, funcţie de tipul
membrului pe care-l avem în vedere. Astfel, folosind secvenţa
. . . . . .
union una
{
int i;
float f;
}v;
datele din variabila v vor fi privite ca întregi, dacă selectăm v.i sau reale, dacă selectăm v.f.
Câmpurile i şi f se referă la aceeaşi adresă: în v.i se memorează sizeof(int) octeţi care
încep la această adresă, iar în v.f, sizeof(float) octeţi care încep la aceeaşi adresă.
Folosirea memoriei se face conform următoarei schiţe:
v. i
v. f
Figura 9.1. Modul de folosire a memoriei de către câmpurile uniunii una
Este posibilă iniţializarea unei uniuni cu o constantă care trebuie să aibă tipul primului
câmp declarat.
De exemplu, secvenţa de program
union
{
int i;
float f;
}x={324};
9
Fundamentele programării 2020-2021
Curs 9
va avea ca efect iniţializarea primului câmp (x.i=324).
Exemplul 9.3. Afişarea primului şi ultimului bit din codul binar al unui caracter introdus de la
tastatură.
#include <stdio.h>
#include <conio.h>
int main()
{
struct biti
{
unsigned prim :1;
unsigned :6;
unsigned ultim:1;
};
union char_bit
{
char x;
struct biti y;
}tasta;
printf("\n Tastati un caracter: ");
tasta.x=getche();
if (tasta.y.prim)
printf("\n Primul bit are valoarea 1");
else
printf("\n Primul bit are valoarea 0");
if (tasta.y.ultim)
printf("\n Ultimul bit are valoarea 1");
else
printf("\n Ultimul bit are valoarea 0");
return 0;
}
10
Fundamentele programării 2020-2021
Curs 9
Exemplul 9.4. Se doreşte crearea unei liste cu rezultatele sesiunii de examene pentru o
grupă cu un număr de 30 de studenţi. Pentru verificarea corectitudinii informaţiilor memorate,
programul trebuie să conţină şi afişarea listei. Se presupune că un student, la sfârşitul sesiunii,
poate fi într-una din situaţiile următoare:
a luat toate examenele şi i s-a putut încheia o medie pe sesiune;
a luat toate examenele (să presupunem că acestea sunt în număr de cinci), dar nu este
încheiată media;
a picat cel puţin un examen.
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <malloc.h>
struct catalog
{
char npr[30];
char tip;
union
{
int nota;
int note[5];
struct lista *resta;
}var;
}stud[30];
11
Fundamentele programării 2020-2021
Curs 9
int main()
{
char disci[10],ch;
int i,k,n;
/* introducerea datelor */
printf("\nN=");
scanf("%d",&n);
for (i=0;i<n;++i)
{
printf("\nNume Prenume=");
scanf("%s",stud[i].npr);
do
{
printf("\nTip informatie (m,e,r)=");
ch=getche();
}
while (ch != 'm' && ch != 'e' && ch != 'r');
stud[i].tip=ch;
switch (stud[i].tip)
{
case 'm':printf("\nMedia = ");
scanf("%d",&stud[i].var.nota);
break;
case 'e':printf("\nIntroduceti notele \n");
for (k=0;k<5;k++)
{
printf("\nNota[%d]=",k);
scanf("%d",&stud[i].var.note[k]);
}
break;
case 'r':res=NULL;
printf("\nDiscipline restante si note;");
printf(" pentru terminare tastati stop ");
printf("\n Disciplina =");
scanf("%s",disci);
while (strcmp(disci,"stop"))
{
stud[i].var.resta=(list*)malloc(sizeof(list));
strcpy(stud[i].var.resta->disc,disci);
printf("\n Nota restanta=");
scanf("%d",&stud[i].var.resta->noter);
stud[i].var.resta->leg=res;
res=stud[i].var.resta;
printf("\n Disciplina =");
scanf("%s",disci);
}
break;
} // end switch
12
Fundamentele programării 2020-2021
Curs 9
} // end for
/* afisarea informatiei */
for (i=0;i<n;i++)
{
printf("\nNume Prenume=%s",stud[i].npr);
switch(stud[i].tip)
{
case 'm':printf("\n Media la examene=%d",stud[i].var.nota);
break;
case 'e':printf("\n Notele la examen sunt: \n");
for (k=0;k<5;k++)
printf("\n Nota[%d]=%d",k,stud[i].var.note[k]);
break;
case 'r':printf("\n Disciplinele restante sunt: \n");
res=stud[i].var.resta;
while(res)
{
printf("\n Disciplina=%s",res->disc);
printf("\n Nota restanta = %d",res->noter);
res=res->leg;
}
break;
} // end switch
} // end for
return 0;
}
9.4. Enumerări
O enumerare reprezintă o listă de constante întregi cu nume. Forma generală a unei
enumerări este:
enum nume_enumerare{lista_enum}lista_de_variabile;
unde nume_enumerare şi lista_enum sunt opţionale. lista_enum defineşte toate
valorile pe care le pot lua variabilele din lista_de_variabile.
În exemplul următor
enum culoare{rosu,alb,verde,albastru}culoarea_mea;
variabila culoarea_mea poate lua oricare din valorile rosu,alb,verde,albastru. Este
deci validă atribuirea:
culoarea_mea=albastru;
O dată enumerarea definită, putem să ne referim la ea sub forma
13
Fundamentele programării 2020-2021
Curs 9
enum nume_enumerare lista_de_variabile;
De exemplu, construcţia
enum culoare culoarea_ta;
defineşte o nouă variabilă de tip culoare.
Simbolurile din lista de enumerare nu pot fi citite de la tastatură sau afişate pe ecran în
mod direct. Este deci incorectă o secvenţă de forma:
zi=vineri;
printf(”\n ziua este %s”,zi);
unde se tratează un simbol din listă, ca un şir de caractere. În ciuda acestei restricţii enumerările
se folosesc în programe pentru plusul de claritate pe care-l aduc. Este mai sugestiv, de exemplu,
să folosim simbolurile luni,miercuri,joi,vineri, duminica decât, respectiv,
codurile 1,3,4,5,7.
Exemplul 9.5. Afişarea unui simbol din lista de enumerare cu ajutorul şirurilor şi a unei structuri
switch.
#include "stdio.h"
int main()
{
enum culoare{rosu,alb,verde,albastru} culoarea_mea;
culoarea_mea=alb;
switch (culoarea_mea)
{
case rosu:printf("\n rosu"); break;
case alb:printf("\n alb"); break;
case verde:printf("\n verde"); break;
case albastru:printf("\n albastru"); break;
}
14
Fundamentele programării 2020-2021
Curs 9
return 0;
}
. . . . . .
scurt_in i;
complex z;
boolean b=false;
i=321;
z.re=0.73; z.in=-72.9;
Facem precizarea că numele iniţial al tipului poate fi folosit în continuare alături de
tipurile nou definite. De exemplu, declaraţiile:
short int j;
scurt_in i;
sunt compatibile.
De asemenea, se pot folosi mai multe declaraţii typedef pentru a crea diferite nume noi
pentru acelaşi tip, ca în exemplul următor:
typedef int integer;
typedef integer intreg;
typedef intreg intr;
Utilitatea declaraţiei typedef constă, în primul rând, în aportul de claritate pe care-l poate
aduce unui program. Folosirea ei este recomandată mai ales pentru tipuri care se reprezintă mai
15
Fundamentele programării 2020-2021
Curs 9
complicat (de exemplu tipurile struct, union, enum). Exemplele date sunt edificatoare în acest
sens.
16
Fundamentele programării 2020-2021
Curs 10
Noţiunea de funcţie ocupă în limbajul C un loc central. După cum ştim, un program C
poate fi privit ca un ansamblu de funcţii situate la acelaşi nivel. Consecinţa majoră constă în
posibilitatea de comunicare între oricare două funcţii ale programului. Funcţia main() este
obligatorie în orice program: execuţia unui program începe cu o instrucţiune din main() şi sfârşeşte
cu o instrucţiune din main(). Pentru a uşura înţelegerea elementelor de limbaj prezentate până
acum, toate exemplele au avut în vedere programe alcătuite dintr-o singură funcţie, funcţia main().
În această secţiune vom prezenta elementele care ne vor permite să construim un program din două
sau mai multe funcţii. O funcţie poate fi definită sau declarată. Definiţia unei funcţii cuprinde
antetul funcţiei (declaratorul) şi corpul funcţiei. Declararea unei funcţii se referă doar la
elemente din antetul ei.
tip nume_functie(lista_declaratii_parametri)
{
corpul functiei
}
Specificatorul tip reprezintă tipul rezultatului întors de funcţie şi poate preciza orice tip
de date acceptat în C, mai puţin tipul tablou. Din acest punct de vedere funcţiile se pot clasifica în:
• funcţii care întorc un rezultat;
• funcţii care nu întorc un rezultat.
Funcţiile care întorc un rezultat se pot clasifica în:
• funcţii care întorc valori întregi;
• funcţii care nu întorc valori întregi.
Funcţiile care specifică în mod explicit că tipul lor este int întorc, evident, valori întregi.
De asemenea, valori întregi întorc şi funcţiile declarate char sau cele pentru care specificatorul tip
lipseşte (convenţia implicită). Clasificarea făcută are următoarea motivaţie: aşa cum se va vedea
în secţiunea următoare, pentru buna funcţionare a programului, funcţiile care nu întorc valori
întregi şi cele care nu întorc un rezultat trebuie definite sau declarate înainte de a fi apelate.
Pentru o funcţie care nu întoarce nici un rezultat tipul specificat obligatoriu trebuie să fie
void.
Dacă tip lipseşte, aşa cum am precizat, tipul considerat implicit este int. De altfel, tipul
void este introdus de standardul C tocmai pentru a înlătura confuzia dintre funcţiile care întorc
1
Fundamentele programării 2020-2021
Curs 10
implicit valori întregi şi funcţiile care nu întorc un rezultat. Precizarea tipului void previne
folosirea incorectă, în expresii, a funcţiilor care nu întorc rezultate.
Funcţia main() returnează către sistemul de operare care o apelează, o valoare întreagă (de
obicei 0 dacă programul nu a avut erori şi o valoare diferită de 0, în caz contrar). Dacă totuşi, în
implementarea folosită, main() nu returnează valori, atunci tipul ei se va declara void.
Lista_declaratii_parametri cuprinde enumerarea parametrilor formali (sau
fictivi) ai funcţiei, fiecare parametru fiind precedat de tipul său. Cele două paranteze rotunde care
încadrează lista_declaratii_parametri trebuie să existe obligatoriu, chiar şi în situaţia
în care lista este vidă. Mai mult, recomandarea este ca precizarea unei liste vide de parametri să se
facă prin scrierea cuvântului void între cele două paranteze rotunde.
Corpul functiei, împreună cu acoladele formează o instrucţiune compusă, ce poate conţine
instrucţiuni şi eventuale declaraţii de tipuri de date şi variabile utilizate de aceste instrucţiuni.
Pentru revenirea în funcţia apelantă se foloseşte în mod normal instrucţiunea de salt return.
Forma generală a acestei instrucţiuni este:
return expresie;
unde expresie poate să lipsească.
Este evident că o funcţie de tip void nu trebuie să conţină o instrucţiune return care
specifică o expresie. Dacă totuşi se încearcă returnarea valorii date de o expresie, valoarea
respectivă rămâne nedefinită. În concluzie, o funcţie de tip void trebuie să conţină cel mult
instrucţiunea return fără precizarea expresiei. Dacă nu există nicio instrucţiune return în corpul
funcţiei, revenirea în funcţia apelantă se face automat după executarea ultimei sale instrucţiuni.
Utilizarea instrucţiunii return, cu precizarea expresiei, este obligatorie în funcţiile care
întorc o valoare (nu sunt de tip void), iar valoarea expresiei este tocmai valoarea întoarsă. Această
valoare trebuie să fie compatibilă cu rezultatul întors de funcţie, iar dacă este cazul, întocmai ca la
operaţia de atribuire, se fac conversiile necesare. Dacă la astfel de funcţii instrucţiunea return
lipseşte, valoarea întoarsă de funcţie e nedefinită. Dacă instrucţiunea return există, însă fără
precizarea expresiei, se presupune întoarcerea unei valori inutile.
În corpul unei funcţii pot fi mai multe instrucţiuni return, adică se poate reveni în
programul apelant din orice punct al funcţiei. Valoarea întoarsă de funcţie poate să fie folosită sau
nu în prelucrări, nefolosirea ei nefiind o eroare.
Exemple de definire a unor funcţii:
2
Fundamentele programării 2020-2021
Curs 10
void mesaj1(int x)
{
if(x)
printf(”\n Ati tastat bine!”);
else
printf(”\n Ati tastat gresit!”);
}
void mesaj2(void)
{
printf(”\n Intrerupeti executia programului!”);
}
Orice funcţie care returnează valori de un tip diferit de tipul int trebuie declarată sau
definită înainte de a fi apelată. Declaraţiile trebuie făcute la începutul programului (înainte de
definirea oricărei funcţii) pentru a informa compilatorul asupra tipului rezultat şi a putea genera
un cod corect. Deoarece verificările de tip se fac în faza de compilare (şi nu mai târziu la linkeditare
sau execuţie) absenţa declaraţiei funcţiei, înainte de a fi apelată, conduce fie la eroare de compilare
- dacă funcţia de unde se face apelul şi funcţia apelantă sunt în acelaşi fişier program, fie la rezultate
imprevizibile - dacă cele două funcţii în cauză sunt în fişiere diferite. Forma de declarare a unei
funcţii se numește prototipul funcţiei.
Prototipul unei funcţii are forma:
int main()
{
float r;
3
Fundamentele programării 2020-2021
Curs 10
printf("\n Raza cercului=");
scanf("%f",&r);
printf("\n Aria cercului este %lf",aria(r));
return 0;
}
// definitia functiei aria
double aria(float r)
{
const double pi=3.14159265;
return pi*r*r;
}
#include <stdio.h>
#include <math.h>
int main()
{
float y,x;
printf("\n x=");
scanf("%f",&x);
y=f(x)+2.5;
printf("\n y=%f",y);
return 0;
}
4
Fundamentele programării 2020-2021
Curs 10
//definitia functiei f
float f(float x)
{
if (x>0)
return exp(x)+1.45;
else
return sqrt(-x)+2.3;
}
5
Fundamentele programării 2020-2021
Curs 10
Transferul informaţiei între funcţia apelantă şi funcţia apelată se poate face prin valoare
sau prin referinţă.
Transferul prin valoare constă în copierea valorii parametrului actual în zona de memorie
a parametrului formal corespunzător în momentul efectuării apelului. Practic, la apelul funcţiei,
valoarea parametrului actual va fi depusă în stivă (stack) odată cu variabilele locale ale funcţiei şi
cu adresele de revenire în funcţia apelantă. Dacă funcţia apelată are prototip, valoarea parametrului
actual se converteşte la tipul parametrului formal corespunzător indicat în prototip (exact ca în
cazul unei atribuiri). În cazul unei incompatibilităţi între tipuri se semnalează eroare.
Dacă funcţia nu are prototip (lucru nerecomandat), la apelare, tipurile char şi short sunt convertite
la int, float la double, iar tablou la pointer.
Să remarcăm faptul că, în cazul transferului prin valoare, valorile parametrilor
actuali nu sunt afectate de prelucrările din corpul funcţiilor apelate: aceste prelucrări
vizează copii ale parametrilor actuali şi nu parametrii înşişi.
Pentru ca funcţia apelată să poată modifica valoarea unei variabile indicată ca
parametru actual trebuie să i se cunoască adresa sa. Soluţia este ca parametrul formal
corespunzător să fie declarat pointer şi să i se transmită la apel adresa variabilei şi nu
valoarea. Acest mod de transfer se cheamă transfer prin referinţă.
În scopul ilustrării celor două modalităţi de transfer, prin valoare şi prin referinţă,
prezentăm mai jos un exemplu clasic: comutarea a două elemente utilizând două funcţii, funcţia
comut1() şi funcţia comut2().
int main()
{
int a,b;
a=3;
b=5;
comut1(a,b);
printf("\nRezultate comut1 a=%d,b=%d",a,b);
comut2(&a,&b);
printf("\nRezultate comut2 a=%d,b=%d",a,b);
return 0;
}
void comut1(int x,int y)
{
int z=x;
x=y;
y=z;
}
6
Fundamentele programării 2020-2021
Curs 10
void comut2(int *x,int *y)
{
int z=*x;
*x=*y;
*y=z;
}
a=3 x=5 a → 5 x
b=5 y=3 b → 3 y
Să observăm că, datorită modului de transfer prin valoare, prelucrările din funcţia
comut1() nu afectează variabilele a şi b care rămân neschimbate (necomutate). Comutarea este
realizată de funcţia comut2(). Aici, prelucrările din funcţie se referă direct la valorile conţinute
în a şi b.
O excepţie de la regula transmiterii prin valoare o reprezintă cazul când parametrul actual
este un tablou. Deoarece copierea valorilor componentelor tabloului într-un parametru formal ar fi
constituit, în general, un consum mare de timp s-a adoptat soluţia transmiterii către funcţie doar a
adresei de început a tabloului. În consecinţă, parametrul transmis poate fi numele tabloului care
este un pointer constant către primul element din tablou. Parametrul formal corespunzător poate fi
declarat într-una din modalităţile următoare:
• Este declarat ca tablou cu dimensiunea egală cu dimensiunea tabloului transmis;
• Este declarat ca tablou fără a da dimensiunea primei componente;
• Este declarat ca pointer.
Prezentăm mai jos exemple care să ilustreze cele trei cazuri.
7
Fundamentele programării 2020-2021
Curs 10
Exemplul 10.4. Calcularea sumei elementelor unei matrici a(4x3).
Varianta 1 – parametrul formal este o matrice de dimensiune 4x3
#include <stdio.h>
int main()
{
int a[4][3],i,j,s;
// citirea elementelor matricii a
for (i=0;i<4;i++)
for (j=0;j<3;j++)
{
printf("\n a[%i][%i]=",i,j);
scanf("%d",&a[i][j]);
}
s=suma(a); //calculul sumei elementelor matricii a folosind functia suma()
printf("\n Suma=%d",s); // afisarea sumei
return 0;
}
8
Fundamentele programării 2020-2021
Curs 10
// citirea valorilor m si n
do
{
printf("\n m=");
scanf("%d",&m);
printf("\n n=");
scanf("%d",&n);
}
while (m<1 || m>4 || n<1 || n>3);
for (i=0;i<m;i++)
for (j=0;j<n;j++)
{
printf("\n a[%d][%d]=",i,j);
scanf("%d",&a[i][j]);
}
s=suma(a,m,n ); //calculul sumei folosind functia suma()
printf("\n suma=%d",s); //afisarea sumei
return 0;
}
Comentariu. Se observă că funcţia suma() poate prelucra matrici pentru care prima
dimensiune m este oarecare, iar a doua dimensiune n satisface condiţia 0<=n<=2. Deoarece pentru
a adresa elementul x[i][j] compilatorul foloseşte relaţia
&x[i][j]=&x[0][0]+(i*3+j)*sizeof(int)
este clar că, la compilare, cea de-a doua dimensiune a matricii trebuie să fie cunoscută (în cazul
nostru e egală cu 3). În consecinţă, un declarator al funcţiei de forma
int suma(int x[][],int m,int n)
este greşit.
9
Fundamentele programării 2020-2021
Curs 10
Exemplul 10.5. Calcularea sumei elementelor unei matrici a(4x3).
Varianta 3 – parametrul formal este un pointer dublu.
#include <stdio.h>
int suma(int **y,int m,int n);
int main()
{
int a[4][3],i,j,m,n,s;
// citirea valorilor m si n
do
{
printf("\n m=");
scanf("%d",&m);
printf("\n n=");
scanf("%d",&n);
}
while (m<1 || m>4 || n<1 || n>3);
//citirea elementelor matricii*/
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
printf("\n a[%d][%d]=",i,j);
scanf("%d",&a[i][j]);
}
s=suma(a,m,n);
printf("\n Suma=%d",s);
return 0;
}
Comentariu. Această variantă cu pointeri permite folosirea unei funcţii care poate însuma
elementele unei matrici de dimensiuni oarecare. Funcţia primeşte adresa de început a matricii prin
intermediul pointerului dublu y. Prin conversia explicită (int*)y, pointerul y este văzut ca un
10
Fundamentele programării 2020-2021
Curs 10
pointer către int, fapt ce permite referirea elementelor tabloului ca în cazul unui tablou
unidimensional. În consecinţă, referirea la a[i][j] se poate face, simulând funcţia de alocare, sub
forma
((int*)y)[(n+1)*i+j]
Exemplul 10.6. Ordonarea crescătoare a unui vector utilizând algoritmul de sortare prin selecţie.
#include <stdio.h>
int main()
{
int x[20],n;
do
{
printf("\n n=");
scanf("%d",&n);
}
while (n<1 || n>20);
citv(x,n);
sortv(x,n);
afisv(x,n);
return 0;
}
11
Fundamentele programării 2020-2021
Curs 10
x[i]=min;
}
}
Trebuie reţinut faptul că doar transmiterea tabloului în întregime este o excepţie de la regula
de transmitere a parametrilor prin valoare. Dacă se transmite doar o componentă, aceasta este
tratată în mod obişnuit, ca o variabilă simplă. Funcţia par_imp(), din exemplul de mai jos,
decide dacă o componentă oarecare i a tabloului de numere întregi a[7] este pară sau impară.
#include <stdio.h>
int main ()
{
int a[7]={31,30,15,42,26,17,19},i;
do
{
printf("\n Tastati 0,1,2,3,4,5 sau 6 \n");
printf("\n i=");
scanf("%d",&i);
}
while (i<0 || i>6);
par_imp(a[i],i);
return 0;
}
12
Fundamentele programării 2020-2021
Curs 10
if (x%2)
printf("\n Componenta %d este impara",i);
else
printf("\n Componenta %d este para",i);
}
typedef struct
{
char *nume;
int nota;
}CATALOG;
int main()
{
CATALOG x={"Ion",10};
afis(x);
return 0;
}
void afis(CATALOG y)
{
printf("\n %s are nota %d",y.nume,y.nota);
}
Observaţie. Definirea tipului structură CATALOG se face în afara oricărei funcţii (are un
caracter global) tocmai pentru a putea fi “văzută“ din oricare din cele două funcţii, main() sau
afis().
13
Fundamentele programării 2020-2021
Curs 10
Oricare din membrii unei structuri poate fi transmis unei funcţii, fie prin valoare, fie prin
referinţă.
Exemplul 10.9. Funcţiei afis_val() i se furnizează câmpul nota din structura CATALOG, prin
valoare, iar funcţiei afis_ref(), prin referinţă
#include <stdio.h>
typedef struct
{
char *nume;
int nota;
}CATALOG;
int main()
{
CATALOG x={"Gheorghe",7};
afis_val(x.nota);
afis_ref(&x.nota);
return 0;
}
void afis_val(int y)
{
printf("\n Mesaj afis_val: Nota este=%i",y);
}
14
Fundamentele programării 2020-2021
Curs 10
Presupunând că programul de mai jos primeşte în urma compilării şi linkeditării numele
argmain, atunci apelul
argmain Nae
va conduce, după execuţie, la rezultatul: Bine te-am gasit Nae!
15
Fundamentele programării 2020-2021
Curs 10
p=afisare;
După o atribuire de forma pf=f; apelul funcţiei prin intermediul pointerului pf se poate
face
(*pf)(lista_parametri_actuali);
dacă funcţia f este de tip void, sau
nume_variabila=(*pf)(lista_parametri_actuali);
dacă funcţia f întoarce un rezultat.
Exemplu. Secvenţa de program de mai jos,
. . . . . .
p=afisare;
(*p)(3.24,5);
. . . . . .
este perfect validă şi are ca efect apelarea indirectă a funcţiei afisare.
Prezentăm în continuare două din posibilităţile de aplicare a pointerilor la funcţii.
O posibilitate este oferită de tablourile de pointeri către funcţii. Programul de mai jos
selectează aleator una din funcţiile adunare(),scadere(),inmultire() şi
impartire() prin intermediul unui tablou de pointeri către funcţii. Selecţia se face folosind
funcţia rand() care întoarce un număr arbitrar în domeniul 0...RAND_MAX, unde RAND_MAX
are cel puţin valoarea 32767. Prototipul funcţiei se află în fişierul antet <stdlib.h>.
int main()
{
int i,a=8,b=2;
int (*t[])(int,int)={adunare,scadere,inmultire,impartire};
char *nume_t[]={"adunare","scadere","inmultire","impartire"};
srand(time(0));
i=rand()%4;
printf("\n S-a selectat functia %s",nume_t[i]);
printf("\n Rezultatul este %d",(*t[i])(a,b));
return 0;
}
16
Fundamentele programării 2020-2021
Curs 10
int adunare(int x,int y)
{
return x+y;
}
Cea de-a doua posibilitate se referă la transmiterea drept parametri a pointerilor la funcţii.
Să considerăm funcţia
sum(a, b ) daca x 0
fx =
dif (a, b ) daca x0
Se poate folosi o singură funcţie f() pentru definirea lui fx, care va avea ca argumente pe
x şi un pointer la funcţii notat p; acest pointer poate primi atât adresa funcţiei sum() cât și cea a
funcţiei dif().
#include <stdio.h>
int main()
{
float x;
int a=9,b=4;
printf("\nx=");
17
Fundamentele programării 2020-2021
Curs 10
scanf("%f",&x);
if (x>0)
f(x,a,b,sum);
else
f(x,a,b,dif);
return 0;
}
tip g(lista_declaratii_parametri)
{
18
Fundamentele programării 2020-2021
Curs 10
apel f()
}
Stiva
(Stack)
Variabile dinamice
(Heap)
Variabile
globale
Cod program
Stiva este un caz particular de listă, în care principalele operaţii de introducere şi extragere
sunt guvernate de disciplina LIFO (Last In First Out), adică ultimul element intrat este primul
extras. De regulă, stiva se poate reprezenta sub forma
x
Vârf
x
19
Fundamentele programării 2020-2021
Curs 10
Funcții direct recursive
Cazul de recursivitate cel mai des întâlnit este cel al funcţiilor direct recursive. Este
situaţia în care funcţia apelantă şi funcţia apelată coincid. Adâncimea recursivităţii este dată de
numărul de autoapeluri. Pentru a nu se autoapela la infinit, corpul funcţiei trebuie să conţină o
instrucţiune if, care, pe baza testării unei condiţii, asigură după un timp oprirea autoapelului şi
execuţia instrucţiunilor amânate prin autoapel. Conform mecanismului general de apelare, la
fiecare autoapel al funcţiei se salvează în stivă starea curentă a execuţiei sale, adică: adresa de
revenire în program (adresa instrucţiunii cu care se va continua execuţia întreruptă), valorile
variabilelor locale şi valorile parametrilor. Instrucţiunile din funcţie care se găsesc înaintea
instrucţiunii if considerate se ciclează până la oprirea autoapelului. Oprirea autoapelului înseamnă
>coborârea> pas cu pas în stivă: pentru fiecare pas se preiau din stivă valorile corespunzătoare şi
se execută instrucţiunile amânate prin autoapel. Pentru o mai bună înţelegere, prezentăm mai jos
exemplul clasic al calculului recursiv al factorialului. Funcţia factorial se poate defini recursiv
astfel:
1 daca n 1
f (n ) =
n f (n − 1) daca n 1
O posibilă implementare este:
int f(int n)
{
if(n>1)return n*f(n-1);
return 1;
}
sau folosind operatorul condiţional
int f(int n)
{
return(n>1)?n*f(n-1):1;
}
Să considerăm apelul f(4). Acest apel va activa succesiv instrucţiunea n*f(n-1)
pentru n=4,3,2. Pentru fiecare din aceste apeluri în stivă se vor depune parametrii actuali
4,3,2,1. Apelul f(1) furnizează rezultatul 1 pentru rezolvarea instrucţiunii amânate
2*f(1), care este valoarea lui f(2).
Cu această valoare se calculează valoarea f(3)=3*f(2) şi în final valoarea
f(4)=4*f(3). Se observă că rezolvarea fiecărui autoapel înseamnă deplasarea pe un nivel
inferior al stivei, adică parcurgerea în ordine inversă a şirului de parametri actuali 4,3,2,1.
Schematic, la fiecare apel, stiva va arăta succesiv astfel:
20
Fundamentele programării 2020-2021
Curs 10
1
2 2
3 3 3
4 4 4 4
Stivă vidă Apel f(4) Apel f(3) Apel f(2) Apel f(1)
int main()
{
int x=6;
g(x);
return 0;
}
void g(int x)
{
if (x)
{
g(x-2);
printf("%2i",x);
}
}
Rezultatul execuţiei programului este 2 4 6.
Pentru a înţelege modul de lucru al programului să presupunem la modul ideal o
multiplicare a funcţiei g(). Se poate imagina astfel următoarea schemă de apel:
21
Fundamentele programării 2020-2021
Curs 10
main() g(6) g(4) g(2) g(0)
{
int x=6 if(6) if(4) if(2) if(0)
g(6); { { { iesire din
return 0; g(4); g(2); g(0); autoapel
} printf printf printf
("%i",x); ("%i",x); ("%i",x);
} } }
Condiţia de ieşire din recursivitate este realizată când argumentul x al funcţiei g() devine
0. În acest moment se revine în funcţie la prima instrucţiune de după apel, adică la printf().
Se execută printf() cu valoarea corespunzătoare lui x, adică 2. Se revine în funcţie la prima
instrucţiune de după apel, adică se execută printf() pentru x=4. Analog se revine în funcţie la
printf() care se execută pentru x=6. În final se revine în funcţia main() şi procesul se încheie.
Atragem atenţia că “multiplicarea” funcţiei recursive nu se realizează în realitate; există o
singură funcţie care se execută pentru diverse valori ale parametrilor.
Programele care folosesc funcţii recursive nu sunt întotdeauna cele mai bune variante de
rezolvare a unor probleme sub aspectul timpului de calcul şi al memoriei folosite. Se poate spune
că în general metodele recursive, mai ales când adâncimea recursivităţii este mare, sunt
neperformante. De exemplu, pentru funcţiile prezentate mai sus, variantele iterative sunt simple şi
eficiente. De asemenea, aceste variante sunt mai uşor de depanat. Totuşi, de cele mai multe ori
funcţiile recursive pun în lumină mult mai bine esenţa problemei de rezolvat. Implementarea
recursivă a factorialului reprezintă un astfel de exemplu. Pentru o serie de probleme, însă,
rezolvarea folosind funcţii recursive reprezintă singura soluţie viabilă din punct de vedere practic.
De exemplu, algoritmul de sortare rapidă QuickSort sau binecunoscuta problemă a turnurilor
din Hanoi sunt dificil de implementat fără a utiliza recursivitatea.
Ne vom opri cu exemplificarea la problema turnurilor din Hanoi, care are următorul
enunţ:
Se dau trei tije a,b,c. Pe tija a se află n discuri perforate, de diametre diferite, aşezate
unul peste celălalt în ordinea descrescătoare a diametrelor. Se cere să se găsească toate mutările
prin care cele n discuri de pe tija a sunt aduse pe tija b în aceeaşi ordine, utilizând tija c ca o tijă
ajutătoare. O mutare înseamnă deplasarea unui singur disc de pe o tijă pe alta, peste un disc de
diametru mai mare.
Vom nota prin ab, deplasarea unui disc de pe tija a pe tija b şi în mod analog orice
deplasare de pe o tijă pe alta. Evident, pentru n=1 soluţia problemei este ab, pentru n=2, şirul
22
Fundamentele programării 2020-2021
Curs 10
de mutări ac,ab,cb, iar pentru n=3,ab,ac,bc,ab,ca,cb,ab. Pe măsură ce n creşte se
observă şi creşterea dificultăţii de rezolvare. Problema se rezolvă relativ simplu, dacă punem în
evidenţă caracterul ei recursiv.
Astfel, se poate spune că mutarea a n discuri de pe tija a pe tija b utilizând tija c este echivalentă
cu succesiunea de paşi:
• mutarea a n-1 discuri de pe tija a pe tija c utilizând b;
• mutarea singurului disc rămas, de pe tija a, pe tija b;
• mutarea celor n-1 discuri de pe tija c pe tija b utilizând a.
Grafic, rezolvarea se poate schiţa astfel:
a b c
n-
1
a b c
n-1
n <=> 1
Figura 10.6. Schema deplasărilor celor n discuri de pe tija a, pe tija b, utilizând tija c
Notând cu Hanoi (n, a, b, c) soluţia problemei, ţinând cont de cele spuse anterior obţinem
exprimarea sa recursivă.
ab n = 1
Hanoi(n, a , b, c) =
Hanoi(n − 1, a , c, b)abHanoi(n − 1, c, b, a )
Exemplul 10.14. Programul care implementează funcția Hanoi(n,a,b,c)
#include <stdio.h>
int main()
{
int n;
printf("\n Numarul de discuri, n=");
scanf("%i",&n);
printf("\n Solutia este ");
23
Fundamentele programării 2020-2021
Curs 10
hanoi(n,'a','b','c');
return 0;
}
24
Fundamentele programării 2020-2021
Curs 11
Poziţia octetului curent (octetul care va fi prelucrat) este memorată într-o variabilă specială
numită indicator de poziţie. Pe baza indicatorului de poziţie se poate face modificarea octetului
curent, citirea sau scrierea la o anumită poziţie în fişier. După efectuarea operaţiilor de citire sau
scriere indicatorul de poziţie este incrementat cu un număr de octeţi. De asemenea, este posibilă
citirea valorii indicatorului de poziţie sau setarea lui la o anumită valoare.
Un flux se reprezintă printr-un pointer la o entitate de tip FILE care conţine informaţii
despre: poziţia curentă în flux, indicatori de eroare şi de sfârşit de fişier, zone tampon (buffere)
asociate.
Fluxul text presupune transferul de caractere organizate în linii caractere (o linie se termină
prin caracterul newline). Într-un flux text pot interveni anumite conversii de caracter şi din acest
motiv este posibil să existe anumite nepotriviri între caracterele introduse în flux şi cele rezultate
în urma transferului (de exemplu, un caracter newline poate fi convertit într-o pereche de caractere
retur de car-avans rand).
Fluxul binar reprezintă o succesiune de octeţi care nu suportă nici o conversie în timpul
transferului.
1
Fundamentele programării 2020-2021
Curs 11
Celelalte fişiere ale programului trebuie deschise în mod explicit folosind funcţia fopen(),
care conectează un flux la un anumit fişier. În această lucrare vor fi considerate doar fişiere pe
disc.
unde:
numefis este numele fişierului, precedat eventual de calea de acces, iar mod este un şir
de caractere care precizează modul deschiderii. Variabila mod precizează caracterul text sau binar
al deschiderii precum şi scopul deschiderii (citire, scriere, adăugare, citire-scriere).
Observaţii. Pentru a indica explicit modul text se poate adăuga sufixul t. De asemenea, valorile
mod de tipul "r+b" sau "r+t" se pot scrie şi sub forma "rb+","rt+".
Încercarea de a deschide un fişier care nu există, în mod citire, conduce la eroare. Dacă un
fişier este deschis pentru scriere în modul "w"("wt"sau"wb") atunci informaţia conţinută în el
va fi distrusă. Dacă fişierul este deschis pentru scriere şi nu există deja un fişier cu acel nume,
atunci el va fi creat. Pentru actualizare (citire şi scriere) fişierul trebuie deschis folosind caracterul
'+' în cadrul modului; în acest fel, dacă nu există, el va fi creat, iar dacă există, nu va fi distrus.
Dacă se deschide un fişier în modul "a", datele vor fi adăugate la sfârşitul fişierului dacă
acesta deja există, iar dacă nu există se creează un fişier nou.
2
Fundamentele programării 2020-2021
Curs 11
Dacă operaţia de deschidere a unui fişier este încununată de succes funcţia fopen() creează
o structură FILE şi întoarce adresa sa. Indicatorul de poziţie ia valoarea 0 dacă fişierul este deschis
în modurile "r" sau "w" şi o valoare egală cu numărul de octeţi ai fişierului, dacă fişierul este
deschis în modul "a". Dacă operaţia de deschidere a fişierului nu a avut succes (de exemplu
fişierul nu există, este protejat la scriere sau discul este plin) funcţia fopen() întoarce valoarea
NULL.
Operaţia simetrică deschiderii unui fişier, adică operaţia de închidere a fişierului este
realizată cu ajutorul funcţiei fclose(). Ea are următorul prototip:
int fclose(FILE *fp);
Efectul funcţiei fgetc() este următorul: funcţia returnează valoarea caracterului dacă
operaţia a avut succes sau EOF când s-a atins sfârşitul fişierului sau are loc o eroare.
3
Fundamentele programării 2020-2021
Curs 11
11.3. Funcții pentru transferul șirurilor de caractere
O generalizare a funcţiilor care activează la nivel caracter o reprezintă funcţiile care
realizează transferuri de şiruri de caractere. Prototipurile acestor funcţii se află în fişierul antet
stdio.h şi au următoarea formă:
Funcţia fputs() scrie în fişier şirul precizat la adresa sir. În caz de succes întoarce ultimul
caracter scris, iar în caz de insucces se întoarce caracterul EOF.
Funcţia fgets() citeşte un şir de lungime cel mult lg-1 caractere şi le transferă în şirul
str. Dacă se întâlneşte un caracter newline, citirea e oprită iar caracterul va fi incorporat în
str. Şirul va fi completat automat cu caracterul ’\0’. În caz de succes funcţia întoarce adresa
şirului str, iar în caz de insucces sau sfârşit de fişier, se întoarce valoarea NULL.
Exemplul 11.1. Se creează un fişier prin adăugare, citind şiruri de la tastatură. Se listează
apoi fişierul în linii de n caractere. Numele fişierului şi valoarea lui n se citesc de la tastatură
#include <stdio.h>
int main()
{
int n;
char x[200],*fc;
char numefis[11];
FILE *f;
printf("\n Dati numele fisierului: ");
gets(numefis);
printf("\n Dati lungimea liniilor de afisare: ");
scanf("%d",&n);
if ( (f=fopen(numefis,"a")) == NULL)
{
printf("\n Nu se poate deschide fisierul");
exit(1);
}
printf("\n Adaugati siruri?(d/n)");
if (tolower(getche()) == 'd')
{
printf("\n Tastati siruri caractere despartite prin Enter,");
printf(" pentru sfarsit tastati cuvantul stop\n");
4
Fundamentele programării 2020-2021
Curs 11
gets(x);
while (strcmp(x,"stop"))
{
fputs(x,f);
gets(x);
}
fclose(f);
Funcţia fscanf() întoarce numărul de valori pentru care citirea, conversia şi memorarea
datelor a fost făcută corect. În cazul în care apare o eroare înainte de a se începe citirea, se
returnează EOF.
5
Fundamentele programării 2020-2021
Curs 11
Funcţia fprintf() returnează numărul de caractere scrise efectiv sau o valoare negativă în
caz de insucces.
Exemplul 11.2. Programul creează fişierul formatat pe disc având numele ”grupa”
cuprinzând numele, prenumele şi nota la examen pentru studenţii unei grupe. Pentru verificarea
corectitudinii informaţiei din fişier, conţinutul acestuia se afişează pe ecran.
#include <stdio.h>
#include <string.h>
typedef struct catalog
{
char nume[15];
char prenume[20];
short nota;
}struc;
int main()
{
FILE *f;
int n,i;
struc x;
if ( !(f=fopen("grupa","w")) )
{
printf("\n Nu se poate deschide fisierul");
exit(1);
}
printf("\n Introduceti numarul de studenti din grupa ");
scanf("%d",&n);
if ( !(f=fopen("grupa","r")) )
{
printf("\n Nu se poate deschide fisierul!");
exit(1);
}
6
Fundamentele programării 2020-2021
Curs 11
fscanf(f,"%s %s %i",x.nume,x.prenume,&x.nota);
printf("\n%10s %10s %3d",x.nume,x.prenume,x.nota);
}
return 0;
}
Observaţie. Funcţia de citire scanf() este echivalentă cu fscanf(stdin, ... ), iar funcţia de
scriere printf() cu fprintf (stdout, ... ).
7
Fundamentele programării 2020-2021
Curs 11
int feof(FILE *fp);
Funcţia întoarce valoarea adevărat (valoare diferită de zero), dacă s-a întâlnit sfârşitul
fişierului şi valoarea fals (egală cu zero) dacă nu s-a întâlnit sfârşitul fişierului. Funcţia feof() se
poate folosi atât la fişiere binare cât şi la fişiere text.
11.7. Funcția REWIND()
Funcţia rewind() poziţionează indicatorul de poziţie al fişierului la începutul fişierului.
Prototipul său este:
void rewind(FILE *fp);
#include <stdio.h>
8
Fundamentele programării 2020-2021
Curs 11
/* crearea fisierului */
for (i=0;i<n;i++)
{
printf("\n Nume=");
scanf("%s",x.nume);
printf("\n Prenume=");
scanf("%s",x.prenume);
printf("\n Nota=");
scanf("%d",&x.nota);
if (fwrite(&x,sizeof(x),1,f) != 1)
{
printf("\n Eroare la scriere");
exit(1);
}
}
/* aducerea indicatorului de pozitie la valoarea 0 */
rewind(f);
/* afisarea continutului fisierului */
for (i=0;i<n;i++)
if (fread(&x,sizeof(x),1,f) != 1)
{
if (feof(f)) break;
printf("\n Eroare la citire");
}
else
printf("\n Nume=%s Prenume=%s Nota=%d",x.nume,x.prenume,x.nota);
fclose(f);
}
9
Fundamentele programării 2020-2021
Curs 11
nume semnificaţie
SEEK_SET începutul fişierului
În cazul în care operaţia de poziţionare reuşeşte, funcţia returnează 0, iar în caz de insucces
o valoare diferită de 0.
Funcţia fgetpos() are prototipul:
int fgetpos(FILE *fp, fpos_t *poz);
şi are ca efect memorarea valorii curente a indicatorului de poziţie în obiectul indicat de poz. În
caz de eroare funcţia returnează o valoare diferită de 0, iar în caz contrar valoarea 0. Tipul fpos_t
e definit în stdio.h ca typedef long fpos_t.
Funcţia fsetpos() are prototipul:
int fsetpos(FILE *fp,const long int *poz);
Apelul are următorul efect: indicatorul de poziţie va primi valoarea memorată în obiectul
indicat de poz. Această valoare trebuie să fie obţinută anterior printr-un apel fgetpos(). În caz
de succes funcţia returnează valoarea 0 iar în caz contrar o valoare diferită de 0.
Funcţia ftell() are prototipul:
long int ftell(FILE *fp);
Ea returnează valoarea curentă a indicatorului de poziţie al fişierului asociat pointerului
fp. În caz de eroare valoare returnată este 0, iar dacă fluxul nu este asociat unui dispozitiv care să
permită căutări aleatoare, valoarea returnată este nedefinită.
Exemplul 11.4. Programul permite poziţionarea pe o înregistrare a fişierului construit cu
programul de la Exemplul 11.3; în funcţie de opţiunea utilizatorului se modifică sau nu nota
înregistrării.
#include <stdio.h>
10
Fundamentele programării 2020-2021
Curs 11
int main()
{
char numefis[8];
long int *p;
int k;
FILE *f;
struc x;
printf("\nNume fisier =");
scanf("%s",numefis);
f=fopen(numefis,"r+b");
printf("\n Dati numarul inregistrarii ");
scanf("%d",&k);
fseek(f,0,SEEK_END);
if ( 1<=k && sizeof(struc)*(k-1) < ftell(f) )
{
fseek(f,sizeof(struc)*(k-1),SEEK_SET);
fread(&x,sizeof(struc),1,f);
printf("\n Inregistrarea %i este:\n",k);
printf("\n Nume=%s,Prenume=%s,Nota=%d",x.nume,x.pren,x.nota);
printf("\n Doriti modificarea notei ?(d/n)");
if (toupper(getche())=='D')
{
printf("\nNota noua este=");
scanf("%d",&x.nota);
fgetpos(f,p);
*p-=sizeof(struc);
fsetpos(f,p);
fwrite(&x,sizeof(struc),1,f);
}
}
else
printf("\nInregistrarea nu este in fisier!");
printf("\n Inregistrarile din fisier sunt:\n");
fseek(f,0,SEEK_SET);
while( !feof(f) )
{
fread(&x,sizeof(struc),1,f);
if ( !feof(f) )
printf("\nNume=%s Prenume=%s Nota=%d",x.nume,x.pren,x.nota);
}
fclose(f);
return 0;
}
11
Fundamentele programării 2020-2021
Curs 12
O directivă preprocesor este totdeauna precedată de simbolul # (diez), trebuie să fie singură
pe o linie şi nu se termină cu semnul ; . Cele mai utilizate directive sunt #define şi #include.
12.1. Directivele #DEFINE, #UNDEF Şi #INCLUDE
Directiva #define are forma generală:
#define nume_macro secventa_caractere
unde:
nume_macro reprezintă identificatorul care va fi înlocuit în text, ori de câte ori apare,
prin secventa_caractere.
De exemplu, în secvenţa de mai jos
#define M 20
#define eroared ”eroare la scriere pe disc”
int x[M],i;
. . . . .
for(i=0;i<M;i++)
printf(”%i”,x[i]);
1
Fundamentele programării 2020-2021
Curs 12
if(y)
printf(eroare);
. . . . .
efectul directivelor #define constă în înlocuirea identificatorilor M şi eroared prin secvenţele
20, respectiv ”eroare la scriere pe disc” oriunde apar ei în program.
Se observă că folosirea directivei #define măreşte lizibilitatea programului, uşurând
întreţinerea sa. Astfel, pentru a modifica dimensiunea vectorului şi toate secvenţele de program
care o privesc, este suficient să schimbăm doar linia #define M 20.
Dacă lungimea şirului secventa_caractere depăşeşte un rând, se poate continua pe rândul
următor dacă se inserează un backslash (\) la sfârşitul primului rând.
Exemplu:
#define LUNG ”Acest sir este insuportabil \
de lung”
Deoarece directiva #define permite înlocuirea unui simbol printr-un text, în particular
acel text poate fi o instrucţiune sau un nume macro prezent într-o directivă #define anterioară.
Exemplu:
#define B 20
#define C 30
#define D 40
#define ARIA B*C
#define VOLUM ARIA*D
#define EROARE printf(”\n Eroare”)
. . . . .
v=VOLUM
. . . . .
if (x<11)
EROARE;
. . . . .
Nu trebuie confundată substituţia în text care se face utilizând directiva #define, cu o
atribuire. De exemplu, expresia:
A=(B+5)*D
2
Fundamentele programării 2020-2021
Curs 12
nu este descrisă corect prin secvenţa
#define B 20
#define C B+5
#define D 10
. . . . .
A=C*D
. . . . .
În realitate, C se înlocuieşte textual prin B+5, adică avem A=B+5*D ceea ce este cu totul
altceva.
Corect este:
#define B 20
#define C (B+5)
#define D 10
. . . . .
A=C*D
Directiva #define poate fi parametrizată obţinându-se o macrodefiniţie (sau macro). În
această situaţie directiva are forma:
#define nume_macro(parametri) str
Apariţia în program a secvenţei formată din identificatorul nume_macro urmat de
argumente efective conduce la înlocuirea acestei secvenţe cu textul str în care parametrii formali
sunt înlocuiţi cu argumente efective.
Efectul folosirii unei macrodefiniţii este asemănător utilizării unei funcţii.
De exemplu, funcţia f(x)=x2+1 poate fi scrisă cu ajutorul macroului
#define f(x) ((x)*(x)+1)
Dacă în programul în care este prezentă această macrodefiniţie avem de exemplu:
y=3*f(2);
valoarea atribuită lui y va fi 15. Practic, înainte de compilare textul f(2) este înlocuit cu
((2)*(2)+1), ceea ce explică rezultatul. Parantezele rotunde sunt doar aparent redundante.
Ele permit obţinerea unui rezultat corect în orice situaţie. Astfel, dacă macrodefiniţia ar fi:
#define f(x) x*x +1
rezultatul evaluării expresiei
3
Fundamentele programării 2020-2021
Curs 12
y=3*f(3-1);
nu este, cum ne-am aştepta, valoarea 15, ci 6. Acest lucru se datorează faptului că macrodefiniţia
înseamnă înainte de toate o înlocuire de text. Aşadar, litera x va fi înlocuită cu 3-1, iar în membrul
drept al expresiei vom avea 3*3-1*3-1+1=6, ceea ce explică rezultatul. Pentru a evita astfel de
situaţii se recomandă încadrarea parametrilor şi a textului care va înlocui nume_macro între
paranteze rotunde, aşa cum s-a făcut de altfel iniţial.
Folosirea unui macro în locul unei funcţii are avantajul reducerii timpului de execuţie,
deoarece apelul funcţiei implică “urcarea” în stivă la apel şi ”coborârea” în stivă la revenirea în
programul principal. Pe de altă parte, folosirea macrodefiniţiei este avantajoasă şi din punct de
vedere al memoriei folosite numai dacă textul care reprezintă funcţia nu este întins: macrodefiniţia
înseamnă mărirea codului prin substituirile de text care se fac, în timp ce codul unei funcţii apare
o singură dată, indiferent de numărul de apeluri care se fac.
O directivă #define poate fi anulată cu ajutorul directivei #undef. În felul acesta se
delimitează asocierea făcută între un nume_macro cu o anumită secventa_caractere, oferind
posibilitatea unei noi asocieri.
Forma generală a directivei #undef este:
#undef nume_macro
Exemplu:
#define M 20
#undef M
#define M 30
Directiva #include are forma generală:
#include <nume_fisier>
sau
#include ”nume_fisier”
Efectul directivei constă în includerea fişierului sursă în fişierul unde este activată directiva.
Fişierul nume_fisier se numeşte fişier header (sau fişier antet) şi are extensia <.h>. În
cazul în care nume_fisier are specificată şi o cale de căutare completă, preprocesorul caută
direct în calea respectivă indiferent de prezenţa ghilimelelor sau a parantezelor unghiulare.
Dacă nu este specificat decât fişierul nume_fisier, în căutarea fişierului se ţine cont de
ghilimele sau paranteze unghiulare astfel: când se utilizează parantezele unghiulare, fişierul
destinat includerii va fi căutat într-unul sau mai multe directoare standard definite la implementare;
dacă se folosesc ghilimelele căutarea se va face mai întâi în directorul curent şi apoi în directoarele
standard.
4
Fundamentele programării 2020-2021
Curs 12
Un fişier antet poate conţine definiţiile unor tipuri de date, prototipuri de funcţii, declaraţii
de variabile şi de constante, macrodefiniţii şi directive de includere. Se constată că directivele de
includere pot fi imbricate.
Exemplu de fişier antet:
/* fisier antet ex.h */
typedef struct pers
{
char num x[35];
int varsta
}persoana;
float copiere(int sursa, float dest);
extern x;
const ore_zi=24;
#define LMIN=200;
#include ”ante.h”
Observaţii.
Un fişier antet nu poate să conţină definiţii de funcţii, de date sau constante structurate.
După cum se poate vedea modulele antet au rol declarativ. Cu ajutorul directivei de includere orice
program C poate utiliza entităţile deja cuprinse într-un fişier antet ceea ce este evident un avantaj.
În cazul programelor multifişier prin interfaţa pe care o realizează între modulele acestora,
el asigură consistenţa declaraţiilor relative la aceeaşi entitate. Prototipurile funcţiilor standard se
găsesc grupate în diferite fişiere antet situate într-un director special numit Standard Header
Directory. Astfel în ”stdio.h” se declară funcţiile standard de intrare/ieşire, în ”string.h” funcţiile
de prelucrare a fişierelor, în ”math.h” funcţiile matematice obişnuite etc.
Includerea acestor prototipuri în program, prin intermediul fişierelor antet corespunzătoare,
permite folosirea corectă a funcţiilor aflate în biblioteca C standard.
5
Fundamentele programării 2020-2021
Curs 12
#if expresie_constanta
sectiune_program
#endif
Efectul directivei constă în compilarea sectiunii_program numai dacă
expresie_constanta este adevărată. În caz contrar sectiune_program este ignorată.
O variantă cu alternativă se poate construi folosind şi directiva #else. În acest caz se poate
scrie:
#if expresie_constanta
sectiune_program1
#else
sectiune_program2
Dacă expresie_constanta este adevărată se compilează sectiune_program1,
iar în caz contrar sectiune_program2.
Există posibilitatea de a obţine o scară if_else_if folosind directiva #elif ca mai jos:
#if expresie_constanta_1
instructiuni
#elif expresie_constanta_2
instructiuni
#elif expresie_constanta_3
instructiuni
. . . . . . .
#elif expresie_constanta_n
secventa_instructiuni
#endif
Efectul este acela că, pentru prima expresie_constanta_i, i {1,2,…,n},
adevărată (considerată de sus în jos), se compilează instrucţiunile asociate, iar restul se ignoră. În
exemplul de mai jos, pentru calculul unei sume se poate opta, pentru varianta cu for (număr de
iteraţii cunoscut) sau varianta cu while (număr de iteraţii necunoscut) funcţie de valoarea
macroului ITER.
6
Fundamentele programării 2020-2021
Curs 12
Exemplul 12.1. Programul ilustrează utilizarea facilităţii de compilare condiţionată
#include <stdio.h>
/* forme de iteratie:
0 se foloseste while
1 se foloseste for
*/
#define ITER 0
int main()
{
int x,s=0;
#if ITER
{
int i,n;
printf("\n n=");
scanf("%i",&n);
for (i=0;i<n;i++)
{
printf("\n x=");
scanf("%i",&x);
s+=x;
}
}
#else
{
printf("\n x=");
scanf("\n %i",&x);
while (x)
{
s+=x;
printf("\n x=");
scanf("\n %i", &x);
}
}
#endif
printf("\n Suma este %i",s);
return 0;
}
Evident, folosind eventual directiva #elif, în exemplul dat se poate introduce şi opţiunea de
folosire a structurii do_while.
7
Fundamentele programării 2020-2021
Curs 12
Directiva #ifdef are forma generală:
#ifdef nume_macro
sectiune_program
#endif
Dacă nume_macro este definit se compilează sectiune_program, în caz contrar
aceasta nu se compilează.
În mod analog, directiva #ifndef are forma generală:
#ifndef nume_macro
sectiune_program
#endif
cu înţelesul evident: dacă nume_macro nu este definit se compilează sectiune_program,
iar în caz contrar nu.
Atât #ifdef cât şi #ifndef pot avea alternative cu #else, dar nu pot fi folosite cu
directiva #elif. Aceste directive pot fi înlocuite de directiva #if dacă expresia care se testează
este de forma:
defined nume
sau
defined(nume)
Atât prima cât şi a doua expresie iau valoarea adevarat dacă nume a fost definit şi valoarea
fals dacă nume nu a fost definit.
Exemplul 12.2. Programul ilustrează folosirea directivei #ifdef
#include <stdio.h>
#define M 10
int main()
{
#ifdef M
printf("\n Este definit M");
#else
printf("\n M nu e definit");
#endif
return 0;
}
8
Fundamentele programării 2020-2021
Curs 12
O formă echivalentă este dată în exemplul următor:
Exemplul 12.3. Programul ilustrează folosirea directivei #if asociată cu defined
#include <stdio.h>
#define M 10
int main()
{
#if defined(M)
printf("\n Este definit M");
#else
printf("\n M nu e definit");
#endif
return 0;
}