Sunteți pe pagina 1din 151

Fundamentele programării 2020-2021

Curs 1

1. Concepte de bază. Dată, informaţie, cunoştinţe. Calculator, informatică

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.

Pentru o mai bună înţelegere a noţiunilor de dată, informaţie, cunoştinţe, să considerăm


următorul exemplu:
- Date: Ion este tatăl lui George. George este tatăl lui Alex.
- Informaţie: Ion este bunicul lui Alex.
- Cunoştinţe: X este bunicul lui Y, dacă X este tatăl lui Z şi Z este tatăl lui Y.

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.

2. Etapele rezolvării unei probleme cu calculatorul

O listă a etapelor pe care programatorul le parcurge în mod uzual în rezolvarea


problemelor poate fi privită ca un important ajutor în obţinerea unor rezultate bune. Evident,
etapele enumerate mai jos trebuie privite mai degrabă ca un ghid, în sensul că parcurgerea lor
conştiincioasă nu va conduce în mod obligatoriu la rezultatul scontat. A ignora aceste etape este
însă la fel de dăunător cu a exagera importanţa lor. În general, se acceptă ca fiind necesare
următoarele faze în rezolvarea unei probleme cu calculatorul:
1. analiza problemei;
2. proiectarea algoritmului de rezolvare;
3. descrierea algoritmului;
4. codificarea algoritmului (obţinerea programului);
5. testarea programului;
6. execuţia programului, obţinerea rezultatelor şi interpretarea lor;
7. întreţinerea programului.

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. Limbaje de programare. Paradigme de programare

Un limbaj de programare reprezintă un limbaj artificial proiectat astfel încât să comunice


instrucţiuni unei masini, de exemplu unui calculator.
Există în prezent sute (poate mii?) de limbaje de programare. În permanenţă, ele
evoluează, se maturizează, se transformă, se moştenesc sau se influenţează. Apar mereu şi
limbaje noi, dar sunt şi limbaje care pur şi simplu la un moment dat dispar.

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ă.

Cum alegem un anumit limbaj de programare pentru rezolvarea unei anumite


probleme?
Deşi foarte multe limbaje au caracter universal, totuşi anumite trăsături ale problemei de
rezolvat pot recomanda un anumit tip de limbaj. Câteva idei în acest sens:
 Pentru o problemă în care predomină prelucrările numerice se va alege un limbaj în
care există facilităţi specifice de prelucrare şi/sau biblioteci conţinând funcţii/subrutine
specializate, cum ar fi Wolfram, Matlab, R, Python etc.
 Dacă problema necesită utilizarea unor tehnici de inteligenţă artificială se pot folosi
limbaje ca Lisp, Python, C++, Java, Prolog etc.
 Dacă este importantă dezvoltarea rapidă a aplicaţiei, se vor selecta limbaje în care
programarea este simplificată precum Python sau Basic.Net sau limbaje pentru care
există framework-uri specializate pentru dezvoltarea rapidă a aplicaţiilor, precum C#,
Javascript sau PHP.
 Dacă scopul este dezvoltarea unei componente specializate de aplicaţie, precum front-
end-ul sau back-end-ul aplicaţiilor web, se vor alege limbaje care dau rezultate bune în
acest sens, precum Javascript sau PHP.
 Dacă ţinta este obţinerea unor aplicaţii optimizate şi sigure (din punct de vedere al
gestiunii memoriei, al consumului de procesor şi al altor puncte de vedere), pot fi alese
limbaje precum Rust sau Go lang.
 Este important şi tipul de calculator pentru care se dezvoltă aplicaţia respectivă. Dacă
pentru PC-urile care folosesc Windows sau Linux există o multitudine de limbaje de
programare, lucrurile sunt mai puţin variate pentru calculatoarele Apple, pentru
acestea existând un limbaj dominant, numit Swift. Pentru calculatoare mai specializate
cum sunt sistemele cu microcontroller (de exemplu Arduino), sau calculatoarele de tip
SBC (Single Board Computer) cum este Raspberry Pi, se pot utiliza puţine limbaje de
programare, uzual C, C++ sau Python.

Tinând cont de faptul că programarea este o activitate cu caracter comercial/o profesie,


trebuie ţinut cont şi de faptul ca şansa de angajare a unui programator este determinată şi de
popularitatea limbajelor pe care le cunoaşte. Există site-uri specializate care urmăresc tendinţele
în materie de popularitate a limbajelor de programare, site-uri care ar trebui avute în vedere tot
timpul de către un programator care vrea să aibă succes pe piaţa muncii. Exemple de astfel de
site-uri sunt:
- TIOBE Index: https://www.tiobe.com/tiobe-index/
- PYPL: http://pypl.github.io/PYPL.html

Î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

2.1. Noțiunea de algoritm


Un algoritm reprezintă o succesiune finită de paşi, care executaţi într-o ordine bine stabilită,
conduc de la un set de date, numite date de intrare, la un alt set de date, numite date de ieşire.
Un algoritm se caracterizează prin:
 generalitate – furnizează rezultate pentru o mulțime de date de intrare;
 finititudine – procesul trebuie să fie finit pentru a furniza un rezultat concret;
 neambiguitate – fiecare pas se execută după reguli bine precizate.
Cerința ca pașii algoritmului să fie executabili se referă la capacitatea lor efectivă de a
produce rezultate. De exemplu, comanda “sortează în ordine crescătoare toate numerele întregi
pozitive” nu poate fi pasul unui algoritm, deoarece, a sorta toate numerele întregi pozitive (o
infinitate) este o acțiune imposibilă.
Un algoritm este alcătuit din operaţii de bază și structuri de control.
Operaţiile de bază permit modificarea, transferul şi memorarea datelor. Operaţiile de bază
sunt dintr-un algoritm sunt:
 citirea (operație de intrare);
 scrierea (operație de ieșire);
 atribuirea.
Structurile de control precizează ordinea în care se execută operaţiile dintr-un algoritm.
Teorema de structură elaborată în 1966 de Böhm și Jacopini furnizează justificarea
conceptului de programare structurată implementat în limbajele de programare moderne.
Conform acestei teoreme, orice algoritm cu un singur punct de început și un singur punct de
terminare a execuției poate fi reprezentat ca o combinație de trei structuri algoritmice
fundamentale:
 secvența de două sau mai multe operații;
 decizia – alegerea unei operații din două alternative posibile;
 iterația cu test inițial – repetarea unei secvențe cât timp este adevărată o anumită condiție.
Se observă că cele trei structuri de control corespund unor procese naturale ale
raționamentului uman: enumerare, decizie, repetiție. Acest lucru explică și ușurința și naturalețea
cu care sunt descriși algoritmii folosind structurile de mai sus.

2.2. Reprezentarea algoritmilor. Schemă logică şi pseudocod


O dată stabilite datele problemei de rezolvat (datele de intrare şi datele de ieşire), precum şi
paşii care vor fi parcurşi pentru obţinerea datelor de ieşire, algoritmul poate fi reprezentat în mai
multe moduri:
 limbaj natural;
 schemă logică;
 pseudocod;
 limbaj de programare.

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:

SIMBOL GRAFIC DESCRIERE

Blocul de start/stop marchează începutul/sfârşitul


algoritmului

Bloc de calcul - conţine una sau mai multe


instrucţiuni de atribuire

Bloc de intrare/iesire - se utilizează atât la citirea


datelor cât şi la afişarea lor

NU DA Bloc de decizie (selecţie)

Simboluri utilizate la interconectarea diferitelor


5
părţi ale schemei logice

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.3. Codificarea operaţiilor de bază şi a structurilor de control în schemă logică şi


pseudocod

Codificarea operaţiilor de bază


 Atribuirea se codifică în schemă logică și în pseudocod astfel:

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”.

 Citirea se codifică în pseudocod prin cuvântul cheie CITEŞTE.


 Scrierea se codifică în pseudocod prin cuvântul cheie SCRIE.

Codificarea structurilor de control


 Secvenţa este o succesiune de paşi din algoritm, scrişi de regulă pe rânduri separate, ce
se vor executa în ordinea scrierii lor. Dacă dorim ca o secvenţă de instrucţiuni să fie
tratată ca o singură instrucţiune, vom folosi simbolul . În acest caz, spunem că avem
de-a face cu o instrucţiune compusă.
ta
ab
bt

 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.

Schemă logică Pseudocod

DA DACĂ condiţie ATUNCI secvenţă1


NU
condiţie
ALTFEL secvenţă2
secvenţă2 secvenţă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.

Un dezavantaj al schemei logice faţă de pseudocod este că nu există simboluri grafice


specifice pentru reprezentarea structurilor de control de tip iterativ, ele reprezentându-se prin
combinaţii ale celorlalte simboluri. În pseudocod există cuvinte cheie pentru implementarea
iteraţiei, prezentate în cele ce urmează.

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.

Schemă logică Pseudocod

CÂT TIMP condiţie EXECUTĂ

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).

2.4 Alte structuri algoritmice

Pentru a uşura implementarea şi a spori claritatea programelor, majoritatea limbajelor de


programare utilizează şi alte structuri algoritmice, echivalente logic cu structurile algoritmice
fundamentale prezentate.
Astfel, în cazul deciziei există decizia cu ramură vidă şi decizia generalizată (sau
selecţia).

 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

Modul de execuţie al deciziei generalizate este următorul:


se evaluează expresie
dacă exist[ i1,2,...,nastfel încât expresie ci se execută secventai şi se iese
din structură
dacă pentru orice i1,2,...,nexpresie ci se execută secventan+1 şi se iese
din structură

Observaţie. Secvenţa secventan+1 este opţională.

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.

Schemă logică Pseudocod

iv1

PENTRU iv1,v2,r EXECUTĂ


secvenţă
secvenţă
ii+r

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.

Schemă logică Pseudocod

secvenţă REPETĂ secvenţă CÂT TIMP condiţie

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.

2.5. Aplicaţii rezolvate

Aplicaţia 1: Să se reprezinte în schemă logică şi în pseudocod, algoritmul pentru calculul


expresiei:
abc
E , a,b,c R
3
Rezolvare:
Primul pas în rezolvarea problemei îl reprezintă stabilirea datelor problemei: date de intrare,
date de ieşire, alte date - numite date auxiliare sau intermediare sau de stare. În tabelul 1.1 sunt
prezentate variabilele folosite în algoritm pentru aceste date.
Tabelul 1.1. Datele pentru calculul expresiei E = (a+b+c) / 3

Variabile de intrare Variabile de stare Variabile de ieşire


a,b,c – cele trei numere - E – număr real, rezultatul
reale din expresie; expresiei.

Este prezentat în cele ce urmează algoritmul, descris în schemă logică şi în pseudocod,


pentru calculul expresiei E=(a+b+c)/3.

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

Variabile de intrare Variabile de stare Variabile de ieşire


a,b – numere reale, - x – număr real, soluţia
coeficienţii ecuaţiei; ecuaţiei.

Algoritmul este prezentat în schemă logică şi în pseudocod.

START
START
CITEŞTE a,b
Citeşte a, b
DACĂ a0 ATUNCI

NU
a0
DA x-b/a

SCRIE x
x - b/a
NU DA ALTFEL
b0
Scrie x
DACĂ b0 ATUNCI

Scrie Scrie SCRIE ‘ec.imposibila’


’Ec. nedeterminata’ ’Ec. imposibila’
ALTFEL
SCRIE ‘ec.nedeterminata’

STOP

STOP

Se observă că algoritmul conţine două structuri de decizie. Soluţia ecuaţiei se calculează


doar în cazul în care pentru variabila a s-a citit o valoare diferită de 0. În caz contrar, în funcţie
de valoarea citită pentru variabila b, se afişează unul dintre mesajele corespunzătoare ecuaţiei
imposibile sau ecuaţiei nedeterminate.

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

Variabile de intrare Variabile de stare Variabile de ieşire


n – număr întreg; i – număr întreg, contor p – număr întreg,
cu care se parcurg factorialul calculat ca
numerele de la 1 la n; produs.
Algoritmul, în schemă logică şi în pseudocod este următorul:

START

Citeşte n
START
p1 CITEŞTE n

i1 p 1

pp* i PENTRU i1,n EXECUTĂ

ii+1 pp*i

NU DA SCRIE p
in
STOP
Scrie p

STOP

Se observă că algoritmul conţine o structură de iteraţie cu contor crescător. Produsul p se


iniţializează cu 1 (elementul neutru al înmulţirii), iar apoi, pentru fiecare valoare a contorului i
între 1 şi n, produsul se actualizează, atribuindu-se valoarea de la pasul precedent înmulţită cu
valoarea contorului. (pp* i ).

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

Capitolul 3. Limbajul de programare C

3.1. Generalităţi şi scurt istoric despre limbajul C

 C este un limbaj de programare de uz general, cu facilităţi pentru paradigma de


programare procedurală structurată.

 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

3.2. Locul limbajului C în familia limbajelor de programare


Familia limbajelor de programare se poate clasifica în: limbaje de nivel coborât,
limbaje de nivel înalt şi limbaje de nivel mediu.

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.

3.3. Structura generală a unui program C

Structura generală a unui program C este cea prezentată în Figura 3.1.

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
}

tip f1(lista de parametri)


{
declaratii locale
instructiuni
}

tip f2(lista de parametri)


{
declaratii locale
instructiuni
}
. . . . .

tip fn(lista de parametri)


{
declaratii locale
instructiuni
}
Figura 3.1. Structura generală a unui program C
Cuvintele main, f1,f2,…,fn sunt nume de funcţii C. Orice funcţie dintr-un program
poate apela oricare altă funcţie din program. Excepţie face funcţia main() care poate apela alte
funcţii, dar nu poate fi apelată dintr-o funcţie a programului. La execuţia unui program C prima
funcţie apelată este main(): execuţia programului începe şi se termină cu instrucţiuni din main().
Ea este obligatorie şi poate figura oriunde în program. De obicei este plasată la începutul
programului, pentru a-i mări lizibilitatea.

Directivele preprocesor sunt instrucţiuni destinate compilatorului. Compilatorul face


transformări preliminare asupra textului înainte de a începe compilarea. Faza preprocesării are
drept rezultat obţinerea unei unităţi de compilare. Unitatea de compilare este analizată sintactic
(compilată), iar rezultatul este depus în module obiect. Modulele obiect rezultate, împreună cu
modulele obiect corespunzătoare funcţiilor de bibliotecă folosite sunt “legate” cu ajutorul
programului linkeditor (editor de legături) obţinându-se programul executabil. Directivele se
constituie ca un limbaj în interiorul limbajului C şi oferă anumite facilităţi: compilarea
condiţionată a unor porţiuni de cod, înlocuirea în text a unui identificator la fiecare apariţie cu un
set de caractere, includerea unui fişier sursă în program etc.

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.

Apelul funcţiei se face sub forma:


nume_functie(lista de parametri actuali)
şi presupune transferul controlului execuţiei programului către instrucţiunile din corpul funcţiei,
execuţia acestora şi revenirea în funcţia apelantă în punctul unde se execută operaţia care
urmează. Schema de apel este următoarea:

funcţie funcţie
apelantă apelată

apel functie

Figura 3.2 - Schema de apel a unei funcţii.

O funcţie C nu poate fi declarată sau definită în interiorul altei funcţii.


Declaratiile globale se referă la entităţi (tipuri de date, variabile etc.) care sunt
recunoscute de toate funcţiile. Declaratiile locale limitează valabilitatea acestor entităţi doar la
nivelul funcţiei unde se fac. Prezenţa prototipului unei funcţii în zona declaraţiilor globale face
posibilă recunoşterea sa în toate funcţiile care o apelează şi permite evitarea unor erori de
apelare, încă din faza de compilare.
În construcţia unui program C se pot folosi două categorii de funcţii:
 funcţii utilizator, elaborate de programator;
 funcţii standard (predefinite), care pot fi preluate din biblioteca standard a
limbajului C.

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).

Un exemplu simplu de program C este cel care afişează un mesaj pe ecran.

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

Capitolul 4. Alfabetul și vocabularul limbajului C

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.

4.1. Alfabetul limbajului C

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

 litere mici 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:
, . ; : ? ’ ( ) [ ] < > ” ! | \ / ~ # &

^ * - = + { } %

Prezentăm în Tabelul 4.1 denumirea unora din semnele enumerate.

Tabelul 4.1 Câteva semne folosite în C și denumirea lor


Semn Nume Semn Nume
| bară verticală / slash
\ backslash ~ tilda
# diez _ liniuță de subliniere
(underscore)
& ampersand ^ săgeată sus

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.

Tabelul 4.2 Codurile backslash din C și semnificația lor


Coduri backslash Semnificație
\a alarmă
\b backspace (recul cu o poziție)
\f form feed (salt pagină nouă)
\n newline (salt la rând nou și de la capăt)
\r carriage return (retur car)
\t horizontal tab (tab orizontal)
\v vertical tab (tab vertical)
\’ apostrof
\” ghilimele
\0 caracter nul
\\ backslash
\ddd caracter ASCII în notație octală
\xdd caracter ASCII în notație hexazecimală

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

pascal _cs _ds _es _ss

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.

Tabelul 4.4 Lista operatorilor C și semnificația lor


Operator Semnificație
[] paranteze drepte (stângă si dreaptă)
() paranteze rotunde (stângă si dreaptă)
. membru structură

5
Fundamentele programării 2020-2021
Curs 4

 referință indirectă la membru de structură


++ incrementare (prefix sau postfix)
-- decrementare (prefix sau postfix)
sizeof dimensiunea unei variabile sau unui tip în octeți
~ NOT pe bit
! NOT logic
& adresă, +I pe bit
* indirectare, înmulțire
/ împărțire
+ adunare
- scădere, negație aritmetică
<< deplasare logică pe bit la stînga
>> deplasare logică pe bit la dreapta
== egalitate
!= neegalitate
< mai mic
> mai mare
<= mai mic sau egal cu
>= mai mare sau egal cu
^ SAU exclusiv pe bit
 SAU pe bit
&& ȘI logic
|| SAU logic
?: operator condițional
= atribuire simplă
op= atribuire compusă
% modul
, virgula (operator de secvențiere)

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

Capitolul 5. Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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.

5.1. Tipurile fundamentale de date în C


Tipurile de date fundamentale din C se împart în cinci categorii: char, int, float, double
și void. Primele patru tipuri se mai numesc și tipuri aritmetice și se referă respectiv la valori
caracter, întregi, reale în simplă precizie și reale în dublă precizie. Tipul de date void indică
absența oricărei valori și este utilizat, de exemplu, la descrierea funcțiilor care nu returnează nici
o valoare.
Dimensiunea zonei de memorie alocate și domeniul de valori asociate tipurilor aritmetice
pot să difere funcție de varianta de implementare a limbajului și de tipul de procesor folosit.
Standardul C nu precizează decât domeniul minimal de valori al fiecărui tip de date, nu și
dimensiunea sa. În majoritatea implementărilor însă, tipul char ocupă un octet, int ocupă doi
octeți, iar float patru octeți.
Domeniul de valori poate fi modificat utilizând modificatorii de tip. Aceștia sunt:
signed, unsigned, short și long. Modificatorii se pot aplica astfel:
• signed, unsigned, short și long se pot aplica tipului int;
• signed și unsigned se pot aplica tipului char;
• long se poate aplica tipului double.
Efectul aplicării modificatorilor signed sau unsigned asupra tipurilor de date întregi constă
în interpretarea diferită, din punct de vedere al semnului, a informației memorate. Să considerăm
o configurație binară de lungime N, biții fiind numerotați ca mai jos:
N-1 N-2 ^ 1 0

Figura 5.1 – Configuraţie binară de N biţi

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.

Tabelul 5.1. Tipuri de date întregi în limbajul C


Tip de date Dimensiune de Domeniu (interval) de valori
memorare

char 1 byte (octet) [-128, 127] sau [0, 255]

unsigned char 1 byte [0, 255]

signed char 1 byte [-128, 127]

[-32768, 32767] sau


int 2 sau 4 bytes [2147483648, 2147483647

unsigned int 2 sau 4 bytes [0, 65535] sau [0, 4294967295]

short 2 bytes [-32768, 32767]

unsigned 2 bytes [0, 65535]


short

long 8 bytes sau (4 [-9223372036854775808,


bytes pentru SO 9223372036854775807]
pe 32 biti)

unsigned long 8 bytes [0, 18446744073709551615]

2
Fundamentele programării 2020-2021
Curs 5

În tabelul 5.2. sunt prezentate tipurile de date reale în limbajul C.

Tabelul 5.1. Tipuri de date reale în limbajul C


Tip de date
Dimensiune Domeniu (interval) de valori Precizie (nr.
de memorare zecimale)
float 4 bytes [1.2E-38, 3.4E+38] 6 zecimale
double 8 bytes [2.3E-308, 1.7E+308] 15 zecimale
long double 10 bytes [3.4E-4932, 1.1E+4932] 19 zecimale

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).

Exemplul 5.1. Tipuri de date întregi


#include <stdio.h>
#include <limits.h>

int main() {

printf("CHAR_BIT : %d\n", CHAR_BIT);


printf("CHAR_MAX : %d\n", CHAR_MAX);
printf("CHAR_MIN : %d\n", CHAR_MIN);
printf("INT_MAX : %d\n", INT_MAX);
printf("INT_MIN : %d\n", INT_MIN);
printf("LONG_MAX : %ld\n",LONG_MAX);
printf("LONG_MIN : %ld\n",LONG_MIN);
printf("SCHAR_MAX : %d\n", SCHAR_MAX);
printf("SCHAR_MIN : %d\n", SCHAR_MIN);
printf("SHRT_MAX : %d\n", SHRT_MAX);
printf("SHRT_MIN : %d\n", SHRT_MIN);
printf("UCHAR_MAX : %d\n", UCHAR_MAX);
printf("UINT_MAX : %u\n", UINT_MAX);
printf("ULONG_MAX : %lu\n",ULONG_MAX);
printf("USHRT_MAX : %d\n", USHRT_MAX);
return 0;
}

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.

5.2. Variabile și tipuri de date


Asocierea dintre numele unei variabile și un anumit tip de date se face folosind
declarațiile. Forma generală a declarației unei variabile este:

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.

Exemple de declaraţii de variabile:

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;

Orice variabilă definită (adică declarată și inițializată) păstrează în continuare atributul de


bază al variabilei, adică poate fi modificată. Dacă se dorește <înghețarea> asocierii dintre o
variabilă și o anumită valoare se utilizează modificatorul de acces (sau calificatorul) const.
Practic, efectul unei declarații de genul

const tip nume_variabila;

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.3. Constante și tipuri de date


În Capitolul 4 (vezi paragraful Vocabularul limbajului), am prezentat o clasificare a
constantelor în: întregi, reale, caracter, șir. Se pune problema, cărui tip de date îi aparține o
constantă numerică? Când constanta este caracter, răspunsul este simplu: tipului char. De
asemenea, constanta în virgulă mobilă (în notație uzuală cu punct sau în notație științifică) va
aparține tipului double. Pentru celelalte constante numerice compilatorul va considera implicit
încadrarea în cel mai mic tip de date compatibil. De exemplu, 23 este de tip int, 65000 de tip
unsigned, 2000002 de tip long int. Încadrarea într-un tip de date se poate face și explicit
adăugând constantei unul din sufixurile L sau U, dacă e întreagă sau F sau L, dacă e reală.
Constanta întreagă cu sufixul L este de tip long, iar cu sufixul U, de tip unsigned. Constanta
reală cu sufixul F are tipul float, iar dacă e urmată de sufixul L are tipul long double.

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ă:

• funcții generale de intrare/ieșire (scanf() și printf());


• funcții speciale de intrare/ieșire:
• funcții pentru citirea și scrierea caracterelor;
• funcții pentru citirea și scrierea șirurilor de caractere.

Funcțiile generale de intrare/ieșire printf() și scanf()

Forma generală a funcției de afișare printf() este:

int printf(sir_format,lista_de_argumente);
unde:

• sir_format poate conține: mesaje pentru utilizator, secvente escape și descriptori de


format pentru valorile care se afișează;
• lista_de_argumente reprezintă variabile sau expresii al căror conținut se va afișa.
Funcția întoarce numărul de caractere scrise efectiv sau o valoare negativă în caz de
insucces.
Precizări:
• Descriptorii de format servesc la efectuarea conversiilor dintre reprezentarea externă şi
internă (la citire) și între reprezentarea internă şi externă (la scriere); formatul extern
presupune succesiuni de caractere, iar cel intern succesiuni de cifre binare;
• Atât la scriere cât și la citire, descriptorii de format sunt puşi în corespondenţă de la stânga
spre dreapta cu elementele listei de argumente. Argumentul trebuie să fie compatibil cu tipul
anunţat în descriptorul de format corespunzător;
• Dacă numărul de argumente este mai mic decât numărul descriptorilor de format, datele de
ieşire sunt nedefinite; dacă numărul argumentelor este mai mare decât numărul descriptorilor
de format, argumentele în plus sunt ignorate.
În Tabelul 5.2 prezentăm lista celor mai utilizați descriptori folosiți de funcția printf() și
semnificația lor.
Tabelul 5.2 Descriptori de format
Descriptori Utilizare

%u numere întregi zecimale fără semn


%d sau %i numere întregi zecimale cu semn

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

Descriptorul %x are ca efect afișarea cifrelor hexazecimale A,B,C,D,E,F cu literă


mică; dacă se folosește %X se afișează cu litere mari.
Dacă se folosește %e litera ”e” din notația științifică apare ca e, iar dacă se folosește %E,
apare ca majusculă (litera E).

Valorile de tip long int se afișează utilizând %ld,%li,%lu,%lo sau %lx.

Valorile de tip short int se afișează utilizând %hd,%hi,%hu,%ho sau %hx.

Pentru a afișa valori double se va alege una din variantele: %lf,%le,%lE,%lg,%lG,


iar pentru valori long double una din variantele: %Lf,%Le,%LE,%Lg,%LG.
Tabelul 5.2 prezintă descriptori de format fără caracteristici de lungime, precizie și
aliniere. Folosiți astfel, ei aliniază implicit valorile la stânga și folosesc spațiu de afișare necesar
reprezentării acestor valori după cum urmează:

%f afișează implicit partea întreagă, punctul și 6 cifre la partea subunitară;

%e sau %E afișează implicit o cifră la partea întreagă, 6 cifre la partea subunitară,


caracterul e sau E și exponentul precedat de + sau -;
%g sau %G alege reprezentarea cu cel mai mic număr de caractere dintre cea uzuală și cea
științifică.
%d,%i,%c,%o,%x,%X,%s,%p folosesc un număr de coloane egal cu numărul de
caractere ce trebuie afișate.

Exemplificăm folosirea descriptorilor prezentați și a secvențelor de evitare cu ajutorul


următoarelor programe:

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 */

printf("\n %s \n","un sir de caractere (string)");


}

Dimensiunea câmpului de afișare, precizia și modul de aliniere pot fi stabilite prin


simboluri plasate între semnul % și specificatorul descriptorului de format (i,d,u,f etc.).
Astfel, un întreg pozitiv aflat între % și specificatorul descriptorului de format indică
dimensiunea minimă a câmpului de afișare. Dacă șirul sau numărul care se afișează are mai
multe caractere decât dimensiunea minimă precizată, atunci afișarea va fi integrală, în caz
contrar se completează cu spații până la realizarea dimensiunii minime. Dacă dorim ca această

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 */

printf("\n %3s","un sir de caractere");


printf("\n %25s","un sir de caractere");
return 0;
}

Precizia de afișare se specifică printr-un punct urmat de un întreg pozitiv. Specificatorul


de precizie astfel obținut se plasează imediat după dimensiunea câmpului de afișare (când este
precizată). Interpretarea lui depinde de tipul de date avut în vedere.

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).

Un descriptor de format prevăzut cu specificator de dimensiune și specificator de precizie


poate fi aplicat unui șir cu lungimea cuprinsă între valoarea specificatorului de dimensiune și cea
a specificatorului de precizie. Dacă se depășește valoarea specificatorului de precizie, șirul se
trunchiază.

Se poate impune alinierea la stânga a datelor plasând semnul – (minus) imediat după
semnul % în cadrul descriptorului.

Programul următor ilustrează toate cazurile prezentate.

Exemplul 5.8. Afişarea unor valori, cu specificarea preciziei de afișare.

#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;
}

Funcția de citire scanf() are forma generală:

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 *=.

Funcțiile scanf() și printf() sunt funcții de intrare/ieșire standard cu destinație generală.


Din considerente de eficiență (cod mai mic, viteză de execuție sporită, comoditate în programare
etc.) limbajul C pune la dispoziția utilizatorului și funcții cu destinație specială.

5.5. Funcţii speciale pentru citirea/scrierea caracterelor la nivelul consolei


Standardul C18 prevede două funcții simetrice pentru transferul caracterelor la nivelul
consolei: funcțiile getchar() și putchar(). Ele își au prototipurile în <stdio.h>.

Funcția pentru citire getchar() are forma generală

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.

Deși nu aparțin standardului C18, totuși, funcțiile getch(), getche(), putch()


sunt incluse frecvent în biblioteca standard a compilatoarelor C. Prototipurile acestor funcții sunt
în fișierul header <conio.h>.
Funcțiile getch() și getche() sunt echivalente funcțional cu funcția getchar().
Forma generală este:

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ă

int putchar(int ch);


unde ch este caracterul care se afișează.

Funcția întoarce în caz de succes caracterul scris, iar în caz contrar EOF.

Funcția putch() este echivalentă funcțional cu putchar() și are forma generală


int putch(int ch);

unde ch este caracterul care se afișează.


Ea este de asemenea o funcție nestandard (nu e definită de standardul C) frecvent
utilizată.

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;
}

5.6. Funcții speciale pentru citirea/scrierea șirurilor de caractere la nivelul consolei


Funcții speciale pentru citirea/scrierea șirurilor de caractere la nivelul consolei sunt
gets() și puts(). Ambele funcții își au prototipurile în fișierul header <stdio.h>. Aceste
prototipuri vor fi prezentate detaliat într-un capitol următor al cursului. Deoarece în construcția
acestor prototipuri intervine noțiunea de pointer, ne limităm aici la a spune că prin apelul

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.

Dăm ca exemplu secvența de program:


. . . . . . .
gets(x);
. . . . . . .
printf(”\n Sirul citit este =”);
puts(x);
. . . . . . .

5.7. Operatori. Clasificare


Operatorii sunt elemente de bază ale limbajului care arată ce operații trebuie executate
asupra unor operanzi. În C, operanzi pot fi constantele, numele de variabile, numele de funcții,
expresiile.

Bogata familie de operatori conferă limbajului C o trăsătură aparte.

Clasificarea operatorilor C se poate face după mai multe criterii:

• după numărul de operanzi prelucrați (unari, binari, ternari);


• după prioritatea avută în evaluarea expresiilor (clase de precedență);
• după tipul operanzilor (aritmetici, relaționali, logici și la nivel de bit).

OPERATORI UNARI, BINARI, TERNARI


Ţinând cont de numărul de operanzi prelucrați, în C există operatori unari, binari și
ternari. Clasele rezultate nu sunt disjuncte, în sensul că, de exemplu, un operator unar poate fi și
binar. Astfel, în expresia -3 operatorul - (minus) este unar, iar în expresia a-3, este binar.

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) ,

Funcționează, de asemenea, reguli de asociere de la stânga la dreapta sau de la dreapta la


stânga. Singurii operatori care se asociază de la dreapta la stânga sunt operatorii unari și
operatorul ?:, restul se asociază de la stânga la dreapta.

Operatorul de atribuire = ocupă un loc aparte în familia operatorilor. Cu ajutorul lui


putem să atribuim unei variabile o anumită valoare. Forma sa generală este:

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;.

Compilatorul C permite ca în expresii de genul

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ă.

Considerațiile de mai sus referitoare la conversia tipurilor sunt valabile și în situația în


care cel puțin unul dintre cei doi operanzi ai operatorului de atribuire sunt variante ale tipului int
(char, signed, unsigned, short, long) sau ale tipului double (long double).

În C este posibilă atribuirea multiplă, ca în exemplul de mai jos:

x=y=z=s=0;
Efectul este atribuirea valorii 0 variabilelor x,y,z,s.

De asemenea, în anumite situații se pot folosi operatorii de atribuire compusă. Practic,


orice atribuire de genul:

variabila=variabila operator expresie;


unde operator poate fi ales din lista de operatori *, /, %, +, -, >>, <<, , &, ^ se
poate scrie simplificat sub forma:

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:

x=x+1 se poate scrie x++ sau ++x,

x=x-1 se poate scrie x-- sau --x.

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,

expresia y=++x; e echivalentă cu secvența x=x+1; y=x;

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.

Folosirea operatorilor de incrementare și decrementare este recomandabilă nu doar din


rațiuni de simplificare a scrierii programelor ci și datorită faptului că majoritatea compilatoarelor
C generează coduri obiect foarte rapide în astfel de cazuri.

Operatorul de secvențiere (virgula) permite construirea unei expresii ca o listă de alte


expresii. Expresiile din listă se evaluează de la stânga la dreapta. Valoarea și tipul întregii
expresii este dat de ultima expresie din listă. De exemplu, în urma execuției secvenței de
program:
. . . . .

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;

Operatorii ?:,[,],*,&,sizeof,•,-> vor fi prezentați mai pe larg în capitolele


unde prezența lor este necesară: operatorul ?: la instrucțiuni condiționale, parantezele pătrate
[ și ] la tipul de date tablou, *, & și sizeof la pointeri, • și -> la tipurile de date struct și union.

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;
}

Operatorii logici din C sunt:


! NU logic

&& SI logic

|| SAU logic
Modul de acțiune al acestor operatori este prezentat în Tabelul 5.4.

Tabelul 5.4 Tabla valorilor de adevăr pentru operatori logici


a b a && b a || b !a
0 0 0 0 1
0 1 0 1 1
1 0 0 1 0
1 1 1 1 0

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)

expresia a>b&&c<=d e echivalentă cu (a>b)&&(c<=d)


Concluzia este că în astfel de situații prezența parantezelor este opțională. Există însă
cazuri în care parantezele sunt absolut necesare; de exemplu, dacă dorim să negăm expresia
a>3 vom scrie !(a>3) și nu !a>3. Expresia !(a>3) va returna corect valoarea 0 sau 1
funcție de mărimea lui a, în timp ce expresia !a>3 are totdeauna valoarea 0 (datorită priorității
mai mari a operatorului ! față de > se evaluează mai întâi !a care poate fi 0 sau 1; oricare ar fi
valoarea lui a, rezultă în final valoarea 0).

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ă.

Deoarece prezența parantezelor nu reduce viteza de execuție a expresiilor, ele pot fi


folosite, alături de spații, la creșterea gradului de lizibilitate a unui program. Iată două expresii
echivalente:

a=c<d||d>=c&&a<3;

a = (c<d) || (d>=c) && (a<3);


Este evident că cea de a doua expresie este mai ușor de citit.

OPERATORI LA NIVEL DE BIT


Operatorii la nivel de bit din C permit programatorului să manevreze biții aparținând
unor valori de tip char sau int (ei nu se aplică tipurilor de date float, double sau long double).
Aceasta este una din acele facilități care apropie limbajul C de nivelul limbajelor de asamblare.

Operatorii la nivel de bit sunt:

& 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

Se observă că a^b ia valoarea 1 numai dacă biții a și b sunt diferiți.

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 0000 0101


0010 0100 0010 0100
x&y= 0000 0100 x|y= 0010 0101

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

x=0000 0101 și y=0010 0100

0000 0101

0010 0100

x^y= 0010 0001


să presupunem că y este cheia. Dacă între codificarea obţinută (x^y) și cheia y efectuăm din
nou operaţia ^ se obţine rezultatul iniţial x (decodificarea).

0010 0001

0010 0100

(x^y)^y= 0000 0101


Forma generală a operatorilor de deplasare este:

variabila << numar_intreg, pentru deplasare la stânga

și

variabila >> numar intreg, pentru deplasare la dreapta.

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.

Exemplu. Să considerăm declarația

unsigned char x=7;


Numărul 7 este reprezentat în baza 2 ca 0000 0111. Atunci, x<<1 va produce 0000 1110.
Dacă vom continua cu x<<5 se va obține 1100 0000 ceea ce arată pierderea unui bit.

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:

9 se reprezintă prin 0000 1001

~9 este 1111 0110

~9+1 este 1111 0111

-9 se reprezintă prin 1111 0111


În consecință, ținând cont de precizarea făcută mai sus

x>>2 va produce 1111 1101


Observaţii. Deplasarea spre stânga cu n poziţii echivalează cu o înmulţire a variabilei cu
2n, iar deplasarea spre dreapta cu n poziţii echivalează cu împărţire a variabilei cu 2n. Aceste
deplasări (operaţii shift) sunt mai rapide decât operaţiile corespunzătoare de înmulţire sau
împărţire cu 2n.

Exemplu:
Dacă x=7 atunci:

x<<1 va produce 0000 1110 adică, 7x21=14


Dacă x=-8, atunci x>>2 va produce 1111 1110 adică, -2=-8/22.

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:

• Variabile de tip char și short se convertesc la tipul int.


• Dacă un operand este long double
Atunci al doilea este convertit la long double;
Altfel dacă un operand este double
Atunci al doilea e convertit la double;
Altfel dacă un operand este float
Atunci al doilea e convertit la float;
Altfel dacă un operand este unsigned long
Atunci al doilea e convertit la unsigned long;
Altfel dacă un operand este long
Atunci al doilea e convertit la long;
Altfel dacă un operand este unsigned
Atunci al doilea este convertit la unsigned.

Conversii de tip explicite (operatorul cast)


Operatorul de conversie explicită (cast) acționează temporar, forțând schimbarea
tipului expresiei la care se referă. Forma generală a operatorului este:
(tip)expresie;
unde tip este tipul la care dorim să se facă conversia expresiei.

De exemplu, rezultatul evaluării expresiei

(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

Capitolul 6. Instrucțiuni de control în limbajul C

În acest capitol se prezintă instrucţiunile de control ale unui program C: instrucţiunea


expresie, instrucţiunile decizie (sau de selecţie), instrucţiunile iterative (repetitive sau de ciclare)
şi instrucţiunile se salt. În situaţia în care sintaxa limbajului impune utilizarea unei singure
instrucţiuni, dar logica programului cere folosirea unei secvenţe de instrucţiuni, secvenţa de
instrucţiuni se organizează ca o instrucţiune bloc (sau instrucţiune compusă). O instrucţiune bloc
începe cu { şi se termină cu }.

6.1. Instrucțiunea expresie

Instrucţiunea expresie are forma generală:

expresie;

unde expresie are efect lateral (conţine o atribuire sau reprezintă un apel de funcţie).

Secvenţa de program următoare conţine exemple de instrucţiuni expresie:

. . . . . . .

x=(a+b)*c;

x+=2;

p++;

6.2. Instrucțiuni de decizie

6.2.1. Instrucțiunea if

Forma generală a instrucţiunii if este:

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.

Schemă logică Pseudocod

DA DACĂ condiţie ATUNCI instructiune1


NU
condiţie
ALTFEL instuctiune2
instructiune2 instructiune1

Figura 6.1. Structura de decizie

Exemplul 6.1. Programul care calculează şi afişează radicalul dintr-un număr.

#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;
}

Dacă alternativa else a instrucţiunii if este vidă atunci se poate scrie:


if(expresie)
instructiune_1

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.

Exemplul 6.2. Programul care calculează maximul dintre două numere.

#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;
}

Dacă în structura unei instrucţiuni if intervin alte instrucţiuni if avem de a face cu o


structură de instrucţiuni if incluse sau imbricate. Forma generală a unei astfel de structuri este
următoarea:
if(expr1)
instr1;
else
if(expr2)
instr2;
else
. . . . . .
if(exprn)
instrn;
else
instrn+1

Efectul execuţiei unei instrucţiuni if imbricate este următorul: dacă nu există i,


i=1,...,n astfel încât expresia expri să fie adevărată, se execută instrn+1, în caz
contrar se execută prima instrucţiune (considerând evaluarea de sus în jos), instri pentru care
expri este adevărată, iar restul instrucţiunilor se ignoră.

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

deplasată spre dreapta). Din acest motiv se foloseşte de obicei forma:

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;

formă care va fi întotdeauna preferată.

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”);

În ideea creării unor programe >dense> se pot scrie construcţii de genul


if( (c=getch() ) == ’a’)
putch(’a’);

else

printf(”\n caracter diferit de a”);


unde expresia condiţională conţine o atribuire.

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.

6.2.2. Operatorul condițional ? :

Operatorul condiţional ?: este un operator ternar, iar forma sa generală este:

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

6.2.3. Instrucțiunea switch

Instrucţiunea switch permite selecţia unei alternative din mai multe posibile într-o formă
comodă şi elegantă.

Forma generală a instrucţiunii este:


switch(expresie)

case c1:secventa de instructiuni1;break;

case c2:secventa de instructiuni2;break;

. . . . . . . .

case cn:secventa de instructiunin;break;

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ă;

* secvenţa instructiunii, pentru orice i=1,...,n+1 se poate constitui sau


nu într-un bloc de instrucţiuni, poate să fie vidă sau nu;
* break este o instrucţiune care permite saltul la prima instrucţiune aflată după
structura switch. Prezenţa sa este opţională.
Efectul instrucţiunii este următorul:
* Dacă există un i astfel încât expresia selectoare este egală cu ci se execută
secvenţa instructiunik,k>=i până la primul break întâlnit sau până la sfârşitul
instrucţiunii switch.
* Dacă pentru orice i=1,...,n constantele ci sunt diferite de expresia selectoare se
execută instructiunen+1, dacă există opţiunea default sau se iese direct din switch, dacă
aceasta lipseşte.

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.

6.3. Instrucțiuni iterative

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

6.3.1. Instrucțiunea while

Forma generală a instrucţiunii while este:

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.

Schemă logică Pseudocod

CÂT TIMP condiţie EXECUTĂ

instructiune
NU DA
condiţie instructiune

Figura 6.2. Structura de iteraţie cu test iniţial

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;
}

6.3.2. Instrucţiunea for

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.

Forma generală a instrucţiunii for este:

for(initializare;conditie;actualizare)

instructiune;

Semnificaţia tradiţională a celor trei componente este următoarea:

 initializare este de regulă o instrucţiune de atribuire folosită pentru


iniţializarea contorului ciclului;
 conditie este o expresie care determină sfârşitul ciclului;
 actualizare se referă la felul în care se modifică variabila contor.
Instrucţiunea for este echivalentă cu secvenţa de instrucţiuni

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ă.

Exemplul 6.7. Programul care realizează suma a n numere reale.

#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;
}

Se observă absenţa componentei actualizare.

 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.

 Oricare din componentele initializare,conditie, actualizare ale


instrucţiunii for poate să lipsească, însă delimitatorii ; trebuie să fie prezenţi. Lipsa
componentei conditie este interpretată ca fiind echivalentă cu prezenţa unei expresii
condiţionale adevărate. Din acest motiv, secvenţa

13
Fundamentele programării 2020-2021
Curs 6
for(;;);
creează un ciclu infinit sau o buclă eternă.

6.3.3. Instrucţiunea do-while

Este recomandabil să se folosească instrucţiunea do-while când instrucţiunea care


reprezintă corpul ciclului trebuie executată cel puţin o dată.

Forma generală a acestei instrucţiuni este:

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.

Schemă logică Pseudocod

instructiune
REPETĂ instructiune CÂT TIMP condiţie

DA
condiţie

NU

Figura 6.3. Structura de iteraţie cu test final

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;
}

6.3.4. Cicluri imbricate (incluse)

Există posibilitatea ca o instrucţiune de ciclare să includă o altă instrucţiune de ciclare; în


acest caz se spune că ciclurile sunt imbricate sau incluse.

Exemplul 6.12. Folosirea ciclurilor imbricate

#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;
}

Rezultatul execuţiei programului de mai sus este:


1
1 2
1 2 3
1 2 3 4
1 2 3 4 5
Pentru o valoare a variabilei contor i, variabila contor j a ciclului interior variază de i ori;
se spune că indicele interior variază mai repede ca indicele exterior.

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;
}

6.4. Instrucţiuni de salt


În limbajul C există patru instrucţiuni care execută un salt necondiţionat în program:
break, continue, goto, return. Deoarece instrucţiunea return se referă la revenirea din funcţia
apelată la punctul unde s-a făcut apelul, ea va fi tratată mai târziu în capitolul dedicat funcţiilor în
C.

6.4.1. Instrucţiunea break

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.

6.4.2. Instrucţiunea continue

Instrucţiunea continue se foloseşte numai în corpul unui ciclu; ea forţează execuţia


următoarei iteraţii a ciclului, omiţându-se instrucţiunile prezente între cele două iteraţii (locul
unde se află plasată în corpul ciclului şi sfârşitul acestuia). Efectul instrucţiunii este următorul:

 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;
}

6.4.3. Instrucţiunea goto

Forma generală a instrucţiunii goto este:

goto eticheta;
.
.
.
eticheta:instructiune;

unde eticheta este numele de identificare al instrucţiunii instructiune.


Efectul execuţiei instrucţiunii goto este saltul necondiţionat la instrucţiunea identificată
prin eticheta. Saltul poate fi făcut înainte sau după instrucţiunea goto. Folosirea frecventă a
acestei instrucţiuni în programe afectează claritatea acestora, îngreunând înţelegerea lor. Ea
poate fi însă evitată în orice situaţie utilizând celelalte structuri de control din C. Dăm mai jos un
exemplu tipic de folosire a instrucţiunii goto: ieşirea forţată dintr-un ciclu sau dintr-o
instrucţiune switch.

Exemplul 6.16. Programul reprezintă o altă implementare a Exemplului 6.10.

#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!");
}

6.4.4. Instrucţiunea return

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

Capitolul 7. Tablouri, șiruri de caractere

Notaţia indicială a variabilelor nu este nouă; ea a fost şi este folosită în special de


matematicieni ori de câte ori este nevoie ca o prelucrare să fie exprimată cât mai sintetic. Este
6
evident că prelucrarea S=a+b+c+d+e+f arată mult mai puţin elegant decât S =  xi În ultimul
i =1

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.

Tablourile reprezintă tocmai implementarea noţiunilor de vector, matrice sau masiv


multidimensional într-un limbaj de nivel înalt. Ele reprezintă colecţii omogene de date (adică
de acelaşi tip), stocate în locaţii de memorie învecinate. Accesul la oricare din elementele
tabloului se face cu ajutorul indicilor.

7.1. Tablouri unidimensionale


Declaraţia unui tablou unidimensional se face după forma generală:

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.

Dimensiunea zonei continue alocate se calculează prin relaţia


dimens_alloc=dim*sizeof(tip).
De exemplu, prin declaraţia

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).

Exemplul 7.1. Citirea și afișarea unui vector de n numere întregi

#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;
}

7.2. Tablouri multidimensionale


Dacă elementele unui tablou unidimensional sunt la rândul lor tablouri unidimensionale
obţinem un tablou bidimensional sau o matrice. De exemplu, declaraţia:
int x[4][3];
se poate interpreta astfel: x este un tablou cu 4 elemente x[i], i=0,1,2,3. Fiecare element
x[i] cu i=0,1,2,3 este un tablou cu 3 elemente întregi x[i][j] cu j=0,1,2.

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).

Exemplul 7.2. Citirea și afișarea unei matrice a(nxm) de numere întregi

#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:

declaratie tablou={lista valori_initiale};


unde valorile_initiale sunt expresii constante compatibile cu tipul de bază al tabloului.
Exemple:
int vector[6]={-7,-2,95,21,5,-23};

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

Pentru a sugera modul de iniţializare al tablourilor multidimensionale se pot folosi acolade


despărţitoare în interiorul listei de valori.

Astfel, matricea a se poate scrie mai sugestiv

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.

Să notăm în cazul general cu dim produsul dim1*dim2*...*dimn şi cu nval_in


numărul constantelor din lista de iniţializare. Atunci:

• 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.

Se observă că formula de alocare nu depinde de prima dimensiune a tabloului, fapt ce


permite următoarele iniţializări echivalente cu cele prezentate la începutul secţiunii:

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};

7.4. Tablouri și șiruri de caractere


În C nu există un tip special pentru definirea unui şir de caractere. Şirurile de caractere se
construiesc cu ajutorul tablourilor unidimensionale.

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).

Dacă nu se precizează dimensiunea, se poate face următoarea iniţializare:

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.

Legătura dintre şiruri şi tablouri unidimensionale permite referirea indexată la caracterele


şirului. Folosind acest lucru, în exemplul de mai jos se selectează şi afişează doar unele dintre
literele şirul sir (din două în două).

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

7.5.1. Funcții cu prototipul in <stdio.h>

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.5.2. Funcții cu prototipul in <string.h>

Fişierul antet <string.h> conţine prototipurile funcţiilor specializate pentru


manipularea şirurilor de caractere. Iată câteva dintre acestea:
• Funcţia strcpy() cu forma generală:
strcpy(sir_destinatie, sir_sursa);
copiază sir_sursa în sir_destinatie;

• Funcţia strcmp() cu forma generală:


int strcmp(sir1, sir2);
compară cele doua şiruri caracter cu caracter şi întoarce o valoare:

7
Fundamentele programării 2020-2021
Curs 7
▪ <0 dacă sir1<sir2;
▪ =0 dacă sir1=sir2;
▪ >0 dacă sir1>sir2.

Comparaţia şirurilor se face lexicografic.

• Funcţia strlen() cu forma generală:


unsigned int strlen(sir);
întoarce numărul de elemente dintr-un şir;

• Funcţia strcat() cu forma generală:


char *strcat(sir1, sir2);
adaugă sir2 la sfârşitul şirului sir1 (concatenare).

Exemplul 7.5. Se citeşte de la tastatură un număr neprecizat de şiruri de caractere. Citirea se


termină când se întâlneşte şirul ”stop”. Programul stabileste şirul “minim” (în sens
lexicografic) şi îl afişează împreună cu lungimea sa.

#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

În modul obişnuit de alocare (alocarea statică), asocierea nume de variabilă, zonă de


memorie este constantă pe toată durata execuţiei programului. Alocarea se face în faza de
compilare, iar dealocarea pe parcursul execuţiei programului este imposibilă.
Pointerii sunt variabile în care se pot memora adrese de memorie. Ei oferă posibilitatea
de a aloca dinamic memoria, adică pe parcursul execuţiei unui program se pot aloca sau dealoca
zone de memorie asociate lor.
Forma generală de declarare a unui pointer este:
tip *var_pointer;
unde tip reprezintă tipul de date asociat pointerului var_pointer. Mai corect, tip
reprezintă tipul de date care pot fi indicate (de la verbul to point din limba engleza) de către
var_pointer. Exemple de declaraţii de pointeri:
int *x,*y;
void *z;
float *p[10];
Declaraţiile de mai sus se citesc astfel: x, y sunt pointeri către int, z e pointer către
orice sau nimic, iar p este un tablou de pointeri către float, adică p[0],p[1],...,p[9] sunt
pointeri către float.
Orice variabilă pointer trebuie iniţializată cu o valoare validă
(0 sau o adresă; obiectele au adrese diferite de zero, iar pentru 0 se foloseşte constanta NULL din
fişierul header <stdio.h>).

8.1. Operatorii & și *


La o variabilă pointer putem fi interesaţi fie de adresa pe care o memorează, fie de
informaţia memorată la această adresă (informaţia utilă), fie de amândouă. O modalitate de a
obţine adresele unor variabile spre a fi memorate într-un pointer este oferită de operatorul &
numit operator de referenţiere sau adresare. Operatorul * numit operator de dereferenţiere sau
indirectare permite accesul (indirect) la informaţia memorată în zonele de memorie referite de
pointeri. Dacă p este un pointer şi conţine valoarea unei adrese de memorie, *p va furniza
conţinutul locaţiei a cărei adresă e memorată în p.
Atribuirea din secvenţa de program:

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().

8.2. Pointeri și tablouri unidimensionale


În C, numele unui tablou este echivalent cu un pointer constant către primul element al
tabloului, adică sunt echivalente referirile:
t &t &t[0]
unde t e pointer către tipul de bază al tabloului şi reprezintă adresa la care e memorat tabloul.
În consecinţă, e corectă declaraţia:
int t[10],*p=t;
dar nu este corectă o secvenţă de genul

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.

8.3. Pointeri către tipul char și șiruri de caractere


Se pot memora constante şir la o adresă indicată de un pointer către char. De exemplu, o
atribuire de genul
char *spoint=”Bine v-am gasit!”;
este validă, deoarece, întâlnind-o, compilatorul va memora şirul
generând pointerul spoint către adresa şirului. Dacă dorim să afişăm pe ecran mesajul conţinut
în şir este suficient să scriem
printf(”%s”,spoint).
O formulă de afişare ca aceasta este utilă mai ales dacă mesajul este lung şi trebuie afişat
frecvent.
Într-o declaraţie de forma
char *sir;
sir poate avea mai multe interpretări: pointer către char, şir de caractere sau vector de
caractere alocat dinamic. Dacă şirul e declarat ca un tablou de caractere spaţiul necesar e alocat
automat, iar dacă declaraţia este ca un pointer la char programatorul trebuie să aloce în mod
explicit spaţiu sau să atribuie adresa unui şir existent (cazul de mai sus).
8.4. Pointeri către tablouri unidimensionale
Declararea unui pointer către un tablou unidimensional de dimensiune dim se face sub
forma:
tip (*var_pointer)[dim];
Să considerăm următoarea secvenţă de program:
int x[10],(*t)[10]=&x;
t++;
Pointerul va conţine adresa unui tablou cu 10 întregi (de fapt adresa primului element al
tabloului), iar t++ va conţine adresa t=&x+sizeof(int)*10.
Observaţii:
Parantezele rotunde din declaraţie sunt esenţiale pentru individualizarea pointerilor către
tablouri. Dacă ar fi omise, declaraţia

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;
}

Exemplul 8.3. O altă implementare a Exemplului 8.2.


#include <stdio.h>
int main()
{
int x[7]={1,2,3,4,5,6,7},i,*p,*q,y;
/* afisarea vectorului initial */
printf("\n Vectorul initial este:");

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;
}

8.6. Pointeri dubli şi tablouri bidimensionale


Un pointer către un pointer la un anumit tip se numeşte pointer dublu către acel tip.
Declaraţia unui pointer dublu se face sub forma
tip **var_pointer;
Semnificaţia acestei declaraţii este următoarea: var_pointer poate memora adresa
unui pointer către tip.
În Figura 8.1 se prezintă schema acestui tip de adresare.

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

Capitolul 9. Structuri, uniuni, enumerări, declaraţii typedef

Structurile şi uniunile reprezintă pentru programator două posibilităţi importante de a


modela colecţii de date eterogene din punct de vedere al tipului de date. Enumerările permit
crearea unor tipuri de date noi cu ajutorul unor liste de constante întregi cu nume. Cu ajutorul
declaraţiilor typedef se pot modifica numele unor tipuri de date existente. În acest capitol se va
arăta cum se definesc tipurile de date struct, union şi enum, cum se folosesc concret în aplicaţii
şi cum se poate redenumi un tip de date deja definit.

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

De exemplu, dacă avem declarat pointerul pers3 astfel,


struct agenda *pers3;
referirea la câmpul nume al acestei variabile se face sub forma

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.

O variabilă de tip structură poate fi iniţializată pe linia de declarare în maniera obişnuită:

declaratie structura={lista_valori_initiale};

De exemplu, prin declaraţia


struct agenda pers1={”Dan”,”Ion”,”Ploiesti,str.Tei”,165239};

câmpurile nume,prenume,adresa,telefon ale variabilei pers1, vor primi în ordine,


valorile enumerate în listă.

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.

De exemplu, se poate imagina structura:


struct info_pers
{
struct agenda info;
int varsta;
float salariu;
}angajat;
Referirea la un câmp al structurii incluse se face urmând modelul construcţiei
angajat.info.nume
care indică numele unui angajat.

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;

Referirea la un element al tabloului se face după modelul

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.

Un rând din listă se descrie cu ajutorul structurii:

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);
}

/* afiseaza elementele listei */


printf("\n Elementele listei sint \n\a");
p=q;
while (p!=NULL)
{
printf("%i ",p->data); //(*p).data
p=p->leg;
}
return 0;
}

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 ->.

Totuşi, nu toate facilităţile oferite de structuri sunt valabile şi în cazul special al


câmpurilor de biţi. De exemplu, nu se poate utiliza operatorul & pentru a lua adresa unui câmp.
De asemenea, numele unui câmp nu poate fi numele unui tablou, iar ordinea câmpurilor este
dependentă de implementare.
Câmpurile de biţi îşi găsesc utilitatea în special în programarea de sistem (gestionarea
regiştrilor hardware, construcţia interfeţelor cu dispozitivele de intrare/ieşire etc.) sau pentru a
face economie de memorie.
De exemplu, să considerăm un dispozitiv cu 6 componente. Dacă fiecărei componente îi
ataşăm un bit de stare (1 funcţionează, 0 – nu funcţionează) starea întregului dispozitiv poate fi
descrisă prin structura:

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;

Să observăm că biţii comp5 şi comp6 au fost omişi, lucru acceptat de compilator.


O structură poate conţine atât câmpuri obişnuite, cât şi câmpuri de biţi ca în exemplul de
mai jos:
struct stare_student
{
char nume[40];
unsigned nota:4;
unsigned adm:1; /*admis sau respins*/
unsigned restante:3; /*numar de restante*/
}student;

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).

Folosind uniunile şi câmpurile de biţi se poate accesa cu uşurinţă informaţia la nivel de


bit a unei variabile.

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;
}

Pentru interpretarea corectă a informaţiei dintr-o uniune, de obicei, se păstrează într-o


variabilă ajutătoare informaţii privind conţinutul curent al uniunii. Pentru ilustrare am ales
următorul exemplu.

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.

Soluţia problemei este următoarea: lista va fi creată în tabloul stud de 30 componente.


Fiecare componentă va fi o structură (numită catalog) care va conţine:

 câmpul npr pentru memorarea numelui şi prenumelui studentului (40 de caractere);


 câmpul tip (un caracter) unde aflăm informaţii despre conţinutul curent al uniunii care
urmează;
 uniunea sit cu următoarele câmpuri:
 media studentului, când câmpul tip are valoarea ’m’;
 vectorul note[5], cu cele 5 note de la examene, când câmpul tip are valoarea ’e’;
 un pointer către prima componentă a unei liste liniare simplu înlănţuită, care conţine
numele disciplinelor restante, când câmpul tip are valoarea ’r’.

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

typedef struct lista


{
char disc[10];
int noter;
struct lista *leg;
}list;
list *res;

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_enumerare reprezintă valori întregi, conform regulei: valoarea


primului simbol din enumerare este 0, valoarea celui de-al doilea simbol este 1 etc. În exemplul
prezentat, rosu are valoarea 0, alb valoarea 1, verde valoarea 2, iar albastru valoarea 3.

Aceste valori pot fi însă impuse, ca în exemplul următor:

enum zile_sapt{luni=1,miercuri=3,joi,vineri, duminica=7}zi;


Simbolul care nu este iniţializat va primi o valoare cu o unitate mai mare decât valoarea
simbolului precedent. Astfel, joi are valoarea 4, iar vineri valoarea 5.

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;
}

9.5. Declaraţii typedef


Declaraţia typedef permite asocierea unui nou nume unui tip de date deja definit. Forma
generală pentru typedef este:
typedef tip nume_nou;
unde tip reprezintă un tip de date valid (predefinit sau definit de utilizator), iar nume_nou este
noul nume asociat. Accentuăm faptul că typedef nu creează tipuri de date noi, ci doar
redenumeşte tipuri existente.
Exemplu:
typedef short int scurt_in;
typedef struct
{
float re;
float in;
}complex;
typedef enum{false,true}boolean;
În aceste condiţii au sens declaraţiile, definiţiile şi atribuirile de variabile:

. . . . . .
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.

În al doilea rând, typedef este recomandată pentru a îmbunătăţi portabilitatea unui


program. Dacă anumite tipuri de date sunt dependente de calculatorul pe care se rulează
programul, este bine ca acestea să primească un nume nou cu ajutorul typedef. La o compilare
pe alt tip de calculator se vor modifica eventual numai aceste declaraţii typedef.

De exemplu, considerând că pe un calculator unde întregii se reprezintă pe 16 biţi, am


declarat

typedef int intreg;


trecerea pe un calculator unde întregii se reprezintă pe 32 biţi se va face modificând doar
declaraţia întregilor astfel:

typedef short int intreg;

Deoarece short int pe ultimul calculator se reprezintă pe 16 biţi, variabilele declarate de


tip intreg vor avea aceeaşi lungime pe ambele tipuri de calculatoare.

16
Fundamentele programării 2020-2021
Curs 10

Capitolul 10. Funcții

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.

10.1. Definiția funcțiilor

Definiţia unei funcţii are forma generală:

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:

float maxim(float x,int y)


{
if(x<y)
return y;
else
return x;
}

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!”);
}

10.2. Declararea funcțiilor, prototipuri

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:

tip nume_functie (lista_declaratii_parametri);

lista_declaratii_parametri poate să fie vidă, caz în care între paranteze se va


scrie cuvântul void (sau nu se va scrie nimic) sau poate să conţină numai lista tipurilor
parametrilor. Utilitatea ei constă în faptul că permite compilatorului să verifice corectitudinea
apelului unei funcţii: se fac verificări privind identitatea dintre numărul parametrilor prototipului
şi al parametrilor actuali, iar dacă este necesar, parametrii actuali se convertesc la tipul parametrilor
corespunzători din prototip, înainte de a fi plasaţi în stivă.

Exemplul 10.1. Programul care calculează aria unui cerc.


#include <stdio.h>
double aria(float r); //declaratia functiei aria

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;
}

Exemple de prototipuri de funcţii:


float f(int x, float y);
double comutare(float, float);
void afisare(int y);
int *g(float *p);
char *strcat(char *sir1, char *sir2);

Ultimul exemplu pune în evidenţă contribuţia prototipurilor la creşterea lizibilităţii codului:


funcţia strcat() concatenează şirurile sir1 şi sir2, rezultatul întors fiind tot un şir. Să observăm,
de asemenea, că al doilea exemplu de prototip are lista_declaratii_parametri formată
doar din tipurile parametrilor. Utilitatea folosirii şi a numelor de parametri constă în referirea lor
în eventualele mesaje de avertisment, ceea ce înlesneşte depanarea programului.

Exemplul 10.2. Utilizarea prototipurilor de funcţii

#include <stdio.h>
#include <math.h>

float f(float); // prototipul functiei

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;
}

Limbajul C permite existenţa funcţiilor cu un număr variabil de parametri. Exemplul


tipic pentru astfel de funcţii îl reprezintă funcţiile scanf() şi printf().
Prototipul unei funcţii cu un număr variabil de parametri are forma:
tip nume_functie(lista_declaratii_parametri...);
unde:
lista_declaratii_parametri conţine cel puţin o declaraţie de parametru. Evident
şi declaratorul din definiţia funcţiei trebuie să fie de aceeaşi formă.
Declaraţia
float f(int x,float y...);
reprezintă prototipul care are cel puţin doi parametri, parametrul x si parametrul y.
Deşi prototipurile nu sunt (încă) obligatorii în C (dar obligatorii în C++) avantajele utilizării
lor în programe le recomandă din plin.

10.3. Apelul funcțiilor


Apelul unei funcţii se face sub forma:
nume_functie(lista_parametri)
unde:
• nume_functie este numele funcţiei care se apelează;
• lista_parametri reprezintă listă de valori actuale sau efective care vor fi
transmise funcţiei. Din acest motiv, parametrii din lista_parametri se mai
numesc actuali sau efectivi.
La apelul funcţiei, între parametrii actuali şi parametrii formali se face o corespondenţă
poziţională. Parametrii actuali şi formali situaţi pe poziţii identice trebuie să aibă tipuri identice
sau compatibile. Dacă funcţia apelată nu are prototip, din raţiuni care ţin de definirea iniţială a
limbajului C, se fac implicit următoarele conversii de tip: char şi short la int, float la double,
tablou la pointer. Dacă funcţia are prototip, tipurile precizate în prototip rămân neschimbate. În
acest caz, compilatorul este de cele mai multe ori în măsură să sesizeze prompt orice încercare de
conversie nepermisă între tipul parametrilor actuali şi tipul parametrilor formali corespunzători.
De asemenea, compilatorul va sesiza lipsa de identitate dintre numărul parametrilor actuali şi
formali. Conversiile permise aici sunt cele considerate la operaţiile de atribuire.

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().

Exemplul 10.3. Comutarea a două valori


#include <stdio.h>

void comut1(int x,int y);


void comut2(int *x,int *y);

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;
}

Rezultatul execuţiei programului este:


apel comut1() a=3 b=5
apel comut2() a=5 b=3
Pentru o mai bună înţelegere a modului de lucru a celor două funcţii, prezentăm următoarea
schiţă care reflectă situaţia zonelor de memorie după apelul funcţiilor comut1() şi comut2().

Transfer prin valoare Transfer prin referinţă

main() comut1() main() comut2()

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.

10.4. Transferul tablourilor către funcții

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 suma(int x[4][3]);

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;
}

int suma(int x[4][3])


{
int i,j,s=0;
for (i=0;i<4;i++)
for (j=0;j<3;j++)
s+=x[i][j];
return s;
}

Comentariu. Chiar dacă x este un parametru formal de tip matrice, compilatorul C îl


converteşte automat la un pointer către întregi. Această variantă este de o rigiditate maximă în ceea
ce priveşte dimensiunile matricii prelucrate.

Exemplul 10.4. Calcularea sumei elementelor unei matrici a(4x3).


Varianta 2– parametrul formal este o matrice cu prima dimensiune neprecizată şi a doua egală cu
3
#include <stdio.h>
int suma(int x[][3],int m,int n);
int main()
{
int a[4][3],i,j,m,n,s;

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;
}

int suma(int x[][3],int m,int n)


{
int i,j,s=0;
for (i=0;i<m;i++)
for (j=0;j<n;j++)
s+=x[i][j];
return s;
}

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;
}

int suma(int **y,int m,int n)


{
int i,j,s=0;
for (i=0;i<m;i++)
for(j=0;j<n;j++)
{
s+= ((int*)y)[(n+1)*i+j];
printf("\n %d %d %d",i,j,((int*)y)[(n+1)*i+j]);
}
return s;
}

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]

Observaţii. Un tablou transmis ca parametru către o funcţie poate fi modificat prin


instrucţiunile funcţiei. Acest lucru este posibil deoarece, la apelul funcţiei, nu se execută o copie a
tabloului, ci se transmite doar adresa primei sale locaţii. În exemplul de mai jos funcţia sortv()
ordonează crescător vectorul a[n], 1<=n<=20, folosind algoritmul de sortare prin selecţie.

Exemplul 10.6. Ordonarea crescătoare a unui vector utilizând algoritmul de sortare prin selecţie.

#include <stdio.h>

void sortv(int x[],int n);


void citv(int x[],int n);
void afisv(int c[],int n);

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;
}

void sortv(int x[],int n)


{
int i,k,min,l;
for (i=0;i<n-1;i++)
{
min=x[i];
l=i;
for (k=i+1;k<n;k++)
if (x[k]<min)
{
l=k;
min=x[k];
}
x[l]=x[i];

11
Fundamentele programării 2020-2021
Curs 10
x[i]=min;
}
}

void citv(int x[],int n)


{
int i;
for (i=0;i<n;i++)
{
printf("x[%d]=",i);
scanf("%d",&x[i]);
}
}

void afisv(int x[],int n)


{
int i;
for (i=0;i<n;i++)
printf("\n x[%d]=%d",i,x[i]);
}

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ă.

Exemplul 10.7. Testarea parităţii unei componente oarecare a tabloului a

#include <stdio.h>

void par_imp(int x,int i);

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;
}

void par_imp(int x,int i)


{

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);
}

10.5. Transferul structurilor către funcții


Spre deosebire de tablouri, structurile se pot transmite în întregime unei funcţii dacă sunt
declarate drept parametri valoare. Dacă structurile au dimensiuni mari poate apare inconvenientul
supraîncărcării stivei şi al creşterii timpului de rulare. În această situaţie se poate folosi metoda
transmiterii prin referinţă, prin care se furnizează funcţiei doar adresa structurii şi nu structura
însăşi.
După cum se ştie, se pot face, prin intermediul funcţiei, modificări asupra membrilor
structurii.

Exemplul 10.8. Se iniţializează o variabilă structură în funcţia main() şi se afişează cu ajutorul


funcţiei de afişare afis(). Transmiterea se face prin valoare.
#include <stdio.h>

typedef struct
{
char *nume;
int nota;
}CATALOG;

void afis(CATALOG y);

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;

void afis_val(int y);


void afis_ref(int *y);

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);
}

void afis_ref(int *y)


{
printf("\n Mesaj afis_ref: Nota este=%i",*y);
}

10.6. Argumentele funcției main()


De obicei, funcţia main() este folosită fără argumente în programe. Totuşi, funcţia main()
poate primi informaţii de pe linia de comandă, unde se apelează programul prin intermediul a două
argumente standard, denumite tradiţional argc şi argv.
Argumentul argc reprezintă numărul de argumente din linia de comandă şi are valoarea
cel puţin 1, deoarece numele programului reprezintă primul argument.
Argumentul argv reprezintă un pointer către un tablou de şiruri de caractere. Aşa cum am
precizat, argv[0] reprezintă numele programului, argv[1] al doilea argument de pe linia de
comandă ş.a.m.d..

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!

Exemplul 10.10. Programul ilustrează folosirea funcţiei main() cu argumente


#include <stdio.h>

int main(int argc, char *argv[])


{
printf("\n Bine te-am gasit %s!",argv[1]);
return 0;
}

10.7. Pointeri la funcții


După compilarea şi linkeditarea unui program, fiecărei funcţii componente i se asociază o
adresă de memorie, care este de fapt adresa de început a funcţiei. Un pointer către funcţie este
un pointer care poate conţine această adresă.
Să presupunem că o funcţie f are prototipul
tip f(lista_parametri_formali);
Forma generală a declaraţiei unui pointer pf la funcţia f este:
tip (*pf)(lista_parametri_formali);

Exemplu. Un pointer p la funcţia


void afisare(float,int);
trebuie declarat
void (*p)(float,int);
Se observă că funcţia şi pointerul asociat funcţiei trebuie să aibă tipul şi lista de parametrii
formali, identice.
De asemenea, pointerul precedat de * trebuie să fie inclus între paranteze rotunde datorită
regulilor de precedenţă ale limbajului. Dacă în exemplul de mai sus, în loc de
void (*p)(float,int);
am fi scris
void *p(float,int);
din cauza priorităţii mari a parantezelor rotunde, declaraţia ar fi avut semnificaţia: prototip pentru
funcţia p, care are un singur parametru de tip float şi întoarce un rezultat de tip pointer către void.
Deoarece în C, numele funcţiei este echivalent cu adresa ei de început (asemănător cu
obţinerea adresei unui tablou), un pointer către funcţie poate primi adresa de început a funcţiei
printr-o atribuire de forma:
pf=f;
Referindu-ne la exemplul considerat, avem:

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>.

Exemplul 10.11. Programul ilustrează folosirea pointerilor către funcţii


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

int adunare(int x,int y);


int scadere(int x, int y);
int inmultire(int x, int y);
int impartire(int x, int y);

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;
}

int scadere(int x,int y)


{
return x-y;
}

int inmultire(int x,int y)


{
return x*y;
}

int impartire(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 x0

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().

Exemplul 10.12. Programul ilustrează folosirea pointerilor către funcţii

#include <stdio.h>

int sum(int a,int b);


int dif(int a, int b);
void f(float x,int a,int b,int (*p)(int a,int b));

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;
}

int sum(int a,int b)


{
return a+b;
}

int dif(int a,int b)


{
return a-b;
}

void f(float x,int a,int b,int (*p)(int a,int b))


{
printf("\n f are valoarea %d ", (*p)(a,b));
}

10.8. Funcții recursive


Recursivitatea reprezintă procesul prin care o entitate de un anumit tip se poate defini,
descrie sau prelucra folosind entităţi de acelaşi tip. În cele ce urmează ne vom referi la funcţii
recursive. Există două tipuri de funcţii recursive: direct recursive şi indirect recursive.
Funcţia f() se numeşte direct recursivă dacă în corpul ei apar apeluri la ea însăşi
(autoapeluri), adică este de forma:
tip f(lista_declaratii_parametri)
{
. . . . .
apel f()
. . . . .
}
Funcţiile f() şi g() se numesc indirect recursive (mutual recursive) dacă f() conţine
apeluri la g(), iar g() conţine apeluri la f(). Schematic acest lucru se poate reprezenta astfel:
tip f(lista_declaratii_parametri)
{
apel g()
}

tip g(lista_declaratii_parametri)
{

18
Fundamentele programării 2020-2021
Curs 10
apel f()
}

Înţelegerea notiunii de funcţie recursivă se sprijină pe cunoaşterea modului în care se face


apelul unei funcţii. Orice program C compilat împarte spaţiul de memorie destinat în patru zone
distincte (vezi Figura 10.1): zona cod program, zona variabile globale, zona de manevră (heap)
destinată alocării dinamice a variabilelor şi zona de stivă (stack).

Stiva
(Stack)
Variabile dinamice
(Heap)
Variabile
globale
Cod program

Figura 10.1 Împărţirea spaţiului de memorie de către


un program C

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

Figura 10.2 Reprezentarea statică a stivei


unde vârf reprezintă pointerul care arată spre ultimul element introdus în stivă. Facem precizarea
că, în memoria calculatorului, stiva (stack) creşte de la adrese superioare la adrese inferioare.
La apelul unei funcţii, în stivă se salvează starea curentă a funcţiei apelante (adresa
instrucţiunii cu care se va continua execuţia după ce se revine la functia apelată, valorile
variabilelor locale şi ale parametrilor) şi se alocă spaţiu pentru parametrii actuali şi variabilele
locale ale funcţiei apelate.

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)

Figura 10.3 Starea stivei în timpul execuţiei succesive a autoapelului

Rezolvarea apelurilor se face după următoarea schemă:


 1
2  2
3 3  3
4 4 4  4
f(1)=1 f(2)=1*2 f(3)=1*2*3 f(4)=1*2*3*4 Stiva vidă
Figura 10.4 Stările succesive ale stivei după ieşirea din autoapel

Exemplul 10.13. Un exemplu simplu de utilizare a recursivităţii


#include <stdio.h>

void g(int x);

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);
} } }

x=6 x=4 x=2 x=0


Figura 10.5 Schema de execuţie a funcţiei recursive g()

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>

void hanoi(int n,char a, char b, char c);

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;
}

void hanoi(int n,char a,char b,char c)


{
if (n==1)
printf("\n %c,%c",a,b);
else
{
hanoi(n-1,a,c,b);
printf("\n %c,%c",a,b);
hanoi(n-1,c,b,a);
}
}

24
Fundamentele programării 2020-2021
Curs 11

Capitolul 11. Funcții pentru prelucrarea fișierelor

Limbajul C nu are instrucţiuni de intrare/ieşire. Operaţiile de intrare/ieşire sunt realizate


prin intermediul funcţiilor incluse în biblioteca standard a limbajului. Acest lucru îi conferă o mare
flexibilitate ştiind că în general operaţiile de intrare/ieşire depind puternic de sistemul de operare
folosit. Operaţiile de intrare/ieşire sunt realizate cu ajutorul unor dispozitive foarte diverse:
tastatură, display, unităţi de disc, unităţi de bandă etc. Pentru tratarea lor unitară, fişierele pe astfel
de dispozitive sunt privite ca secvenţe ordonate de octeţi.

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.

Practic, un dispozitiv fizic este transformat de sistemul de fişiere al limbajului C într-un


dispozitiv logic numit flux (stream). Astfel, funcţiile de intrare/ieşire îşi păstrează generalitatea
deoarece nu se referă la dispozitive fizice specifice în mod direct, ci prin intermediul fluxului
conectat la ele. Acest fapt asigură portabilitatea codului generat de compilator.

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.

Există două tipuri de fluxuri: text şi binare.

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.

11.1. Funcțiile de deschidere FOPEN() și de închidere FCLOSE()


Lansarea în execuţie a unui program C are drept consecinţă şi crearea în mod automat a trei
fluxuri text standard: stdin pentru intrare, stdout pentru ieşire şi stderr pentru ieşire erori.
Denumirile stdin, stdout, stderr sunt de fapt numele unor pointeri constanţi către tipul FILE.

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.

Prototipul funcţiei fopen() este

FILE *fopen(const char *numefis, const char *mod);

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).

Valorile posibile pentru mod sunt:


• deschidere fişier în mod text:
"r" pentru citire

"w" pentru scriere

"a" pentru adăugare

"r+" pentru citire şi scriere

"w+" pentru citire şi scriere

"a+" pentru citire şi scriere (actualizare) prin adăugare la sfârşitul fişierului

• deschidere fişier în mod binar:


Se adaugă la valorile arătate mai sus sufixul b, adică se obţin valorile:
"rb","wb","ab","r+b","w+b","a+b", pentru fiecare valoare explicaţia fiind similară cu
cea prezentată la modul text.

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);

unde fp este pointerul returnat la apelul funcţie fopen().

Operaţia de închidere a unui fişier înseamnă:

• deconectarea fluxului de fişier (fapt important deoarece există o limitare a numărului


de fişiere deschise simultan);
• golirea buferului de ieşire în fişier (transferul pe disc a datelor aflate încă în bufer) dacă
fişierul e deschis în mod scriere sau actualizare;
• abandonarea datelor necitite din buferul de actualizare, dacă fişierul este deschis pentru
citire sau actualizare.
Dacă operaţia de închidere are succes funcţia întoarce valoarea 0, iar în caz contrar EOF
(macroul EOF are în general valoarea -1).

11.2. Funcții pentru citirea și scrierea unui caracter


Funcţia pentru citirea unui caracter dintr-un fişier are prototipul:

int fgetc(FILE *fp);


Echivalent poate fi folosită şi funcţia getc(). Existenţa celor două funcţii cu acelaşi efect
este motivată de compatibilitatea cu versiuni mai vechi ale limbajului C.

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.

Funcţia pentru scrierea unui caracter într-un fişier are prototipul:

int fputc(int ch,FILE *fp);


Ca şi în cazul funcţiei fgetc(), există o funcţie echivalentă numită putc(). Efectul funcţiei
este următorul: funcţia scrie în fişier un caracter pe care-l returnează dacă operaţia a avut succes.
În caz de eroare se va returna EOF.

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ă:

• pentru scriere în fişier:


int fputs(const char *sir, FILE *fp);

• pentru citire din fişier:


char *fgets(char *str, int lg, FILE *fp);

Apelul acestor funcţii are următorul efect:

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);

// se deschide fisierul pentru citire


if ( (f=fopen(numefis,"r")) == NULL)
{
printf("\n Nu se poate deschide fisierul! ");
exit(1);
}
// afisarea continutului fisierului
printf("\n Fisierul in linii de %d caractere este:\n",n);
fc=fgets(x,n+1,f);
while (fc)
{
puts(x);
fc=fgets(x,n+1,f);
}
fclose(f);
}
return 0;
}

11.4. Funcțiile pentru transfer cu format, FSCANF() și FPRINTF()


O modalitate simplă de a realiza operaţii de transfer cu format, este oferită de funcţiile
fprintf() şi fscanf(). După cum sugerează şi numele lor ele se comportă la nivelul fişierelor exact
ca funcţiile scanf() şi printf() la nivelul consolei. Datele formatate scrise într-un fişier cu ajutorul
funcţiei fprintf() pot fi examinate cu ajutorul unui editor text.

Prototipurile funcţiilor fscanf() şi fprintf() sunt următoarele:

int fscanf(FILE *fp,const char *sir_format);

int fprintf(FILE *fp,const char *sir_format);

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);

// citirea de la tastatura si scrierea in fisier */


for (i=0;i<n;i++)
{
printf("\n Nume student=");scanf("%s",x.nume);
printf("\n Prenume student=");
scanf("%s",x.prenume);
printf("\n Nota=");scanf("%d",&x.nota);
fprintf(f,"%10s %10s %3d\n",x.nume,x.prenume,x.nota);
}
fclose(f);

if ( !(f=fopen("grupa","r")) )
{
printf("\n Nu se poate deschide fisierul!");
exit(1);
}

// afisarea pe ecran a continutului fisierului */


printf("\n Continutul fisierului este:\n");
for (i=0;i<n;i++)
{

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, ... ).

11.5. Funcțiile FREAD() Şi FWRITE() pentru transferul blocurilor de date


În ciuda avantajelor evidente, transferul cu format al datelor este mai lent decât transferul
datelor sub forma binară datorită conversiilor în format ASCII. De asemenea, în general, fişierul
creat cu date ASCII formatate foloseşte memorie mai multă decât un fişier binar.
O modalitate eficientă pentru transferul unui volum mare de date o reprezintă folosirea
funcţiilor fread() şi fwrite(). Prototipurile acestor funcţii sunt:
size_t fread(void *buf, size_t nr_oct, size_t nb, FILE *fp);
size_t fwrite(const void *buf, size_t nr_oct, size_t nb,
FILE *fp);
Tipul de date size_t este definit în ”stdio.h” şi aici are semnificaţia unui întreg fără
semn.
Funcţia fread() citeşte din fişierul asociat pointerului fp, nb blocuri, fiecare bloc având
lungimea nr_oct octeţi şi le transferă în zona de memorie indicată de pointerul buf. Valoarea
întoarsă de funcţia fread() este numărul de blocuri citite efectiv. Dacă s-a ajuns la sfârşitul
fişierului sau dacă a intervenit o eroare această valoare este strict mai mică decât nb.
Funcţia fwrite() scrie în fişierul asociat pointerului fp, din zona indicată de pointerul buf,
nb blocuri, fiecare bloc având lungimea egală cu nr_octeti. Valoarea întoarsă de funcţia
fwrite() este egală cu numărul de blocuri scrise efectiv; ea va fi mai mică decât nb dacă la scriere
intervine o eroare.
11.6. Funcția FEOF()
Funcţia feof() permite determinarea momentului când a fost întâlnit sfârşitul fişierului.
Acest lucru este util deoarece în anumite situaţii returnarea valorii EOF poate avea şi alte cauze.
Astfel, s-a văzut că funcţia fgetc() returnează EOF atât la întâlnirea sfârşitului de fişier cât şi în
caz de eroare. De asemenea, în cazul citirii dintr-un fişier binar este posibil ca valoarea EOF să fie
returnată în mod natural indicându-se în mod eronat sfârşit de fişier sau eroare.
Prototipul funcţiei feof() este:

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);

Exemplu de program care utilizează funcțiile: FREAD(), FWRITE(), FEOF(),


REWIND()
Exemplul 11.3. Programul de mai jos creează un fişier cu n înregistrări, al cărui nume este
introdus de la tastatură. După ce indicatorul de poziţie este adus la începutul fişierului cu ajutorul
funcţiei rewind(), conţinutul înregistrărilor este citit şi afişat pe ecran. Valoarea n se citeşte de la
tastatură.

#include <stdio.h>

typedef struct catalog


{
char nume[20];
char prenume[25];
short nota;
}struc;

int main(int argc, char * argv[])


{
FILE *f;
int i,n;
char numefis[10];
struc x;
printf("\n Dati numele fisierului: ");
gets(numefis);
if ( !(f=fopen(numefis,"w+b")) )
{
printf("\n Nu se poate deschide fisierul!");
exit(1);
}
printf("\n Dati va rog numarul de inregistrari: ");
scanf("%d",&n);

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);
}

11.8. Funcții pentru citirea și modificarea indicatorului de poziție al fișierului:


FSEEK(), FGETPOS(), FSETPOS(), FTELL()
Pe lângă funcţia rewind() deja prezentată, standardul C cuprinde următoarele funcţii care
pot fi folosite în prelucrarea fişierelor: fseek(), fgetpos(), fsetpos(), ftell().
Funcţia fseek() are prototipul:
int fseek(FILE *fp, long nr_octeti, int origine);
Efectul funcţiei este poziţionarea indicatorului de poziţie al fişierului asociat pointerului
fp, la valoarea specificată de origine la care se adaugă deplasarea egală cu nr_octeti.
Valoarea pentru origine trebuie să fie unul din următoarele nume de macrocomandă
definite în fişierul stdio.h:

9
Fundamentele programării 2020-2021
Curs 11
nume semnificaţie
SEEK_SET începutul fişierului

SEEK_CUR poziţia curentă

SEEK_END sfârşitul 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>

typedef struct catalog


{
char nume[20];
char pren[25];
short nota;
}struc;

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

Capitolul 12. Directive preprocesor

De regulă, instrucţiunile unui program sunt destinate procesorului. Un program C poate


conţine însă şi instrucţiuni care se adresează compilatorului (preprocesorului).
Aceste instrucţiuni se numesc directive şi permit prelucrări ale textului fişierului sursă
înainte de compilare sau chiar influenţarea procesului de compilare. Astfel, cu ajutorul directivelor
preprocesor se poate include un fişier sursă în alt fişier sursă, se pot înlocui diferite simboluri în
textul fişierului sursă sau se pot ignora la compilare anumite blocuri din text.
Standardul C pune la dispoziţia programatorului următoarele directive:

#if #else #include #line

#ifdef #elif #define #error

#ifndef #endif #undef #pragma

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.

12.2. Directivele de compilare condiționată: #IF, #ELIF, #IFDEF, #IFNDEF și


directiva #ERROR
Directivele de compilare condiţionată permit compilarea anumitor secţiuni de program
funcţie de valoarea unei expresii. Deoarece testele asupra expresiei se fac în faza de compilare
expresia trebuie să conţină numai constante.
Directiva #if are forma generală:

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;
}

Ambele versiuni afişează acelaşi mesaj: Este definit M.


Directiva #error se foloseşte de obicei la depanarea programului şi are forma generală:
#error mesaj_eroare
unde mesaj_eroare este un text care nu se pune între ghilimele. Efectul directivei este întreruperea
compilării şi afişarea mesajului.

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