Sunteți pe pagina 1din 340

2 Programarea în limbajul C

Limbajul C a fost inventat şi implementat prima dată în


anii ’70 de către Dennis Ritchie, programator de sistem la Bell
Laboratories. El îş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, sistem care
cunoaşte astăzi o ascensiune constantă printre sistemele de
operare existente.
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 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.
Apariţia microcalculatoarelor a contribuit la răspândirea
spectaculoasă a limbajului C în diverse variante. Deşi diferenţa
dintre ele nu a fost niciodată semnificativă, totuşi, pentru
eliminarea anumitor neconcordanţe, în anul 1983 o comisie
specială începe lucrul pentru elaborarea standardului ANSI
(American National Standards Institute) al limbajului C, care
3 Programarea în limbajul C

apare 6 ani mai târziu, în anul 1989. La ora actuală majoritatea


compilatoarelor C sunt compatibile cu acest standard.
În primele 12 capitole ale lucrării se prezintă limbajul C
standard. Datorită importanţei domeniului s-a considerat utilă şi
o iniţiere, în capitolul al 13-lea, în grafica pe calculator, folosind
funcţiile video Borland C++.

OCUL 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.
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
4 Programarea în limbajul C

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
nume binecunoscute: Fortran, Cobol, Basic, Pascal 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.

ÂTEVA TRASĂTURI ALE LIMBAJULUI C

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
5 Programarea în limbajul C

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 (32 cuvinte după
standardul ANSI C). 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.

TRUCTURA GENERALĂ A UNUI PROGRAM C

Structura generală a unui program C este următoarea:


directive preprocesor
declaratii globale
tip main(lista de parametri)
{
declaratii locale
instructiuni
}
6 Programarea în limbajul C

tip f1(lista de parametri)


{
declaratii locale
instructiuni
}

tip f2(lista de parametri)


{
declaratii locale
instructiuni
}
. . . . .

tip fn(lista de parametri)


{
declaratii locale
instructiuni
}

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.
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.
7 Programarea în limbajul C

De obicei este plasată la începutul programului, pentru a-i mări


lizibilitatea.
Directivele preprocesor sunt instrucţiuni destinate
compilatorului, care 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..
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:
8 Programarea în limbajul C

antet

corpul funcţiei

Dacă se prezintă doar antetul funcţiei, se spune că


funcţia este declarată.
Declaraţia modernă a 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
9 Programarea în limbajul C

Figura 1.1 - 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.
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
10 Programarea în limbajul C

î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). Modul de construire a programelor
multifişier este abordat în Capitolul 9. Un exemplu simplu de
program C este următorul:

Exemplul 1.1
Primul program C
#include ”stdio.h”

void main(void)
{
printf(”\nPrimul program C!”);
}

Se observă că programul este alcătuit dintr-o singură funcţie, funcţia main() şi foloseşte funcţia
standard printf() cu prototipul în ”stdio.h”.
11 Programarea în limbajul C

TESTE DE CONTROL

1.1 Limbajul C a fost inventat de:

a) Niklaus Wirth
b) Dennis Ritchie
c) Brian Kernighan

1.2 Limbajul C este:

a) un limbaj de nivel coborât


b) un limbaj de nivel mediu
c) un limbaj de nivel înalt

1.3 În C, o funcţie:

a) nu se poate declara sau defini în interiorul alteia


b) poate apela o altă funcţie
c) este alcătuită din antet şi un bloc de declaraţii şi
instrucţiuni delimitat de cuvintele begin şi end

1.4 Funcţia main()

a) poate să lipsească dintr-un program C


b) este obligatorie şi figurează prima în program
c) este obligatorie şi poate figura oriunde în program

1.5 În C, o funcţie:

a) poate include declaraţia sau definiţia altei funcţii


12 Programarea în limbajul C

b) poate fi declarată sau definită în interiorul altei funcţii


c) se poate apela numai din main()
d) se poate apela din orice altă funcţie

RĂSPUNSURI

1.1-b 1.2-b 1.3-a, b 1.4-c 1.5-d


Capitolul 2 – Elemente de bază ale limbajului C 10

Aşa cum se întâmplă cu orice limbaj artificial, temelia pe care


se clădesc programele C este alcatuită 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.

LFABETUL LIMBAJULUI

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:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
· litere mici ale alfabetului englez:
abcdefghijklmnopqrstuvwxyz
· 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 2.1 denumirea unora din semnele
enumerate.
Capitolul 2 – Elemente de bază ale limbajului C 11

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

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 2.2.
Capitolul 2 – Elemente de bază ale limbajului C 12

Tabelul 2.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”.
Capitolul 2 – Elemente de bază ale limbajului C 13

OCABULARUL 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ă ţie. Prezentăm mai jos următorii
atomi lexicali:

· identificatori (nume)
· constante
· operatori
· semne de punctuaţie
· simboluri special

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 ANSI 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 are 32 de cuvinte cheie, din care 27 au fost
definite de varianta originală a limbajului C (standardul
Kernighan/Ritchie).
Capitolul 2 – Elemente de bază ale limbajului C 14

Tabelul 2.3 Cuvinte cheie după standardul Kernighan / Ritchie


auto break case char continue
default do double else extern
float for goto if int
long register return short sizeof
static struct switch typedef union
unsigned while

Tabelul 2.4 Cuvinte cheie adăugate de standardul ANSI C


const enum signed void volatile
După cum se poate observa cuvintele cheie din C se scriu cu
litere mici. Pe lângă cuvintele cheie rezervate de standardul ANSI 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


Capitolul 2 – Elemente de bază ale limbajului C 15

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,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. 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-
Capitolul 2 – Elemente de bază ale limbajului C 16

· 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 2.5 prezentăm lista operatorilor C şi semnificaţia lor.

Tabelul 2.5 Lista operatorilor C şi semnificaţia lor


Operator Semnificaţie
[] paranteze drepte (stângă si dreaptă)
() paranteze rotunde (stângă si dreaptă)
. membru structură
® 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
Capitolul 2 – Elemente de bază ale limbajului C 17

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

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.
Capitolul 2 – Elemente de bază ale limbajului C 18

SEMNE DE PUNCTUAŢIE

Semnele de punctuaţie folosite în C sunt: ... # : {}


Utilizarea lor va fi exemplificată pe parcursul lucrării.

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 @ in FoxPro”.
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 */. 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.
Capitolul 2 – Elemente de bază ale limbajului C 19

TESTE DE CONTROL

2.1 Un identificator este:

a) o secvenţă de cifre, liniuţe de subliniere şi litere


b) o secvenţă de cifre, liniuţe de subliniere şi litere, primul
caracter din secvenţă fiind obligatoriu liniuţă de subliniere
sau literă
c) o secvenţă de cifre, litere, spaţii, liniuţe de subliniere

2.2 În C un identificator poate fi scris:

a) numai cu litere mici


b) numai cu litere mari
c) combinat, cu litere mici şi litere mari

2.3 Dacă într-un identificator C se înlocuieşte o literă mică (mare) cu


litera sa omoloagă mare (mică) atunci:

a) identificatorul obţinut este considerat identic cu primul


b) se obţine un identificator diferit
Capitolul 2 – Elemente de bază ale limbajului C 20

2.4 În C există:

a) patru tipuri de constante: întregi, reale, caracter, şir


b) patru tipuri de constante: naturale, reale, complexe, caracter
c) cinci tipuri de constante: întregi, reale, complexe, caracter,
şir

2.5 În C secvenţa de cifre 0631 este interpretată ca:

a) o constantă în bază 10
b) o constantă în bază 8
c) o constantă în bază 16

2.6 Secvenţa de caractere 0xABC poate fi:

a) un identificator
b) o constantă hexazecimală
c) şi una şi alta

2.7 Cuvintele cheie:

a) au semnificaţii date de programator


b) au semnificaţii prestabilite
c) au semnificaţii date de contextul în care sunt utilizate

2.8 Despre constantele reale 3.0E-2 şi .03 se poate afirma că:

a) sunt greşite deoarece conţin punct în loc de virgulă


b) sunt corecte şi reprezintă valori diferite
c) sunt corecte şi reprezintă aceeaşi valoare
d) sunt greşite deoarece prima conţine litera E, iar a doua nu
are parte întreagă
Capitolul 2 – Elemente de bază ale limbajului C 21

2.9 Secvenţa ’a’ reprezintă:

a) un şir
b) un caracter

2.10 Secvenţa

’Citirea matricii A’
reprezintă:
a) un şir
b) un comentariu
c) nici una, nici alta

2.11 Construcţiile
’a’
şi
”a”:
a) reprezintă acelaşi lucru
b) reprezintă un şir, respectiv un caracter
c) reprezintă un caracter, respectiv un şir

RĂSPUNSURI

2.1-b 2.2-c 2.3-b 2.4-a 2.5-b


2.6-b 2.7-b 2.8-c 2.9-b 2.10-c
2.11-c
22
Capitolul 3 - 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.

IPURILE 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.
23
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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 ANSI 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 signed,
unsigned, short şi long se pot aplica tipului int, signed şi
unsigned, tipului char, iar long, 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 3.1 – Configuraţie binară de N biţi


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

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 C nu există tipul de date boolean. Din acest motiv
funcţionează următoarea convenţie: orice expresie diferită de
zero are valoarea adevărat, iar dacă e egală cu zero, valoarea
fals.
25
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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

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
26
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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

ONSTANTE ŞI TIPURI DE DATE

În Capitolul 2 (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
27
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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.

UNCŢ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.
28
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

FUNCŢIILE PRINTF() ŞI SCANF()

Aceste funcţii reprezintă echivalentele pentru consolă a


funcţiilor de intrare/ieşire pentru fişiere, fprintf() şi fscanf(),
funcţii care vor fi prezentate în detaliu în Capitolul 10.

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
29
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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 3.1 prezentăm lista celor mai utilizaţi
descriptori folosiţi de funcţia printf() şi semnificaţia lor.

Tabelul 3.1 Descriptori de format

Descriptori Utilizare
%u
numere întregi zecimale fără semn
%d sau %i numere întregi zecimale cu semn
%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
30
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

ş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 3.1 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 -;
31
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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

Afişarea valorilor şi sumei a două numere întregi, sub forma:


x=valoare y=valoare
suma=valoare
# include "stdio.h"

void main(void)
{
int x=10, y=-43;
printf ("\n\tx=%d\t\y=%d\n\t suma=%i", x,y, x+y);
}
32
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Afişarea unei constante întregi şi a valorilor sale în octal şi


hexazecimal pe câte un rând.
#include "stdio.h"

void main(void)
{
const x=4529;
printf("\n numarul este=%d\n",x);
printf("\n valoarea in octal este=%o",x);
printf("\n valoarea in hexazecimal este=%x",x);
}

Afişarea unui caracter şi a codului său ASCII; afişarea se va


termina cu un semnal sonor.
#include "stdio.h"

void main(void)
{
char a='Q';
printf("\n caracterul %c are codul ASCII=%i\a",a,a);
}
33
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Afişarea unor valori folosind diverşi descriptori de format;


comentariile arată efectul execuţiei funcţiei printf().
#include "stdio.h"

void main(void)
{
char ch;
short k;
int i;
long int j;
float x;
clrscr();

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","testare");
}
34
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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ă 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:
35
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Afişarea unor valori folosind descriptori de format cu


precizarea dimensiunii câmpului de afişare
#include "stdio.h"

void main(void)
{
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","testare"); /* testare */


printf("\n %10s","testare"); /* testare */
}

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.
36
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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.
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:
37
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Afişarea unor valori folosind diverse facilităţi ale


descriptorilor prezentaţi
#include "stdio.h"

void main(void)
{
int i;
double x;

i=4567;
printf("\n i=%3.7i",i); /* i=0004567 */
printf("\n i=%7.3i",i); /* i= 4567 */
printf("\n i=%-7.3i",i); /* i=4567 */

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

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


int scanf(sir_format,lista_de_argumente);
38
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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.


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.
39
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

· 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. Un
exemplu de program care citeşte şiruri de caractere este
prezentat în Capitolul 5 (vezi paragraful Funcţii pentru
prelucrarea şirurilor de caractere).
· 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
40
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Citirea numerelor reale x şi X de la tastatură, calculul


produsului x*X şi afişarea lui în format exponenţial.
#include "stdio.h"

void main(void)
{
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", X);
}

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.

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 *=.
41
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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

FUNCŢII SPECIALE PENTRU CITIREA/SCRIEREA


CARACTERELOR LA NIVELUL CONSOLEI

Standardul ANSI C 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(void);

şi întoarce următorul caracter care va fi citit. Dacă s-a atins


sfărşitul şirului sau se produce o eroare se întoarce EOF.
Deşi nu aparţin standardului ANSI C, totuşi, funcţiile
getch(), getche(), putch() sunt incluse frecvent în biblioteca
standard a compilatoarelor compatibile DOS. 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);
42
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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 ANSI C) frecvent utilizată.
43
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Citirea si afişarea unui caracter folosind funcţiile speciale


getche() şi putch()

#include "stdio.h"
#include "conio.h"

void main(void)
{
char x;
printf("\n Tastati o litera! ");
x=getche();
printf("\n Multumesc! Ati tastat litera ");
putch(x);
getch();
}

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 sunt prezentate în Capitolul 12 al lucrării. Deoarece
în construcţia acestor prototipuri intervine noţiunea de pointer,
prezentată în detaliu în Capitolul 6, ne limităm aici la a spune
că prin apelul
gets(sir_destinatie);

se citeşte un şir de la tastatură în sir_destinatie, iar apelul


puts(sir);
44
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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

Un program complet care utilizează funcţiile gets() şi


puts() este prezentat în Capitolul 5 (vezi paragraful Funcţii
pentru prelucrarea şirurilor de caractere).

PERATORI. 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ţă);
45
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

· 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
: operand 3.

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.
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 3.2.
46
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Tabelul 3.2 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)
= += -= *= etc.
14 (atribuire)
,
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
47
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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
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;
48
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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 referitore 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:
49
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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;

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ă
50
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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);
51
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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 2.5 şi din prezentarea
făcută anterior operatorilor ++ şi --. În plus vom face

următoarele observaţii:
52
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

· 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ărtirii lui 7 la 2.


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

OPERATORI RELAŢIONALI ŞI LOGICI

Afişarea valorilor unor expresii în care intervin operatori


relaţionali
#include "stdio.h"
#include "conio.h"

void main(void)
{
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*/
getch();
}

Operatorii relaţionali din limbajul C sunt: <, >, <=, >=,


==, !=. Semnificaţia lor rezultă din Tabelul 2.5. Î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:

Operatorii logici din C sunt:

! NU logic
&& SI logic
|| SAU logic
54
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Modul de acţiune al acestor operatori este prezentat în Tabelul


3.3.

Tabelul 3.3 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 3.2 rezultă că operatorii logici && şi || au o


prioritate mai mică decât operatorii relaţionali. Din acest motiv:

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).
55
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

Dacă într-o expresie formată din operanzi legaţi prin


operatorul || , iar valoarea primului operand este 1, valoarea
expresiei este 1 (vezi Tabelul 3.2 ) ş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.
56
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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

Tabelul 3.4 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 3.3 şi Tabelul 3.4 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:
57
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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
58
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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.
59
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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.
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.
60
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

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


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

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.

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


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

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.


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

ONVERSII DE TIP EXPLICITE (OPERATORUL CAST)

Operatorul de conversie explicită (cast) acţionează


temporar, formâ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).
64
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

TESTE DE CONTROL

3.1 În C există tipurile fundamentale de date:

a) char, int, float, double, void


b) char, integer, real, double, void
c) char, int, float, double, nul, boolean
d) character, string, real, void

3.2 Modificatorii de semn

a) schimbă semnul unei expresii


b) schimbă semnul numai pentru valori întregi
c) au ca efect interpretarea diferită din punct de vedere al
semnului, a informaţiei memorate într-o anumită zonă

3.3 Declaraţiile
char x;
şi
signed char x;
a) sunt echivalente
b) sunt greşite
c) sunt corecte
65
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.4 Declaraţiile
short int x;
şi
int x;
a) sunt echivalente
b) sunt greşite
c) sunt corecte

3.5 Declaraţia
float x,y;
a) este echivalentă cu float x;float y;
b) este greşită
c) este echivalentă cu x,y:float;
d) este echivalentă cu real x,y;

3.6 Linia de program


char ch=’A’,Z;
are semnificaţia:
a) variabila ch ia valori de la A la Z
b) variabila ch este de tip char şi este iniţializată cu
valoarea ’A’, iar variabila Z este de tip char
c) tipul de date char ia valori de la A la Z
66
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.7. Liniile de program


const ore_zi=24;
int ore_zi=24;
a) sunt echivalente
b) sunt corecte şi compatibile
c) sunt corecte şi incompatibile

3.8 Secvenţa de program


int x=10,y=20,z=5,w=7;
printf(”\n x=%i y=%i z=%d”,x,y);
afişează:
a) x=10 y=20 z=5
b) date de ieşire nedefinite
c) x=10 y=20

3.9 Secvenţa de program:


int x=10,y=20,z=5,w=7;
printf(”\n x=%d y=%i”,x,y,z);
a) afişează x=10 y=20

b) afişează x=10 y=20 z=5

c) este greşită

3.10 Instrucţiunea printf() de mai jos


printf(”\nuu!\taurul \n-are importanta!”);
afişează
a) \nuu!\taurul\n-are importanta!
b) taurul n-are importanta!
c) uu!aurul -are importanta!
d) uu! aurul
-are importanta!
67
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.11 Secvenàa de program


int i=10,j=20;
printf(”\n i=%i,j=%i”,j,i);
afişează
a) i=10, j=20
b) i=20, j=10
c) i=10% j=20%

3.12 Secvenţa de program


int i=10,j=20;
printf(”\n i=%i,j=%j”,i,j);
a) afişează i=10,j=20

b) este greşită
c) afişează i=%10,j=%20

3.13 Secvenţa de program


char a=’q’;
printf(”\n a=%d”,a);

a) este greşită deoarece %d este descriptor pentru tipul


int, nu pentru tipul char
b) este corectă şi afişează codul ASCII al caracterului q
c) este corectă şi afişează codul ASCII al caracterului a
d) este corectă şi afişează caracterul q

3.14 Secvenţa de program


float x=32.75;
printf(”\n x=%e,x=%f”,x,x);
a) este greşită deoarece argumentul x se repetă
b) este corectă şi va afişa x=32.75,x=32.75
c) este corectă şi va afişa x=3.275000e+01,x=32.750000
68
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.15 Secvenţa de program


int x=439;
printf(”\n %o”,x);
afişează:
a) 439
b) numărul 439 scris în baza 8
c) numărul 439 scris în baza 16

3.16 Secvenţa de program


int x=1011;
printf(”\n %x”,x);
afişează:
a) valoarea lui x în binar
b) valoarea lui x în hexazecimal
c) valoarea lui x în octal

3.17 Secvenţa de program


int x=12;
float y=31.42;
printf(”\n x=%f y=%d”,x,y);
a) afişează x=12 y=31.42

b) afişează x=12.0 y=31


c) este greşită

3.18 Secvenţa de program


float x=10.5;
printf(”\n x=%-10.5f”,x);
a) afişează x=-10.5
b) afişează x=10.50000
69
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

c) este greşită

3.19 Secvenţa de program


float x=10.5;
printf(”\n x=%10.5”,x);
a) afişează x=10.5
b) afişează x= 10.50000

c) afişează x=10.50000

3.20 Despre secvenţele de program


float x;
scanf(”%f”,x);
şi
float x;
scanf(”%f”,&x);
se poate afirma că:
a) sunt corecte şi au acelaşi efect
b) prima secvenţă este corectă şi a doua incorectă
c) prima secvenţă este incorectă şi a doua corectă

3.21 Secvenţa de program


printf(”%.3s”,”abcde”);
a) afişează abc
b) afişează abcde
c) este greşită

3.22 Dacă şirul care trebuie citit de la tastatură este abcdef


atunci secvenţa
scanf(”%3s”,sir);
70
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

a) este greşită, deoarece variabila sir nu e precedată de


operatorul &
b) este greşită, deoarece şirul de intrare are 6 caractere,
iar descriptorul %s prevede doar 3 caractere
c) este corectă, dar se citesc doar caracterele abc

3.23 Secvenţa de program


scanf(”%d;%f;%s”,&x,&y,sir);
a) este greşită, deoarece descriptorii de format sunt
despărţiţi prin semnul ;
b) este greşită, deoarece variabila sir nu e precedată de
operatorul &
c) este corectă şi realizează corect citirea dacă datele din
fluxul de intrare sunt despărţite prin semnul ;

3.24 Operatorii în C pot fi:

a) unari, binari
b) unari, binari, ternari
c) unari, binali, termali

3.25 Operatorii + şi - pot fi:

a) numai unari
b) numai binari
c) unari sau binari
71
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.26 Secvenţa de program:


float x;
int i;
x=34.21;
i=x;
a) este greşită deoarece se atribuie valoarea reală din x
variabilei întregi i
b) este corectă
c) este corectă, iar i va primi valoarea 34

3.27 Secvenţa de program


int i;
float x;
i=34;
x=i;

a) este greşită, deoarece se atribuie valoarea întreagă


din i variabilei reale x
b) este corectă
c) este corectă, iar x va primi valoarea 34 convertită în
virgulă mobilă.

3.28 Linia de program


V=(A=B*b)*h;

a) este eronată deoarece conţine operatorul de atribuire =


de două ori
b) este corectă
c) este corectă şi este echivalentă cu secvenţa de
program
72
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

A=B*b;
V=A*h;

3.29 Linia de program


a=b=c=1;

a) este corectă şi e echivalentă cu secvenţa


a=1;
b=1;
c=1;

b) este corectă şi e echivalentă cu secvenţa


1=a=b=c;

c) este greşită deoarece operatorul de atribuire apare de


mai multe ori

3.30 Expresia
x+=1;

a) este greşită
b) este corectă şi echivalentă cu x=x+1;
c) este corectă şi echivalentă cu x++;
d) este corectă şi echivalentă cu ++x;

3.31 Expresia
y=--x;

a) este greşită
b) este corectă şi echivalentă cu secvenţa
x=x-1;
y=x;
73
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

c) este corectă şi echivalentă cu secvenţa


y=x;
y=x-1;

3.32 Expresia
y=x--;

a) este greşită
b) este corectă şi echivalentă cu secvenţa
x=x-1;
y=x;
c) este corectă şi echivalentă cu secvenţa
y=x;
x=x-1;

3.33 În urma execuţiei secvenţei de program:


int i,j;
i=19;
j=i/4;
a) j ia valoarea 4
b) j ia valoarea 4.75
c) j ia valoarea 5

3.34 În urma execuţiei secvenţei de program:


int i,j;
i=19;
j=i%4;
a) j ia valoarea 3
b) j ia valoarea 4
c) j ia valoarea 4.75

3.35 Dacă a,b,c,d sunt variabile numerice atunci expresia


(a<b)||(c>d) se poate scrie:
74
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

a) a<b||c>d
b) a<(b||c)>d
c) c>d||a<b
3.36 În secvenţa de program
int x=3,y=4,a,b,z;
scanf(”%i %i”,&a,&b);
z=(y>x)||(a<b);

variabila z va lua

a) o valoare nedefinită
b) valoarea 1
c) o valoare care depinde de a şi b

3.37 În secvenţa de program


int x=3,y=4,a,b,z;
scanf(”%i %i”,&a,&b);
z=(x>y)&&(a<b);

variabila z va lua

a) o valoare nedefinită
b) valoarea 0
c) o valoare care depinde de a şi b

3.38 În secvenţa de program


int a=3,b=4,x,y,z;
z=(x=a+b,y=x);

a) x ia valoarea 7
b) y ia valoarea 7
c) z ia valoarea 7
75
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.39 Operatorii pe bit se aplică

a) valorilor 0 sau 1 pe care le iau anumite variabile de tip


int
b) variabilelor de tip char şi int
c) variabilelor de tip float şi double

3.40 Dacă doi biţi notaţi a şi b au valorile a=1 şi b=1 atunci:

a) a|b ia valoarea 1
b) a&b ia valoarea 1
c) a^b ia valoarea 0
d) ~a ia valoarea 0

3.41 Dacă doi biţi notaţi a şi b au valorile a=1 şi b=0 atunci:

a) a|b ia valoarea 1
b) a&b ia valoarea 0
c) a^b ia valoarea 1
d) ~b ia valoarea 1

3.42 Secvenţa de program


int x=192;
printf(”\n x=%”, x<<1);
afişează
a) 91
b) 192
c) 384
76
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.43 Complementul faţă de 1 al numărului întreg i se obţine


ca:

a) ~i
b) 1-i
3.44 Complementul faţă de 2 al numărului întreg i se obţine
ca:
a) ~i+1
b) ~i-1
c) 2-i
3.45 Operaţia x<<3 echivalează cu:

a) o înmulţire a lui x cu 3
b) o împărţire a lui x la 3
c) o înmulţire a lui x cu 23
d) o impărţire a lui x cu 23

3.46 Operaţia x>>2 echivalează cu:

a) o înmulţire a lui x cu 2
b) o împărţire a lui x la 2
c) o înmulţire a lui x cu 22
d) o impărţire a lui x cu 22
77
Capitolul 3 - Expresii în C. Funcţii de intrare/ieşire uzuale pentru consolă

3.47 Secvenţa de program


int i=7;
float x;
x=(float)i/4;
printf(”\n x=%f”,x);
. . . . .
a) este greşită
b) este corectă şi afişează x=1.750000
c) este corectă şi afişează x=1

RĂSPUNSURI

3.1-a 3.2-c 3.3-a, c 3.4-c 3.5-a


3.6-b 3.7-c 3.8-b 3.9-a 3.10-d
3.11-b 3.12-a 3.13-b 3.14-c 3.15-b
3.16-b 3.17-c 3.18-b 3.19-b 3.20-c
3.21-a 3.22-c 3.23-c 3.24-b 3.25-c
3.26-b, c 3.27-b, c 3.28-b, c 3.29-a 3.30-b, c, d
3.31-b 3.32-c 3.33-a 3.34-a 3.35-a, c
3.36-b 3.37-b 3.38-a, b, c 3.39-b 3.40-a, b, c,
d
3.41-a, b, c, d 3.42-c 3.43-a 3.44-a 3.45-c
3.46-d 3.47b
Capitolul 4 - Instrucţiuni de control ale programului 60

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

NSTRUCŢIUNI 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++;
getch();
Capitolul 4 - Instrucţiuni de control ale programului 61

NSTRUCŢIUNI DE DECIZIE

INSTRUCŢIUNEA IF

Forma generală a instrucţiunii if este:


if(expresie)
instructiune_1;
else
instructiune_2;

Efectul instrucţiunii este următorul: dacă expresie este


adevărată (diferită de zero) se execută instructiune_1 în caz
contrar (expresie este egala cu zero) se execută

Fals Expresie Adevărat

Instrucţiune_2 Instrucţiune_1

Figura 4.1. Modul de execuţie al instrucţiunii if


instructiune_2. Figura 4.1 ilustrează modul de execuţie al

instrucţiunii if.
Capitolul 4 - Instrucţiuni de control ale programului 62

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


#include "stdio.h"
#include "conio.h"
#include "math.h"

void main(void)
{
float x,y;
printf("\n Introduceti x=");
scanf("%f",&x);
if (x<=0)
printf("\n Calcul imposibil");
else
{
y=sqrt(x);
printf("\n y=%f",y);
}
getch();
}

Observaţii:
· Sintaxa generală a instrucţiunii if cere pe ambele alternative
câte o instrucţiune. În situaţia în care pe o alternativă sunt
necesare mai multe instrucţiuni acestea vor fi grupatecu
ajutorul acoladelor într-o instrucţiune bloc. Astfel, în exemplul
de mai sus, secvenţa

{
y=sqrt(x);
printf("\n y=%f",y);
}

este o instrucţiune bloc.


Capitolul 4 - Instrucţiuni de control ale programului 63

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

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. Modul de
execuţie al instrucţiunii if cu ramură vidă este ilustrat în Figura
4.2.
Fals Adevărat
Expresie

Instrucţiune

Figura 4.2. Modul de execuţie al instrucţiunii if cu ramură


vidă
Capitolul 4 - Instrucţiuni de control ale programului 64

Programul calculează maximul dintre două numere.


#include "stdio.h"
#include "conio.h"

void main(void)
{
float x,y,max;
printf("\n x=");
scanf("%f",&x);
printf("\n y=");
scanf("%f",&y);;
max=x;
if (max<y)
max=y;
printf("\n Maximul dintre x=%.2f si y=%.2f
este=%.2f",x,y,max);
getch();
}

· 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
Capitolul 4 - Instrucţiuni de control ale programului 65

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


Capitolul 4 - Instrucţiuni de control ale programului 66

Programul citeşte coordonatele unui număr şi stabileşte în


ce cadran se află acesta.
#include "stdio.h"
#include "conio.h"

void main(void)
{
float x,y;
printf("\n abscisa x=");
scanf("%f",&x);
printf("\n ordonata y=");
scanf("%f",&y);
if (x>=0 && y>=0)
printf("\n Numarul apartine cadranului I");
else if (x<0 && y>=0)
printf("\n Numarul apartine cadranului II");
else if(x<0 && y<0)
printf("\n Numarul apartine cadranului III");
else
printf("\n Numarul apartine cadranului IV");
getch();
}

· Ţinând cont că în limbajul C orice expresie diferită de zero


este adevărată, o secvenţă de genul
if(expr!=0)
instructiune;

este echivalentă cu
if(expr)
instructiune;

formă care va fi întotdeauna preferată.


Capitolul 4 - Instrucţiuni de control ale programului 67

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

b) if(x)
if(y)
printf(”\n x si y nenuli”);
else;
else
printf(”\n x nul”);

c) if(x&&y)
Capitolul 4 - Instrucţiuni de control ale programului 68

printf(”\n x si y nenuli”);
else
if(!x)
printf(”\n x nul”);

· În ideea creerii 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)

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.
Capitolul 4 - Instrucţiuni de control ale programului 69

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;

În plus, expresia expr1 ? expr2 : expr3 va lua valoarea expr2


sau expr3 după cum expr1 este adevărată sau nu.
Capitolul 4 - Instrucţiuni de control ale programului 70

Programul afişează maximul dintre două numere a şi b citite


de la tastatură.
#include "stdio.h"
#include "conio.h"

void main(void)
{
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",a,b,a<b?b:a);
getch();
}

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;
}
Capitolul 4 - Instrucţiuni de control ale programului 71

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.
Programul citeşte una din literele a,A,m,M,p,P de la
Capitolul 4 - Instrucţiuni de control ale programului 72
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"
#include "conio.h"

void main(void)
{
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 !");
}

getch();
}

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 nici o 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.
Capitolul 4 - Instrucţiuni de control ale programului 73

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

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.
Capitolul 4 - Instrucţiuni de control ale programului 74

Fals
Condiţie

Adevărat

Instrucţiune

Figura 4.3. Modul de lucru al instrucţiunii while

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.
Capitolul 4 - Instrucţiuni de control ale programului 75

Calculul lungimii unui şir de caractere citit de la tastatură;


sfârşitul şirului este marcat de tasta Enter (caracterul ’\r’).
#include "stdio.h"
#include "conio.h"

void main(void)
{
int i=0;
printf("\n Tastati un sir:\n");
while (getche()!='\r')
i++;
printf("\n Lungimea sirului =%d",i);
getch();
}

INSTRUCŢIUNEA FOR

În C, instrucţiunea for prezentă şi în alte limbaje, are


implementarea cea 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:
Capitolul 4 - Instrucţiuni de control ale programului 76

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

De aici rezultă şi efectul execuţiei sale: se execută blocul


instructiune–actualizare cât timp conditia este îndeplinită.
Capitolul 4 - Instrucţiuni de control ale programului 77

Programul realizează suma a n numere reale.


#include "stdio.h"
#include "conio.h"

void main(void)
{
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);
getch();
}

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:
Capitolul 4 - Instrucţiuni de control ale programului 78

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.

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.
#include "stdio.h"
#include "conio.h"

void main(void)
{
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;
}
getch();
}
Capitolul 4 - Instrucţiuni de control ale programului 79

Exemple:

· Dacă în programul precedent renunţăm la ideea de a afişa


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


for(;;);

creează un ciclu infinit sau o buclă eternă.


Capitolul 4 - Instrucţiuni de control ale programului 80

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.
Capitolul 4 - Instrucţiuni de control ale programului 81

Instrucţiune

Adevărat Fals
Condiţie

Figura 4.4. Modul de execuţie al instruc\iunii do-while

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ă).
Capitolul 4 - Instrucţiuni de control ale programului 82

Exemplul 4.9.

Programul 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ă una din tastele care conţin literele d
sau D.
#include "stdio.h"
#include "conio.h"

void main(void

{
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');
}
Capitolul 4 - Instrucţiuni de control ale programului 83

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"
#include "conio.h"

void main(void)
{
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");
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 != '/');
getch();
}
Capitolul 4 - Instrucţiuni de control ale programului 84

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.

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


Noutatea faţă de Exemplul 4.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 "conio.h"
#include "math.h"

void main(void)
{
float x;
do
Capitolul 4 - Instrucţiuni de control ale programului 85

{
printf("\n x=");
scanf("\n %f",&x);
}
while (x<0);
printf("\n Radical din x=%.2f este y=%.4f",x,sqrt(x));
getch();
}

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

Programul ilustrează folosirea ciclurilor imbricate:


#include "stdio.h"
#include "conio.h"

void main(void)
{
int i,j;
for (i=1;i<=5;i++)
{
for (j=1;j<=i;j++)
printf("%2d",j);
printf("\n");
};
getch();
}

sunt imbricate sau incluse.


Capitolul 4 - Instrucţiuni de control ale programului 86

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:
Programul rezolvă următoarea problemă: cât timp se citeşte
de la tastatură o valoare x diferită de zero se afişează şirul87
Capitolul 4 - Instrucţiuni de control ale programului

de numere x,x+1,x+2,x+3,x+4. Acest proces continuă sau


se întrerupe la cerere.
#include "stdio.h"
#include "conio.h"

void main(void)
{
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');
}

NSTRUCŢ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.
Capitolul 4 - Instrucţiuni de control ale programului 88

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.

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>.
#include "stdio.h"
#include "conio.h"
#include "stdlib.h"

void main(void)
{
int i,x;
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;
}
getch();
}
Capitolul 4 - Instrucţiuni de control ale programului 89

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.

Programul tipăreşte de 10 ori câte 7 numere extrase aleator.


#include "stdio.h"
#include "conio.h"
#include "stdlib.h"

void main(void)
{
int i,j;
for (i=0;i<10;i++)
{
printf("\n");
for (j=0;j<10;j++)
{
printf("%5d",rand()%10+1);
if (j>=7) break;
}
}
getch();
}
Capitolul 4 - Instrucţiuni de control ale programului 90

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.

Programul citeşte de la tastatură 10 numere întregi


însumându-le doar pe cele strict pozitive.
#include "stdio.h"
#include "conio.h"

void main(void)
{
int i,x,s;
for (i=s=0;i<10;i++)
{
printf("\n x=");
scanf("%i",&x);
if (x<=0) continue;
s+=x;
}
printf("\n Suma este=%d",s);
getch();
}
Capitolul 4 - Instrucţiuni de control ale programului 91

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 executiei instructiunii goto este saltul


neconditionat la instructiunea identificată prin eticheta. Saltul
poate fi facut inainte sau dupa instructiunea goto. Folosirea
frecventa a acestei instructiuni in programe afecteaza
claritatea acestora, ingreunand intelegerea lor. Ea poate fi insa
evitata in orice situatie utilizand celelalte structuri de control din
C. Dam mai jos un exemplu tipic de folosire a instructiunii goto:
iesirea fortata dintr-un ciclu sau dintr-o instructiune switch.
Capitolul 4 - Instrucţiuni de control ale programului 92

EXEMPLUL 4.17

Programul reprezintă o altă implementare a Exemplului


4.10.
#include "stdio.h"
#include "conio.h"
void main(void)
{
float a,b;
char ch;
for(;;)
{
clrscr();
printf("\n a=");
scanf("%f",&a);
printf("\n b=");
scanf("%f",&b);
printf("\n + Adunare");
printf("\n - Scadere");
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;
}
getch();
}
STOP:printf("\n Am renuntat!");
getch();
}
Capitolul 4 - Instrucţiuni de control ale programului 93

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 8, capitol dedicat funcţiilor C.
Capitolul 4 - Instrucţiuni de control ale programului 94

TESTE DE CONTROL

4.1 Secvenţa de program


int x=-3,y=7;
if (x>0)
printf(”\n x=%i”,x);
else
if (y)
printf(”\n y=%i”,y);
else
printf(”\n y=0”);
a) afişează y=7
b) este greşită deoarece o instrucţiune if conţine o altă
instrucţiune if
c) afişează x=-3

4.2 Secvenţa de program


if(x)
if(y)
y=3;
else
y=5;
este echivalentă cu
a) if(x)
if(y)
y=3;
else;
else
y=5;
b) if(x&&y)
y=3;
else
y=5;
c) if(x)
{
Capitolul 4 - Instrucţiuni de control ale programului 95

if(y)
y=3;
}
else
y=5;
d) nici una din secvenţele a), b), c)

4.3 Secvenţa de program


int x=1,y=2;
if ((x=0)&&(y=4))
printf(”\n x=%i y=%i”,x,y);
a) afişează x=1 y=2
b) afişează x=0 y=4
c) nu afişează nimic deoarece printf() nu se execută

4.4 Secvenţa de program


if(x)
y=1/x;
a) este greşită
b) este echivalentă cu
if (x!=0)
y=1/x;
c) este chivalentă cu
if x!=0
y=1/x;

4.5 Secvenţa de program


if (x>=3)
y=sqrt(x-3);
else;
a) este greşită deoarece conţine semnul ; înainte de else
b) este greşită deoarece după else lipseşte o instrucţiune
C
c) este corectă şi este echivalentă cu secvenţa
Capitolul 4 - Instrucţiuni de control ale programului 96

if(x>=3)
y=sqrt(x-3);

4.6 Secvenţa de program


int x=3,y=6;
if (x>0)
printf(”\n x=%i”,x);
else
y=x+3;
printf(”\n y=%i”,y);
afişează
a) x=3
b) y=6
c) x=3
y=6

4.7 Secvenţa de program


int x=3,y=4;
printf(”\n %i”,x>y?x:y);
a) afişează 3
b) afişează 4
c) este greşită deoarece argumentul funcţiei printf() este o

expresie, nu o variabilă.

4.8 Secvenţa de program


char x=’a’;
switch(x)
{
case ’a’:printf(”\n amic”);
case ’A’:printf(”\n Amare”);
}
afişează
a) amic
b) Amare
Capitolul 4 - Instrucţiuni de control ale programului 97

c) amic
Amare

4.9 Secvenţa de program


int x=3;
if (x=4)
printf(”\n x=%i”,x);
a) este greşită
b) este corectă, dar nu afişează nimic
c) este corectă şi afişează valoarea 4
d) este corectă şi afişează valoarea 3

4.10 Secvenţa de program


char x=’a’;
switch(x)
{
case ’a’:printf(”\n amic”);break;
case ’A’:printf(”\n Amare”);
}
a) afişează amic
b) afişează Amare
c) afişează
amic
Amare
d) este greşită deoarece al doilea case nu se termină cu
break
Capitolul 4 - Instrucţiuni de control ale programului 98

4.11 Secvenţa de program


int i=3;
while(i)
i--;
printf(”%3i”,i);
a) afişează 2 1 0

b) conţine o buclă eternă


c) afişează 0

4.12 Secvenţa de program


int i=3;
while(i)
printf(”%3i”,i);
i--;
a) afişează valoarea 3 de 3 ori
b) afişează valoarea 3 de o infinitate de ori (buclă eternă)
c) afişează 3 2 1

4.13 Secvenţa de program


int i=3;
while(i);
printf(”%3i”,i);
i--;
a) afişează valoarea 3 de 3 ori
b) afişează valoarea 3 de o infinitate de ori (buclă eternă)
c) conţine o buclă eternă şi nu afişează nimic
d) afişează 3 2 1

4.14 Secvenţa de program


int i=3;
while(i)
{
printf(”%3i”,i);
i--;
Capitolul 4 - Instrucţiuni de control ale programului 99

}
a) afişează 3 2 1 0

b) afişează 3 2 1

c) conţine o buclă eternă

4.15 Secvenţa de program


int i=0;
while(i)
printf(”%3i”,i);
a) afişează valoarea 0
b) afişează valoarea 0 de o infinitate de ori (buclă eternă)
c) nu afişează nimic

4.16 Secvenţa de program


int i=0;
for (;i<3;i++);
printf(”%3i”,i);
a) este greşită sintactic deoarece lipseşte componenta de
iniţializare a instrucţiunii for
b) afişează valorile 0 1 2 3

c) afişează valorile 0 1 2

d) afişeaza valoarea 3
e) conţine o buclă eternă afişându-se permanent valoarea
0
f) conţine o buclă eternă şi nu afişează nimic

4.17 Secvenţa de program


int i,j;
for (i=1,j=10;i<=5;i++,j--)
printf(”\n %i %2i”,i,j);
Capitolul 4 - Instrucţiuni de control ale programului 100

a) este greşită deoarece instrucţiunea for conţine două


variabile contor i,j
b) este greşită deoarece componenta iniţializare a
instrucţiunii for conţine două iniţializări
c) este corectă şi afişează
1 10
2 9
3 8
4 7
5 6

4.18 Secvenţa de program


int i;
for (i=1;-3<=i&&i<1;i--)
printf(”\n%3i”,i);
a) nu afişează nimic
b) afişează 0 -1 -2 -3

c) conţine o buclă eternă

4.19 În secvenţa de program


int x=1,i;
for(i=1;x<=3;i++)
scanf(”%i”,&x);
a) funcţia scanf() se execută de 3 ori
b) funcţia scanf() se execută de o infinitate de ori
c) instrucţiunea for este greşită deoarece contorul i nu se
testează niciodată
d) se iese din buclă numai după ce s-a citit o valoare x>3

4.20 Secvenţa de program


int i;
for(i=1,i<=3;i++)
printf(”%3i”,i)
Capitolul 4 - Instrucţiuni de control ale programului 101

a) este greşită
b) conţine o buclă eternă
c) afişează 1 2 3

4.21 Secvenţa de program


int i;
for (i=1,i<=3;i++)
printf(”%3i”,i)
a) conţine o instrucţiune for greşită sintactic
b) afişează 1 2 3

c) conţine o buclă for eternă

4.22 Secvenţa de program


for();
a) este o buclă for eternă
b) este greşită sintactic
c) apelează funcţia for()

4.23 Secvenţa de program


float x=-3.2;
do
scanf(”%f”,&x);
while (x>0);
a) citeşte x până când x>0
b) citeşte x cât timp x>0
c) este greşită deoarece funcţia scanf() nu este pusă între
acolade
d) nu execută nici o citire a lui x

4.24 Secvenţa de program


int j;
Capitolul 4 - Instrucţiuni de control ale programului 102

do
j=3;
while (j<=4);
{
printf(”%3i”,j);
j++;
}
a) afişează valoarea 3
b) afişează valorile 3 4

c) conţine o buclă do-while eternă


d) conţine o buclă while eternă
e) este greşită

4.25 Secvenţa de program


int i=1;
do
printf(”\n%i”,i);
i++;
while(i<=2);
a) afişează 1
2
b) este greşită deoarece instrucţiunile dintre do şi while
trebuie să fie cuprinse între acolade

4.26 Secvenţa de program


int i,j;
for(i=1;i<=2;i++)
for(j=1;j<=2;j++)
printf(”\n i=%i j=%i”,i,j);
afişează
a) i=1 j=1
i=2 j=2

b) i=1 j=1
i=1 j=2
i=2 j=1
Capitolul 4 - Instrucţiuni de control ale programului 103

i=2 j=2

c) i=1 j=1
i=2 j=1
i=1 j=2
i=2 j=2

4.27 Secvenţa de program


int i,j;
for(i=1;i<=3;i++)
{
printf(”\n”);
for(j=1;j<=i;j++)
printf(”%3i”,j);
}
a) este greşită deoarece limitele de variaţie ale contorului
j depind de contorul i al primului ciclu

b) este corectă şi afişează


1
1 2
1 2 3
c) este corectă şi afişează
1 2 3
1 2
1

4.28 Secvenţa de program


int i;
for(i=1;i<=5;i++)
{
printf(”%3i”,i);
if(i==3)break;
}
a) este greşită deoarece instrucţiunea break se foloseşte
numai asociată cu switch
b) este corectă şi afişează 1 2 3
Capitolul 4 - Instrucţiuni de control ale programului 104

c) este corectă şi afişează 1 2 3 4 5

deoarece instrucţiunea break cuprinsă în corpul unui


ciclu for nu are nici un efect

4.29 Secvenţa de program


int i,j;
for(i=1;i-3;i++)
for(j=0;j-9;j++)
{
printf(”\n Sunt aici!”);
if (!j)break;
}
a) afişează mesajul Sunt aici! de 2 ori
b) afişează mesajul Sunt aici! de 30 ori
c) afişează mesajul Sunt aici! o dată

4.30 Secvenţa de program


int s,i;
for(s=0,i=10;i;i--)
{
s+=i;
if(i-5)continue;
}
a) calculează suma s=10+9+8+7+6+4+3+2+1
b) calculează suma s=10+9+8+7+6+5+4+3+2+1
c) calculează suma s=5

4.31 Secvenţa de program


int s,i;
for(s=0;i=10;i;i--)
{
if(i-5)continue;
s+=i;
}
a) calculează suma s=10+9+8+7+6+4+3+2+1
Capitolul 4 - Instrucţiuni de control ale programului 105

b) calculează suma s=10+9+8+7+6+5+4+3+2+1


c) calculează suma s=5

4.32 Secvenţa de program


int s,i;
for(s=0;i=10;i;i--)
{
if(i==5)continue;
s+=i;
}
a) calculează suma s=10+9+8+7+6+4+3+2+1
b) calculează suma s=10+9+8+7+6+5+4+3+2+1
c) calculează suma s=5

4.33 Secvenţa de program


int i=1;
while(i<3)
{
if!(i==2)continue;
printf(”%3i”,i);
i++;
}
a) afişează 1 şi intră într-o buclă eternă
b) afişează 1 2 3
c) afişează 1 3

4.34 Secvenţa de program


int i=1,s=0;
et1:if(i>10)
goto et2;
s+=i;
i++;
goto et1;
et2:printf(”\n s=%d”,s);
a) este greşită deoarece conţine salturi înapoi
Capitolul 4 - Instrucţiuni de control ale programului 106

b) este corectă şi realizează o iteraţie pentru calculul


primelor 10 numere naturale
c) este corectă şi este echivalentă cu secvenţa
int s,i;
for(s=0,i=1;i<11;i++)
s+=i;
printf(”\n s=%d”,s);

d) este corectă şi este echivalentă cu secvenţa


int i=1,s=0;
while(i<11)
{
s+=i;
i++;
}
printf(”\n s=%d”,s);

RĂSPUNSURI

4.1-a 4.2-d 4.3-c 4.4-b 4.5-c


4.6-c 4.7-b 4.8-c 4.9-c 4.10-a
4.11-c 4.12-b 4.13-c 4.14-b 4.15-c
4.16-d 4.17-c 4.18-a 4.19-d 4.20-a
4.21-a 4.22-b 4.23-b 4.24-c 4.25-b
4.26-b 4.27-b 4.28-b 4.29-a 4.30-b
4.31-c 4.32-a 4.33-a 4.34-b, c, d
Capitolul 5 - Tablouri, şiruri de caractere 96

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
evident că prelucrarea S=a+b+c+d+e+f arată mult mai puţin
6
elegant decât S= å x În ultimul caz cele şase variabile
i
=i 1

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.

ABLOURI 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.
Capitolul 5 - Tablouri, şiruri de caractere 97

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*2=200 de octeţi într-o zonă


continuă de memorie.

Observaţii:

· alocarea se face în timpul compilării;


· o dată făcută alocarea, în scopul măririi vitezei de execuţie
nu se mai face nici o 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]

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
Capitolul 5 - Tablouri, şiruri de caractere 98

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

Comutarea componentelor unui vector şi afişarea lor în


coloană.
#include "stdio.h"
#include "conio.h"

void main(void)
{
int x[50],i,n,c;

/* citirea elementelor vectorului */


printf("\n lungimea sirului n<50 este:");
scanf("%d",&n);
for (i=0;i<n;i++)
{
printf("\n x[%i]=",i);
scanf("%i",&x[i]);
}

/* comutarea elementelor */
for (i=0;i<n/2;i++)
c=x[i];
x[i]=x[n-1-i];
x[n-1-i]=c;
}
/* afisarea elementelor vectorului comutat */
for (i=0;i<n;i++)
printf("\n componenta x[%i] este %i",i,x[i]);
getch();
}
Capitolul 5 - Tablouri, şiruri de caractere 99

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


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
Capitolul 5 - Tablouri, şiruri de caractere 100

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

Calculul şi afişarea sumei a două matrici a şi b de dimensiune


4x3 citite de la tastatură.

#include "stdio.h"
#include "conio.h"

void main(void)
{
int a[10][10],b[10][10],c[10][10];
short i,j,m,n;

/* citirea dimensiunilor mtricilor a,b si c */


do
{
printf("\n m=");
scanf("%i",&m);
printf("\n n=");
scanf("%i",&n);
}
while (m<1 || m>10 || n<1 || n>10);

/* citirea elementelor matricilor a si b */


for (i=0;i<m;i++)
for (j=0;j<n;j++)
{
printf("\n a[%i][%i]=",i,j);
scanf("%i",&a[i][j]);
printf("\n b[%i][%i]=",i,j);
scanf("%i",&b[i][j]);
}

/* calculul matricii suma c=a+b */


for (i=0;i<m;i++)
Capitolul 5 - Tablouri, şiruri de caractere 101

for (j=0;j<n;j++)
c[i][j]=a[i][j]+b[i][j];

/* afisarea matricii suma c */


printf("\n matricea suma este:\n");
for (i=0;i<m;i++)
{
for (j=0;j<n;j++)
printf("%4i",c[i][j]);
printf("\n");
}
getch();
}

NIŢ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:
Capitolul 5 - Tablouri, şiruri de caractere 102

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 b[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
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.
Capitolul 5 - Tablouri, şiruri de caractere 103

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

ABLOURI Ş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
Capitolul 5 - Tablouri, şiruri de caractere 104

cel puţin cu o unitate mai mare decât numărul de caractere al


şirului pe care vrem să-l memorăm.
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.
Capitolul 5 - Tablouri, şiruri de caractere 105

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ă literele mici din
şirul sir.

Selectarea şi afişarea literelor mici dintr-un şir.


#include "stdio.h"
#include "conio.h"

void main(void)
{
int i;
char sir[]="aAbBcCdD";
printf("\n Sirul este: %s",sir);
printf("\n Literele mici din sir sunt: ");
for (i=0;sir[i];i+=2)
printf("%c",sir[i]);
getch();
}
Capitolul 5 - Tablouri, şiruri de caractere 106

UNCŢII PENTRU PRELUCRAREA ŞIRURILOR DE

CARACTERE

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.

Citirea a două şiruri sub forma nume prenume şi afişarea lor


sub forma prenume nume.
#include "stdio.h"
#include "conio.h"

void main(void)
{
char nume[25],prenume[25];
printf("\n Numele=");
scanf("%s",nume);
printf("\n Prenumele=");
scanf("%s",prenume);
printf("\n %s %s",prenume,nume);
getch();
}

Pentru citirea unui şir de la tastatură se poate folosi funcţia


gets() cu forma generală:
gets(sir_destinatie);
Capitolul 5 - Tablouri, şiruri de caractere 107

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.

FUNCŢII CU PROTOTIPUL IN “STRING.H”

Fişierul ”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:
<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;


Capitolul 5 - Tablouri, şiruri de caractere 108

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


char *strcat(sir1, sir2);

adaugă sir2 la sfârşitul şirului sir1 (concatenare).

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 "conio.h"
#include "string.h"

void main(void)
{
char min[20],x[20];
printf("\n Introduceti siruri de caractere ! ");
printf("\n La 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));
getch();
}
Capitolul 5 - Tablouri, şiruri de caractere 109

TESTE DE CONTROL

5.1 Declaraţia
float x[100];
înseamnă
a) rezervarea în memorie a 100 locaţii la adrese
consecutive
b) rezervarea în memorie a 100 locaţii la adrese
întâmplătoare
c) rezervarea a 100 de octeţi pentru variabila reală x
5.2 Secvenţa de program
int x[m][n],m=3,n=2;

a) este corectă deoarece dimensiunile m şi n ale matricii


sunt cunoscute
b) este greşită deoarece m şi n trebuia să fie declaraţi
înainte de x
c) este greşită deoarece la declarare în main(),
dimensiunile unui tablou trebuie să fie constante şi nu
variabile

5.3 Secvenţa de program


int y[10];
y[10]=7;

a) atribuie componentei y[10] a tabloului y valoarea 7


b) este greşită deoarece componentele tabloului sunt
y[0],y[1],y[2],...,y[9]
Capitolul 5 - Tablouri, şiruri de caractere 110

c) este corectă deoarece componentele tabloului sunt


y[1],y[2],...,y[10]

5.4 În secvenţa de program


int x[3][2];
x[1,2]=5;

a) atribuirea x[1,2]=5 este echivalentă cu x[1][2]=5


b) atribuirea x[1,2]=5 este corectă sintactic şi înseamnă
x[2]

5.5 Următoarele trei declaraţii ale tabloului x,


int x[6];
int x[3][2];
int x[2][3];
a) sunt echivalente
b) nu sunt echivalente

5.6 În urma iniţializării tabloului a


int a[3][2]={1,2,3,4,5,6};

componenta a[1][0] are valoarea


a) 2
b) 3
c) altă valoare decât decât 2 sau 3

5.7 Iniţializarea
int a[3][2]={1,2,3,4,5,6};

este echivalent[ cu:


a) int a[3][2]={
{1,2},
{3,4},
{5,6},
};
b) int a[3][2]={
Capitolul 5 - Tablouri, şiruri de caractere 111

{1,4},
{2,5},
{3,6},
};

5.8 Iniţializarea
float x[2][3]={1,2,3,4,5,6};

este echivalentă cu
a) float x[][]={1,2,3,4,5,6};
b) float x[][3]={1,2,3,4,5,6};
c) float x[2][]={1,2,3,4,5,6};

5.9 Secvenţa de program


int x[10],i;
for(i=1;i<=10;i++)
scanf(”%d”,&x[i]);
a) este greşită sintactic
b) nu iniţializează toate componentele vectorului x
c) produce eroare la execuţie

5.10 Secvenţa de program


char x[]=”abcd”;

este echivalentă cu:


a) iniţializarea char x[]={’a’,’b’,’c’,’d’};
b) iniţializarea char x[4]={’a’,’b’,’c’,’d’};
c) iniţializarea char x[]={’a’,’b’,’c’,’d’,’\0’};

5.11 Secvenţa de program


char x[]=”abcd”;
putch(x[1]);
Capitolul 5 - Tablouri, şiruri de caractere 112

a) afişează caracterul ’a’


b) afişează caracterul ’b’
c) este greşită deoarece elementele dintr-un şir nu se pot
referi indexat

5.12 Secvenţa de program


char x[20]=”abcd”;
printf(”\n Lungimea sirului=%i”,strlen(x));

afişeaz[ mesajul
a) Lungimea sirului=20
b) Lungimea sirului=4
c) Lungimea sirului=5
5.13 Secvenţa de program
char x[]= ”rac”;
char y[]= ”arc”;
printf(”%d”,strcmp(x,y));

afişează o valoare
a) egală cu zero
b) mai mică strict decât zero
c) mai mare strict decât zero

5.14 Secvenţa de program


char x[]=”ac”;
char y[]=”ar”;
strcat(x,y);
printf(”\n %s”,x);

afişează
a) acar
b) arac
5.15 În secvenţa de program
Capitolul 5 - Tablouri, şiruri de caractere 113

char x[20];
x=”Ploiesti”;
atribuirea
x=”Ploiesti”;

a) este greşită
b) este greşită, iar copierea şirului ”Ploiesti” în variabila
x se realizează prin strcpy(x,”Ploiesti”);

c) este corectă

5.16 Secvenţa de program


char x[20];
gets(x);
puts(x);
afişează acelaşi şir dacă înlocuim gets(x); cu:
a) gets(&x);
b) scanf(”%s”,&x);
c) scanf(”%s”,x);
5.17 În secvenţa de program
int i;
char x[20]=”xyzw”;
puts(x);
apelul
puts(x);
poate fi înlocuit cu:
a) printf(”\n %s”,x);
b) for(i=0;i<5;i++)
putch(x[i]);
Capitolul 5 - Tablouri, şiruri de caractere 114

RĂSPUNSURI

5.1-a 5.2-c 5.3-b 5.4-b 5.5-b


5.6-b 5.7-a 5.8-b 5.9-b 5.10-c
5.11-b 5.12-b 5.13-c 5.14-a 5.15-a, b
5.16-a, b, c 5.17-a, b
Capitolul 6 – Pointeri 112

Î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.
Capitolul 6 – Pointeri 113

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

PERATORII & Ş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.
Capitolul 6 – Pointeri 114

Atribuirea din secvenţa de program


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
Capitolul 6 – Pointeri 115

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
int *p;
float x,y;
x=62549.21;
p=&x;
y=*p;

este greşită deoarece se încearcă folosirea pointerului către


întregi 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.
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:
Capitolul 6 – Pointeri 116

Afişarea valorii unei variabile pointer


#include "stdio.h"
#include "conio.h"

void main(void)
{
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);
getch();
}

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
Capitolul 6 – Pointeri 117

atribuită oricărui pointer cu semnificaţia că nu indică nimic (nu


conţine nici o 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(),

prezentate în Capitolul 12, Funcţii pentru gestiunea memoriei


heap. 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)
prezentate în acelaşi paragraf cu funcţiile de alocare.
Drept exemplu, considerăm secvenţa de program:
int *p;
. . . . . . .
/* 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);
Capitolul 6 – Pointeri 118

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

OINTERI Ş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;
Capitolul 6 – Pointeri 119

dar nu este corectă o secvenţă de genul


int t1[5],t2[5];
t1=t2;
deoarece ea constituie o încercare de a modifica pointerul
constant t1.

OINTERI 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
într-un tabel de şiruri generând pointerul spoint către adresa
şirului. În tabel se vor memora toate constantele şir din
program. Dacă dorim sa afişăm pe ecran mesajul conţinut în
şir este suficient să scriem

printf(spoint) sau 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;
Capitolul 6 – Pointeri 120

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

OINTERI 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
int *t[10];
Capitolul 6 – Pointeri 121

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.

PERAŢII ARITMETICE CU POINTERI

Au sens operaţiile de adunare (scădere) ale unui pointer


cu un intreg.
Capitolul 6 – Pointeri 122

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 int se
memorează pe 2 octeţi, iar tipul float pe 4 octeţi.
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.
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]
Capitolul 6 – Pointeri 123

Comutarea elementelor egal departate de capetele unui


vector şi afişarea vectorului obţinut.
#include "stdio.h"
#include "conio.h"

void main(void)
{
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[%i]=%i",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[%i]=%i",i,*p);
p++;
}
getch();
}
Capitolul 6 – Pointeri 124

Programul reprezintă o altă implementare a Exemplului 6.2.


#include "stdio.h"
#include "conio.h"

void main(void)
{
int x[7]={1,2,3,4,5,6,7},i,*p,*q,y;

/* afisarea vectorului initial */


printf("\n Vectorul initial este:");
for (i=0;i<7;i++)
printf("\nx[%i]=%i",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[%i]=%i",i,*p);
p++;
}
getch();
}

Observaţie. Secvenţa de instrucţiuni


printf("\nx[%i]=%i",i,*p);
p++;

din programul de mai sus putea fi scrisă echivalent:


printf("\nx[%i]=%i",i,p[i]);
Capitolul 6 – Pointeri 125

ceea ce arată că pointerul p poate fi folosit cu indice, întocmai


ca un tablou.

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

Afişarea valorilor unui pointer p combinat în expresii cu


operatorii * şi ++.
#include "stdio.h"

void main(void)
{
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 */
getch();
}
Capitolul 6 – Pointeri 126

OINTERI 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 6.1 se prezintă schema acestui tip de adresare.

Pointer Pointer
Adresă Adresă Valoare

Figura 6.1 Adresare indirectă (pointer dublu)

Exemplu:
int **p,*q,x;
x=10;
q=&x;
p=&q;

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.
Capitolul 6 – Pointeri 127

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),
Capitolul 6 – Pointeri 128

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].
Capitolul 6 – Pointeri 129

TESTE DE CONTROL

6.1 Un pointer este:

a) o adresă de memorie
b) o variabilă care poate memora adrese de memorie
c) o zonă de memorie dinamică

6.2 În secvenţa de program


int x,*p;
p=&x;

atribuirea este:

a) corectă, deoarece se atribuie unui pointer o adresă de


memorie
b) incorectă, deoarece trebuia precedată de o atribuire
pentru variabila x

6.3 În secvenţa de program


int x=10,*p;
p=x;
atribuirea este:
a) corectă, deoarece se atribuie lui p o variabilă alocată
static
b) corectă, deoarece variabila x a fost iniţializată
Capitolul 6 – Pointeri 130

c) incorectă, deoarece pointerului p trebuie să i se


atribuie adrese de memorie

6.4 În secvenţa de program


int *p,x;
x=31;
*p=x;

atribuirea este:

a) corectă
b) incorectă, deoarece pointerul p nu indică nici o locaţie
c) este corectă şi echivalentă cu p=p*x;

6.5 Secvenţa de program


int x,*p,*q;
x=11;
p=q=&x;
printf(”%d”,*p);

a) afişează valoarea 11
b) este greşită, deoarece în expresia
p=q=&x;

semnul = apare de două ori


c) este greşită, deoarece descriptorul de format pentru
pointeri este %p şi nu %d
Capitolul 6 – Pointeri 131

6.6 Secvenţa de program


int x,y,*p;
x=5;
y=10;
p=&x;
&y=p;
printf(”%d”,y);

a) este corectă şi afişează valoarea 5


b) este incorectă, deoarece atribuirea &y=p; este
incorectă
c) este corectă şi afişează valoarea 10

6.7 Secvenţa de program


int *x[10],y=10;
x[1]=&y;
printf(”%d”,*x[1]);

a) este greşită, deoarece componenta x[1] nu poate


primi drept valoare o adresă
b) este corectă şi afişează valoarea 10
c) este greşită deoarece expresia *x[1] este greşită

6.8 Secvenţa de program


char *s=”Imi place limbajul C!”;
int i;
for(i=0;s[i];i++)
printf(”\n %c”,s[i]);

a) este greşită, deoarece s este declarat ca pointer către


char şi este folosit ca un tablou

b) este corectă deoarece un pointer către char poate fi


interpretat ca şi un tablou de caractere
Capitolul 6 – Pointeri 132

c) este corectă şi afişează mesajul ”Imi place limbajul


C!” pe verticală

6.9 O declaraţie de forma:


char *p;

poate avea interpretarea:

a) variabila p este pointer către char


b) variabila p poate conţine adresa unui şir de caractere
c) variabila p este numele unui vector de caractere
alocate dinamic

6.10 Secvenţa de program


int x[10],*p=x;

a) este echivalent[ cu secvenţa


int x[10];
*p=x;

b) este incorectă, deoarece în locaţia indicată de p se


memorează o valoare nedefinită x
c) este corectă şi este echivalentă cu
int x[10],*p=&x[0];

d) este corectă şi este echivalentă cu


int x[10],*p=x[0];

e) este corectă şi este echivalentă cu


int x[10],*p=&x;

6.11 În secvenţa de program


float x[10],y[10],*p;
x=y;
Capitolul 6 – Pointeri 133

p=x;

a) prima atribuire este corectă


b) a doua atribuire e corectă
c) ambele atribuiri sunt corecte

6.12 Declaraţia
int (*x)[10];

a) este greşită
b) este echivalentă cu
int *x[10];

c) este corectă şi reprezintă declaraţia unui pointer către


un tablou unidimensional de 10 componente
6.13 În secvenţa de program
int x[3];
int(*p)[3];
p=x;
p++;

pointerul p:

a) indică componenta x[1] a vectorului x[10]


b) indică locaţia cu adresa x+10*sizeof(int)
6.14 Secvenţa de program
int *p,x[]={1,2,3};
p=x;
p+=2;
printf(”%d”,*p);

a) afişează valoarea 3
b) afişează valoarea 2
Capitolul 6 – Pointeri 134

c) este greşită, deoarece în expresia p=x; se atribuie un


nume de tablou unui pointer
d) este greşită, deoarece adunarea unui întreg la un
pointer (expresia p+=2;) este interzisă

6.15 Componenta i a unui tablou x[10],0<=i<=9 se poate


indica prin

a) x[i]
b) x+i
c) &x[i]
d) *(x+i)
6.16 Valoarea componentei i a unui tablou x[0],0<=i<=9 se
poate obţine folosind notaţia
a) x[i]
b) x+i
c) &x[i]
d) *(x+i)

6.17 Secvenţa de program


int *p,**q,x=10;
p=&x;
q=&p;
printf(”\n %d”,**q);
a) este greşită, deoarece în declaraţie q e precedat de
două semne * şi nu doar de unul
b) este greşită, deoarece intenţia de a memora adresa
unui pointer (expresia q=&p;) tot într-un pointer este o
eroare
Capitolul 6 – Pointeri 135

c) este corectă, deoarece operaţiile cu pointerul p şi


pointerul dublu q sunt corecte
d) este corectă şi afişează valoarea 10

RĂSPUNSURI

6.1-b 6.2-a 6.3-c 6.4-b 6.5-a


6.6-b 6.7-b 6.8-b, c 6.9-a, b, c 6.10-c, e
6.11-b 6.12-c 6.13-b 6.14-a 6.15-b, c
6.16-a, d 6.17-c, d
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 130

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.

TRUCTURI

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;


Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 131

· 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.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 132

struct rand_tel
{
char nume[15];
char prenume[20];
char adresa[50];
unsigned long int telefon;
}pers1,pers2;

Aici, pers1 şi pers2 sunt nume de variabile care vor avea tipul
struct rand_tel. Această declaraţie este echivalentă cu:
struct rand_tel
{
char nume[15];
char prenume[20];
char adresa[50];
unsigned long int telefon;
};
struct rand_tel 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);
. . . . . .
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 133

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


(*pers3).nume

Mărimea unei structuri se calculează utilizând operatorul


sizeof. De exemplu, mărimea structurii rand_tel este dată de
sizeof(struct rand_tel). 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};
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 134

De exemplu, prin declaraţia


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

În interiorul unei structuri numele câmpurilor trebuie să fie


diferite, însă două structuri diferite pot avea nume de câmpuri
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 135

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.

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.

Construirea unei liste şi afişarea elementelor sale.


#include "stdio.h"
#include "conio.h"
#include "alloc.h"
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 136

/*
programul creaza si afiseaza o lista simplu inlantuita
dintr-un sir de date cu marcatorul de sfirsit 0
*/

void main(void)
{
int x;
struct nod
{
int data;
struct nod *leg;
}*p,*q;
q=NULL;
printf("\nIntroduceti elementele listei\n\a");
printf("\nx=");
scanf("%d",&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)
{
printf("%i ",(*p).data);
p=p->leg;
}
getch();
}

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;
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 137

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

Ordonarea unei liste folosind algoritmul de sortare prin


selecţie.
#include "stdio.h"
#include "conio.h"
#include "string.h"

void main(void)
{
short i,j,l,n;
struct catalog
{
char npr[30];
short nota;
}stud[30],studint;
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 138

/* introducerea datelor */
printf("\n n=");
scanf("%i",&n);
for (i=0;i<n;i++)
{
printf("\n Nume=");
scanf("%s",&stud[i].npr);
printf("\n Nota=");
scanf("%i",&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;
studint=stud[i];
stud[i]=stud[l];
stud[l]=studint;
}

/* afisarea catalogului ordonat */


printf("\n Catalogul ordonat este:\n");
for (i=0;i<n;i++)
printf("\n %s %i",stud[i].npr,stud[i].nota);
getch();
}

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;
.
.
.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 139

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;
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 140

unsigned comp5:1;
unsigned comp6:1;
}stare;

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.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 141

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;

NIUNI

O uniune este o structură de date care permite folosirea


în comun a aceleiaşi zone de memorie de către 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, 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 ->.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 142

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:
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 143

v. i

v. f

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

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.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 144

Exemplul 7.3.

Afişarea primului şi ultimului bit din codul binar al unui


caracter introdus de la tastatură.
#include "stdio.h"
#include "conio.h"

void main(void)
{
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");
getch();
}

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ătoarul exemplu.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 145

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


Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 146

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 "alloc.h"

void main(void)
{
char disci[10],ch;
int i,k,n;
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];
/* introducerea datelor */
printf("\nN=");
scanf("%i",&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)
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 147

{
case 'm':printf("\nMedia = ");
scanf("%i",&stud[i].var.nota);
break;
case 'e':printf("\nIntroduceti notele \n");
for (k=0;k<5; k++)
{
printf("\nNota[%i]=",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*/
} /* 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=%i",stud[i].var.nota);

break;
case 'e':printf("\n Notele la examen sunt: \n");
for (k=0;k<5;k++)
printf("\n
Nota[%i]=%i",k,stud[i].var.note[k]);
break;
case 'r':printf("\n Disciplinele restante
sunt: \n");
res=stud[i].var.resta;
while(res)
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 148

{
printf("\n Disciplina=%s",res->disc);
printf("\n Nota restanta = %d",
res->noter);
res=res->leg;
}
break;
} /* end switch */
} /* end for */
getch();
}

NUMERĂ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
enum nume_enumerare lista_de_variabile;
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 149

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.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 150

Afişarea unui simbol din lista de enumerare cu ajutorul


şirurilor şi a unei structuri switch.
#include "stdio.h"
#include "conio.h"

void main(void)
{
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;
}
getch();
}

ECLARAŢ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.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 151

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.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 152

Folosirea ei este recomandată mai ales pentru tipuri care se


reprezintă mai 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ăţii 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.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 153

TESTE DE CONTROL

7.1 Secvenţa de program


struct
{
int x;
float y;
}z;
scanf(”%d”,&x);

este greşită, deoarece:


a) la structura din secvenţă lipseşte numele structurii
b) în apelul funcţiei scanf(), câmpul x este tratat ca o
variabilă

7.2 Selectarea unui câmp din structură se face utilizând


simbolul:
a) .
b) ;
c) ®
d) :
e) %
7.3 Dacă p este un pointer către structura
struct numere
{
int x;
float y;
};

atunci expresia p®x este echivalentă cu:


Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 154

a) *p.x
b) (*p).x
c) *p.(x)
7.4 Atribuirea informaţiei dintr-o variabilă structură x unei alte
variabile structură y de acelaşi tip se poate face:

a) scriind y=x;
b) atribuind valorile câmpurilor structurii x câmpurilor
corespunzătoare din y

7.5 Secvenţa de program


int y;
struct num
{
char *x;
int y;
}a,*p;
y=10;
a®y=y;
p.x= ”Eroare”;

este greşită deoarece:


a) y este în acelaşi timp nume de variabilă şi de câmp
b) câmpul x nu este selectat cu ® ci cu ajutorul
operatorului punct
c) câmpul y nu este selectat cu operatorul punct, ci cu
ajutorul operatorului ®

7.6 Structura
struct persoana
{
int salariu;
struct persoana *p;
struct inform
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 155

{
long int telefon;
char *adresa;
}inf;
}pers;

este:
a) greşită, deoarece câmpul pointer p indică către
structura din care face parte
b) corectă
c) greşită, deoarece structura persoana include o altă
structură (structura inform)

7.7 Secvenţa de program


struct a
{
int x;
float z;
}w;
struct b
{
int x;
float y;
}z;
w.x=1;
z.x=2;

a) este greşită, deoarece structurile a şi b conţin un câmp


cu nume comun (câmpul x)
b) este corectă, deoarece două structuri diferite pot avea
câmpuri cu nume comune
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 156

7.8 Structura
struct a
{
int x;
float y[3][2];
}a;

a) este incorectă, deoarece conţine un câmp care este


tablou
b) este corectă, iar referirea la elementul y[0][1] se
poate face sub forma a.y[0][1].

7.9 Secvenţa de program


struct material
{
int cant;
float pu,valoare;
}maga[3];
maga[0].cant=100;

este:
a) incorectă deoarece componentele tabloului maga[5]
sunt de tip struct
b) incorectă deoarece câmpurile pu şi valoare nu sunt
declarate sub forma
float pu;
float valoare;
c) este corectă

7.10 Lungimea unei structuri se află

a) adunând lungimile truturor câmpurilor care compun


structura
b) utilizând operatorul sizeof
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 157

7.11 Structura
struct a
{
unsigned adm:1;
int nota;
}elev;

a) este greşită deoarece conţine un câmp de biţi de


lungime 1 bit
b) este corectă, deoarece o structură poate conţine atât
câmpuri obişnuite cât şi câmpuri de biţi

7.12 Structura
struct a
{
unsigned:4;
unsigned x:2;
float y:3;
};
este greşită deoarece
a) primul câmp de biţi nu are nume
b) al treilea câmp de biţi nu este de tip unsigned sau
signed

7.13 Membrii unei uniuni

a) împart aceeaşi zonă de memorie


b) au rezervate zone de memorie diferite
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 158

7.14 Operatorii de selectare . şi ® se pot folosi pentru selecţia


câmpurilor

a) numai la structuri
b) la structuri şi la uniuni
c) numai la uniuni

7.15 Secvenţa de program


union
{
float a;
int b;
}z={10};

a) este greşită deoarece nu are nume


b) este corectă iar câmpului a i se atribuie valoarea 10
c) este corectă iar câmpului b i se atribuie valoare 10

7.16 Lungimea unei uniuni se află

a) calculând maximul dintre lungimile membrilor uniunii


b) utilizând operatorul sizeof

7.17 Secvenţa de program


enum masini{Trabant,Dacia,Audi,Marcedes}masina_ta;
masina_ta=Dacia;
printf(”\n Masina ta este %s”,masina_ta);

a) este incorectă deoarece constanta simbolică Dacia


este tratată ca un şir
b) este corectă şi afişează cuvântul Dacia
c) este incorectă deoarece este ilegală atribuirea
masina_ta=Dacia;
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 159

7.18 Numele simbolice dintr-o lista de enumerare

a) se pot atribui unei variabile de tip corespunzător


b) se pot citi de la tastatură
c) se pot scrie nemijlocit
d) pot figura ca o constantă case într-o instrucţiune
switch

7.19 În secvenţa de program


enum luna_an{ianuarie,martie=3,mai};

valoarea ataşată numelui simbolic mai este:


a) 3
b) 4
c) 5
7.20 Declaraţia
typedef enum{false,true}boolean;

a) este incorectă deoarece lipseşte numele tipului


enumerare
b) este corectă şi asociază enumerării un nume nou
(boolean)
c) este corectă şi defineşte în C tipul boolean pe care
standardul ANSI C nu-l prevede.
Capitolul 7 – Structuri, uniuni, enumerări, declaraţii typedef 160

RĂSPUNSURI

7.1-b 7.2-a, c 7.3-b 7.4-a, b 7.5-b, c


7.6-b 7.7-b 7.8-b 7.9-c 7.10-b
7.11-b 7.12-b 7.13-a 7.14-b 7.15-b
7.16-b 7.17-a 7.18-a, d 7.19-b 7.20-b
154 Programarea în limbajul C

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. Acest lucru înseamnă că, spre deosebire de Pascal, nu
se poate defini o funcţie în interiorul alteia. 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.

EFINIŢIA FUNCŢIILOR

În limbajul C există două forme de definire a unei funcţii:


forma modernă şi forma clasică. Deşi standardul ANSI C
recomandă folosirea formei moderne, totuşi, din motive de
portabilitate, standardul acceptă şi forma clasică de definire a
funcţiilor. Acest lucru permite rularea unor programe vechi pe
compilatore noi sau chiar a unor programe noi pe compilatoare mai
vechi.
În versiunea modernă 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;
155 Programarea în limbajul C

· 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 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.
156 Programarea în limbajul C

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ă
nici o 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 în variantă modernă:
float maxim(float x,int y)
{
if(x<y)
return y;
else
return x;
}

void mesaj1(int x)
{
if(x)
printf(”\n Ati tastat bine!”);
157 Programarea în limbajul C

else
printf(”\n Ati tastat gresit!”);
}

void mesaj2(void)
{
printf(”\n Intrerupeti executia
programului!”);
}

Forma generală a unei definiţii clasice pentru o funcţie este:


tip nume_functie(par1,par2,...,parn)
tip par1;
tip par2;
. . . . .
tip parn;
{
instructiuni
}

Se observă că, în forma clasică, declaraţia parametrilor nu se


mai face în antetul funcţiei. Prima funcţie din exemplul precedent
poate fi rescrisă în forma clasică astfel:
float maxim(x,y)
float x;
int y;
{
if(x<y)
return y;
else
return x;
}

Spre deosebire de cazul definiţiei moderne, unde fiecare


parametru din listă este precedat de tipul său indiferent dacă există
parametri de acelaşi tip, definiţia clasică permite declararea tipului
comun al parametrilor o singură dată.
De exemplu, secvenţa de program:
minim(int x,int y,int z)
{
instructiuni
158 Programarea în limbajul C

}
se scrie echivalent în forma clasică
minim(x,y,z)
int x,y,z;
{
instructiuni
}

ECLARAREA FUNCŢIILOR, PROTOTIPURI

Orice funcţie care returnează valori de un tip diferit de date 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.
Există două modalităţi de a declara o funcţie: forma
tradiţională şi forma modernă (prototip de funcţie).
159 Programarea în limbajul C

Forma tradiţională este specifică fazei de început a evoluţiei


limbajului C. Structura unei astfel de declaraţii este următoarea:
tip nume_functie();

Programul calculează aria unui cerc.


#include "stdio.h"
#include "conio.h"

/* declaratia functiei aria */


double aria();

void main(void)
{
float r;
printf("\n Raza cercului=");
scanf("%f",&r);
printf("\n Aria cercului este %lf",aria(r));
getch();
}

/* definitia functiei aria */


double aria(float r)
{
const double pi=3.14159265;
return pi*r*r;
}

După cum se poate constata, în forma tradiţională a declaraţiei


unei funcţii lipseşte orice referire la parametrii ei.
Forma modernă de declarare a unei funcţii, numită prototipul
funcţiei, constituie o extensie a declaraţiei clasice.
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 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
160 Programarea în limbajul C

convertesc la tipul parametrilor corespunzători din prototip, înainte


de a fi plasaţi în stivă.
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.

Utilizarea prototipurilor de funcţii


#include "stdio.h"
#include "conio.h"
#include "math.h"

/* prototipul functiei */
float f(float);

void main(void)
{
float y,x;
161 Programarea în limbajul C

printf("\n x=");
scanf("%f",&x);
y=f(x)+2.5;
printf("\n y=%f",y);
getch();
}

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

PELUL FUNCŢIILOR

Apelul unei funcţii se face sub forma:


162 Programarea în limbajul C

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.
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
163 Programarea în limbajul C

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

Comutarea a două valori


#include "stdio.h"
#include "conio.h"

void comut1(int x,int y);


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

void main(void)
{
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);
getch();
}
void comut1(int x,int y)
{
int z=x;
x=y;
y=z;
}

void comut2(int *x,int *y)


{
int z=*x;
*x=*y;
*y=z;
}
164 Programarea în limbajul C

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.
165 Programarea în limbajul C

RANSFERUL 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,
aşa cum am văzut în Capitolul 6, 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.
Exemple:

Calcularea sumei elementelor unei matrici a(4x3).


Varianta 1 – parametrul formal este o matrice de dimensiune 4x3
#include "stdio.h"
#include "conio.h"

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

void main(void)
{
int a[4][3],i,j,s;

/* citirea elementelor matricii a */


for (i=0;i<4;i++)
166 Programarea în limbajul C

for (j=0;j<3;j++)
{
printf("\n a[%i][%i]=",i,j);
scanf("%d",&a[i][j]);
}
/*
calculul sumei elementelor matricii a
folosind functia suma()
*/
s=suma(a);
/* afisarea sumei */
printf("\n Suma=%d",s);
getch();
}

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.

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"
#include "conio.h"

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

void main(void)
{
int a[4][3],i,j,m,n,s;

/* citirea valorilor m si n */
167 Programarea în limbajul C

do
{
printf("\n m=");
scanf("%i",&m);
printf("\n n=");
scanf("%i",&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[%i][%i]=",i,j);
scanf("%d",&a[i][j]);
}

/*
calculul sumei elementelor matricii a
folosind functia suma()

*/
s=suma(a,m,n );

/* afisarea sumei */
printf("\n suma=%d",s);
getch();
}

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)
168 Programarea în limbajul C

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
suma(int x[][],int m,int n)

este greşit.

Calcularea sumei elementelor unei matrici a(4x3).


Varianta 3 – parametrul formal este un pointer dublu.
#include "stdio.h"
#include "conio.h"

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

void main(void)
{
int a[4][3],i,j,m,n,s;

/* citirea valorilor m si n */
do
{
printf("\n m=");
scanf("%i",&m);
printf("\n n=");
scanf("%i",&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[%i][%i]=",i,j);
scanf("%d",&a[i][j]);
}

/*
calculul sumei elementelor matricii a
folosind functia suma()
*/
169 Programarea în limbajul C

s=suma(a,m,n);
/* afisarea sumei */
printf("\n Suma=%d",s);
getch();
}

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*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 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*i+j]
Introducând variabila auxiliară z, funcţia suma() se poate scrie
mai clar sub forma:
suma(int **y,int m,int n)
{
int i,j,s=0;
int *z=(int*)y;
for (i=0;i<m;i++)
for(j=0;j<n;j++)
s+=z[n*i+j];
return s;
}
Evident, instrucţiunea
s+=z[n*i+j];
se putea scrie şi sub forma
s+=*(z+n*i+j);
170 Programarea în limbajul C

În locul pointerului dublu y se poate folosi un pointer către


tablou. În acest caz prototipul funcţiei se va scrie sub forma
suma(int(*y)[],int m,int n);
iar funcţia va fi
suma(int(*y)[],int m,int n);
{
int i,j,s=0;
int *z=(int*)y;
for (i=0;i<m;i++)
for(j=0;j<n;j++)
s+=z[n*i+j];
return s;
}

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.

Ordonarea crescătoare a unui vector utilizând algoritmul de


sortare prin selecţie.
#include "stdio.h"
#include "conio.h"

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


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

void main(void)
{
int x[20],n;
do
{
printf("\n n=");
scanf("%d",&n);
}
while (n<1 || n>20);
citv(x,n);
sortv(x,n);
171 Programarea în limbajul C

afisv(x,n);
getch();
}
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];
x[i]=min;
}
}

void citv(int x[],int n)


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

void afisv(int x[],int n)


{
int i;
for (i=0;i<n;i++)
printf("\n x[%i]=%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ă.
172 Programarea în limbajul C

Testarea parităţii unei componente oarecare a tabloului a


#include "stdio.h"
#include "conio.h"

void par_imp(int x,int i);

void main (void)


{
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);
getch();
}

void par_imp(int x,int i)


{
if (x%2)
printf("\n Componenta %i este impara",i);
else
printf("\n Componenta %i este para",i);
}

RANSFERUL STRUCTURILOR CĂTRE FUNCŢII

Spre deosebire de tablouri, structurile se pot transmite în


întregime unei funcţii dacă sunt declarate drept parametrii 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.
173 Programarea în limbajul C

După cum se ştie, se pot face, prin intermediul funcţiei, modificări


asupra membrilor structurii.

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"
#include "conio.h"

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

void afis(CATALOG y);

void main(void)
{
CATALOG x={"Ion",10};
afis(x);
getch();
}

void afis(CATALOG y)
{
printf("\n %s are nota %i",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().
Despre caracterul global sau local al declaraţiilor de variabile se
vor prezenta detalii în Capitolul 9.
174 Programarea în limbajul C

Exemplul 8.10
Se iniţializează o variabilă structură în main() şi se face
modificarea unui câmp de-al ei în funcţia modi(). Transmiterea se
face prin referinţă.
#include "stdio.h"
#include "conio.h"

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

void modi(CATALOG *y);


void afis(CATALOG y);

void main(void)
{
CATALOG x={"Vasile",8};
printf("\n Mesaj initial: ");
afis(x);
modi(&x);
printf("\n Mesaj modificat: ");
afis(x);
getch();
}

void modi(CATALOG *y)


{
char *nume_nou;
printf("\n Modificati numele?(D/N)");
if (toupper(getche())=='D')
{
printf("\n Nume Nou=");
scanf("%s",nume_nou);
strcpy(y->nume,nume_nou);
}
printf("\n Modificati nota?(D/N)");
if (toupper(getche())=='D')
{
printf("\n Nota Noua=");
scanf("%i",&y->nota);
}
}
175 Programarea în limbajul C

void afis(CATALOG y)
{
printf("%s are nota=%i",y.nume,y.nota);
}

Observaţii:
· Funcţia toupper() are forma generală
int toupper(int c);
şi are ca efect convertirea literei c în literă mare, dacă e cazul.
· Funcţia care converteşte o literă mare în literă mică este
tolower() şi are forma generală:
int tolower(int c);
· În situaţia în care argumentul nu este literă, cele două funcţii
lasă caracterul c neschimbat.
Oricare din membrii unei structuri poate fi transmis unei
funcţii, fie prin valoare, fie prin referinţă.

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"
#include "conio.h"

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

void afis_val(int y);


void afis_ref(int *y);

void main(void)
176 Programarea în limbajul C

{
CATALOG x={"Gheorghe",7};
afis_val(x.nota);
afis_ref(&x.nota);
getch();
}
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);
}

RGUMENTELE FUNCŢIEI main()

De obicei, funcţia main() este folosită fără argumente în


programe. În general, acest lucru se specifică folosind cuvântul cheie
void. 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..
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!
177 Programarea în limbajul C

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


#include ”stdio.h”
#include ”conio.h”

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


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

OINTERI 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);
178 Programarea în limbajul C

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:
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”.
179 Programarea în limbajul C

Programul ilustrează folosirea pointerilor către funcţii


#include "stdio.h"
#include "conio.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);

void main(void)
{
int i,a=8,b=2;

int (*t[])(int,int)={adunare,scadere,inmultire,
impartire};
char *nume_t[]={"adunare","scadere","inmultire",
"impartire"};
for (i=0;i<4;i++)
{
printf("\n S-a selectat functia %s",nume_t[i]);
printf("\n Rezultatul este %d",(*t[i])(a,b));
}
getch();
}

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;
}
180 Programarea în limbajul C

Cea de-a doua posibilitate se referă la transmiterea drept


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

Programul ilustrează folosirea pointerilor către funcţii


#include "stdio.h"
#include "conio.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));

void main(void)
{
float x;
int a=9,b=4;
printf("\nx=");
scanf("%f",&x);
if (x>0)
f(x,a,b,sum);
else
f(x,a,b,dif);
getch();
}

int sum(int a,int b)


{
return a+b;
}

int dif(int a,int b)


181 Programarea în limbajul C

{
return a-b;
}

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


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

UNCŢ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 tip
f(lista_declaratii_parametri) g(lista_declaratii_parametri)
{ {
apel g() apel f()
} }
182 Programarea în limbajul C

Î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 8.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 8.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
x
V`r f x

Figura 8.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.
183 Programarea în limbajul C

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 apelantă, valorile variabilelor locale şi ale
parametrilor) şi se alocă spaţiu pentru parametrii actuali şi variabilele
locale ale funcţiei apelate.

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ţiile apelante ş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)
184 Programarea în limbajul C

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

ä 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 8.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 8.4 Stările succesive ale stivei după ieşirea din autoapel
185 Programarea în limbajul C

Programul constituie un exemplu simplu de utilizare a


recursivităţii
#include "stdio.h"
#include "conio.h"

void g(int x);

void main(void)

{
int x=6;
g(x);
getch();
}

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:
186 Programarea în limbajul C

main() g(6) g(4) g(2) g(0)

{
int x=6 if(6) if(4) if(2) if(0)
g(6); { { { iesire
din
getch(); 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 8.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ă
187 Programarea în limbajul C

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 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
1
a b c

n-1
n <=> 1

n
188 Programarea în limbajul C

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

Programul implementează funcţia Hanoi (n, a, b, c)


#include "stdio.h"
#include "conio.h"

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

void main(void)

{
int n;
clrscr();
printf("\n Numarul de discuri, n=");
scanf("%i",&n);

printf("\n Solutia este ");


hanoi(n,'a','b','c');
getch();
}

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);
}
}
189 Programarea în limbajul C

TESTE DE CONTROL

8.1 Definiţia unei funcţii C se poate face

a) numai în formă modernă


b) atât în formă clasică cât şi în formă modernă
8.2 Definiţia modernă
float min(int a,int b)
{
instructiuni
}
este echivalentă în forma clasică cu secvenţa:
a) float min(int a,b)
{
instructiuni
}
b) float min(a,b)
int a,b;
{
instructiuni
}
c) float min()
{
int a,b
instructiuni
}
d) float min(a,b)
{
int a,b;
instructiuni
}
8.3 O funcţie declarată fără tip întoarce

a) o valoare de tip int


b) o valoare de tip float
c) o valoare de tip void
190 Programarea în limbajul C

8.4 O funcţie C nu poate să întoarcă

a) o valoare de tip tablou


b) o valoare de tip struct
c) un pointer de tip void
8.5 Lista declaraţii parametrii din prototipul unei funcţii

a) poate să lipsească complet, dar trebuie păstrate parantezele


rotunde
b) poate să lipsească şi acest lucru să fie semnalizat prin
cuvântul void
c) poate să conţină doar numele tipurilor de date ale
parametrilor
d) poate să conţină doar numele parametrilor
8.6 Funcţia
float f(int x)
{
if(x)
return -1;
else
return 1;
}

a) este incorect scrisă deoarece are două instrucţiuni return


b) este corect scrisă şi întoarce la apel valorile -1 sau 1
convertite la tipul float
c) este incorect scrisă deoarece nu are semnul ; după
declaratorul float f(int x)
8.7 Pentru a lucra corect, funcţia
f(float x)
{
float y;
y=x;
}
191 Programarea în limbajul C

a) nu trebuie să conţină o instrucţiune return, deoarece este


implicit de tip void
b) trebuie să conţină o instrucţiune return
c) trebuie să conţină o instrucţiune de forma return
expresie
8.8 Funcţia
void f(int x,int *y)
{
*y=x;
}
a) este scrisă incorect deoarece corpul funcţiei nu conţine o
instrucţiune return de întorcere în funcţia apelantă
b) este scrisă corect iar întorcerea în funcţia apelantă este
determinată de întâlnirea acoladei }
8.9 Funcţia
void f(float x)
{
float y=x;
return y*y;
}
a) este greşit scrisă, deoarece fiind de tip void nu este necesar
să conţină o instrucţiune return
b) este corect scrisă, deoarece instrucţiunea return este
obligatorie
8.10 Declaraţia unei funcţii înainte de apelare este obligatorie dacă
funcţia este:

a) de tip int
b) de tip float
c) de orice tip diferit de tipul int
8.11 În declaraţia unei funcţii

a) prezenţa listei de declaraţii parametrii este opţională


192 Programarea în limbajul C

b) dacă lista declaraţii parametrii este vidă trebuie scris


obligatoriu cuvântul void între parantezele rotunde
c) dacă lista declaraţii parametrii este vidă este recomandabil
să se scrie cuvântul void între parantezele rotunde
d) numele de parametri pot lipsi, dar tipurile de date la care
aparţin parametrii trebuie să existe obligatoriu
8.12 O declaraţie de forma
tip nume_functie();
reprezintă
a) forma tradiţională de declaraţie a unei funcţii
b) forma modernă de declaraţie a unei funcţii (prototipul
funcţiei)
8.13 Secvenţa de program
float f(int a,float b)
poate fi considerată
a) declaraţia (prototipul) unei funcţiei f()
b) antetul din definiţia funcţiei f()
c) atât prototip cât şi antet de funcţie
8.14 Numărul parametrilor din lista de parametri actuali ai unei
funcţii

a) trebuie să fie obligatoriu fix


b) este totdeauna variabil
c) poate fi atât fix cât şi variabil
8.15 Prototipurile sunt importante deoarece

a) permit compilatorului să verifice corectitudinea apelului


unei funcţii
b) contribuie la creşterea lizibilităţii codului sursă
8.16 Corespondenţa dintre parametri actuali şi formali se poate face

a) la întâmplare
193 Programarea în limbajul C

b) poziţional
8.17 Parametrii actuali şi formali care au aceeaşi poziţie în lista de
parametri trebuie să aibă obligatoriu
a) acelaşi tip
b) tipuri compatibile
8.18 Transferul informaţiei între funcţia apelantă şi funcţia apelată se
poate face
a) numai prin valoare
b) numai prin referinţă
c) şi prin valoare şi prin referinţă
8.19 Transferul prin valoare constă în

a) copierea valorii parametrului actual în zona de memorie a


parametrului formal corespunzător, în momentul efectuării
apelului
b) transmiterea adresei parametrului actual parametrului formal
corespunzător care trebuie să fie un pointer
8.20 Secvenţa de program
void f(int x);

void main(void)
{
int x=7;
f(x);
printf(”\nx= %d”,x);
}

void f(int x)
{
x=10;
}
a) este greşită, deoarece parametrul formal şi actual au acelaşi
nume
b) este corectă şi afişează x=10
c) este corectă şi afişează x=7
194 Programarea în limbajul C

8.21 Secvenţa de program


void f(int *x);

void main(void)
{
int x=7;
f(&x);
printf(”\n x=%d”,x);
}

void f(int *x)


{
*x=10;
}
a) este greşită, deoarece parametrului formal i se transmite o
adresă şi nu o valoare
b) este corectă şi afişează x=7
c) este corectă şi afişează x=10
8.22 Secvenţa de program
s(int x[2][3]);

void main(void)
{
int y[2][4]={1,2,3,4,5,6,7,8},sum;
sum=s(y);
printf(”\n Suma=%d”,sum);
}

s(int x[2][3])
{
int i,sum=0;
for(i=0;i<2;i++)
for(j=0;j<3;j++)
sum+=x[i][j];
return sum;
}
a) este corectă şi afişează suma=24
b) este corectă şi afişează suma=21
c) este incorectă, deoarece parametrul formal x =i parametrul
formal y trebuie să aibă aceeaşi dimensiune
195 Programarea în limbajul C

8.23 Secvenţa de program


s(int x[],int m);

void main(void)
{
int y[3]={1,2,3},m=3,suma;
suma=s(y,m);
printf(”\n Suma=%d”,sum);
}

s(int x[],int m)
{
int i,sum=0;
for(i=0;i<m;i++)
sum+=x[i];
return sum;
}
a) este greşită, deoarece parametrul formal x nu are precizată
dimensiunea
b) este corectă, deoarece la apel se transmite numele tabloului
(care este pointer către prima componentă a tabloului) şi
dimensiunea sa
196 Programarea în limbajul C

8.24 Secvenţa de program


void afis(int x[][],int m,int n);

void main(void)
{
int y[2][3]={1,2,3,4,5,6},m=1,n=3;
afis(y,m,n);
}

void afis(int x[][],int m,int n)


{
int i,j;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
printf(”\n x[%i][%i]=%d”,i,j,x[i][j]);
}
a) este incorectă, deoarece parametrul tablou x nu poate avea
prima dimensiune neprecizată
b) este incorectă deoarece parametrul tablou x nu poate avea
atât prima cât şi a doua dimensiune neprecizată
c) este corectă
8.25 Secvenţa de program
void f(int x);

void main(void)
{
int y[10];
. . . . . .
f(y[2]);
}

void f(int x)
{
int z;
z=x;
. . . . . .
}
197 Programarea în limbajul C

a) este corectă
b) este greşită, deoarece parametrul actual al funcţiei f() este o
componentă de tablou
8.26 Secvenţa de program
void f(int y[2]);

void main(void)
{
int x[2]={3,4};
f(x);
printf(”\n x[0]=%d,x[1]=%d”,x[0],x[1]);
}

void f(int y[2])


{
y[0]=y[1];
}
a) afişează x[0]=3,x[1]=4
b) afişează x[0]=4,x[1]=4
c) este greşită

8.27 În limbajul C tablourile pot fi transmise unei funcţii


a) numai prin referinţă
b) numai prin valoare
c) şi prin valoare şi prin referinţă
8.28 Structurile pot fi transmise unei funcţii

a) numai prin referinţă


b) numai prin valoare
c) şi prin valoare şi prin referinţă
8.29 Un membru al unei structuri poate fi transmis unei funcţii

a) numai prin referinţă


b) numai prin valoare
c) şi prin valoare şi prin referinţă
d) numai o dată cu întreaga structură
198 Programarea în limbajul C

8.30 Declaraţiile
float (*p)(float,int);
şi
float *p (float,int);
a) sunt echivalente
b) nu sunt echivalente: prima reprezintă un pointer către o
funcţie de tip float, care are un parametru de tip float şi unul
de tip int, iar a doua reprezintă prototipul funcţiei p() de tip
pointer către float, care are un parametru de tip float şi unul
de tip int
c) sunt corecte
8.31 Secvenţa de program
void f(int x);

void main(void)
{
int x=7;
f(x);
}

void f(int x)
{
printf(”%3i”,x);
if(x)
f(x-1);
}
afişează
a) 7 6 5 4 3 2 1 0
b) 0 1 2 3 4 5 6 7
8.32 Secvenţa de program
void f(int x);

void main(void)
{
int x=7;
f(x);
}
199 Programarea în limbajul C

void f(int x)
{
if(x)
f(x-1);
printf(”%3i”,x);
}
afişează
a) 7 6 5 4 3 2 1 0
b) 0 1 2 3 4 5 6 7
8.33 Secvenţa de program
void f(int x);

void main(void)
{
int x=7;
f(x);
}

void f(int x)
{
f(x-1);
printf(”%3i”,x);
}
a) afişează 7 6 5 4 3 2 1 0
b) afişează 0 1 2 3 4 5 6 7
c) apelează de o infinitate de ori funcţia f()
8.34 Secvenţa de program
void f(void)
{
float x=23.5;
printf(”\n x=%f”,x);
}

void main(void)
{
f();
printf(”\n x=%f”,x);
}
200 Programarea în limbajul C

a) afişează x=23.500000
b) afişează x=23.500000
x=23.500000
c) este greşită, deoarece în main() se utilizează variabila x care
nu este declarată aici

RĂSPUNSURI

8.1-b 8.2-b 8.3-a 8.4-a 8.5-a, b, c


8.6-b 8.7-c 8.8-b 8.9-a 8.10-b, c
8.11-a, c, d 8.12-a 8.13-b 8.14-c 8.15-a, b
8.16-b 8.17-b 8.18-c 8.19-a 8.20-c
8.21-c 8.22-c 8.23-b 8.24-b 8.25-a
8.26-b 8.27-a 8.28-c 8.29-c 8.30-b, c
8.31-a 8.32-b 8.33-c 8.34-c
Capitolul 9 – Clase de memorare pentru variabile 200

Aşa cum s-a văzut, tipul variabilei determină semnificaţia


valorilor pe care la poate lua variabila şi operaţiile permise
asupra sa.
Clasa de memorare a unei variabile indică locul unde i se
va aloca spaţiu şi durata de viaţă a zonei de memorie asociate.
Declaraţia completă a unei variabile are forma:

specificator_clasa tip variabila;

PECIFICATORII CLASELOR DE MEMORARE

Există patru specificatori pentru clase de memorare: auto,


register, static, extern respectiv pentru clasele automatic,
register, static, extern.
Atunci când declaraţia variabilei nu conţine în mod explicit
clase de memorare, aceasta este stabilită implicit pe baza
locului unde este declarată variabila.
Variabilele din clasa automatic sunt declarate în interiorul
funcţiilor şi nu sunt recunoscute în afara blocului de declarare
(sunt locale blocului respectiv). Durata de viaţă a unei astfel de
variabile coincide cu timpul de execuţie al blocului; ea este
creată la intrarea în bloc şi distrusă la ieşirea din bloc. Locul de
memorare al variabilelor auto este stiva; la începutul execuţiei
unui bloc ele sunt create pe stivă, iar la sfârşitul execuţiei
distruse prin descărcarea stivei. În mod uzual locul de
declarare al variabilelor locale este la începutul blocului format
Capitolul 9 – Clase de memorare pentru variabile 201

din corpul întregii funcţii, însă, există şi posibilitatea de a


declara variabile în blocuri strict incluse în corpul funcţiei.
Variabilele declarate în interiorul unei funcţii care nu au
specificată clasa de memorare sunt considerate în mod implicit
ca aparţinând clasei automatic.

Funcţia citeste() din programul următor citeşte un număr


întreg x dacă n=1 şi n numere întregi (n<=100) pe care le
depune într-un vector x[100] dacă n>1
#include "stdio.h"
#include "conio.h"

void citeste(int n);

void main(void)
{
int n;
printf("\n n=");
scanf("%i",&n);
citeste(n);
getch();
}

void citeste(int n)
{
if (n==1)
{
int x;
printf("\n x=");
scanf("%i",&x);
}
else
{
int x[100],i;
for (i=0;i<n;i++)
{
printf("\n x[%i]= ",i);
scanf("%i",&x[i]);
Capitolul 9 – Clase de memorare pentru variabile 202

}
}
printf("\n S-au alocat %i octeti
",sizeof(int)*n);
}

Se observă că alocarea memoriei atât pentru întregul x


cât şi pentru vectorul x se face condiţionat. Avantajul declarării
într-un bloc condiţional constă în faptul că se va aloca memorie
numai dacă este nevoie. Folosirea aceluiaşi nume x pentru
cele două zone alocate este posibilă deoarece alocările se fac
în blocuri diferite.
Parametrii formali ai unei funcţii aparţin de asemenea
clasei automatic. Ei se comportă ca orice variabilă locală
funcţiei respective având în plus posibilitatea de a primi valorile
corespunzătoare ale parametrilor actuali.
O variabilă din clasa automatic sau register poate fi
iniţializată; iniţializarea se face la fiecare intrare în bloc.
Specificatorul de clasă register este indicat a se aplica
variabilelor locale şi parametrilor formali ai unei funcţii care
sunt frecvent utilizaţi de program. În mod normal, o variabilă
care aparţine clasei register ar trebui să fie memorată în
regiştrii unităţii centrale şi nu în memoria RAM, fapt de natură
să îmbunătăţească substanţial viteza operaţiilor făcute asupra
sa. Totuşi, este evident că, deşi putem declara oricâte variabile
cu specificatorul register, doar o parte din ele vor avea loc în
Capitolul 9 – Clase de memorare pentru variabile 203

regiştrii unităţii centrale. De asemenea, în general,


specificatorul are efect doar asupra variabilelor de tip char sau
int. Din motive de portabilitate, cele care sunt în plus vor fi
tratate normal. Datorită specificului lor (sunt folosite intens,
sunt de obicei de tip întreg) variabilele contor se pretează
destul de bine, pentru a fi declarate cu specificatorul register.
Unei variabile din clasa register nu i se poate aplica operatorul
de luare a adresei & (nu are adresă proprie). Asemănător cu
variabilele din clasa automatic, variabilele din clasa register
sunt create la intrarea în bloc. Variabilele auto şi register care
nu sunt iniţializate explicit conţin valori iniţiale nedefinite.
Există situaţii când este necesar ca o variabilă locală să-
şi păstreze valoarea de la un apel la altul al funcţiei în care
este declarată. În acest caz variabila trebuie să fie declarată cu
specificatorul static. Ea este recunoscută, de asemenea,
numai în interiorul funcţiei unde a fost declarată, doar că
memorarea sa se va face în locaţii fixe de memorie, iar durata
sa de viaţă coincide cu durata de execuţie a programului. O
variabilă declarată static poate fi iniţializată, dar iniţializarea se
face o singură dată, la compilare.
Capitolul 9 – Clase de memorare pentru variabile 204

Calculul sumei primelor n numere impare folosind o variabilă


declarată static
#include "stdio.h"
#include "conio.h"

suma(void);

void main(void)
{
int n,i,y;
printf("\n Numarul de elemente din suma=");
scanf("%i",&n);
for (i=1,y=1;i<n;i++)
y+=suma();
printf("\n Suma primelor %i numere impare este
%i",n,y);
getch();
}
suma(void)
{
static int s=1;
s+=2;
return s;
}

Avantajul folosirii variabilelor locale constă în


imposibilitatea de a modifica o astfel de variabilă în afara
blocului unde a fost declarată. Folosirea în plus a
specificatorului static permite crearea unor funcţii
Capitolul 9 – Clase de memorare pentru variabile 205

independente (vezi exemplul funcţiei suma() de mai sus) ce pot


fi inserate, fără probleme, în biblioteci.
Alternativa la folosirea specificatorului static pentru a
păstra valoarea sumelor parţiale între două apeluri era
folosirea unei variabile globale.
O variabilă globală se declară totdeauna în afara oricărei
funcţii (înainte de prima sa utilizare, dar cel mai frecvent la
începutul programului). Ea poate fi folosită în orice funcţie din
program şi are o adresă fixă de memorie. Drept exemplu
considerăm programul anterior, uşor modificat.

Se observă că valorile intermediare ale sumei calculate


sunt memorate succesiv în variabila globală s, unde sunt
păstrate de la un apel la altul al funcţiei suma(). Folosirea
variabilelor globale este recomandată în cazul în care funcţiile
care le folosesc sunt în număr mare. Ele pot servi ca valori de

Calculul sumei primelor n numere impare folosind o variabilă


globală
#include "stdio.h"
#include "conio.h"

/* variabila globala s */
int s=1;
suma(void); Capitolul 9 – Clase de memorare pentru variabile 206

void main(void)
{
int n,i,y;
printf("\n Numarul de elemente din suma=");
scanf("%i",&n);
for (i=1,y=1;i<n;i++)
y+=suma();
printf("\n Suma primelor %i numere impare
este %i",n,y);
getch();
}

suma(void)
{
s+=2;
return s;
}

comunicare între funcţii. Deoarece pot fi folosite de orice


funcţie, există însă pericolul să fie alterate accidental. Dacă la
acest lucru adăugăm faptul că variabilele globale ocupă
memorie pe toată durată execu\iei programului, chiar şi după
ce nu mai sunt necesare, tragem concluzia că, în general, nu
se va prefera o variabilă globală în locul unei variabile locale.
Variabilele globale afectează aspectul de sine stătător al unei
funcţii: funcţia va face referire nu numai la variabile declarate
în corpul său, ci şi la variabile declarate în afara sa.
Variabilele globale fac parte implicit din clasa extern.
Există posibilitatea ca un program C să fie alcătuit din funcţii
editate în fişiere separate (program multifişier).
Capitolul 9 – Clase de memorare pentru variabile 207

Ca o variabilă globală declarată într-un fişier să poată să fie


recunoscută şi în funcţiile celorlalte fişiere trebuie ca în
acestea din urmă să fie folosit în mod explicit, la declarare,
specificatorul extern. În felul acesta se evită alocarea de
memorie pentru aceeaşi variabilă în fiecare fişier, fapt ce ar
produce eroare la linkeditarea modulelor de program. O
variabilă globală poate fi iniţializată, iar iniţializarea ei trebuie
făcută o singură dată şi anume în fişierul în care apare
declarată fără specificatorul extern.
Pentru exemplificare să transformăm programul anterior
într-un program multifişier presupunând că funcţia main() va fi
editată în fişierul princ.c, iar funcţia suma() în fişierul sum.c.

Exemplu de program multifişier


/* fisierul princ.c */

#include "stdio.h"
#include "conio.h"

/* variabila globala s */
int s=1;

suma(void);

void main(void)
{
int n,i,y;
printf("\n Numarul de elemente din suma=");
scanf("%i",&n);
for (i=1,y=1;i<n;i++)
Capitolul 9 – Clase de memorare pentru variabile 208

y+=suma();
printf("\n Suma primelor %i numere impare este
%i",n,y);
getch();
}
/* fisierul sum.c */
/* variabila globala s */
extern int s;

suma(void)
{
s+=2;
return s;
}

Se observă că variabila globală s a fost definită (declarată


şi iniţializată) în fişierul princ.c, unde i se alocă şi memorie, iar
în fişierul sum.c a fost declarată folosind specificatorul extern
(fără a i se mai aloca memorie).
O variabilă externă declarată într-un program multifişer
poate fi recunoscută doar de fişierul în care a fost declarată
(ascunsă) dacă i se aplică specificatorul static. Avantajul este
că funcţiile din celelalte fişiere nu pot afecta accidental
valoarea unei astfel de variabile. Ca o consecinţă, declararea
în două fişiere distincte a aceleiaşi variabile externe cu
specificatorul static va avea ca efect obţinerea a două entităţi
distincte: modificarea uneia dintre ele nu va afecta cealaltă
entitate.
Pentru a exemplifica efectul specificatorului static asupra
unei variabile globale modificăm problema anterioară, cerând
ca funcţia suma() să poată calcula la cerere fie suma primelor
Capitolul 9 – Clase de memorare pentru variabile 209

n numere naturale pare, fie suma primelor n numere naturale

impare. În acest scop, adăugăm o funcţie de iniţializare a


variabilei s, init(). Funcţiile init() şi suma() vor face parte
dintr-un fişier distinct sum_init.c, iar variabila globală s va fi
declarată static în scopul de a fi recunoscută doar în aceste
două funcţii. Acest lucru permite definirea în fişierul princ.h a
variabilei globale s fără nici o legătură cu variabila s declarată
în fişierul sum_init.c.

Exemplul 9.5

Programul ilustrează efectul specificatorului static asupra


unei variabile globale
/* fisierul princ.c */

#include "stdio.h"
#include "conio.h"

/* variabila globala s */
int s=0;

suma(void);
void init(int);

void main(void)
{
int n,i;
printf("\n Doriti suma primelor numere pare(P)
sau impare(I)? ");
if (toupper(getche()) == 'P' )
init(-2);
else
init(-1);
printf("\n Numarul de termeni ai sumei=");
scanf("%i",&n);
for (i=0;i<n;i++)
Capitolul 9 – Clase de memorare pentru variabile 210

s+=suma();
printf("\n Suma numerelor este %i",s);
getch();
}

/* fisier sum_init.c */

static int s;

suma(void)
{
s+=2;
return s;
}

void init(int v_init)


{
s=v_init;
}

Variabilele locale sau globale declarate static şi


neiniţializate explicit sunt iniţializate implicit cu valoarea 0.
Dacă o variabilă externă şi o variabilă locală (sau
parametru formal) au aceleaşi nume, în funcţia unde este
declarată variabila locală referirea la numele comun are în
vedere variabila locală.

Programul afişează valoarea variabilei x, declarată local,


adică x=1.42

#include "stdio.h"
#include "conio.h"

/* variabila globala x */
int x=1;
Capitolul 9 – Clase de memorare pentru variabile 211

void main(void)
{
/* variabila locala x */
float x=1.42;
printf("\n x=%.2f",x);
getch();
}

În general, declaraţiile făcute într-un bloc asupra unei


variabile sunt mai puternice decât cele făcute asupra aceleiaşi
variabile în exterior, în sensul că referirea la variabilă în cadrul
blocului vizează declaraţia locală blocului.

Programul ilustrează faptul că declaraţia unei variabile într-un


bloc interior este mai puternică decât declaraţia sa într-un
bloc exterior: se va afişa succesiv
x=4.13
x=3
#include "stdio.h"

void main(void)
{
float x=4.13;
printf("\n x=%.2f",x);
{
int x=3;
printf("\n x=%i",x);
}
getch();
}
Capitolul 9 – Clase de memorare pentru variabile 212

O funcţie poate să aparţină clasei extern sau static. Dacă


specificatorii extern sau static sunt omişi, funcţia se consideră
implicit în clasa extern.
O funcţie care aparţine clasei extern este recunoscută (vizibilă)
în fişierul unde este definită sau declarată, ca şi în toate
fişierele care alcătuiesc programul.
O funcţie declarată static este recunoscută doar în fişierul
unde este declarată, nu şi în celelalte fişiere ale programului.
Într-un program multifişier, pot exista două funcţii cu acelaşi
nume cu condiţia să fie declarate static în fişiere diferite.
Capitolul 9 – Clase de memorare pentru variabile 213

TESTE DE CONTROL

9.1 Locul de memorare al variabilei auto este:

a) stiva
b) memoria heap

9.2 Clasa de memorare auto

a) trebuie declarată explicit întotdeauna


b) este considerată implicit pentru variabilele care sunt
declarate în interiorul unei funcţii şi care nu au
specificată clasa de memorare

9.3 Apelul funcţiei


void f(void)
{
float i=1;
{
int i=2;
printf(”\n %d”,i);
}
}

a) produce eroare, deoarece variabila i este declarată


atât de tip float cât şi de tip int
b) are ca efect afişarea valorii 2
c) are ca efect afişarea valorii 1
Capitolul 9 – Clase de memorare pentru variabile 214

9.4 Secvenţa de program


register int x=10;
int *p;
p=&x;

a) este corectă, deoarece atribuie unui pointer de tip int


adresa unei variabile de tip int
b) este incorectă, deoarece variabilelor care au clasa de
memorare register nu li se poate aplica operatorul &

9.5 Care din afirmaţiile următoare sunt adevărate?

a) iniţializarea unei variabile din clasa auto sau register


se face de fiecare dată când se execută blocul în care
sunt declarate
b) iniţializarea unei variabile din clasa static se face o
singură dată şi anume la compilare
c) afirmaţiile a) şi b) sunt adevărate

9.6 O variabilă din clasa de memorare static

a) este memorată în stivă


b) este memorată în locaţii cu adrese fixe
c) este recunoscută doar în interiorul funcţiei unde a fost
declarată
d) îşi păstrează valoarea de la un apel la altul al funcţiei

9.7 O variabilă este numită globală atunci când

a) este declarată în afara funcţiei main()


b) este declarată în afara oricărei funcţii
Capitolul 9 – Clase de memorare pentru variabile 215

9.8 Variabilele globale fac parte implicit din clasa

a) register
b) auto
c) extern

9.9 Care din afirmaţiile următoare sunt adevărate?

a) o variabilă globală declarată într-un fişier al unui


program multifişier este recunoscută automat şi în
funcţiile celorlalte fişiere
b) într-un program multifişier, o variabilă globală trebuie
declarată identic în toate fişierele programului
c) pentru a fi recunoscută de toate funcţiile programului
multifişier, o variabilă globală trebuie declarată obişnuit
într-unul din fişiere, iar în celelalte declaraţii trebuie să
fie precedată de specificatorul extern
d) precedată de specificatorul static, o variabilă globală
este recunoscută doar de funcţiile fişierului în care
este declarată

RĂSPUNSURI

9.1-a 9.2-b 9.3-b 9.4-b 9.5-a, b, c


9.6-b, c, d 9.7-b 9.8-c 9.9-c, d
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 214

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.
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 215

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


Capitolul 10 – Funcţii pentru prelucrarea fişierelor 216

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.
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);
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 217

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

UNCŢ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 (de fapt, din motive istorice, un întreg cu octetul
de rang superior egal cu zero) 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 (de fapt, din motive istorice, un întreg cu octetul de rang
superior zero) pe care-l returnează dacă operaţia a avut succes. În caz
de eroare se va returna EOF.

Programul creează un fişier de caractere pe disc, având numele


dat ca argument în linia de comandă; fişierul va fi citit caracter cu
caracter, iar caracterele diferite de cifrele zecimale 0,1,2,...,9
vor fi afişate.
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 218

#include "stdio.h"
#include "conio.h"

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


{
FILE *f;
char ch;
if (argc!=2)
{
printf("\n Numele fisierului nu e pe linia
de comanda");
exit(1);
}

/* deschiderea fisierului pentru scriere */


if ( (f=fopen(argv[1],"w")) == NULL)
{
printf("\n Eroare la deschidere fisier");
exit(1);
}

/* scrierea fisierului */
printf("\n Tastati un sir de caractere! \n");
while ( (ch=getchar()) != '\n')
putc(ch,f);
fclose(f);
/* Deschiderea fisierului pentru citire */
if ( !(f=fopen(argv[1],"r")) )
{
puts("\n Nu se poate deschide fisierul");
exit(1);
}

/* citirea din fisier si afisarea caracterelor


diferite de cifre zecimale */
printf("\n Sirul fara cifre zecimale este:\n");
while ( (ch=fgetc(f)) != EOF)
{
if(ch<'0' || ch>'9')
putchar(ch);
}
fclose(f);
}
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 219

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

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"
#include "conio.h"

void main(void)
{
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: ");
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 220

scanf("%i",&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");
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 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);
}
getch();
}
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 221

UNCŢ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.
Funcţia fprintf() returnează numărul de caractere scrise efectiv
sau o valoare negativă în caz de insucces.

Programul creează fişierul formatat pe disc având numele


”formtext” 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 "conio.h"
#include "string.h"

void main(void)
{
FILE *f;
int n,i;
typedef struct catalog
{
char nume[15];
char prenume[20];
short nota;
}struc;
struc x;
if ( !(f=fopen("grupa","w")) )
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 222

{
printf("\n Nu se poate deschide fisierul");
exit(1);
}
printf("\n Introduceti numarul de studenti din grupa
");
scanf("%i",&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("%i",&x.nota);
fprintf(f,"%s %s %i",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++)
{
fscanf(f,"%s %s %i",x.nume,x.prenume,&x.nota);
printf("\n%s %s %i",x.nume,x.prenume,x.nota);
}
getch();
}

Observaţie. Funcţia de citire scanf() este echivalentă cu


fscanf(stdin, ... ), iar funcţia de scriere printf() cu fprintf (stdout,
...).
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 223

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

UNCŢ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
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 224

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

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

XEMPLU DE PROGRAM CARE UTILIZEAZĂ FUNCŢIILE:


fread(), fwrite(), feof(), rewind()
Programul de mai jos creează un fişier cu n înregistrări, al cărui
nume este dat pe linia de comandă. 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ă.

Exemplu de program care utilizează funcţiile: fread(), fwrite(),


feof(), rewind()
#include "stdio.h"
#include "conio.h"

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


Capitolul 10 – Funcţii pentru prelucrarea fişierelor 225

{
FILE *f;
int i,n;

typedef struct catalog


{
char nume[20];
char prenume[25];
short nota;
}struc;
struc x;
if (argc!=2)
{
printf("\n Nu ati dat numele fisierului!");
exit(1);
}
if ( !(f=fopen(argv[1],"w+b")) )
{
printf("\n Nu se poate deschide fisierul!");
exit(1);
}
printf("\n Dati va rog numarul de inregistrari: ");
scanf("%i",&n);

/* 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("%i",&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=%i",
x.nume,x.prenume,x.nota);
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 226

fclose(f);
}

UNCŢ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 ANSI 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:

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
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 227

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

Programul permite poziţionarea pe o înregistrare a fişierului


construit cu programul de la Exemplul 10.4; în funcţie de opţiunea
utilizatorului se modifică sau nu nota înregistrării.
#include "stdio.h"
#include "conio.h"

void main(void)
{
char numefis[8];
long int *p;
int k;
FILE *f;

typedef struct catalog


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

printf("\nNume fisier =");


scanf("%s",numefis);
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 228

f=fopen(numefis,"r+b");

printf("\n Dati va rog 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);
getch();
}
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 229

TESTE DE CONTROL

10.1 Sunt iniţializate întotdeauna cu valoarea 0


a) variabilele locale şi globale declarate static şi neiniţializate
explicit
b) variabilele locale neiniţializate explicit
10.2 În C operaţiile de intrare/ieşire se realizează cu ajutorul
a) unor instrucţiuni speciale
b) unor funcţii speciale din biblioteca standard
10.3 Fluxul (streamul)
a) este un dispozitiv logic de interfaţă, care asigură
generalitatea funcţiilor de intrare/ieşire faţă de diversitatea
dispozitivelor fizice specifice acestor operaţii
b) se reprezintă printr-o entitate de tip FILE
c) poate fi de două tipuri: text sau binar
10.4 Fişierul deschis în mod ”w”
a) va fi creat dacă nu există
b) va pierde informaţiile dacă există deja
c) va putea fi completat cu noi informaţii dacă există deja
10.5 Fişierul deschis în mod ”a”
a) se creează dacă nu există
b) permite adăugarea de date la sfârşitul său dacă există deja
c) va pierde informaţiile dacă există deja
10.6 Pentru citirea unui caracter dintr-un fişier de pe disc se poate
folosi funcţia
a) fgetc()
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 230

b) getch()
c) getc()
10.7 Apelurile de funcţii
fgetc(stdin);
şi
getch();
a) au acelaşi efect: ambele citesc un caracter de la tastatură
b) au efecte diferite
10.8 Pentru scrierea unui caracter într-un fişier pe disc se poate folosi
funcţia
a) fputc()
b) putc()
c) putch()
10.9 Apelurile de funcţii
fputc(stdout,nume_caracter);
şi
putch(nume_caracter);
a) au acelaşi efect: ambele afişează un caracter pe monitor
b) au efecte diferite
10.10 Pentru citirea unui şir de caractere dintr-un fişier creat pe disc
se poate folosi funcţia
a) fgets()
b) gets()
10.11 Pentru scrierea unui şir de caractere într-un fişier creat pe disc
se poate folosi funcţia
a) puts()
b) fputs()
10.12 Apelurile de funcţii
fgets(nume_sir,nr_caractere,stdin);
şi
gets(nume_sir);
a) au acelaşi efect: ambele citesc un şir de caractere de la
tastatură
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 231

b) au efecte diferite
10.13 Funcţiile fprintf() şi fscanf()
a) transferă blocuri de date
b) transferă date formatate
10.14 Care afirmaţie de mai jos este adevărată?
a) apelul fscanf(stdin, ...) are acelaşi efect cu apelul scanf()
b) apelul fprintf(stdout, ...) are acelaşi efect cu apelul printf()
10.15 Pentru transferul unui volum mare de date este recomandabilă
folosirea funcţiilor
a) fread() şi fwrite()
b) fscanf() şi fprintf()
10.16 Funcţia feof()
a) întoarce valoarea adevarat dacă nu s-a atins sfârşitul
fişierului şi fals în caz contrar
b) întorce valoarea fals dacă nu s-a atins sfârşitul fişierului şi
adevarat în caz contrar
10.17 Funcţia rewind() poziţionează indicatorul de poziţie al
fişierului
a) la începutul său
b) la sfârşitul său
10.18 Funcţia fseek()
a) poziţionează indicatorul de poziţie al fişierului la o valoare
calculată relativ faţă de una din valorile de referinţă:
începutul fişierului, sfârşitul fişierului, poziţia curentă în
fişier
b) setează valoarea indicatorului de poziţie la o valoare dată
c) memorează valoarea indicatorului de poziţie într-un obiect
din memorie
10.19 Care afirmaţii sunt adevărate?
a) funcţia fgetpos() setează valoarea indicatorului de poziţie la
o valoare dată
Capitolul 10 – Funcţii pentru prelucrarea fişierelor 232

b) funcţia fsetpos() memorează valoarea indicatorului de


poziţie într-un obiect din memorie
c) afirmaţiile 1 şi 2 sunt false
d) funcţia ftell() întoarce valoarea curentă a indicatorului de
poziţie

RĂSPUNSURI

10.1-a 10.2-b 10.3-a, b, c 10.4-a, b 10.5-a, b


10.6-a, c 10.7-a 10.8-a, b 10.9-a 10.10-a
10.11-b 10.12-a 10.13-b 10.14-a, b 10.15-a
10.16-b 10.17-a 10.18-a 10.19-c, d
Capitolul 11 – Directive către preprocesor 234

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 ANSI 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.
Capitolul 11 – Directive către preprocesor 235

IRECTIVELE #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]);
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.
Capitolul 11 – Directive către preprocesor 236

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

nu este descrisă corect prin secvenţa


#define B 20
#define C B+5
#define D 10
Capitolul 11 – Directive către preprocesor 237

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


Capitolul 11 – Directive către preprocesor 238

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

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
Capitolul 11 – Directive către preprocesor 239

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.
Capitolul 11 – Directive către preprocesor 240

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.
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.
Capitolul 11 – Directive către preprocesor 241

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

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

#if expresie_constanta
sectiune_program
#endif
Capitolul 11 – Directive către preprocesor 242

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.
Capitolul 11 – Directive către preprocesor 243

Programul ilustrează utilizarea facilităţii de compilare


condiţionată
#include "stdio.h"
#include "conio.h"

/* forme de iteratie:
0 se foloseste while
1 se foloseste for
*/

#define ITER 0

void main(void)
{
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)
{
Capitolul 11 – Directive către preprocesor 244

s+=x;
printf("\n x=");
scanf("\n %i", &x);
}
}
#endif
printf("\n Suma este %i",s);
getch();
}

Evident, folosind eventual directiva #elif, în exemplul dat


se poate introduce şi opţiunea de folosire a structurii do_while.

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)
Capitolul 11 – Directive către preprocesor 245

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.

Programul ilustrează folosirea directivei #ifdef


#include "stdio.h"
#include "conio.h"
#define M 10

void main(void)
{
#ifdef M
printf("\n Este definit M");
#else
printf("\n M nu e definit");
#endif
getch();
}

O formă echivalentă este dată în exemplul următor:

Programul ilustrează folosirea directivei #if asociată cu


defined
#include "stdio.h"
#include "conio.h"
#define M 10

void main(void)
{
#if defined(M)
printf("\n Este definit M");
#else
printf("\n M nu e definit");
Capitolul 11 – Directive către preprocesor 246

#endif
getch();
}

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.
Capitolul 11 – Directive către preprocesor 247

TESTE DE CONTROL

11.1 Directiva

#define N 10

a) este greşită, deoarece nu se termină cu semnul ;


b) este corectă şi are ca efect atribuirea valorii 10
identificatorului N
c) este corectă şi are ca efect înlocuirea, oriunde apare
în program, a identificatorului N cu 10

11.2 Secvenţa de program

#define A 10
#define B A+20

a) este greşită deoarece cea de-a doua directivă #define


conţine macroul A din prima directivă #define
b) este corectă şi are ca efect înlocuirea în textul
programului a identificatorului B cu textul 10+20

11.3 Secvenţa de program

#define X 10
#define Y X+5
. . . . . .
int v;
v=X+3*Y;
. . . . . .
Capitolul 11 – Directive către preprocesor 248

a) este greşită deoarece cea de-a doua directivă #define


conţine macroul X din prima directivă #define
b) este corectă, iar variabilei v i se atribuie valoarea 45
c) este corectă, iar variabilei v i se atribuie valoarea 55

11.4 Pentru a anula efectul directivei

#define M 20

se foloseşte

a) #define
b) #define M
c) #undef
11.5 Directiva

#define f(x) 3*x


. . . . . .
int y;
y=f(4-1);

are ca efect

a) atribuirea valorii 9 variabilei y


b) atribuirea valorii 11 variabilei y
c) semnalarea unei erori, deoarece f(x) este folosită ca
o funcţie

11.6 Folosirea unei macrodefiniţii în locul unei funcţii

a) reduce timpul de execuţie al programului


b) micşorează codul programului
Capitolul 11 – Directive către preprocesor 249

11.7 Directiva #include permite

a) includerea unui fişier C într-alt fişier C


b) includerea unui fişier antet într-un fişier C
c) includerea unui fişier antet într-un fişier antet

11.8 Directiva #include se poate folosi

a) numai sub forma #include <nume_fisier>


b) numai sub forma #include ”nume_fisier”
c) atât sub forma 1 cât şi sub forma 2

11.9 Un fişier antet

a) poate conţine prototipuri de funcţii


b) poate conţine definiţii de funcţii
c) poate conţine definiţii de constante
d) poate conţine definiţii de date sau constante
structurate
e) poate conţine directive #define şi #include

11.10 Directiva #if

a) permite execuţia condiţionată a unei secţiuni de


program
b) permite compilarea condiţionată a unei secţiuni de
program
Capitolul 11 – Directive către preprocesor 250

c) permite compilarea unei secţiuni de program funcţie de


valoarea unei expresii constante

11.11 Directiva #ifdef

a) permite compilarea condiţionată a unei secţiuni de


program dacă o expresie e adevărată
b) permite compilarea unei secţiuni de program
condiţionată de definirea unui macro
c) este echivalentă cu
#if defined nume
şi cu
#if defined(nume)
unde nume reprezintă numele unui macro.

RĂSPUNSURI

11.1-c 11.2-b 11.3-b 11.4-c 11.5-b


11.6-a 11.7-b, c 11.8-c 11.9-a, c, e 11.10-b, c
11.11-b, c
Capitolul 12 – Funcţii standard 248

Biblioteca C standard cuprinde o mare varietate de


funcţii, instrumente puternice şi eficiente în elaborarea
programelor. O parte din aceste funcţii, cea mai uzuală, a fost
deja prezentată de-a lungul lucrării. În continuare prezentăm
prototipurile funcţiilor standard C, mai importante, însoţite de
un scurt comentariu privind argumentele lor, valorile întoarse şi
efectul acţiunii lor.
Se poate face o clasificare a funcţiilor C după cum
urmează:
· funcţii pentru operaţii de intrare/ieşire
· funcţii pentru prelucrarea caracterelor
· funcţii pentru prelucrarea şirurilor de caractere
· funcţii pentru gestiunea memoriei heap
· funcţii matematice
· funcţii pentru gestiunea timpului

UNCŢII PENTRU OPERAŢII DE INTRARE/IEŞIRE

FUNCŢII DESCHIDERE/ÎNCHIDERE FIŞIER

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


Capitolul 12 – Funcţii standard 249

deschide fişierul cu numele numefis şi îl asociează unui flux


identificat printr-un pointer. Modul de deschidere este
specificat de mod (vezi Capitolul 10). Intoarce un pointer către
o structură FILE dacă operaţia a avut succes şi un pointer de
valoare NULL, dacă operaţia a eşuat.
int fclose(FILE *fp);

închide fişierul deschis cu fopen(). Pointerul fp este pointerul


fişier returnat la apelarea funcţiei fopen().

FUNCŢII CARE CONTROLEAZĂ INDICATORUL DE


POZIŢIE ÎN FIŞIER
int fseek(FILE *fp,long nr_octeti,int origine);

poziţionează indicatorul de poziţie în fişierul fp la valoarea


specificată de origine la care se adaugă deplasarea egală cu
nr_octeti (vezi Capitolul 10).
int fgetpos(FILE *fp,long int *poz);

memorează valoarea curentă a indicatorului de poziţie în


obiectul indicat de poz. În caz de succes se întoarce o valoare
diferită de 0, iar în caz de eroare valoarea 0.
int fsetpos(FILE *fp,const long int *poz);

setează indicatorul de poziţie la valoarea *poz, obţinută


anterior cu funcţia fgetpos(). În caz de succes se întoarce o
valoare diferită de 0, iar în caz de eroare valoarea 0.
long ftell(FILE *fp);
Capitolul 12 – Funcţii standard 250

întoarce valoarea curentă a indicatorului de fişier sau -1 în caz


de eroare.
void feof(f);

întoarce o valoare diferită de 0 dacă indicatorul de poziţie se


află la sfârşitul fişierului şi 0 în caz contrar.

FUNCŢII PENTRU CITIREA ŞI SCRIEREA

CARACTERELOR

int fgetc(FILE *fp);

întoarce valoarea caracterului când operaţia de citire din


fişierul fp are succes sau EOF când s-a atins sfârşitul fişierului
sau a avut loc o eroare.
int fputc(int c,FILE *fp);

întoarce caracterul scris în fişierul fp, în caz de succes sau


EOF în caz de eroare.
int ungetc(int c,FILE *fp);

se repune un caracter în bufferul asociat fişierului fp. Se


întoarce c în caz de succes sau EOF în caz de eroare.
int getc(FILE *fp);

este similară cu fgetc() (este macro-instrucţiune).


int getchar(void);

este echivalentă cu fgetc(stdin);


int putc(int c, FILE *fp);

este similară cu fputc() (este macro-instrucţiune).


Capitolul 12 – Funcţii standard 251

int putchar(int c);

este echivalentă cu fputc(int c,stdout);

FUNCŢII PENTRU CITIREA ŞI SCRIEREA ŞIRURILOR DE


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

citeşte din fişier un şir de lungime cel mult lg-1 caractere şi le


transferă în şirul str. În caz de succes este întoarsă adresa
şirului str, iar în caz de eroare sau dacă se atinge sfârşit de
fişier se întoarce valoarea NULL.
int fputs(const char *sir, FILE *fp);

scrie în fişierul fp şirul precizat la adresa sir. În caz de succes


întoarce ultimul caracter scris, iar în caz de insucces întoarce
caracterul EOF.
char *gets(char *sir);

citeşte un şir de caractere de la tastatură şi îl depune în sir.


Caracterul ’\n’ nu se include în şir şi se adaugă terminatorul
’\0’. Funcţia întoarce sir în caz de succes sau EOF în caz de

eroare.
int puts(const char *sir);

scrie şirul sir pe ecran întorcând ultimul caracter scris sau


EOF în caz de eroare.
Capitolul 12 – Funcţii standard 252

FUNCŢII DE INTRARE/IEŞIRE CU FORMAT

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

întoarce numărul de valori pentru care citirea, conversia şi


memorarea datelor a fost făcută corect. Dacă apare o eroare
înainte de a începe citirea se întoarce EOF.
int scanf(const char *sir_format);

este echivalentă cu funcţia


int fscanf(stdin, const char *sir_format);

int sscanf(char *sir, const char *sir_format);

citeşte date din şirul de caractere sir conform formatului.


Valorile întoarse de funcţiile scanf() şi sscanf() sunt
similare celor întoarse de funcţia fscanf().

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

întoarce numărul de caractere scrise efectiv sau o valoare


negativă în caz de insucces.
int printf(const char *sir format);

este echivalentă cu funcţia


fprintf(stdout, const char *sir_format);
int sprintf(char *sir,const char *sir_format);

scrie date conform formatului în şirul sir.


Similare cu funcţiile fprintf(), printf(), sprintf() sunt
funcţiile vfprintf(), vprintf() respectiv vsprintf(), pe care nu le
Capitolul 12 – Funcţii standard 253

prezentăm în această lucrare. Pentru documentare se poate


consulta [2].

FUNCŢII PENTRU CITIREA ŞI SCRIEREA BLOCURILOR


DE DATE
size_t fread(void *buf, size_t nr_oct, size_t nb,
FILE *fp);

citeşte din fişierul fp, nb blocuri, fiecare cu lungimea nr_oct şi


le transferă în zona specificată de buf. Întoarce numărul de
blocuri citite efectiv.
size_t fwrite(const void *buf,size_t nr_oct,
size_t nb, FILE *fp);

scrie în fişierul fp, nb blocuri preluate din zona specificată de


buf, fiecare bloc având lungimea egală cu nr_octeti. Se

întoarce numărul de blocuri scrise efectiv.

FUNCŢII PENTRU TESTAREA APARTENENŢEI LA CLASE


DE CARACTERE

Prototipurile acestor funcţii apar în fişierul antet


”ctype.h”. Funcţiile se aplică unui parametru de tip întreg şi

întorc valori diferite de 0 dacă e satisfăcută condiţia de


apartenenţă şi 0 în caz contrar.

int isalnum(int c); testează dacă c e alfanumeric

int isalpha(int c); testează dacă c e alfabetic


Capitolul 12 – Funcţii standard 254

int iscntrl(int c); testează dacă c e caracter de control

(FF,NL, CR, HT, VT, BEL, BS);


int isdigit(int c); testează dacă c este cifră zecimală

int isgraph(int c); testează dacă c este caracter grafic fără

spaţiu
int islower(int c); testează dacă c este litera mică

int isprint(int c); testează dacă c e caracter tipăribil

inclusiv spaţiu
int ispcmct(int c); testează dacă c este caracter tipăribil

fără spaţiu sau caracter alfanumeric


int isspace(int c); testează dacă c este CR, FF, HT, VT,

NL, spaţiu

int isupper(int c); testează dacă c este literă mare

int isxdigit(int c); testează dacă c este cifră hexazecimală

(0-9, A-F sau a-f);

Tot aici amintim funcţiile tolower() şi toupper():

int tolower(int c); converteşte o literă mică la litera mare

corespunzătoare

int toupper(int c); converteşte o literă mare la litera mică

corespunzătoare

Pentru ambele funcţii, c rămâne nemodificat dacă nu este


literă!
Capitolul 12 – Funcţii standard 255

UNCŢII PENTRU PRELUCRAREA ŞIRURILOR DE CARACTERE

Se deosebesc trei categorii de funcţii:

· funcţii pentru citirea şi scriere şirurilor, prezentate deja


(care au prototipurile în stdio.h)
· funcţii cu prototipul in string.h (copiere, comparare,
concatenare, iniţializare şiruri)
· funcţii de conversie a şirurilor (cu prototipul in
stdlib.h)

FUNCŢII CU PROTOTIPUL IN STRING.H

Există două categorii de funcţii: funcţii care se referă la


şiruri care se termină prin ’\0’ şi funcţii în care şirurile sunt
văzute pur şi simplu ca tablouri de caractere (fără terminatorul
’\0’).

Funcţiile care se referă la şiruri cu terminator ’\0’ sunt:


char *strcat(char *sir1, const char *sir2);

concatenează sir2 la sir1 şi returnează sir1.


char *strchr(const char *sir, int c);

caută c în şirul de caractere sir întorcând în caz de succes


poziţia lui, iar în caz contrar valoarea NULL.
int strcmp(const char sir1, const char sir2);
Capitolul 12 – Funcţii standard 256

compară lexicografic şirurile sir1 şi sir2 şi întoarce o valoare

<0 dacă sir1<sir2

=0 dacă sir1=sir2

>0 dacă sir1>sir2

char *strcpy(char *sir1, const char *sir2);

copiază şirul sir2 în şirul sir1 şi întoarce sir1.


size_t strcspn(const char *sir1, const char *sir2);

întoarce lungimea prefixului şirului sir1 care nu conţine nici


unul din caracterele şirului sir2.
char *strerror(int er_cod);

întoarce un pointer către un şir ce reprezintă un mesaj de


eroare corespunzător codului er_cod.
size_t strlen(const char *sir);

întoarce numărul de caractere din şirul sir, exclusiv


terminatorul ’\0’.
char *strncat(char *sir1, const char *sir2,
size_t nr);

concatenează cel mult nr caractere din sir2 la sir1 şi întoarce


sir1.
int *strncmp(const char *sir1, const char *sir2, size_t
nr);

compară cel mult nr caractere din sir1 şi sir2 şi întoarce


valori la fel ca funcţia strcmp().
char *strncpy(char *sir1, const char *sir2,
size_t nr);
Capitolul 12 – Funcţii standard 257

copiază cel mult nr caractere din şirul sir2 în şirul sir1 şi


întoarce sir1.
char *strpbrk(const char *sir1, const char *sir2);

întoarce un pointer la prima apariţie în sir1 a oricărui caracter


din sir2 sau NULL dacă nici un caracter din sir2 nu se află în
sir1.
char * strrchr(const char *sir, int c);

întoarce pointerul la ultima apariţie a lui c în sir sau NULL


dacă c nu apare în sir.
size_t strspn(const char *sir1, const char *sir2);

întoarce lungimea prefixului şirului sir1 care conţine numai


elemente din sir2.
char *strstr(const char *sir1, const char *sir2);

întoarce un pointer la prima apariţie în sir1 a şirului sir2 sau


NULL dacă sir2 nu este subşir al şirului sir1.
char *strtok(char *sir1, const char sir2);

caută în sir1 subşiruri delimitate de caractere din şirul sir2.

Următoarele funcţii se referă la tablouri de caractere


(fără a avea terminatorul ’\0’ ca la şiruri).
void *memchr(const void *tablou, int c, size_t nr);

caută în tabloul indicat prin tablou, prima apariţie a lui c printre


cele nr caractere. Întoarce adresa lui în caz de succes sau
NULL în caz contrar.
void memcmp(const void *tablou1, const void *tablou2,
size_t nr);
Capitolul 12 – Funcţii standard 258

compară în sens lexicografic primele nr caractere ale


tablourilor tablou1 şi tablou2. Întoarce un întreg calculat ca la
funcţia strcmp().
void *memcpy(void *tablou1, const void *tablou2, size_t
nr);

copiază nr caractere din tablou2 în tabloul indicat de tablou1.


Dacă tablourile se suprapun efectul este nedefinit.
void *memmove(void *tablou1, const void *tablou2,
size_t nr);

copiază nr caractere din tablou2 în tablou1. Tablourile se pot


suprapune.
void *memset(void *tablou, int c, size_t nr);

copiază c (octetul său inferior) în primele nr caractere ale


tabloului tablou.

FUNCŢII DE CONVERSIE A ŞIRURILOR

Următoarele funcţii realizează conversii ale şirurilor de


caractere în valori numerice.
double atof(const char *str);

întoarce rezultatul conversiei şirului de caractere indicat de


pointerul str la un număr real. Şirul trebuie să conţină o
valoare numerică scrisă corect, în caz contrar se întoarce o
valoare nedefinită. Delimitatorul final poate fi orice caracter
diferit de punct şi de literele e sau E (care intervin în mod
natural în reprezentarea unui număr în virgulă mobilă).
int atoi(const char *str);
Capitolul 12 – Funcţii standard 259

întoarce rezultatul conversiei şirului de caractere indicat de


pointerul str la un număr întreg. Şirul trebuie să conţină o
valoare întreagă corect scrisă, în caz contrar se întorce o
valoare nedefinită. Delimitatorul final poate fi orice caracter
diferit de cifră.
int atol(const char *str);

întoarce rezultatul conversiei şirului de caractere indicat de


pointerul str la o valoare long int. Şirul trebuie să conţină o
valoare întreagă validă, în caz contrar se întoarce o valoare
nedefinită. Delimitatorul final poate fi orice caracter diferit de
cifră.
double strtod(char *str, char **sf);

întoarce rezultatul conversiei şirului de caractere indicat de


pointerul str la un număr în format double, ignorând spaţiile
libere iniţiale. Şirul trebuie să conţină o valoare numerică
corect scrisă, în caz contrar se întoarce valoarea zero.
Delimitatorul final poate fi orice caracter diferit de punct şi de
literele e sau E. În *sf se depune pointerul la delimitatorul final
din sir.
long strtol(const char *str, char **sf, int radix);

întoarce rezultatul conversiei şirului de caractere indicat de


pointerul str ignorând spaţiile libere iniţiale într-un număr de
tip long, reprezentat în baza de numeraţie stabilită de radix.
Dacă radix este zero se consideră că baza este 8, 10 sau 16
în caz contrar baza este dată de valoarea radix aflată
Capitolul 12 – Funcţii standard 260

obligatoriu între 2 şi 36. Delimitatorul poate fi orice caracter


care nu poate intra în componenţa unui număr întreg. Dacă nu
se poate realiza conversia se întoarce valoarea zero. În *sf se
depune pointerul la delimitatorul final din şir.
unsigned long strtoul(const *start, char **sf,
int radix);

acţionează similar cu funcţia strtol() cu excepţia faptului că


întoarce în caz de succes o valoare unsigned long.

UNCŢII PENTRU GESTIUNEA MEMORIEI HEAP

void *calloc(size_t nr, size_t nr_oct);

alocă o zonă compactă din memoria heap pentru un tablou


având nr elemente fiecare de lungime nr_oct octeţi. Întoarce
un pointer către primul octet al tabloului sau NULL dacă nu
există suficientă memorie pentru alocare.
void *free(void *ptr);

dealocă zona de memorie indicată de ptr, alocată anterior cu


funcţiile calloc(), malloc() sau realloc().
void *malloc(size_t nr_oct);

alocă o zonă compactă din memoria heap de dimensiune


nr_oct octeţi. Întoarce un pointer la primul octet din zona de

memorie alocate sau NULL dacă nu există spaţiu disponibil.


void *realloc(void *ptr, size_t nr_oct);
Capitolul 12 – Funcţii standard 261

realocă zona de memorie indicată de ptr (alocată anteriror cu


ajutorul funcţiilor calloc() şi malloc()) la valoarea dată de
nr_oct octeţi. Întoarce un pointer la zona realocată sau NULL

dacă nu există suficient spaţiu de memorie pentru alocarea


celor nr_oct octeţi, situaţie în care zona iniţială de memorie
rămâne nemodificată.

UNCŢII MATEMATICE

Standardul ANSI C defineşte 22 funcţii matematice


frecvent utilizate în programe. Prototipurile lor sunt conţinute
de fişierul antet ”math.h”. Argumentele funcţiilor sunt, cu mici
excepţii, de tip double iar valorile întoarse sunt de tip double.
Principalele erori care pot apare la apelul funcţiilor sunt: eroare
de domeniu şi eroare de reprezentare.
Eroarea de domeniu apare atunci când argumentele
funcţiei sunt în afara domeniului său de definiţie (de exemplu
valori negative folosite drept argumente pentru funcţia
logaritm), iar eroarea de reprezentare apare atunci când
rezultatul întors de funcţie nu poate fi reprezentat.
Aceste erori provoacă setarea variabilei globale
incorporate errno la valorile EDOM respectiv ERANGE. Cele
două macrocomenzi sunt definite în fişierul antet ”errno.h”. În
situaţiile de eroare descrise se întoarce o valoare definită la
implementare, în cazul erorii de domeniu şi respectiv valoarea
Capitolul 12 – Funcţii standard 262

HUGE_VAL (o valoare <mare> definită în fişierul antet


”math.h”) în cazul erorii de reprezentare. Argumentele
funcţiilor trigonometrice care sunt unghiuri sunt considerate în
radiani.

double acos(double x); arccos de x

double asin(double x); arcsinus de x

double atan(double x); arctangentă de x

double atan2(double y,double x); arctangentă de y/x

double ceil(double x); cel mai mic întreg >= x

double cos(double x); cosinus de x

double cosh(double x); cosinus hiperbolic de x

double exp(double x); exponenţială e la x

double fabs(double x); valoarea absolută a lui x

double floor(double x); cel mai mare întreg <= x

double fmod(double x,int y); restul împărţirii lui x la y

văzuţi ca întregi (x modulo y)


double frexp(double x,double exp);intoarce valoarea
mantisei numărului x si memorează
valoarea exponentului în exp astfel
încât x=mantisa*2exp (mantisa e
cuprinsă între 0.5 şi 1).
double ldexp(double x,int exp); calculează x*2exp

double log(double x); calculează logaritm natural de x

double log10(double x); calculează logaritm zecimal de x


Capitolul 12 – Funcţii standard 263

double modf(double x,double *pin); descompune un număr

x de tip double în partea sa


fracţionară pe care o întoarce şi
partea întreagă pe care o
memorează în pin
double pow(double x,double y); întoarce xy pentru x şi y

aparţinând domeniul de
definiţie
double sin(double x); sinus de x

double sinh(double x); sinus hiperbolic de x

double sqrt(double x); rădăcina pătrată a lui x

double tan(double x); tangentă de x

double tanh(double x); tangentă hiperbolică de x

UNCŢII PENTRU GESTIUNEA TIMPULUI

Prototipurile funcţiilor care gestionează timpul se găsesc


în fişierul header ”time.h”. Acest header conţine definiţiile a
patru tipuri de date:

size_t, clock_t, time_t şi tm.

Tipul size_t reprezintă un întreg fără semn. Tipurile


clock_t şi time_t se folosesc la reprezentarea orei şi a datei

sistemului, ca date de tip long integer. Structura standard de


tip struct tm are alcătuirea următoare:
Capitolul 12 – Funcţii standard 264

struct tm
{
int tm_sec;/* secunde 0...59 */
int tm_min;/* minute 0...59 */
int tm_hour;/* ore 0...23 */
int tm_mday;/* ziua din luna 1...31 */
int tm_mon;/* luna 0...11 */
int tm_year;/* anul >=1900 */
int tm_wday;/* ziua saptamanii 0...6 */
int tm_yday;/* ziua din an 0...365 */
int tm_isdst;/* indicator al orei de vara este:
>0 daca functioneaza
=0 daca nu functioneaza
<0 daca nu exista informatii in domeniu */
}

Listăm mai jos prototipurile celor mai uzuale funcţii


cuprinse în fişierul ”time.h”
char *asctime(const struct tm *ptr);

converteşte timpul din structura indicată de pointerul ptr într-


un şir de forma

zi luna data ore:minute:secunde an\n\0, unde

pointerul ptr este obţinut cu ajutorul funcţiei localtime() sau


cu functia gmtime().
clock_t clock(void);

Întoarce numărul de impulsuri de ceas, efectuate o dată cu


lansarea programului în execuţie. Transformarea în secunde
se face împărţind această valoare la macroul

CLOCKS_PER_SEC care dă numărul de bătăi pe secundă ale


ceasului.
char *ctime(const time_t *ptr);
Capitolul 12 – Funcţii standard 265

converteşte timpul de calendar din structura indicată de


pointerul ptr într-un şir de forma
zi luna data ore:minute:secunde an\n\0

Pointerul ptr este obţinut cu ajutorul funcţiei time(). Ea


este echivalentă cu asctime(local(time));
double diff_time(time_t timp2, timp_t timp1);

întoarce valoarea timp2-timp1 exprimată în secunde.


struct tm *gmtime(const time_t *ptr);

converteşte timpul de calendar indicat de pointerul ptr în Timp


Coordonat Universal (Universal Coordinated Time) întorcând
un pointer la o structură de tip tm. Dacă nu este disponibil se
întoarce NULL. Pointerul ptr este obţinut, printr-un apel al
funcţiei time().
struct tm *localtime(const time_t *ptr);

converteşte tipul de calendar indicat de pointerul ptr în timp


local întorcând un pointer la o structură de tip tm. Dacă nu e
disponibil se întoarce NULL. Pointerul ptr e obţinut printr-un
apel al funcţiei time().
size_t strftime(char *sir, size_t dimmax,
const char *sir_format, const struct tm *ptr);

converteşte datele din structura indicată de pointerul ptr în


şirul sir, conform cu formatul sir_format.
time_t time(time_t *ptr);

întoarce ora curentă calendaristică a sistemului sau –1 dacă nu


există această facilitate.
Capitolul 12 – Funcţii standard 266

Se poate apela cu ptr=NULL sau cu o adresă a unei


variabile de tip time_t. În ultimul caz valoarea întoarsă se
atribuie de asemenea variabilei indicate de ptr.

LTE FUNCŢII UTILE


int abs(int intr); /* fisier header stdlib.h */

întoarce un întreg egal cu valoarea absolută a întregului intr.


void clearerr(FILE *fp); /* fisier header stdio.h */

şterge indicatorii de eroare şi de sfârşit de fişier.


div_t div(int numarator, int numitor);
/* fisier header stdlib.h */

întoarce o structură de tip div_t care conţine câtul şi restul


împărţirii numărător la numitor în membrii quot, respectiv rem ai
acestei structuri.
void exit(int cod); /* fisier header graph.h */

produce ieşirea dintr-un program şi revenirea în sistemul de


operare. Dacă valoarea parametrului cod este 0 sau
EXIT_SUCCES se indică terminarea cu succes a programului,
iar dacă este diferit de 0 sau EXIT_FAILURE se indică
terminarea cu eroare a programului.
int ferror(FILE fp); /* fisier header stdio.h */
Capitolul 12 – Funcţii standard 267

întoarce fie valoarea 0 indicând faptul că nu are loc o eroare


sau o valoare diferită de 0 dacă indicatorul de eroare asociat
lui fp este poziţionat.
int fflush(FILE fp); /* fisier header stdio.h */

goleşte bufferul asociat unui fişier deschis pentru scriere sau


citire/scriere. Efectul este nedefinit dacă fişierul a fost deschis
numai pentru citire. Întoarce EOF în caz de eroare sau 0 în caz
normal.
long labs(long lintr); /* fisier header stlib.h */

întoarce un întreg egal cu valoarea absolută a întregului lintr.


ldiv_t ldiv(long numarator, long numitor);
/* fisier header stdlib.h */

întoarce o structură de tip ldiv_t care conţine câtul şi restul


împărţirii între numărător şi numitor în membrii quot şi rem de
tip long ai acestei structuri.
int remove(const char *numefis);
/* fisier header stdio.h */

şterge fişierul cu nume numefis. Întoarce 0 în caz de succes şi


o valoare diferită de 0 în caz de eroare.
int rename(const char *nume_nou,
const char *nume_vechi); /* fisier header stdio.h */

redenumeşte un fişier. Întoarce 0 în caz de succes şi o valoare


diferită de 0 în caz de eroare.
int rand(void); /* fisier header stdlib.h */

întoarce un număr întreg uniform aleator din intervalul


[0,RAND_MAX].
Capitolul 12 – Funcţii standard 268

int srand(unsigned int seed);


/* fisier header stdlib.h */

iniţializează generatorul de numere aleatoare cu valoarea


seed.
void qsort(void *buf, size_t nr, size_t lung,
int(*cmp)(const void *arg1, const void *arg2));
/* fisier header stdlib.h */

sortează în ordine crescătoare tabloul


buf[0],buf[1],…,buf[nr-1] folosind metoda QuickSort.

Parametrul lung reprezintă mărimea în octeţi a fiecărui


element. Funcţia de comparare este *cmp şi trebuie să întoarcă

o valoare<0 dacă arg1<arg2


o valoare=0 dacă arg1=arg2
o valoare>0 dacă arg1>arg2
void bsearch(const void *cheie, const void *buf,
size_t nr, size_t lung, int(*cmp)(const void *arg1,
const void *arg2)); /* fisier header stdlib.h */

caută în tabloul ordonat buf[0],buf[1],…,buf[nr-1] întorcând


un pointer către primul membru care corespunde cheii indicată
de cheie, în caz de succes, sau NULL în caz contrar.
Semnificaţia parametrilor este cea descrisă la funcţia qsort().
Capitolul 13 – Funcţii video C 264

Un program care beneficiază de o interfaţă prietenoasă cu


utilizatorul, are şanse mai mari să fie cumpărat, faţă de un program care
ignoră acest <detaliu> foarte important azi. De asemenea, prezentarea
rezultatelor numerice însoţite de grafice, tabele, histograme desene etc.,
reprezintă o modalitate de informare mai atractivă şi eficientă a
utilizatorului. Iată de ce, calitatea interfeţei progam-utilizator este, alături
de timpul de execuţie şi necesarul de memorie, unul din indicatorii
principali prin care se poate aprecia un program.
Controlul complet asupra ecranului se realizează cu ajutorul unor
funcţii speciale, numite de obicei funcţii video. Din cauza diferenţelor
existente între diferite tipuri de calculatoare, standardul ANSI C nu
defineşte astfel de funcţii. Totuşi, ţinând cont de importanţa lor deosebită,
prezentăm în această secţiune, pentru familiarizare, câteva funcţii video
furnizate de Borland Turbo C++ pentru calculatoare IBM PC şi
compatibile, echipate cu adaptoare grafice sub sistemul de operare DOS.
Evoluţia adaptoarelor video sau interfeţelor grafice, este marcată de
dorinţa de a obţine pe ecran reprezentări la nivelul calităţii fotografice.
Adaptoarele video conţin memorie RAM video, cu ajutorul căreia se
realizează reîmprospătarea continuă a imaginii de pe videomonitor şi
logica necesară generării semnalelor de comandă pentru funcţionarea
videomonitorului. Ele implementează cele două moduri de lucru: grafic şi
text. Adaptorul MDA (Monochrome Display Adapter) permite numai
afişare în mod text. Alte tipuri de adaptoare video (în ordinea apariţiei lor)
sunt: Hercules Monochrome Graphics Adapter, CGA (Color Graphics
Adapter), EGA (Enhanced Graphics Adapter), VGA (Video Graphics Array
Adapter). Ele permit afişarea în mod text şi în mod grafic. Cele două
moduri de lucru nu sunt active simultan.
Capitolul 13 – Funcţii video C 265

În modul text fiecare caracter de pe monitor este


reprezentat în memoria video prin doi octeţi: primul octet
conţine codul ASCII al caracterului, iar al doilea atributele de
afişare. Structura octetului care codifică atributele de afişare
este următoarea:

B F F F C C C C

unde:

CCCC reprezintă codul culorii caracterului;

FFF reprezintă codul culorii fondului;

B indică afişare continuă (valoarea 0) sau


intermitentă (valoarea 1).

Ecranul este privit ca o reţea celulară, fiecare celulă


putând memora un caracter. Originea reţelei este în colţul din
stânga sus şi are coordonatele (1,1). Coordonata x creşte de
la stânga spre dreapta, iar coordonta y creşte de sus în jos în
planul ecranului.
În modul grafic ecranul este văzut ca o reţea foarte fină
de puncte numite pixeli. Pixelul este cea mai mică zonă ce
poate fi <aprinsă> pe ecran. Culoarea unui pixel este
reprezentată printr-un număr de biţi în memoria video. Astfel,
imaginea afişată este construită punct cu punct în memoria
video. Originea se află în colţul din stânga sus şi are
Capitolul 13 – Funcţii video C 266

coordonatele (0,0). Coordonatele x şi y cresc şi descresc


asemănător ca în modul text.
Adaptoarele video sunt caracterizate prin rezoluţie
spaţială şi rezoluţie de culoare. Rezoluţia spaţială în modul
text se referă la număr de rânduri şi număr de coloane, iar în
modul grafic înseamnă număr de linii şi număr de pixeli pe
linie. Rezoluţia de culoare are în vedere numărul de culori
afişabile simultan.
Ţinând cont de simplitatea modului de reprezentare,
modul text se caracterizează prin viteză mare de afişare şi
necesar de memorie mic. Este evident însă că afişarea în mod
text nu permite reprezentări pe ecran de mare fineţe. Din acest
punct de vedere modul grafic este ideal. Preţul plătit constă
într-un necesar mare de memorie şi viteză de afişare redusă.
Ambele probleme au fost însă rezolvate cu succes, iar tendinţa
este de a înlocui definitiv modul text cu modul grafic.
Atât în modul text cât şi în modul grafic există
posibilitatea de a preciza zone dreptunghiulare de ecran în
care se pot afişa texte, imagini grafice, în timp ce restul
ecranului rămâne nemodificat. Aceste zone, active la un
moment dat, se numesc ferestre. Definirea unei ferestre se
face cu ajutorul unor funcţii speciale care precizează
coordonatele colţului stânga sus şi a colţului dreapta jos pentru
fereastra respectivă. O dată definită fereastra, coordonatele
folosite de celelalte funcţii video vor fi relative la această
Capitolul 13 – Funcţii video C 267

fereastră. Noua origine va fi situată în colţul din stânga sus a


ferestrei, iar coordonatele x şi y relative, la fereastra activă,
cresc sau descresc asemănător cu situaţia în care este activat
tot ecranul. De altfel, fereastra implicită pentru ambele moduri
este tot ecranul.

UNCŢII VIDEO ÎN MODUL TEXT

Funcţiile video în modul text au prototipul în ”conio.h” şi


se împart în următoarele categorii:

· Funcţii pentru stabilirea modului;


· Funcţii pentru definirea şi gestionarea ferestrelor;
· Funcţii pentru controlul atributului;
· Funcţii pentru afişarea şi modificarea textului.

UNCŢII PENTRU STABILIREA MODULUI

void textmode(int mod);

unde mod poate lua una din valorile BW40 (40 coloane alb-
negru), C40 (40 coloane color), BW80 (80 coloane alb-negru),
C80 (80 coloane color), MONO (80 coloane monocrom), LASTMODE

(modul precedent). Valorile BW40, C40, BW80, C80, MONO,


LASTMODE pot fi înlocuite cu 0, 1, 2, 3, 7 respectiv -1.
Capitolul 13 – Funcţii video C 268

UNCŢII PENTRU DEFINIREA ŞI GESTIONAREA

FERESTRELOR

FUNCŢII PENTRU DEFINIREA FERESTRELOR

void window(int x1, int y1, int x2, int y2);

defineşte o fereastră cu colţul stânga-sus în punctul de


coordonate
(x1, y1) şi cu colţul dreapta-jos în punctul de coordonate (x2,
y2).

FUNCŢII PENTRU GESTIONAREA FERESTRELOR

void gettextinfo(struct text_info *ptr);

completează o structură de tip text_info, conţinută în


”conio.h”, cu informaţii despre fereastră. Structura conţine

coordonatele colţurilor stânga-sus, dreapta-jos, atributul


curent, modul text curent, dimensiunile ecranului, poziţia
curentă a cursorului şi se află la adresa indicată de ptr.
void gotoxy(int x, int y);

poziţionează cursorul, în fereastra activă, în punctul de


coordonate (x,y).
int wherex(void);

întoarce coordonata x a poziţiei curente a cursorului.


int wherey(void);
Capitolul 13 – Funcţii video C 269

întoarce coordonata y a poziţiei curente a cursorului.

UNCŢII PENTRU CONTROLUL ATRIBUTULUI

void textattr(int atribut);

modifică valorile atributului (culoare caracter, culoare fond,


caracterul continuu sau intermitent al afişării) printr-un singur
apel.
void textbackground(int culoare);

setează culoarea fondului pe care apar caracterele. Valorile


posibile sunt: BLACK, BLUE, GREEN, CYAN, RED,
MAGENTA, BROWN, LIGHTGRAY sau 1,2,3,4,5,6, care
înlocuiesc corespunzător constantele simbolice enumerate.
void textcolor(int culoare);

setează culoarea caracterelor. Valorile posibile pentru culoare


sunt cele listate la funcţia textbackground() la care se
adaugă: DARKGRAY, LIGHTBLUE, LIGHTGREEN,
LIGHTCYAN, LIGHTRED, LIGHTMAGENTA, YELLOW,
WHITE sau 8,9,10,11,12,13,14,15, care înlocuiesc
corespunzător constantele simbolice enumerate.
void highvideo(void);

are ca efect afişarea cu intensitate sporită a caracterelor.


void lowvideo(void);

are ca efect afişarea cu intensitate redusă a caracterelor.


void normvideo(void);
Capitolul 13 – Funcţii video C 270

are ca efect afişarea caracterelor cu intensitatea existentă la


intrarea în execuţie a programului.

UNCŢII PENTRU AFIŞAREA ŞI MODIFICAREA TEXTULUI

int cprintf();

afişează un text formatat pe ecran, în fereastra curentă. Este


asemănătoare cu funcţia printf().
int cputs(const char *sir);

afişează şirul sir în fereastra curentă, întorcând ultimul


caracter afişat.
int getch(void);
int getche(void);
int putch(int c);

au efectul prezentat la funcţiile cu aceleaşi sume din


standardul
ANSI C.
int gettext(int x1,int y1,int x2,int y2,
void *tablou_dest);

copiază un text cuprins în zona desemnată de colţul stânga-


sus de coordonate (x1,y1) şi colţul dreapta-jos de coordonate
(x2,y2) în zona de memorie tablou_dest. Întoarce 0 dacă
operaţia nu a avut succes şi diferit de 0 în caz contrar.
int puttext(int x1,int y1,int x2,int y2,
void *tablou_sursa);
Capitolul 13 – Funcţii video C 271

afişează conţinutul tabloului tablou_sursa în zona de ecran


definită de colţul stânga-sus de coordonate (x1,y1) şi colţul
dreapta-jos de coordonate (x2,y2).
void clreol(void);

şterge caracterele de la poziţia curentă a cursorului până la


sfârşitul liniei.
void clrscr(void);

şterge fereastra curentă şi poziţionează cursorul în originea


(1,1).
void delline(void);

şterge linia pe care se află cursorul.


void insline(void);

inserează o linie goală sub linia pe care se află cursorul.


int movetext(int x1,int y1,int x2,int y2, int x,
int y);

copiază conţinutul din zona definită de colţul din stânga-sus de


coordonate (x1,y1) şi colţul din dreapta-jos de coordonate
(x2,y2), în zona de aceeaşi dimensiune care are colţul din
stânga-sus de coordonate (x,y).
Capitolul 13 – Funcţii video C 272

UNCŢII VIDEO PENTRU MODUL GRAFIC

Limbajul Turbo C++ are peste 70 funcţii video ce pot fi


folosite în modul grafic (pe scurt, funcţii grafice) care se
găsesc grupate în biblioteca grafică graphics.lib. Folosirea
lor necesită includerea fişierului header ”graphics.h”.

Operaţia de linkeditare nu include implicit (ca în cazul


bibliotecii standard) module obiect din biblioteca graphics.lib.
Din acest motiv, dacă se lucrează sub un mediu integrat
trebuie bifată caseta Graphics Library din meniul Option/Linker.
Dacă se lucrează în mod comandă trebuie folosită o comandă
de forma
tcc nume_program graphics.lib

Trecerea în modul grafic nu este posibilă fără încărcarea


în memorie a unor rutine speciale destinate adaptorului video
prezent, numite şi drivere.
Prezentăm mai jos câteva din funcţiile grafice, cele mai
utile, grupate astfel:

· Funcţii pentru controlul sistemului grafic;


· Funcţii pentru controlul ecranului şi al ferestrelor ;
· Funcţii pentru manevrarea imaginilor şi a pixelilor;
· Funcţii pentru afişarea textelor în modul grafic;
· Funcţii pentru controlul culorii;
· Funcţii pentru desenare şi umplere.
Capitolul 13 – Funcţii video C 273

Funcţiile din graphics.lib sunt funcţii far, iar pointerii


manipulaţi de ele sunt de tip far (far este un modificator de
adresă utilizat pentru a referi adrese situate în afara zonei de
date de 64 Ko).

UNCŢII PENTRU CONTROLUL SISTEMULUI GRAFIC

void far closegraph(void);

închide sistemul grafic, eliberează memoria alocată la


încărcarea driverului şi revine la modul text existent anterior
apelului funcţiei initgraph().
void far detectgraph(int far *grdriver,
int far *gmode);

întoarce în grdriver numărul asociat driverului, iar gmode va


indica modul cu rezoluţie maximă pentru adaptorul şi driverul
existent.
char *far getdrivername(void);

întoarce numele driverului încărcat.


int for getmaxx(void);
int far getmaxy(void);

întorc numărul maxim de pixeli pe orizontală şi pe verticală


(rezoluţia spaţială curentă).
char *far grapherrormsg(int cod_eroare);

întoarce adresa mesajului asociat codului de eroare.


int far graphresult(void);
Capitolul 13 – Funcţii video C 274

întoarce codul erorii rezultat din execuţia unei funcţii grafice.


void far initgraph(int far *grdriver, int far *gmode,
char far *cale);

iniţializează sistemul grafic. Parametrul cale reprezintă un şir


de caractere prin care se precizează calea de căutare a
fişierului care conţine driverul. Dacă şirul este vid, căutarea
se face în driverul curent.
void far setgraphmode(int gmode);

setează un nou mod grafic pentru driverul instalat, şterge


ecranul şi restaurează toate valorile implicite.
void far restorecrtmode(void);

restabileşte modul video existent anterior apelului funcţiei


initgraph() fără a închide sistemul grafic.

UNCŢII PENTRU CONTROLUL ECRANULUI,

MANEVRAREA IMAGINILOR ŞI A PIXELILOR

CONTROLUL ECRANULUI

void far cleardevice(void);

şterge tot ecranul şi aduce cursorul în poziţia (0,0).


void far clearviewport();

şterge fereastra curentă şi aduce cursorul în poziţia (0,0).


void far getviewsettings(struct viewporttype
far *viewport);
Capitolul 13 – Funcţii video C 275

determină dimensiunile ferestrei curente. Informaţiile sunt date


de structura viewporttype declarată în fişierul ”graphics.h” şi
are forma:
struct viewporttype
{
int left,top,right,bottom;
int clip;
};

void far setactivepage(int pag);

specifică pagina activă pentru ieşiri grafice (pot fi până la opt


pagini, parametrul pag putând lua una din valorile 0,1,...7).
void far setviewport(int x1, int y1, int x2, int y2,
int clip);

defineşte o fereastră cu colţul stânga-sus, în punctul de


coordonate (x1,y1) şi colţul dreapta-jos, în punctul de
coordonate (x2,y2). Dacă clip are o valoare diferită de 0,
desenele care depăşesc fereastra sunt trunchiate, în caz
contrar nu.
void far setvisualpage(int pag);

specifică pagina care va fi vizualizată pe ecran.

MANEVRAREA IMAGINILOR

void far getimage(int x1, int y1, int x2, int y2,
void far *buf);

copiază zona de memorie video corespunzătoare zonei


dreptunghiulare de pe ecran determinată de coordonatele
(x1,y1), (x2,y2) în tabloul buf.
Capitolul 13 – Funcţii video C 276

unsigned far imagesize(int x1,int y1,int x2,int y2);

întoarce numărul de octeţi necesari pentru memorarea imaginii


dreptunghiulare de pe ecran determinată de coordonatele
(x1,y1), (x2,y2).
void far putimage(int x1,int y1,void far *buf,
int operator);

afişează pe ecran, într-o zonă dreptunghiulară, având colţul din


dreapta sus în punctul de coordonate (x1,y1), imaginea
memorată în buf, afectată însă de operator. Operatorul poate
lua valorile COPY_PUT (copiere simplă), XOR_PUT (SAU exclusiv
între imagini), OR_PUT (SAU între imagini), AND_PUT (SI între
imagini), NOT_PUT (negativul imaginii). Constantele simbolice
pot fi înlocuite cu valorile numerice 0,1,2,3 respectiv 4.
Operatorii XOR_PUT, OR_PUT şi AND_PUT se referă la operaţiile bit
cu bit, corespunzătoare, dintre imaginea existentă pe ecran şi
imaginea memorată în buf, iar NOT_PUT se referă la negarea bit
cu bit a imaginii de pe ecran).

MANEVRAREA PIXELILOR

unsigned far getpixel(int x,int y);

întoarce culoarea pixelului aflat în poziţia (x,y).


void far putpixel(int x,int y,int culoare);

afişează pe ecran pixelul de coordonate (x,y) de culoarea dată


de parametrul culoare.
Capitolul 13 – Funcţii video C 277

UNCŢII PENTRU AFIŞAREA TEXTELOR ÎN MODUL

GRAFIC

void for gettextsettings(struct textsettingstype far


*textinfo);

permite obţinerea unor informaţii despre fontul curent, direcţie,


mărime, aliniere prin intermediul pointerului textinfo care
indică o structură de forma:
struct textsettingstype
{
int font;
int direction;
int charsize;
int horiz;
int vert;
};

void far outtext(char far *text);

afişează text în poziţia punctului curent.


void far outtextxy(int x, int y, char far *text);

afişează text în poziţia dată de (x,y).


void far settingsjustify(int oriz, int vert);

aliniază textul relativ la poziţia curentă. Implicit alinierea


orizontală este la stânga textului, iar cea verticală la baza
textului. Sunt următoarele posibilităţi:

orizontal

LEFT_TEXT (0) punct curent la stânga textului

CENTER_TEXT (1) punct curent în centrul textului

RIGHT_TEXT (2) punct curent la dreapta textului


Capitolul 13 – Funcţii video C 278

vertical

BOTTOM_TEXT (0) punct curent la baza textului

CENTER_TEXT (1) punct curent în centrul textului

TOP_TEXT (2) punct curent deasupra textului

Valorile 0,1,2 din paranteze reprezintă alternative la


folosirea constantelor simbolice.
void far settingsstyle(int font, int directie,
int marime);

precizează fontul, direcţia (orizontală sau verticală) şi mărimea


caracterelor care compun textul.
int far textheight(char far *text);
int far textwidth(char far *text);

întorc înălţimea, respectiv lungimea măsurată în pixeli pentru


text.

UNCŢII PENTRU CONTROLUL CULORII

int far getbkcolor(void);

întoarce culoarea curentă a fondului.


int far getcolor(void);

întoarce culoarea curentă de desenare.


int far getmaxcolor(void);

întoarce indexul ultimului element din paletă reprezentând o


culoare în modul grafic curent.
void far getpalette(struct palettetype *far paleta);
Capitolul 13 – Funcţii video C 279

întoarce paleta curentă şi dimensiunea ei prin intermediul


pointerului paleta către o structură de forma:
struct palettetype
{
unsigned char dimens_paleta;
signed char colors[16];
};

void far setallpalette(struct palettetype far


*paleta);

schimbă toată paleta.


int far setbkcolor(int culoare_noua);

schimbă culoarea fondului în culoarea_noua.


void far setcolor(int culoare_noua);

setează culoarea curentă de desenare drept culoare_noua.


void far setpalette(int index, int culoare);

arată poziţia din paletă unde se setează culoarea culoare.

UNCŢII PENTRU DESENARE ŞI UMPLERE

FUNCŢII PENTRU DESENARE

void far arc(int x, int y, int unghi_inceput,


int unghi_sfarsit, int raza);
Capitolul 13 – Funcţii video C 280

desenează un arc de cerc de rază raza, începând cu unghi-


inceput şi terminând cu unghi-sfarsit. Cele două unghiuri

pot lua valori între 0 şi 360 grade. Când unghi-inceput este 0,


iar unghi-sfarsit 360 grade se obţine un cerc.
void far circle(int x, int y, int raza);

desenează un cerc de rază raza şi centru (x,y).


void far drawpoly(int nr, int far *tab_coord);

desenează o linie frântă care trece prin nr puncte ale căror


coordonate se află în tabelul tab_coord de forma:
tab_coord[nr][2];

Se uneşte punctul de coordonate


(tab_coord[0][0],tab_coord[0][1]) cu punctul de coordonate
tab_coord[1][0],tab_coord[1][1] ş.a.m.d..
void far getaspectratio(int far *xaspect,
int far *yaspect);

permite obţinerea valorilor pentru parametrii xaspect şi


yaspect. Aceste valori folosesc la corecta dimensionare a

desenelor, care, în mod normal ies deformate din cauza


diferenţei (întâlnite la majoritatea adaptoarelor) între lungimea
şi lăţimea unui pixel.
void far getlinesettings(struct linesettingstype far
*inf_line);

oferă informaţii despre stilul, modelul şi lungimea liniei prin


intermediul pointerului inf_line. Acest pointer arată către
structura linesettingstype declarată în ”graphics.h”.
Capitolul 13 – Funcţii video C 281

int far getx(void);


int far gety(void);

întorc coordonatele (x,y) ale punctului curent.


void far line(int x1, int y1, int x2, int y2);

desenează o linie din punctul de coordonate (x1,y1) în punctul


de coordonate (x2,y2).
void far linerel(int dx, int dy);

desenează o linie între punctul curent de coordonate (x,y) şi


punctul de coordonate (x+dx,y+dy).
void far lineto(int x, int y);

desenează o linie între punctul curent şi punctul de coordonate


(x,y).
void far moverel(int dx, int dy);

mută poziţia curentă din punctul de coordonate (x,y) în punctul


de coordonate (x+dx,y+dy).
void far moveto(int x, int y);

mută poziţia punctului curent în punctul de coordonate (x,y).


void far rectangle(int x1, int y1, int x2, int y2);

desenează un dreptunghi cu colţul din stânga-sus în punctul


de coordonate (x1,y1) şi colţul din dreapta-jos în punctul de
coordonate (x2,y2).
void far setlinestyle(int stil, unsigned model,
int grosime);

setează stilul şi grosimea liniei. Parametrul stil poate lua una


din valorile 0,1,2,3,4 sau corespunzător constantele
simbolice: SOLID_LINE (linie continuă), DOTTED_LINE (linie
Capitolul 13 – Funcţii video C 282

punctată), CENTER_LINE (linie centrată), DASHED_LINE (linie


întreruptă), USERBIT_LINE (linie definită de utilizator). Dacă
stilul este USERBIT_LINE trebuie dat modelul utilizatorului în
parametrul model, sub forma unui cod pe 16 biţi. Parametrul
grosime poate lua 2 valori: NORM_WIDTH (sau 1) şi THICK_WIDTH

(sau 3) pentru 1 pixel lăţime respectiv 3 pixeli lăţime.

FUNCŢII PENTRU UMPLERE

void far bar(int x1, int y1, int x2, int y2);

desenează şi umple o bară delimitată de punctul (x1,y1)


stânga-sus şi (x2,y2) dreapta-jos.
void far bar3d(int x1, int y1, int x2, int y2,
int adancime, int capac);
desenează şi umple o bară tridimensională delimitată de
punctul (x1,y1) stânga-sus, (x2,y2) dreapta-jos şi având
adancime pixeli. Dacă capac are valoarea 0 se desenează şi

>capacul> barei, iar în caz contrar nu. Ultima variantă permite


plasarea în >stivă> a mai multor bare.
void far fillellipse(int x,int y, int razax,
int razay);

desenează şi umple o elipsă de centru (x,y) şi raze razax şi


razay.
void far fillpoly(int nr, int far *tab_coord_poligon);

desenează şi umple un poligon ale cărui vârfuri au


coordonatele în punctele tabloului tab_coord_poligon.
void far floodfill(int x,int y, int culoare_contur);
Capitolul 13 – Funcţii video C 283

umple suprafaţa care conţine punctul (x,y) şi este mărginită de


un contur de culoare, culoare_contur.
void far getfillsettings(struct fillsettingstype,
far *filinfo);

oferă informaţii despre modelul curent de umplere şi culoarea


de umplere. Structura fillesettingstype este declarată în
”graphics.h” şi are forma:
struct fillsettingstype
{
int patern;
int color;
};
void far pieslice(int x, int y, int unghi_inceput,
int unghi_sfarsit, int raza);
desenează şi umple un sector de cerc de centru (x,y), rază
raza, delimitat de unghi_inceput şi unghi_sfarsit.
void far sector(int x,int y, int raza_x, int raza_y,
int unghi_inceput, int unghi_sfarsit);
desenează şi umple un sector de elipsă de centru (x,y), de
raze raza_x şi raza_y delimitat de unghi_inceput şi
unghi_sfarsit.
void far setfillpattern(char *model, int culoare);

defineşte un model de umplere al utilizatorului.


void far setfillstyle(int model, int culoare);

setează un model de umplere şi o culoare de umplere.


Exemple de modele de umplere: EMPTY_FILL (sau 0) - umplere
cu culoarea de fond, SOLID_FILL (sau 1) - umplere uniformă,
LINE_FILL (sau 2) - haşură orizontală etc..

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