Documente Academic
Documente Profesional
Documente Cultură
Introducere
Programare
3
4 Programare C/C++
Sisteme de operare
Un sistem de operare este un program care controlează întreaga
operare asupra sistemului de calcul. Toate operaţiile de intrare / ieşire, pe
care le realizează un calculator sunt efectuate prin intermediul sistemului de
operare. El administrează, de asemenea, resursele calculatorului şi trebuie să
manipuleze execuţia programelor.
Unul dintre cele mai răspândite sisteme de operare este UNIX, care
se poate găsi pe multe tipuri de calculatoare. Iniţial sistemele de operare
erau, de obicei, asociate cu un tip de calculator; deoarece UNIX este scris, în
primul rând, în C, şi face puţine presupuneri asupra arhitecturii
calculatorului, el este portabil, cu succes, pe diferite calculatoare, cu
minimum de modificări.
Alte sisteme de operare, larg răspândite, cum ar fi MS-DOS,
Windows’95, au fost dezvoltate, în primul rând, pentru calculatoarele IBM
PC.
Compilatoare
Un compilator analizează un program dezvoltat într-un anumit
limbaj de programare şi apoi îl translatează într-o formă adecvată pentru
execuţie, pe un anumit calculator.
Pentru rezolvarea unei anumite probleme, cu ajutorul calculatorului,
trebuie stabilit ce trebuie să realizeze programul respectiv, adică ce
informaţii va include programul, ce calcule trebuie să realizeze şi ce
informaţii trebuie să furnizeze utilizatorului. La acest nivel de planificare,
trebuie gândit în termeni general, nu în termenii unui anumit limbaj de
programare.
Odată definită diagrama conceptuală a programului, se va decide
asupra limbajului de implementare, în funcţie de modul de reprezentare a
datelor şi metodelor de prelucrare, a interfeţei cu utilizatorul, a modului de
organizare a programului. Evident, noi vom alege pentru implementare
limbajul C, care reprezintă scopul acestui curs.
În figura următoare sunt prezentate etapele necesare pentru
introducerea, compilarea şi execuţia unui program dezvoltat în limbajul C.
5
6 Programare C/C++
Programul ce va fi compilat este, mai întâi, editat într-un fişier, denumit, în
general, printr-un nume sugestiv, urmat de extensia c sau cpp, pentru C++,
(deci: nume_fişier.c, sau nume_fişier.cpp), care specifică, că acel fişier
conţine un program C.
Pentru editarea lui se poate utiliza orice editor:
- editorul inclus în mediul de integrat de dezvoltare de programe C,
Turbo C, Borland C++, sau pentru UNIX unul dintre editoarele: vi,
edit, sau emacs. În cazul utilizării unui astfel de editor, extensia
fişierului, dacă nu este specificată de utilizator, va fi, în mod
automat, pusă de către editor.
- indiferent de editorul utilizat, trebuie creat un fişier simplu, care să
nu conţină decât text (de exemplu în WS se utilizează modul
”NonDocument”, iar în WP “Text In/Out”, pentru a salva fişierul în
format DOS).
Acest program, salvat în fişierul specificat, este denumit program sursă,
deoarece reprezintă forma originală a programului, exprimată în limbajul C.
După editarea programului urmează compilarea sa, care se realizează
prin apelarea compilatorului printr-o comandă specifică; sub UNIX această
comandă este:
cc nume_fişier.c
În primul pas al procesului de compilare, acesta verifică fiecare
declaraţie, dacă este conformă cu sintaxa şi semantica limbajului. Dacă sunt
descoperite erori, în această fază, ele sunt raportate utilizatorului şi
compilarea ia sfârşit în această fază. Erorile sunt, apoi, corectate în
programul sursă şi se reia procesul de compilare.
Programare C/C++ 7
7
8 Programare C/C++
În final se realizează “editarea de legături” (link). De obicei acest
pas este realizat automat (sub UNIX, când se utilizează comanda cc). Alte
sisteme de operare pot cere emiterea unei comenzi specifice, pentru a realiza
acest task. Scopul acestei etape este de “editare de legături” este de a
furniza programul în forma finală, executabilă pe calculator. Dacă
programul curent utilizează alte programe, care au fost procesate anterior de
compilator, atunci în cadrul acestei etape programele sunt ”legate”
împreună. De asemenea sunt căutate şi legate împreună (adică editate
legăturile) cu programul obiect, şi programele din biblioteca de programe a
sistemului.
Fişierul obiect, cel cu extensia .o, sub UNIX, este şters de editorul de
legături, după furnizarea programului executabil, dacă nu se fac referiri la
alte fişiere (programe). Totuşi, dacă programul original utilizează mai multe
fişiere sursă, programe multifişiere, fişierele ce conţin cod obiect sunt
salvate. Spre deosebire de compilatorul de C de sub UNIX, cel de sub DOS
(Windows) nu şterge fişierul obiect, când se obţine fişierul executabil.
Fişierul final, rezultat după această etapă, este în format obiect
executabil, şi el este memorat într-un alt fişier, gata de a fi executat (rulat).
Sub DOS, el va fi denumit cu acelaşi nume, dar cu extensia .exe
(nume_fişier.exe). Sub UNIX, el este denumit, în mod implicit a.out. Pentru
execuţie, comanda constă în tastarea numelui fişierului executabil, care are
ca efect “încărcarea” programului cu numele respectiv, în memoria
calculatorului şi începerea execuţiei sale. Pentru a vizualiza, sub UNIX,
aceste fişiere, se poate utiliza comanda ls, care afişează fişierele din
directorul curent. Dacă dorim să păstrăm fişierul executabil, a.out, trebuie
să-l renumim (cu un nume nou), altfel acest fişier va fi înlocuit printr-un nou
fişier a.out, la următoarea compilare a unui alt program.
Când se execută programul, fiecare declaraţie a sa este executată
secvenţial. Dacă programul solicită date de la utilizator, denumite intrări
(input), programul este temporar suspendat pentru introducerea datelor.
Rezultatele afişate de program, denumite ieşiri (output), vor fi afişate la
terminalul de la care a fost executat programul.
Dacă programul nu furnizează rezultatele dorite, va fi necesară
reanalizarea logicii programului; această etapă este denumită “etapa de
depanare”, în care se elimină toate erorile logice. De obicei, acestea sunt
eliminate prin modificări făcute în programul sursă, după care se reia
întregul proces de compilare, editare de legături şi execuţie, până se obţin
rezultatele dorite (corecte).
Programare C/C++ 9
{
int numar; /* declară variabila */
numar=1; /* atribuire valoare */
printf("%d este primul numar \n",numar); /* apel funcţie */
}
/* exemplul 1b */
#include <stdio.h>
main() /* un program simplu */
{
int num; /* se defineşte variabila num */
num = 1; /* se iniţializează variabila num cu valoarea 1 */
printf(”Este un simplu”); /* utilizarea funcţiei printf() */
printf(“exemplu.\n”);
printf(“Numarul favorit este %d, deoarece”);
printf(“este primul.\n”, num);
}
9
10 Programare C/C++
După execuţia acestui program se va afişa următorul mesaj:
Este un simplu exemplu.
Numarul favorit este 1, deoarece este primul.
Vom exemplifica, tot printr-un exemplu simplu, care tipăreşte un mesaj şi o
valoare numerică, folosirea şi a celor două proceduri des utilizate în C++
cin şi cout.
#include <iostream.h>
#include <conio.h>
/*
se afiseaza: Un exemplu de program simplu C++
1 este primul numar
*/
void main(void)
{
int numar;
numar = 1;
cout << "Un exemplu de program simplu C++ " << endl;
cout << numar << " este primul numar" << endl;
getch();
}
11
12 Programare C/C++
Structura generală a unui program C este următoarea:
- #include
- main()
|--------- declaraţii
- function a()
|--------- declaraţii
- function b()
|---------declaraţii:
declaraţii - cuvinte cheie,
cele 5 tipuri de asignări
declaraţii, în funcţie - date,
limbajul C: control
null - operatori
13
14 Programare C/C++
În exemplul anterior fişierul stdio.h, care este furnizat ca parte a
compilatorului C, conţine informaţii despre funcţiile de intrare / ieşire
utilizate de compilator; numele său provine de la standard input / output
header (adică modul antet standard de intrare / ieşire).
Utilizatorii C fac referire la o colecţie de informaţii care se află la
începutul unui fişier, denumit fişier antet (header); implementările tipice
încep cu mai multe module antet. Efectul declaraţiei #include este de a
include acest fişier, adică acelaşi cu copierea întregului conţinut al fişierului
stdio.h în fişierul nostru, la începutul acestuia. În acest modul se află, de
exemplu, funcţia printf. Compilatorul va include doar informaţiile necesare
programului. Această directivă, #include, este adresată preprocesorului C.
Orice informaţie pe care compilatorul nu o utilizează nu devine parte a
programului. Simbolul # identifică această linie ca pe o directivă, nu ca pe o
declaraţie propriu-zisă, şi care este rezolvată de preprocesorul C;
preprocesorul manipulează anumite task-uri înainte de prelucrarea lor de
către compilator.
Programare C/C++ 15
2.2.1. Identificatori
15
16 Programare C/C++
Astfel pe un sistem cu limita de 8 caractere, identificatorii:
variabila1 şi variabila2
vor fi consideraţi ca una şi aceeaşi variabilă: variabil (adică vor fi citite
numai primele 8 caractere). Multe dintre rutinele de bibliotecă utilizează, de
obicei, nume ce încep cu simbolul subliniere, deoarece se presupune că
utilizatorul nu va alege, de obicei, nume ce încep cu acest simbol, şi deci va
utiliza aceste nume cu alte semnificaţii.
Aceşti identificatori trebuie să fie diferiţi de următoarele cuvinte
cheie (rezervate) ale limbajului:
2.2.2. Constante
Constante întregi
O constantă întreagă este o secvenţă de cifre, care poate fi precedată,
sau nu, de semnul + sau - (operatorii binari + sau -, cei care precizează
semnul unei expresii).
Dacă constanta începe cu cifra 0, ea este considerată o constantă
octală (deci, exprimată în baza 8), în care caz toate cifrele care urmează
trebuie să fie între 0 şi 7.
Dacă constanta este precedată de secvenţa 0x sau 0X, ea este o
constantă hexazecimală, şi cifrele care urmează pot fi în domeniul 0-9, sau
a-f (sau A-F); aceste litere (mici, a-f, sau mari, A-F) sunt utilizate pentru a
Programare C/C++ 17
codifica valorile de la 10 la 15 (10=a,…,15=f), care trebuie exprimate, în
hexazecimal, şi care trebuie să ocupe, ca în orice sistem de numeraţie
poziţional, o singură poziţie.
În orice alt caz, constanta întreagă este considerată o constantă
zecimală. O constantă întreagă este reprezentată, în general, pe 2 octeţi.
Dacă constanta zecimală depăşeşte tipul întreg, de bază, se consideră de tip
long int, sau dacă nu este suficient, va fi de tip unsigned long int, şi în acest
caz este reprezentată pe 4 octeţi.
O constantă octală sau hexazecimală care depăşeşte cel mai mare
întreg fără semn de tipul int se consideră, de asemenea, de tip unsigned int,
iar dacă nu este suficient acest format va fi de tip long int, şi dacă şi acest
format nu este suficient, va fi de tip unsigned long int.
Dacă constanta este urmată de litera l sau L, atunci ea este de tip
long int. Dacă valoarea nu poate fi reprezentată de acest tip, ea este tratată
ca unsigned long int.
Dacă constanta este urmată de litera u sau U, atunci ea va fi tratată
ca unsigned int, sau unsigned long int, dacă nu poate fi reprezentată de
primul tip.
Dacă se utilizează ambele sufixe (lu sau LU), atunci constanta
întreagă va fi de tip unsigned long int.
Constante reale
Constantele reale pot fi reprezentate în două moduri:
- în forma normală, adică prin partea întreagă, ce poate fi precedată
de semnul plus sau minus, un punct zecimal şi partea fracţionară.
De exemplu: -12.456
- în forma cu exponent, adică forma precedentă, urmată de litera e
sau E, şi, opţional, de un exponent, care este un întreg, cu semn.
De exemplu:
-1245.6e-2 (sau -1245.6E-2)
şi care reprezintă, de fapt, valoarea:
-1245.6 * 102 , adică -12.456.
În ambele reprezentări partea întreagă şi cea fracţionară sunt constituite
dintr-o succesiune de cifre. La o constantă reală, partea întreagă sau cea
fracţionară pot lipsi, dar nu amândouă simultan; de asemenea, poate lipsi
punctul zecimal, sau litera e (E) şi exponentul.
Constantele reale sunt tratate ca valori cu precizie dublă, adică de tip
double. Dacă dorim ca o valoare de tip real să fie considerată de tipul float,
atunci ea trebuie urmată de litera f sau F. Dacă sunt urmate de litera l sau L,
vor fi considerate de tip long double.
Constante caracter
17
18 Programare C/C++
O constantă de tip caracter este reprezentată de un caracter inclus
între apostrofuri, cum ar fi de exemplu: ’x’. Valoarea unei constante de tip
caracter este valoarea numerică a codului caracterului, din codificarea
ASCII. De exemplu, iată câteva valori asociate unor caractere:
‘0’ - 48 (30H)
....
‘9’ - 57 (39H)
‘A’ - 65 (41H)
‘B’ - 66 (42H)
....
‘a’ - 97 (61H)
‘b’ - 98 (62H)
....
Deoarece codurile asociate sunt succesive, şi valorile ce vor fi asociate sunt
succesive (pentru cifre, litere mari şi litere mici, respectiv).
Constantele caracter pot participa la operaţiile aritmetice, ca şi
oricare alte numere.
Când caracterul care urmează după backslash (\) nu este unul dintre
cele specificate, atunci backslash-ul este ignorat. Toate caracterele setului
ASCII (American Standard Code for Information Interchange) sunt pozitive,
Programare C/C++ 19
sau mai exact sunt fără semn, dar o constantă caracter specificată printr-o
secvenţă de editare poate fi şi negativă (de exemplu ‘\377’ are valoarea -1).
Unele compilatoare de C permit reprezentarea de caractere, ale căror
coduri ocupă 2 sau mai mulţi octeţi. Reprezentarea unor astfel de caractere
începe cu litera L. De exemplu:
L ‘\101\102\103\104’ (ABCD)
este un caracter multi-octet, al cărui cod este memorat pe 4 octeţi. Tipul
acestor constante este wchar_t, cum este definit în fişierul antet standard
<stddef.h>, şi permite exprimarea unui caracter, dintr-un set de caractere, ce
nu pot fi reprezentate cu tipul normal char.
Constante simbolice
O constantă simbolică este un identificator cu valoare de constantă;
valoarea constantei poate fi orice şir de caractere, introdus prin construcţia
(declaraţia) #define, ca de exemplu:
#defineMAX 100
Compilatorul va înlocui toate apariţiile constantei simbolice MAX, cu
valoarea 100. Numele constantelor simbolice se scriu, de obicei, cu litere
mari, fără însă a fi obligatoriu, spre a le deosebi de alţi identificatori.
Constante enumerare
Un identificator care a fost declarat ca o valoare pentru un tip
enumerare este considerat o constantă de un tip particular şi este, de altfel,
tratat ca fiind de tipul int, de către compilator.
19
20 Programare C/C++
există o limită a lungimii unui şir; programele trebuie să parcurgă şirul,
analizându-l pentru a-i determina lungimea.
La alocare memoria fizică necesară este cu un octet mai mult decât
numărul de caractere, scris între ghilimele, datorită adăugării automate a
caracterului ‘\0’, la sfârşitul fiecărui şir.
În mod normal, compilatorul furnizează un pointer (referinţă, adică
adresa de început a şirului) către primul caracter din şir, şi tipul său este
“pointer la char”(char *). Constantele de tip şir de caractere nu pot fi
modificate de program.
Preprocesorul concatenează împreună, automat, constantele de tip şir
de caractere, adiacente. Şirurile pot fi separate sau nu prin blancuri (spaţii
libere):
“un ” “sir ” “de ” “caractere”
Cele trei şiruri sunt echivalente cu un singur şir:
“un sir de caractere”
Iată un exemplu de program simplu, ce utilizează şiruri de caractere:
Observaţii:
1) Funcţia scanf citeşte caracterele până întâlneşte unul dintre
caracterele: spaţiu liber, tab, sau linie nouă (newline). Pentru a citi
mai multe cuvinte (propoziţii) ca un şir, se poate folosi o altă funcţie
de citire gets(), pentru a manipula şiruri, în general.
2) Şirul “x” nu este acelaşi cu caracterul ‘x’. O primă diferenţă este că
‘x’ este un tip de bază (char), iar “x” este un tip derivat, un tablou de
char. O a doua diferenţă este că ‘x’ este un singur caracter, un singur
octet, în care se află codul ASCII al literei x, în timp ce “x” este un
şir de caractere, mai exact doi octeţi: primul este ‘x’, iar cel de-al
doilea este caracterul NULL (‘\0’).
Programare C/C++ 21
Pentru a determina numărul efectiv de caractere dintr-un şir se poate utiliza
funcţia strlen().
În exemplul următor, vom utiliza această funcţie, dar şi operatorul
sizeof, pentru a vedea care este diferenţa dintre aceştia:
21
22 Programare C/C++
Dacă un comentariu apare în interiorul unei constante de tip caracter
sau şir de caractere, el nu este tratat ca separator.
Se pot utiliza şi şiruri de caractere reprezentate pe mai mulţi octeţi,
nu doar pe unul, ca în cazul obişnuit. În acest caz şirul va fi precedat de
litera L :
L “ABCD” este echivalent cu vectorul (şirul):
{L ‘A’, L ‘B’, L ‘C’, L ‘D’, ‘\0’}, care conţine 5 valori de
tipul wchar_t.
2.2.4. Operatori
2.2.5. Separatori
23
24 Programare C/C++
Modificatori Semnificaţie
- Este tipărit la începutul (stânga) câmpului respectiv.
Exemplu: %-20s
+ Valorile cu semn sunt afişate cu semnul + sau -, după
cum sunt pozitive sau negative. Exemplu: %+6.2f
Spaţiu Afişează un spaţiu pentru valorile pozitive când nu se
utilizează semnul +.
Exemplu: printf(“$ %6.2f”, 10) va tipări $ 10.00
# Foloseşte o formă alternativă pentru specificarea
conversiei. Furnizează un 0 iniţial pentru forma o, un
0x iniţial sau 0X pentru formele x şi X. Pentru toate
formele în virgulă mobilă, garantează tipărirea
caracterului punct (.), chiar dacă nu sunt cifre după
punct (pentru forma g sau G previne ca zerourile ce
urmează să fie şterse(înlocuite)). Exemple: %#o,
%#8.0f, %#10.3E
0 Pentru formele numerice umple câmpul cu zerouri, la
început, în loc de spaţii. Acest indicator este ignorat
dacă este prezent -, sau dacă pentru un întreg se
specifică o precizie. Exemple: %010d, %08.3f
dim_câmp O valoare ce reprezintă dimensiunea minimă a
câmpului în care se afişează. Dacă valoarea este mai
mare se extinde la numărul de caractere necesare
pentru a tipări valoarea. Exemplu: %4d
.precizie Reprezintă numărul de zecimale cu care se afişează o
valoare reală (virgulă mobilă) pentru specificatorii e,
E şi f. Pentru f şi G reprezintă numărul maxim de
cifre semnificative. Pentru s reprezintă numărul
maxim de caractere de tipărit. Pentru conversiile
întregi reprezintă numărul minim de cifre; vor fi
utilizate zerouri în faţa numărului pentru a satisface
acest număr minim de cifre. Utilizarea uneori a
punctului (.) este echivalentă cu .0. Exemplu: %5.2f-
afişează real cu două zecimale într-un câmp de cinci
caractere.
h Utilizat cu un specificator întreg pentru a indica o
valoare de tip short int sau unsigned short int.
Exemplu: %hu, %hx, %6.4hd
l Utilizat cu o conversie întreagă pentru a indica o
25
26 Programare C/C++
valoare long int sau unsigned long int.
Exemple: %ld , %8lu
L Utilizat cu o conversie de număr real (virgulă mobilă)
pentru a indica o valoare long double. Exemple: %Lf,
% 10.4Le
Funcţia scanf ()
Această funcţie este una din cele mai folosite, în C, pentru citirea
unei varietăţi de date. Orice tip de valoare tastată de la tastatură reprezintă
un text format din caractere. Dacă vrem să memorăm aceste caractere ca o
valoare numerică programul trebuie să convertească şirul de caractere la o
valoare numerică. Această operaţie este realizată de funcţia scanf() care
converteşte în diferite forme: întregi, numere în virgulă mobilă (reale),
caractere şi şiruri. Într-un fel este inversul funcţiei printf() .
În mod asemănător cu printf(), funcţia scanf() utilizează un şir de
caractere de control urmate de o listă de argumente. Caracterele de control
(sau specificatori de conversie) indică în ce formate este convertită intrarea.
Dar spre deosebire de printf(), care utilizează nume de variabile,
constante şi expresii, scanf() utilizează pointeri la variabile.
Pentru aceasta numele variabilei căreia i se atribuie unul din tipurile
de bază, trebuie precedat de semnul &. Excepţia de la această regulă o
constituie citirea unei valori pentru o variabilă de tip şir de caractere:
char sir[30];
---------------
scanf(“%s”, sir);
Pentru a separa câmpurile, la introducerea datelor, se utilizează spaţii
albe (blancuri, taburi şi linie nouă).
Programare C/C++ 27
Se realizează separat conversia acestor câmpuri succesive, sărindu-se
spaţiile albe ce le despart. Valorile citite sunt asociate, în ordinea citirii,
variabilelor din lista de variabile asociată lui scanf(). Un câmp se poate
întinde pe mai multe linii până la întâlnirea unui spaţiu alb. Singura excepţie
de la această regulă este utilizarea specificatorului %c, cu care se citeşte
caracterul următor, chiar dacă el este spaţiu alb.
27
28 Programare C/C++
În mod asemănător funcţiei printf(), specificatorii de conversie pot fi
precedaţi de modificatori, care sunt prezentaţi, împreună cu semnificaţia lor
în tabela următoare.
Funcţia scanf() returnează numărul de elemente citite cu succes.
Dacă nu se citeşte nici un element, de exemplu se introduce un şir de
caractere nenumerice, când funcţia aşteaptă un număr, funcţia scanf()
returnează valoarea 0. Se returnează EOF, dacă detectează condiţia
cunoscută ca ”End of File” (sfârşit de fişier). EOF este o valoare specială
definită în fişierul stdio.h, având asociată valoarea -1 (#define EOF-1).
Deci pentru citirea corectă a unei valori numerice, de exemplu, se
poate utiliza un ciclu de forma:
while (scanf(“%d”, &numar) == 0);
care se va executa cât timp valoarea returnată de scanf(), adică
numărul de elemente citite, este 0.
Programare C/C++ 29
Modificatori Semnificaţie
* Suprimă atribuirea pentru funcţia scanf(); se utilizează
în printf() în locul specificatorilor de dimensiune de
câmp, deşi se va specifica dimensiune câmpului, dar
printr-un argument
Exemplu: printf(“numarul real:%*.*f\n”, lung_camp,
precizie, numar);
printf(“intreg:%*d\n”, lung_camp, valoare);
Când se utilizează în scanf() determină ca funcţia să
sară peste intrarea respectivă.
Exemplu: scanf(“%*d %*d %d, &n);
va avea ca efect omiterea primilor 2 întregi şi copierea
celui de-al 3-lea în n.
Valoare Reprezintă dimensiunea maximă a câmpului. Citirea se
întreagă opreşte când se atinge dimensiunea maximă sau când
(pentru şir de se întâlneşte primul caracter spaţiu alb.
caractere) Exemplu: %10s
h, l, L %hd, %hi valoarea va fi automat de tip short int
%ho, %hx, %hu - unsigned short int
%ld, %li - long int
%lo, %lx, %lu - unsigned long int
%le, %lf, %lg - double
Utilizând L în loc de l cu e, f, g va fi de tip long double.
În absenţa acestor modificatori: d, i, o şi x - vor
memora valoarea de tip int;
e, f, g - indică tipul float
29
30 Programare C/C++
#include <iostream.h>
void valori(int sir[], int numar_de_elemente)
/* aceasta functie citeste un sir de “numar_de_elemente” valori de
tip interg */
{
int i;
void main(void)
{
int numar[3];
valori(numar, 3); /* se citeste un vector de 3 valori intregi */
cout << "Valorile sirului sunt" << endl;
for (int i = 0; i < 3; i++) /* dupa care se tiparesc */
cout << numar[i] << endl;
}
Programare C/C++ 31
3.Variabile şi tipuri de date.
31
32 Programare C/C++
&. Dacă nu apare acest simbol, în faţa variabilei, atunci se va citi o valoare
reprezentând şirul de caractere introdus (bineînţeles, în acest caz, variabila
trebuie declarată de tip caracter).
3.1.2.Tipul real
33
34 Programare C/C++
funcţia printf ( ) va decide dacă numărul este afişat în forma normală, sau
cea cu exponent. Decizia se bazează pe valoarea exponentului: dacă acesta e
mai mic decât –4, sau mai mare decât 5, se utilizează formatul cu exponent
(%e), altfel se utilizează formatul normal (%f).
În afara formatului float, pentru reprezentarea numerelor reale se
mai pot utiliza tipurile double şi long double. Diferenţele între aceste tipuri
constau în numărul de biţi pe care se face reprezentarea internă, a mantisei şi
exponentului, şi vor corespunde la domenii diferite de valori, ca în tabelul
următor :
Reprezentarea internă (nr.
Abreviere biţi), nr. biţi pentru Domeniul de valori
mantisă / exponent şi
precizia
float 32 ( 23 / 8 ), 8 cifre exacte 3.4e –38 /3.4e+38
double 64 (52 / 11), 15 cifre exacte 1.7e-308/1.7e+308
long double 80 (64 / 15 ), 19 cifre exacte 3.4e-4932 / 1.1e+4932
35
36 Programare C/C++
este numărat ca un caracter. Nu este singurul este exemplu de acest tip;
acestea au fost descrise anterior şi sunt denumite secvenţe de evitare.
Caracterele sunt memorate, în calculator, sub forma unor coduri
numerice (binare); pentru reprezentarea internă a caracterelor se utilizează
codul ASCII (American Standard Code for Information Interchange),
reprezentat pe un octet (8 biţi), din domeniul de valori întregi 0-255. În acest
cod, de exemplu:
litera A este reprezentată prin valoarea întreagă 65,
B- 66, ş.a.m.d.;
litera a este reprezentată prin valoarea 97, b- 98,
ş.a.m.d, sau
cifrele 0-9 prin valori întregi în intervalul 48-57.
Setul standard de caractere ASCII (litere mari şi mici sau cifre şi
caractere speciale) este reprezentat în intervalul de valori întregi 0-127;
domeniul de valori întregi 128-255 reprezintă setul extins de caractere
ASCII.
Pentru a afişa un caracter, asociat unei variabile de tip char, utilizând
funcţia printf ( ) se vor utiliza caracterele %c. Acest format de tipărire, %c,
specifică funcţiei printf ( ) să convertească întregul, corespunzător
variabilei, la caracterul corespunzător.
De exemplu, următorul program, afişează codul zecimal
corespunzător unei taste:
/* codtasta.c- afiseaza codul numeric ASCII pentru un caracter */
#include <stdio.h >
main ( )
{
char car ;
printf (“Apasati o tasta : \n “ ) ;
scanf (“%c, &car ) ; /* citeste codul tastei (caracterului) */
printf (“Codul tastei (caracterului ) \“%c\“ este %d (zecimal), %x”
“ (hexa) %o (octal)\n“, car, car , car, car);
}
37
38 Programare C/C++
122Lu , 0xfffffLu ,
07537Lu
float 12.33f, 3.1e–5F %f, %e, %g
double 12.34 , 3.1e - 5 %f, %e, %g (sau %lf,%le,
%lg)
long double 12.35e , 3.3e - 52 %Lf , %Le, %Lg
39
40 Programare C/C++
41
42 Programare C/C++
+,- Adunare, scădere (operatori binari ) S -> D
<< , >> Deplasare stânga , dreapta S -> D
< Mai mic . S -> D
<= Mai mic sau egal .
> Mai mare .
>= Mai mare sau egal .
== Egalitate S -> D
!= Inegalitate (diferit)
& “ŞI“ bit cu bit S -> D
^ “SAU exclusiv “ , bit cu bit S -> D
| “SAU“ , bit cu bit S -> D
&& ŞI logic S -> D
|| SAU logic S -> D
?: Operator condiţional D -> S
=
*= , /= ,
%= Operatori de atribuire cu variantele . D -> S
+= , -=
&= , ^= ,
|=
<<= , >>=
, Operator secvenţiere ( , ) S -> D
Operatorii aritmetici
Operaţiile uzuale de adunare, scădere, înmulţire şi împărţire se
realizează cu simbolurile folosite de toate limbajele pentru aceste operaţii :
+ , - , * şi /.
Primele două simboluri pot fi folosite şi ca operatori unari (adică cu
un singur operand) pentru a stabili semnul unui operand.
Operatorul / este utilizat atât pentru împărţire de valori reale cât şi
pentru valori întregi, în care caz câtul este trunchiat la un întreg, iar restul
împărţirii a doi întregi e furnizat de operatorul : % - operator
modulo.
Tot în această categorie intră şi operatorii de incrementare, respectiv
decrementare :
++ (increment)
-- (decrement)
care realizează incrementarea / decrementarea valorii operandului cu 1.
Operatorii pot fi utilizaţi în două moduri, diferenţiate după locul plasării
operatorului şi momentul incrementării, astfel:
Programare C/C++ 43
- modul prefixat, în care operatorul e plasat în faţa variabilei afectate; în
acest caz variabila e modificată înainte de utilizarea ei în expresie;
- modul postfixat, în care operatorul e plasat după variabila respectivă,
în acest caz variabila este folosită cu valoarea ei în expresia respectivă şi
apoi este modificată (incrementată /decrementată).
De exemplu, să considerăm o expresie în forma prefixată:
b =2*++a; // ordinea operaţiilor este următoarea:
1) se incrementează a; 2) se înmulţeşte a cu 2; 3) valoarea astfel obţinută
se atribuie lui b;
Dacă aceeaşi expresie o scriem în forma post fixată :
b = 2*a++; //ordinea operaţiilor va fi următoarea:
1) a*2 ; 2) b=a*2 ; 3) a=a+1;
Operaţiile de incrementare / decrementare pot fi realizate în mod
explicit sub forma:
a = a+1 pentru incrementare şi
a = a-1 respectiv pentru decrementare
Alte exemple:
y=2;
n=3;
s = ( y+ n-- )*6 ;
după această instrucţiune s = ( 2 + 3 ) * 6 = 30, iar n = 2.
Alte exemple: y=++x; adică: x=x+1; y=x;
y=x++; adică: y=x; x=x+1;
Nu este recomandabil să se încerce efectuarea de mai multe operaţii
simultan cu incrementarea / decrementarea; de exemplu funcţia:
printf (“%10d , %10d”num, num*num++)
poate afişa: 5, 25 sau 6, 25, în cazul num =5.
Compilatorul de C are libertatea în alegerea ordinii de evaluare a
argumentelor unei funcţii: care sunt primele evaluate. Această libertate
creşte eficienţa compilatorului dar poate produce necazuri dacă se
utilizează ca argument un operator de incrementare.
O altă sursă de erori este o declaraţie de forma :
a= n/2 +5 *(1+n++) ;
în care problema este că, compilatorul nu realizează operaţiile în aceeaşi
ordine pe care o aveţi în minte; adică ordinea de scriere (stânga ->
dreapta) ci în ordinea asociată operatorilor, adică depinde de compilator.
Aici se face în această ordine, dacă se mai pune o paranteză:
a=n/2 +(5* (1+n++) ), se va începe cu (1+n++) iar n-ul
utilizat în operaţia n/2 va fi acesta.
O altă sursă de erori este cazul următor :
n=3 ;
a=n++ +n++;
43
44 Programare C/C++
Valoarea lui n după această instrucţiune va fi sigur 5, dar valoarea pentru
a nu e sigură: se poate utiliza valoarea curentă a lui n în calculul lui a şi
apoi să-l incrementeze de două ori (a=6, n=5), sau se poate utiliza n-ul
curent, se incrementează n, se utilizează pentru al doilea n în expresie şi
apoi se incrementează n a doua oară (deci a=7 , n=5), sau orice altă
alegere.
Pentru a evita astfel de ambiguităţi trebuie evitate aceste situaţii :
1. Să nu se utilizeze operatorii de incrementare / decrementare pentru o
variabilă care apare de mai multe ori într-o expresie.
2. Să nu se utilizeze operatorii de incrementare / decrementare pentru o
variabilă care este parte de mai mult de un argument al unei funcţii;
(care apare în mai multe argumente).
Operatorii de atribuire
Există mai mulţi operatori de atribuire, care se grupează (asociază)
toţi de la dreapta la stânga. Operandul stâng este o valoare –stânga, iar
operandul drept este o expresie. Tipul rezultatului expresiei de atribuire
este tipul operandului (identificatorului) stânga. Rezultatul este valoarea
memorată în operandul stâng, după ce a avut loc atribuirea (adică după
evaluarea membrului drept).
Evaluarea unei expresii poate avea şi efecte laterale, concretizate
prin modificarea valorilor unei variabile care intră în cadrul expresiei ce se
evaluează şi este atribuită operandului stânga. De obicei, în celelalte
limbaje de programare, se vorbeşte despre efecte laterale dacă unul dintre
operanzi este un apel de funcţie, cu efecte laterale.
În C, atribuirea, care poate fi completată şi cu alţi operatori cu efect
de atribuire, oferă posibilitatea de a descrie mult mai compact, printr-o
singură expresie, secvenţe de prelucrări ce ar necesita mai multe
instrucţiuni de atribuire.
Acest avantaj, prezintă însă pericolul ca printr-un abuz de
compactare, facilitate oferită de C, să se scrie programe greu de înţeles şi
cu eventuale erori datorită conversiilor automate sau precedenţei şi
asociativităţi operatorilor.
Pentru a realiza această prelucrare trebuie specificat: adresa de
memorie afectată (valoare–stânga), şi expresia ce se evaluează (valoare-
dreapta) a cărei valoare se va memora la adresa specificată.
Pe lângă operatorul de atribuire, de bază, de forma
valoare_stânga = expresie
el mai poate fi completat şi cu alţi operatori, cu efect de atribuire, de
formele următoare:
valoare_stânga += expresie (adunare)
Programare C/C++ 45
valoare_stânga -= expresie (scădere)
valoare_stânga *= expresie (înmulţire)
valoare_stânga /= expresie (împărţire)
valoare_stânga %= expresie (modulo, rest împărţire întreagă)
valoare_stânga >>= expresie (deplasare dreapta)
valoare_stânga <<= expresie (deplasare stânga)
valoare_stânga &= expresie (ŞI logic, la nivel de bit)
valoare_stânga ^= expresie (SAU exclusiv, la nivel de bit)
valoare_stânga |= expresie (SAU logic, la nivel de bit)
O expresie de forma
valoare_stânga Op= expresie
unde Op este unul dintre operatori +, - , * , / ,% , >> ,<< , & , ^ , |, se
interpretează ca fiind echivalentă cu expresia:
valoare_stânga = valoare_stânga [Op] expresie
în care valoare_stânga este evaluată o singură dată.
De exemplu expresia:
x *= y+1 este echivalentă cu x = x * (y+1)
Pentru operatorii += şi -=, operandul stâng poate fi şi un pointer, în care caz
operandul din dreapta este convertit la întreg. Toţi operanzii din dreapta şi
cei din stânga, care nu sunt pointeri, trebuie să fie de tip aritmetic.
Se pot realiza şi atribuiri multiple:
var1=var2 =expresie
Ordinea de evaluare : expresie
var2 <- expresie
var1 <- var2
45
46 Programare C/C++
3. Rangurile (ordinea) tipurilor, de la cel mai mare la cel mai
mic sunt:
long double, double, float, unsigned long int, long int, unsigned int,
int.
O singură excepţie poate să apară, dacă tipurile long int şi int au
aceeaşi dimensiune; în acest caz tipul unsigned int este înaintea
tipului long int, în lista anterioară. Tipurile short şi char, care nu
apar în această listă, au fost deja promovate la tipul int (sau
unsigned int).
4. Într-o declaraţie de atribuire, rezultatul final al calculelor este
convertit la tipul variabilei căreia i se atribuie valoarea. Această
operaţie poate reprezenta o promovare, descrisa anterior, sau
retrogradare, adică valoarea este convertită la un tip de “rang mai
mic”. O conversie la un “rang mai mic” poate duce la depăşiri sau
pierderi de precizie. De exemplu, atribuirea unei valori de tip real
unei variabile de tip întreg, conduce la trunchierea părţii subunitare,
sau chiar la depăşire dacă partea întreagă a valorii reale este mai
mare decât domeniul valorilor tipului întreg.
5. Pe lângă aceste conversii automate (implicite), programatorul
poate să precizeze tipul conversiei dorite, în mod explicit. Această
metodă de utilizare explicită a unui operator de conversie este
denumită cast (a mula) şi constă în precedarea expresiei cu numele
tipului dorit, între paranteze, astfel: (tip) expresie.
De exemplu, dacă dorim să împărţim două numere întregi a şi b, iar
rezultatul real să nu fie trunchiat, atunci cel puţin unul din cei doi
operanzi, trebuie convertit explicit la tipul real ca în expresia:
(float) a/b.
Acest operator poate fi utilizat şi în operaţiile de atribuire, pentru a
evita depăşirile sau trunchierile menţionate anterior.
Operatori relaţionali
Aceşti operatori permit verificarea unor condiţii, care reflectă relaţia
dintre două valori ce sunt comparate. Rezultatul unei comparaţii este o
valoare de tip logic: adevărat sau fals. În C, însă, o valoare întreagă 0 este
interpretată ca având valoarea logică fals, în timp ce orice valoare întreagă
diferită de 0 este interpretată ca adevărat.
Cei 6 operatori relaţionali sunt :
< (mai mic)
< = (mai mic sau egal)
> ( mai mare)
> = (mai mare sau egal)
= = (egalitate)
Programare C/C++ 47
!= (inegalitate, diferit de ).
Observaţie: să nu se confunde folosirea operatorului de atribuire = în locul
celui de egalitate (sau invers). Regula de asociativitate pentru aceşti
operatori (adică evaluarea expresiilor în care apar) este de la stânga la
dreapta.
Operatori logici
Operatorii logici au ca operanzi, în mod normal, expresii relaţionale,
care pot avea una din cele două valori logice: false (0) sau true (o valoare
diferită de 0). Dat fiind, însă, codificarea valorilor logice prin valori întregi,
operatorii logici se pot aplica şi expresiilor de tip întreg, pe lângă cele
relaţionale.
Operatorii sunt:
&& ŞI logic
|| SAU logic
! Negaţie
Expresiile logice sunt de forma:
expr1 && expr2 –rezultatul este adevărat dacă ambele
expresii sunt adevărate (adică dacă ambele sunt diferite de 0);
expr1 || expr2 – rezultatul este adevărat dacă cel puţin una
din expresii este adevărată (adică cel puţin o expresie diferită de 0);
! expr – dacă expresia este adevărată rezultatul este fals, şi
respectiv invers (adică 0->1, şi o expresie diferită de zero va conduce la un
rezultat 0).
Operanzii nu trebuie să aibă în mod obligatoriu acelaşi tip, dar
fiecare trebuie să aibă unul din tipurile fundamentale sau pointer. Rezultatul
este întotdeauna de tip “int”. Evaluarea expresiilor de acest tip se face,
pentru aceşti operatori, de la stânga la dreapta şi este suspendată dacă
valoarea expresiei este cunoscută la un moment dat, după evaluarea doar a
unor termeni ai expresiei. Astfel dacă primul operand este 0 pentru
operatorul ŞI logic sau diferit de 0 pentru operatorul SAU logic al doilea
operand nu mai este evaluat deoarece rezultatul e deja cunoscut, 0 în primul
caz şi respectiv 1 în cel de-al doilea.
Deci evaluarea expresiilor logice, în C, este întreruptă în momentul
în care valoarea rezultatului devine certă, adică unul sau mai mulţi operanzi
nu mai sunt evaluaţi. Din acest motiv operanzii din expresiile logice trebuie
corect folosiţi, dacă au efecte laterale.
Deci dacă într-o astfel de expresie apare şi o atribuire, aceasta
trebuie făcută înaintea evaluării expresiei, pentru a putea fi siguri că se
efectuează. De exemplu condiţia ca “pătratul de latură l1“ cu colţul stânga –
sus (x1, y1) să acopere un “pătrat de latură l2“, cu colţul stânga – sus (x2,
y2)este:
47
48 Programare C/C++
( x1 < x2 & & y1 < y2 & & (d = l1- l2 ) > 0,
poate să nu actualizeze calculul lui d (dacă x1 > = x2 sau y1 > = y2 ), care
poate fi, însă, necesar, ulterior.
Operatorul condiţional
Este un operator cu trei operanzi (ternar ) şi se utilizează astfel:
expr1 ? expr2 : expr3
Se evaluează prima expresie (expr1) şi dacă ea este adevărată (sau
diferită de 0) rezultatul este valoarea expresiei expr2, altfel rezultatul este
expr3, adică:
dacă expr1! = 0
atunci rezultat = expr2
altfel rezultat = expr3
El se utilizează în situaţiile în care există două variante de obţinere
a unui rezultat, din care se selectează una singură. De exemplu, expresia:
z = ( a >b) ? a : b ;
calculează maximul dintre a şi b şi–l atribuie lui z. Întotdeauna numai una
dintre expresiile a doua şi a treia este evaluată.
Determinarea a valorii absolute a unui număr:
x = (y < 0) ? – y : y ;
Alt exemplu: tipărirea intervalului întreg în care se află un număr real:
#include <stdio.h>
main ()
{
float num;
int limita1, limita2
printf ( ”Introduceti un numar : \n “ )
scanf ( “%f “ , &num ) ;
limita1 = num ;
limita2 = (limita1 > = 0 ) ? limita1+1 : limita1 - 1;
(limita1>=0)? printf (“numarul este in intervalul %d - %d\n“, limita1,
limita2) :
printf (“numarul este in intervalul %d - %d\n“, limita2,
limita1);
}
49
50 Programare C/C++
char strbin[1+8*sizeof(int)];
int numar;
printf ("Programul o valoare intreaga, din zecimal in binar\n");
printf ("Conversia ia sfarsit cu o valoare nenumerica\n");
printf ("numar = ");
while (scanf ("%d", &numar) == 1)
printf ("Valoarea binara a numarului %d este %s\n numar = ",
numar, int_la_str_bin(numar,strbin));
}
char *int_la_str_bin ( n, ps )
int n;
char* ps;
{
int i;
static int dim = 8*sizeof ( int );
for ( i = dim-1 ; i >= 0 ; i--, n >>= 1 )
ps[i] = ( 1&n ) + '0';
ps[dim] = '\0';
return ( ps );
}
Alte exemple:
/* invers.c - functie pentru inversarea ultimilor biti dintr-un numar */
int inver_ultim (int numar , int n )
{
int masca = 0;
int val_bit = 1;
while (n -- > 0 )
{
masca |= val_bit;
val_bit <<= 1;
}
return ( numar ^ masca );
}
void main ( void )
{
int numar, nr_bit;
printf ( "Numarul caruia i se inverseaza ultimii 'n' biti:");
scanf ( "%d", &numar );
printf("Numarul de biti de inversat:");
scanf("%d", &nr_bit);
Programare C/C++ 51
printf("numarul cu ultimii %d biti inversati este: %d\n", nr_bit,
inver_ultim (numar, nr_bit ) );
}
Operatorul sizeof
Acest operator returnează o valoare întreagă, ce reprezintă
dimensiunea în octeţi a operandului. Operandul poate fi o dată obiect
specificată, cum ar fi numele unei variabile, sau poate fi un tip. Dacă este un
tip operandul trebuie închis între paranteze, altfel nu este nevoie: sizeof
(tip); sizeof expresie sau sizeof variabilă.
Operandul lui sizeof nu este evaluat, în cazul expresiilor, deoarece
tipul operanzilor este cunoscut în etapa de compilare, când se determină
efectul acestui operator.
51
52 Programare C/C++
are următorul efect: atribuie lui y valoarea 3, apoi îl incrementează pe y la
valoarea 4 îi adună 2 şi atribuie valoarea 6 lui z; apoi adună 5 la z şi
rezultatul 11 îi este atribuit lui x.
De asemenea se mai utilizează acest operator pentru a include mai
multe informaţii într-o expresie de control a unei bucle for :
for (pas=2,f=0 ; f<100 ; pas*=2 ) f+=pas;
în acest caz se iniţializează pas şi f cu valorile 2 respectiv 0. Alt exemplu:
for ( i=0 , j=0 ; i!=10 ;++i, j-=10)
iniţializează i şi j cu 0 şi respectiv 100, înainte de a începe bucla şi
incrementează valoarea lui i cu 1 şi scade 10 din j, la fiecare execuţie a
ciclului (buclei).
Virgula este utilizată de asemenea în funcţie de context şi ca
separator nu numai ca operator; de exemplu folosirea virgulei în separarea
argumentelor unei funcţii:
printf (“%d%d\n”,f,s);
sau pentru a separa variabile de acelaşi tip.
Dacă virgula apare ca secvenţiator într-o listă de argumente ale unei
funcţii, atunci ea poate să apară numai între paranteze. De exemplu funcţia:
f( a,( b = 2 , b+3 ), c),
are trei argumente, dintre care al doilea argument este 5, iar variabila b va
avea valoarea 2, cea atribuită la apelul lui f(a,5,c).
Virgula utilizată pentru a separa argumentele într-un apel de funcţie,
sau nume de variabile într-o listă de declaraţii, este un separator sintactic de
entităţi şi nu este un exemplu de utilizare a operatorului virgulă.
Deoarece ca toţi operatorii din C furnizează o valoare, valoarea
operatorului virgula este aceea a expresiei cea mai din dreapta. Un alt
exemplu de utilizare a operatorului este:
while (I <100 ) sum+=data[i],++ i;
valoarea lui data[i] se adună la sum, şi apoi i este incrementat. Nu sunt
necesare acolade deoarece este numai o declaraţie ce urmează după while,
ce constă din două expresii separate de operatorul virgulă.
Operatorii de adresare
Aceşti operatori se utilizează pentru a obţine adresa unei entităţi,
deci o valoare-stânga. Operatorul unar * este operatorul de dereferenţiere;
expresia ce-l urmează trebuie să fie un pointer (adică o variabilă ce
memorează adresa), iar rezultatul este o valoare-stânga care se referă la
obiectul pe care-l referă expresia.
Acest operator tratează operandul său ca o adresă, face acces la ea şi
îi obţine conţinutul. De exemplu: y=*px;
Programare C/C++ 53
deci se atribuie lui y conţinutul de la adresa la care face referire px. El poate
fi folosit pentru obţinerea valorii entităţii pentru a o utiliza ca operand în
altă operaţie.
Operatorul unar & este operatorul ce furnizează adresa unui obiect
sau a unui pointer la obiectul respectiv. Operandul este o valoare-stânga, iar
rezultatul este un pointer la obiectul referit de valoarea-stânga.
Exemplu: int x, y;
int *px;/* declarare pointer la o valoarea de tip int */
şi să considerăm pentru aceste declaraţii secvenţa următoare:
px = &x; /* px = adresa (pointer) lui x */
y= *px; /* y = valoarea întreagă referită de px */
care este echivalentă cu: y=x;
Observaţie: dacă pointerul nu referă date de acelaşi tip cu variabila
căreia i se atribuie valoarea rezultă valori eronate, întrucât în funcţie de tipul
la care este asociat pointerul el va referi 1, 2, 4 etc. octeţi în funcţie de tipul
la care este asociat.
Acest operator, &, poate fi aplicat numai la variabile (ce nu pot fi de
tip register), dar nu admite ca operand expresii sau constante. El poate fi
utilizat pentru transmiterea argumentelor de tip adresă (referinţă), în funcţii.
Rezultatul unei operaţii de adresare (referinţă) este interpretat, în
funcţie de context, ca valoare-stânga (dacă este primul operand dintr-o
atribuire) sau ca valoare-dreapta. În declaraţia px = &x; operatorul &
returnează o valoare-stânga respectiv adresa lui x, dar pentru px reprezintă o
valoare-dreapta. Pointerii sunt utilizaţi deoarece, de obicei, conduc la un
cod mult mai compact şi mai eficient decât s-ar obţine altfel.
O declaraţie de forma: pointer ptr; nu este modul normal de a
declara un pointer, deoarece trebuie specificat, pentru referirea de variabile,
tipul de variabilă ce este referit de pointer. Motivul este că diferite tipuri de
variabile ocupă diferite dimensiuni de memorie (număr diferit de octeţi), iar
operaţiile cu pointeri necesită cunoaşterea dimensiunii de memorie, adică ce
tip de date sunt memorate la adresele respective. Chiar şi pentru date ce
ocupă aceeaşi dimensiune, de exemplu float şi long int (32 de biţi),
reprezentarea internă a valorilor este diferită şi deci şi interpretarea trebuie
să fie diferită.
Să considerăm următorul exemplu:
53
54 Programare C/C++
*ip = 0; /* x este acum 0 */
ip = &z[0]; /* ip referă acum z[0] */
*ip = *ip +10;/* se adună 10 la elementul z[0], deci z[0]=10 +10*/
y = *ip +1;/* y ia valoarea referită de ip (z[0]), adică y=20 + 1 */
*ip+ = 1; /* este echivalentă cu ++*ip sau (*ip) ++, adică
valoarea referită de ip este incrementată cu 1, deci ip va
referi z[1]*/
#include <stdio.h>
main()
{
int i1, i2 ;
int *p1, *p2;
i1=5;
p1 = &i1; /* pointer la i1, la valoarea intreaga 5 */
i2 = *p1/2 + 10; /* 5/2 + 10 = 12, deoarece *p1 este de tip int*/
p2 = p1; /* pointerului p2 i se atribuie aceeasi valoare cu p1
*/
printf("i1 = %i, i2 = %i, *p1 = %i , *p2 = %i \n", i1, i2, *p1, *p2 );
printf ("p1 = %p, p2 = %p\n", p1, p2 );
/* tiparire continut variabile pointer p1 si p2 */
printf ("p1 = %x, p2 = %x\n", p1, p2 ); /* tipareste aceleasi adrese */
}
Pe lângă cei doi operatori, descrişi anterior, mai există şi următorii operatori
de adresare:
indexare, reprezentat de [ ], şi care se utilizează pentru a referi un
element, cu indicele specificat, dintr-un tablou furnizat ca prim
operand:
tablou [expresie_indice],
unde tablou este expresie de tip “pointer la tablou”, iar
expresie_indice este o expresie de tip întreg.
selecţia directă reprezentată de punct . şi care se utilizează pentru a
adresa un câmp (componentă) dintr-o structură sau uniune sub forma:
nume_structură . selector. Această operaţie furnizează câmpul cu
Programare C/C++ 55
numele “selector” din structura obţinută în urma evaluării primului
operand.
selecţia indirectă reprezentată de ->, şi care permite accesul la un
câmp dintr-o structură sau uniune, a cărei adresă este furnizată de un
pointer: pointer -> selector.
Această expresie este echivalentă cu:
(* pointer). selector.
Pentru operatorul de indexare s-a prezentat un exemplu, iar pentru
cel de selecţie se vor prezenta în capitolul referitor la structuri.
55
56 Programare C/C++
5. Instrucţiuni
Cele mai multe instrucţiuni din cadrul unui program C sunt instrucţiuni
expresie. O expresie devine instrucţiune dacă expresia este urmată de punct
şi virgula:
expresie;
unde expresia evaluată poate avea efect lateral, deci este reprezentată, de
regulă, de o atribuire sau un apel de funcţie. De exemplu:
x = 0;
i++;
printf (….);
scanf (….);
sau o atribuire cu efect lateral:
y = x++;
Pentru a marca sfârşitul unei instrucţiuni se utilizează ";".
57
58 Programare C/C++
59
60 Programare C/C++
prelucrarea presupusă de utilizator se poate proceda în una din următoarele
forme:
a) if (expresie_1) b) if (expresie_1)
if (expresie_2) {
instrucţiune_2; if (expresie_2)
else ;
instrucţiune_2;
else }
instrucţiune_1; else
instrucţiune_1;
Pentru a selecta mai mult de două alternative posibile se poate
adăuga declaraţia if la clauza else conform sintaxei:
if (expresie_1) echivalent cu : if (expresie_1)
instrucţiune_1; instrucţiune_1;
else if (expresie _2) else
instrucţiune_2; if (expresie_2)
else if (expresie_3) instrucţiune_2;
……… else
else if (expresie_n) if (expresie_3)
instrucţiune_n; ……………
else else
instrucţiune_n+1; if (expresie_n)
instrucţiune_n;
else
instrucţiune_n+1;
Prima formă, construită diferit faţă de cea de-a doua, utilizată
frecvent, este referită ca o construcţie (instrucţiune) else if .
Expresiile sunt evaluate în ordinea în care apar; dacă este întâlnită o
expresie "adevărată", atunci se execută instrucţiunea asociata ei şi astfel se
termină întregul lanţ else if .
Observaţie : deoarece if testează valoarea numerică a expresiei, faţă de zero,
este suficientă forma :
if ( expresie ) sau if (! expresie )
în loc de formele:
if ( expresie != 0 ) sau if ( expresie == 0 )
La cea de-a doua formă trebuie inversate instrucţiunile ce se execută
pentru cele două cazuri, pentru a fi echivalentă cu forma clasică.
Exemple:
/* programul determina daca un numar este par sau nu */
#include <stdio.h>
main()
Programare C/C++ 61
{
int numar, rest;
printf ("Introduceti numarul:");
scanf ("%d", &numar);
rest = numar % 2;
if (rest )
printf ("Este numar impar\n");
else
printf ("Este numar par\n");
}
61
62 Programare C/C++
double a, b, c, delta, real, imag;
printf ("Introduceti coeficientii ecuatiei de gradul al doilea:\ "
"\n\t\t ( ax^2+bx+c=0 )\n a=");
scanf ("%lf", &a);
printf ("b=");
scanf ("%lf", &b);
printf (" c=");
scanf ("%lf", &c);
if (a)
{
delta = pow (b,2) - 4*a*c;
real = -b/2/a;
imag = sqrt(fabs(delta))/2/a;
if (delta >= 0)
printf ("X1=%5.2lf , X2=%5.2lf\n", real-imag,
real+imag);
else
printf ("X1=%5.2lf-i*(%5.2lf), X2=
%5.2lf+i*(%5.2lf)\n",
real, imag, real, imag);
}
else
if(b)
printf ("Ecuatia de grad I cu solutia X=%5.2lf\n"
, -c/b);
else
printf ("Ecuatie degenerata\n");
}
63
64 Programare C/C++
următorul caz (case) şi aşa mai departe până la întâlnirea primei declaraţii
break sau dacă nu există până la sfârşitul instrucţiunii de selecţie.
Dacă nici o etichetă case nu este egală cu valoarea expresiei şi dacă
există prefixul default, atunci se execută prelucrare_d asociată acestuia,
altfel nu se execută nici o instrucţiune din switch.
De fapt forma generală a instrucţiunii de selecţie poate fi exprimată
cu instrucţiunea de decizie if în mod echivalent, astfel (echivalentă structurii
instrucţiunii case cu instrucţiunea break):
if (expresie == eticheta_1)
{
prelucrare_1
}
else if (expresie == eticheta_2)
{
prelucrare_2
}
…………………………….
else if (expresie == eticheta_n)
{
prelucrare_n
}
else
{
prelucrare_d /* echivalentă cu default */
}
În mod asemănător se poate exprima şi forma fără declaraţia break, a
instrucţiunii de selecţie, dar se obţin notaţii mult mai lungi:
if (expresie == eticheta_1)
{
prelucrare_1
}
if (expresie == eticheta_1 || expresie==eticheta_2)
{
prelucrare_2
………………………….
if (expresie == eticheta_1||……||expresie == eticheta_n)
{
prelucrare_n
}
prelucrare_d;
Exemple :
Programare C/C++ 65
/* Numara vocalele dintr-un text introdus de la tastatura */
#include <stdio.h>
void main()
{
int nr_a, nr_e, nr_i, nr_o, nr_u; /* contoare pentru numarul de vocale
*/
char car;
nr_a = nr_e = nr_i = nr_o = nr_u = 0;
printf ("Introduceti un text care se termina cu #\n");
while ( ( car = getchar() ) != '#' )
{
switch (car)
{
case 'A':
case 'a': nr_a++;
break;
case 'E':
case 'e': nr_e++;
break;
case 'I':
case 'i': nr_i++;
break;
case 'O':
case 'o': nr_o++;
break;
case 'U':
case 'u': nr_u++;
}
}
printf ("Numarul de vocale\n A, E, I, O, U este:\n"
"%5d %5d %5d %5d %5d\n",nr_a, nr_e, nr_i, nr_o,nr_u);
}
65
66 Programare C/C++
generat de tasta ENTER sau RETURN */
}
/* caracterul de sfârşit ‘#’ se va introduce la începutul unei linii */
67
68 Programare C/C++
Condiţia de testare, pentru oricare dintre cicluri, este reprezentată de o
expresie cu valoare de tip întreg interpretată ca valoare logică astfel :
- adevărat, adică condiţie îndeplinită, dacă valoarea expresiei este diferită de
zero; în acest caz se execută instrucţiunile din corpul ciclului, după care se
evaluează din nou expresia de testat;
- fals, adică condiţie neîndeplinită, dacă valoarea expresiei este zero, şi care
are ca efect încetarea (normală) a execuţiei instrucţiunii de ciclare.
O diferenţă majoră intre cele două tipuri de cicluri este aceea că ciclurile cu
test iniţial pot să nu se execute nici o dată, dacă condiţia de testat nu este
îndeplinită încă de la început, în timp ce ciclul cu test la sfârşit se execută
cel puţin o dată, întrucât se execută corpul ciclului şi apoi se testează
condiţia de sfârşit de ciclu.
Exemple:
#include <iostream.h>
Programare C/C++ 69
/* programul citeste un caracter si in functie de acesta se termina
sau se executa ciclic;
daca caracterul este ‘d’, ‘D’, ‘n’ sau‘N’ se executa ciclic pana se
introduce un
caracter diferit de acestea; le terminarea programului se emite
un sunet
*/
void main(void)
{
int gata = 0;
char litera;
while (! gata)
{
cout << "\nTasteaza D sau N si apoi Enter pentru a continua:
";
cin >> litera;
if ((litera == 'D') || (litera == 'd') || (litera == 'N') || (litera ==
'n'))
gata = 1;
else
cout << '\a'; //Sunet pentru altfel de caracter
}
cout << "Litera tastata a fost " << litera << endl;
}
69
70 Programare C/C++
printf ("Cel mai mare divizor este: %d\n", a);
}
71
72 Programare C/C++
5.5.2. Instrucţiunea do
Exemple:
/*Tipareste un numar intreg (de maxim 9-10 cifre) in ordie inversa*/
#include<stdio.h>
main()
{
long int numar, rest;
printf ("Introduceti un numar intreg:");
scanf ("%li",&numar);
printf ("Numarul scris in ordine inversa:");
do
{
rest=numar%10;
printf("%d", rest);
numar/=10;
}
while (numar);
printf ("\n");
}
73
74 Programare C/C++
do
{
radacina = (radacina + a/radacina)/2;
modul = a - radacina*radacina;
if (modul < 0)
modul = -modul;
n++;
}
while ( modul > precizia && n <
MAX_TERMENI);
printf ("Radacina patrata din %lg este %lf”
“ obtinuta dupa %d iteratii\n",
a, radacina, n);
printf ("Valoarea returnata de functia standard”
“ sqrt este : %lf\n", sqrt(a));
printf ("Diferenta fata de functia standard este”
“ %lg\n", sqrt(a)-radacina);
}
}
expr_iniţializare;
while (condiţie_ciclu)
{
instrucţiune;
expr_actualizare;
}
Oricare dintre expresiile instrucţiunii for sau chiar toate pot lipsi.
Dacă lipseşte conditie_ciclu, aceasta implică faptul că, clauza while
(condiţie_ciclu) este echivalentă cu while (1),ceea ce înseamnă o condiţie
permanent adevărată.
Expr_actualizare poate conţine una sau mai multe expresii ce sunt
actualizate la fiecare ciclu; acestea sunt separate prin virgule.
Exemple :
/*Program ce afiseaza (genereaza) numerele prime in intervalul [3,n]
intervalul este parcurs cu un pas egal cu 2, deoarece numerele prime
sunt numai printre numerele impare
*/
#include<stdio.h>
void main()
{
long int n, i, div, este_prim;
printf (" Afiseaza numerele prime pana la n=");
scanf ("%ld", &n);
printf ("\n%9d,%9d", 2, 3);
for( i=5 ; i <= n ; i += 2 )
{
div=3;
do
{
este_prim = i % div;
div += 2;
}
while(este_prim && div*div <= i);
if (este_prim)
printf(",%9ld", i);
75
76 Programare C/C++
}
printf(".\n\n");
}
77
78 Programare C/C++
y = x77 corecţie
x77 = x38 * x38 * x1
x38 = x19 * x19 * x0
x19 = x9 * x9 * x1
x9 = x4 * x4 * x1
x4 = x2 * x2 * x0
x2 = x1 * x1 * x0
x1 = 1 * 1 * x1
79
80 Programare C/C++
2)
/* afişare cu ecou a caracterelor, mai puţin tab-urile şi blancuri, */
/* până la sfârşitul liniei curente */
while ((car = getcar( ))!=' \n ' ) /* care poate fi scrisă mai
economic, însă fără */
{ /*continue: */
if (car=='\t || car==' ') /*while ((car = getchar ( )) != '\n') */
continue; /* if (car !=' \t' && car! = ' ') */
putchar(car); /* putchar (car); */
}
De multe ori inversând condiţia pentru un if se elimină utilizarea
instrucţiunii continue.
81
82 Programare C/C++
Această instrucţiune întrerupe parcurgerea secvenţială, normală, a
programului şi realizează un salt necondiţionat la o instrucţiune, etichetată,
în cadrul funcţiei (blocului) curente. Spre deosebire de break, care are un
efect asemănător, dar nu are efect decât la un singur nivel (se limitează la
terminarea unei singure instrucţiuni), instrucţiunea goto poate întrerupe şi o
succesiune de prelucrări (cicluri) incluse unele în altele.
Sintaxa sa este:
goto etichetă
si are ca efect execuţia instrucţiunii precedată de etichetă.
O astfel de instrucţiune etichetată poate fi plasată oriunde în cadrul
funcţiilor. Domeniul de valabilitate al etichetei, care este supusă aceloraşi
reguli ca numele de variabile, este blocul reprezentat de corpul funcţiei în
care apare, excluzând orice sub-bloc în care acelaşi nume a fost redeclarat.
Utilizarea frecventă a acestei instrucţiuni va face programul mai
greu de urmărit şi de înţeles. Din acest motiv această instrucţiune este
utilizată foarte rar, ea nefiind considerată ca parte a unui (bun) stil de
programare.
De exemplu:
a) citirea şi prelucrarea unui şir de numere, ce se termină cu o valoare nulă
(0):
cit_numar: scanf ("%d, & numar);
if ( numar == 0 )
goto etapa2;
. . . . . . . . . /* alte declaraţii */
goto cit_numar; /* se reia citirea numerelor */
etapa2:
. . . . . /* alte prelucrări */
6.1 Tablouri
83
84 Programare C/C++
Sintaxa de bază pentru declararea unui tablou este :
tip_de_bază nume_tablou [dim] ={val_iniţială , val_iniţială ,
…} ;
unde dim este o expresei constantă şi reprezintă numărul de elemente;
De obicei dimensiunea se specifică printr-un identificator (constantă
simbolică), introdus cu ajutorul directivei #define (a preprocesorului).
Această modalitate uşurează munca programatorului în cazul în care se
modifică dimensiunea tabloului, întrucât se face o singură modificare în
program, şi anume a directivei #define.
Numele utilizate pentru a identifica elementele vectorului sunt
denumite indici. Valorile indicilor trebuie să fie valori întregi şi valorile lor
încep de la 0. Deci, dacă un tablou a fost declarat de dimensiune n,
elementele sale vor fi referite prin indici în intervalul 0 ÷ n-1.
Valoarea iniţială, val_iniţială, este o expresie constantă de acelaşi tip
cu tipul se bază şi reprezintă valoarea iniţială asociată elementului respectiv
din tablou; valorile iniţiale sunt asociate în ordine elementelor tabloului. Pot
fi specificate mai puţine valori, în această listă, decât numărul de elemente
ale tabloului, dar nu mai multe . Dacă sunt specificate mai puţine valori, vor
fi iniţializate cu aceste valori, în ordine, atâtea elemente câte valori există în
listă, începând cu primul element. Elementele rămase vor fi iniţializate cu
0.
Dimensiunea unui tablou poate fi omisă în următoarele situaţii :
- se declară un tablou extern şi atunci pentru acesta s-a rezervat spaţiu
în alt fişier sursă ;
- se declară un tablou ca parametru formal al unei funcţii şi în acest
caz declaraţia este echivalentă cu cea a unui pointer ;
- declaraţia conţine o listă de iniţializare şi în această situaţie
dimensiunea tabloului este determinată, implicit, de numărul de
expresii constante din lista de iniţializare ;
Tabloul este o structură cu acces direct deoarece timpul de acces la o
componentă nu depinde de valoarea indicelui (adică de poziţia componentei
în cadru tabloului).
Exemple de declaraţii :
1) int tab_i [20] ;
defineşte un tablou denumit tab_i, cu 20 de elemente de tip întreg;
acestui tablou îi vor fi alocaţi 20* sizeof (int ) = 20*2 = 40 octeţi.
Elementele sale vor fi referite astfel:
tab_i[0] , tab_i[1] , … , tab_i[19] .
2) float tab _f [10] ;
defineşte tabloul tab_f cu 10 elemente de tip float; acestui tablou îi
vor fi alocaţi:
Programare C/C++ 85
10*sizeof (float) = 10*4 =40 octeţi
3) double tab_d [30] ;
un tablou de tip double, cu 30 elemente, căruia îi vor fi alocaţi :
30 *sizeof (double) =30*8 =240 octeţi
4) char tab_c [10] ;
un tablou de caractere, cu 10 elemente, ce va ocupa 10*sizeof =10*1
=10 octeţi
5) int tab_ii [ ] = {1 , 2 , 3 , 4 , 5 };
defineşte un tablou de numere întregi , de 5 elemente , iniţializate cu
valorile
tab_ii [0] = 1 , tab_ii [1] = 2 ;…, tab_ii [4] = 5 ;
declaraţia este echivalentă cu :
int tab [5] = {1 ,2 , 3 , 4 ,5 };
Exemple de programe utilizând tablouri :
#include <iostream.h>
void main(void)
{
char alfabet[27];
// 26 litere plus NULL
char litera;
int index;
// initializare litera si index-ul tabloului alfabet; actualizare litera si
// index
for (litera = 'A', index = 0; litera <= 'Z'; litera++, index++)
alfabet[index] = litera;
alfabet[index] = NULL;
// ultimul caracter tiparibil
cout << "Literele sunt: " << alfabet << endl;
// tiparirea se poate face si astfel :
cout << "Literele sunt :" << endl ;
for ( index = 0; index < 26 ; index++)
// in acest caz nu mai este necesar caracterul NULL
cout << alfabet[index] ;
cout << endl ;
}
85
86 Programare C/C++
2) Generarea numerelor lui Fibonacci; se vor genera cel mult 100 de numere
din acest şir, ce se construieşte cu relaţia:
fib (n) = fib (n-1) + fib (n-2) ; pentru n >= 2 ;
cu fib (0) = 0; fib (1) = 1;
87
88 Programare C/C++
if (prim [index] )
{
printf ( “ %d …/” , index );
for (multiplu = 1 ; multiplu <= n / index ;++
multiplu )
if ( prim[ index * multiplu ] )
{
prim [index * multiplu ] = 0 ;
contor ++;
}
}
} while (contor <n-1) ;
}
89
90 Programare C/C++
matricea fiind memorată pe linii .
Pentru tablouri multidimensionale, deci cu mai mulţi indici, primul
indice este cel care variază cel mai lent, iar ultimul indice variază cel mai
repede.
Exemple :
1) Înmulţirea a două matrice A şi B are ca rezultat matricea C (prodmat.c):
A(la , ca) * B(lb , cb) = C(la , cb ) , cu condiţia ca = lb.
Termenul general al matricei C este:
lb
c (i, j)= ∑ a(i, k )∗b (k , j)
k =1
cu: 1<= i<= la, 1 <= j <= cb
#include <stdio.h>
#include <conio.h>
void main(void)
{
int grad, i, val, x;
int coef[10];
clrscr();
printf ("Programul calculeaza valoarea unui polinom de grad n"
"intr-un punct x dat\n\n");
printf ("Introduceti gradul polinomului:");
scanf ("%d", &grad);
for ( i = grad ; i >= 0 ; i--)
{
printf ("Introduceti coeficientul lui x la puterea %d:", i);
scanf ("%d", &coef[i]);
}
printf ("Introduceti x-ul in care se calculeaza valoarea polinomului:");
scanf ("%d", &x);
91
92 Programare C/C++
val = 0;
for ( i = grad ; i >= 0 ; i--)
val = val * x + coef[i];
printf ("Valoarea este: %d", val);
}
93
94 Programare C/C++
{
char nume [50] ;
printf (INTREB);
scanf (“%s“, nume);
printf (SALUT, “%s”, nume );
printf (“Numele tau are %d litere, dar s-au rezervat %d locatii
“
“de memorie\n”, strlen (nume), sizeof nume);
printf (“Propozitia \“%s\“ , are %d litere “,
INTREB, strlen (INTREB)) ;
printf (“si ocupa %d octeti de memorie \n “ , sizeof INTREB)
}
Exemple:
1)
/*
cnvbazas.c - programul realizeaza conversia unui numar intreg intr-o
alta baza
*/
#include <stdio.h>
#define LUNG_MAX 32
main ()
Programare C/C++ 95
{
long int numar, baza, ind1, ind2;
char cifra [ LUNG_MAX ], car_aux;
printf ("numarul de convertit = ");
scanf ("%ld", &numar);
printf ("conversie in baza :");
scanf ("%ld%", &baza);
if ( numar < 0 )
{ printf ("-");
numar = - numar;
}
ind1 = -1;
do
{ ind1 ++;
if ( numar % baza < 10 )
cifra [ ind1 ] = '0' + numar % baza;
else
cifra [ ind1 ] = 'A' + numar % baza - 10;
numar /= baza;
} while ( numar > 0 );
for ( ind2 = 0; ind2 < ind1/2 ; ind2 ++ )
{
car_aux = cifra [ ind2 ];
cifra [ ind2 ] = cifra [ ind1 – ind2 – 1 ];
cifra [ ind1 – ind2 – 1 ] = car_aux;
}
cifra [ ind1 ] = ‘\0’;
printf ("%s\n", cifra);
}
2)
/*
Programul concateaza doua siruri de caractere, utilizand o
functie, si determina lungimea sirului obtinut
*/
#include <stdio.h>
void main()
{
void concat ( char rezultat[], char s1[], char s2[]);
int lungime ( char s[]);
95
96 Programare C/C++
char sir1[]={"Test pentru"};
char sir2[]={" concatenare siruri de caractere"};
char sir[60];
concat (sir, sir1, sir2);
printf ("Sirul rezultat este:\n%s\n", sir);
printf ("Lungimea sirului este: %d\n", lungime(sir));
}
void concat (char rezultat[], char s1[], char s2[])
{
int i, j;
for ( i = 0 ; s1[i] != '\0' ; i++ )
rezultat[i] = s1[i];
for ( j = 0 ; s2[j] != '\0' ; j++)
rezultat[i+j] = s2[j];
rezultat[i+j] = '\0';
}
int lungime (char s[])
{
int contor;
for ( contor = 0 ; s[contor] != '\0' ; contor++);
return (contor);
}
97
98 Programare C/C++
Pentru a simplifica o astfel de declaraţie, a unor şiruri lungi, se poate
împărţi şirul iniţial în două sau mai multe subşiruri adiacente astfel :
char litere [ ] ={
“abcdefghijklmnoprstuvwxyz”
“ABCDEFGHIJKLMNOPRSTUVWXYZ”};
Exemple:
1)
/*
Numara cuvintele dintr-un text, citit de la consola;
textul se termina cu caracterul ‘#’, la inceputul unei linii noi
*/
#include <stdio.h>
int litera (char c)
{
if(( c> = 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ))
return (1); /* daca este o litera */
else
return (0); /* daca nu este o litera */
}
int numara_cuvinte ( char linie[] )
{
int contor_cuv = 0, cuvant = 1, i = 0;/* cuvant =1, s-a terminat un
cuvant */
while ( linie[i] != '\0' )
{
if( litera ( linie[i] ) )
{
if (cuvant)
{
contor_cuv ++;
cuvant = 0; /* a inceput un nou cuvant */
}
}
else
cuvant = 1;
i++;
}
return ( contor_cuv );
}
void main()
{
int sf_text = 0, total_cuv = 0;
char text[81];
printf ("Introduceti un text terminat prin '#' la inceputul unei
linii.\n");
while ( !sf_text )
{
citeste_linie ( text );
if ( text[0] == '#' )
sf_text = 1;
else
total_cuv += numara_cuvinte (text);
}
printf ("\nNumarul de cuvinte este %d\n", total_cuv);
}
2)
/*
programul elimina spatiile (blancurile) dintr-un sir de caractere
*/
#include <stdio.h>
#include <conio.h>
#include <string.h>
#define DIM 100
void main(void)
99
100 Programare C/C++
{
int i, j;
char sir[DIM], sir1[DIM];
clrscr();
printf ("Porgramul elimina spatiile dintr-un sir\n\n");
printf ("Introduceti sirul:");
gets (sir1);
j = 0; /* contorul de caractere pentru sirul nou, fara spatii */
for( i = 0 ; i < (strlen(sir1)) ; i++ )
{
if (sir1[i] != ' ')
{
sir[j] = sir1[i];
j++;
}
}
sir[j] = ’\0’;
printf("Sirul fara spatii:%s\n",sir);
}
3)
// tiparirea a doua siruri de carcatere unul numai cu litere mari, iar
// celalalt cu litere mici
#include <iostream.h>
#include <string.h> // Contine functiile prototip de transformare:
// strupr si strlwr, prima
// in litere mari, iar cea de-a doua in litere mici.
void main(void)
{
char titlu[] = "Introducere in C++";
Programare C/C++ 101
char tema[] = "Descrierea sirului de caractere";
cout << "Litere mari: " << strupr(titlu) << endl;
cout << "Litere mici: " << strlwr(tema) << endl;
}
4)
/*
invcar.c - inversarea tipului de caractere dintr-un text:
de la litera mare la litera mica si invers
*/
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LIMITA 30
#define DIMENS 81
main()
{
void transforma ( char linie[] );
char text [LIMITA] [DIMENS];
int nr_linie =0 , i;
printf ("Introduceti cel mult %d linii pentru transformare\n",
LIMITA);
printf ("Introducerea se termina cu ENTER, pe o linie noua\n");
while ( nr_linie < LIMITA && gets(text[nr_linie]) != NULL &&
strcmp ( text[nr_linie], "") !=0 )
nr_linie ++;
for ( i = 0 ; i < nr_linie ; i++)
{
transforma ( text[i] );
puts ( text[i] );
}
}
void transforma ( char *ptr_car )
{
while ( *ptr_car != '\0' )
{
if ( isupper ( *ptr_car ) )
*ptr_car = tolower ( *ptr_car );
else if ( islower ( *ptr_car ) )
*ptr_car = toupper ( *ptr_car );
ptr_car++;
101
102 Programare C/C++
}
}
Programare C/C++ 103
6.3 Structuri
103
104 Programare C/C++
O declaraţie de structură care nu este urmată de o listă de variabile
nu alocă memorie; ea descrie numai un şablon, o formă de structură. Dacă
structurii i se asociază un nume, atunci acesta poate fi utilizat ulterior pentru
definirea unor variabile de tip structură cu acelaşi format cu structura
respectivă.
Formatul de iniţializare pentru o variabilă de tip structură este
similar cu acela pentru tablouri. Câmpurile sale pot fi iniţializate prin
includerea listei de valori iniţiale între acolade. Fiecare valoare din listă
trebuie să fie o expresie constantă.
Pentru a selecta (referi) un câmp, o componentă, dintr-o structură se
utilizează operatorul de selecţie ( . ) între numele variabilei de tip structură
şi numele unui câmp al structurii:
variabilă_structură . nume_câmp
În acest caz numele câmpului mai este denumit şi selector datorită
faptului că el selectează componenta din cadrul structurii. Câmpul selectat
se comportă ca o variabilă şi i se pot aplica toate operaţiile ce se pot aplica
variabilelor (datelor) de tipul respectiv.
Iată un exemplu de declarare şi iniţializare a unei structuri pentru
reprezentarea unor cărţi:
struct s_carte
{
char titlu [ 50 ] ;
char autor [ 25 ] ;
float pret;
int nr_pag;
} volum [ 100 ] =
{ { " Programare C", "Vasile Lungu", 2.50, 209 } };
În acest exemplu este iniţializată doar prima "carte" din acest tablou cu 100
de structuri de tip "carte".
În plus, cu o variabilă de tip structură, ca o entitate, se mai pot
efectua operaţiile:
poate fi transmisă ca parametru , pentru funcţii ;
o funcţie poate returna o valoare de tip structură ;
i se poate atribui valoarea altei variabile structură , de acelaşi tip, ca
în exemplul următor:
struct s_planeta {
char nume [20] ;
double raza, orbita;
int vizibila;
};
struct s_planeta planeta [20] ;
Programare C/C++ 105
planeta [ i ] = planeta [ j ] ;
această ultimă declaraţie este echivalentă cu următoarele:
planeta [ i ].nume = planeta [ j ].nume ;
planeta [ i ].raza = planeta [ j ].raza ;
planeta [ i ].orbita = planeta [ j ].orbita ;
planeta [ i ].vizibila = planeta [ j ].vizibila ;
Pentru a afla spaţiul de memorie alocat (numărul de octeţi) unei
variabile de tip structură se poate utiliza operatorul sizeof :
sizeof ( struct nume_structura )
Exemple:
1)
// se initializeaza cateva date pentru un angajat (nume, ID, salariu,
// telefon)
#include <iostream.h>
#include <string.h>
#define DIM_NUME 30
#define NR_TELEF 12
void main(void)
{
struct st_angajat {
char nume[DIM_NUME];
long id;
float salariu;
char telefon[NR_TELEF];
} angajat;
2)
105
106 Programare C/C++
/* carti.c - programul realizeaza inventarul cartilor dintr-o
biblioteca */
#include <stdio.h>
#include <string.h>
/* numarul maxim de caractere pentru : titluri, autori,
si numarul maxim de carti */
#define MAXTIT 150
#define MAXAUT 50
#define MAXCARTI 100
/* STOP = sirul vid de caractere, care va termina introducerea
datelor */
#define STOP ""
struct s_carte
{
char titlu [ MAXTIT ];
char autor [ MAXAUT ];
double pret;
};
main()
{
struct s_carte carte [ MAXCARTI ]; /* tablou cu structura de carte */
int index , contor = 0;
double val;
printf ("Introduceti titlurile cartilor.\n");
printf ("Pentru oprire se va tasta RETURN, la inceputul unei
linii.\n");
printf ("Titlu carte:");
while ( contor < MAXCARTI && strcmp ( gets ( carte [contor].titlu ),
STOP ) != 0 )
{
printf ("Nume autor:");
gets (carte[contor].autor);
printf ("Pretul cartii:");
scanf ("%lf", &val);
while ( getchar() != '\n'); /* sterge linia curenta */
carte[contor++].pret = val;
if ( contor < MAXCARTI )
printf ("Titlu carte:");
}
printf ("Inventarul cartilor introduse :\n");
Programare C/C++ 107
for ( index = 0 ; index < contor ; index++ )
printf ("%s de %s : $ %lf\n", carte[index].titlu,
carte[index].autor, carte[index].pret );
}
3)
/* programul listeaza studentii, dintr-o grupa, in ordinea
descrescatoare a mediilor; se citesc, pentru fiecare student:
numele, notele si se calculeaza media.
Se citesc datele respective pentru studentii dintr-un an
Pentru listare nu se sorteaza vectorul studentilor dupa medii
ci se parcurge vectorul studenti, se determina studentul ce are
cea mai mare medie si nu a fost inca listat; pentru fiecare
student listat se actualizeaza campul “afisat”; aceste campuri au
fost initializate la introducerea datelor, pentru fiecare student.
Ciclul de afisare va lua sfarsit dupa ce s-au listat toti studentii
din grupa respectiva
*/
#include <stdio.h>
#include <string.h>
#define MAXGR 5
#define MAXSTUD 25
#define MAXNOTE 12
#define MAXNUME 15
struct s_student
{
char nume [MAXNUME];
char prenume [MAXNUME];
int nota [MAXNOTE];
double media;
int afisat; /* acest camp precizeaza daca studentul respectiv
a fost listat */
};
struct s_grupa
{
char numeg [MAXNUME];
int nr_stud;
struct s_student stud [MAXSTUD];
};
struct s_an
107
108 Programare C/C++
{
char numean [MAXNUME];
int nr_grupe;
int nr_note;
struct s_grupa grupa [MAXGR];
};
main()
{
struct s_an an;
int ig, is, im, imax, contor;
double medie, medmax;
char numegr [MAXNUME];
printf ("Nume an:");
gets (an.numean);
printf("Numarul de note(materii):");
scanf("%d", &an.nr_note);
while ( getchar() !='\n'); /* sterge restul liniei */
printf ("Numar grupe:");
scanf ("%d", &an.nr_grupe); /* numarul de grupe din an */
while ( getchar() != '\n');
for ( ig = 0 ; ig < an.nr_grupe ; ig++)
{
printf ("Nume grupa:"); /* numele grupei */
gets ( an.grupa[ig].numeg );
printf ("Numar studenti:"); /* numarul de studenti */
scanf ("%d", &an.grupa[ig].nr_stud);
while ( getchar() != '\n');
for ( is = 0 ; is < an.grupa[ig].nr_stud ; is++)
{ /* citesc datele pentru fiecare sudent */
printf ("Nume student (%d):", is+1);
gets ( an.grupa[ig].stud[is].nume );
printf ("Prenume student (%d):", is+1);
gets ( an.grupa[ig].stud[is].prenume );
medie = 0;
for ( im = 0 ; im < an.nr_note ; im++)
{
printf ("Materia (%d):", im+1);
scanf ("%d", &an.grupa[ig].stud[is].nota[im]);
medie += an.grupa[ig].stud[is].nota[im];
}
while ( getchar() != '\n');
Programare C/C++ 109
an.grupa[ig].stud[is].media = medie/an.nr_note;
an.grupa[ig].stud[is].afisat =0; /* initializare camp
‘afisat’ */
}
}
printf ("Grupa de afisat:"); /* numele grupei de afisat */
gets ( numegr );
ig = 0; /* se determina, pe baza numelui, numarul grupei de afisat */
while ( ig < an.nr_grupe && strcmp(an.grupa[ig].numeg,numegr) !=
0)
ig++;
if ( ig >= an.nr_grupe)
printf ("Eroare nume grupa \n"); /* nu avem ce afisa */
else
{
contor = 0; /* contorul numarului de studenti listati */
while ( contor < an.grupa[ig].nr_stud )
{
imax = 0; /* se determina indexul studentului cu media
maxima */
while ( an.grupa[ig].stud[imax].afisat ) /* care nu a fost
listat */
imax++;
medmax = an.grupa[ig].stud[imax].media; /* si
media acestuia */
for ( is = imax + 1 ; is < an.grupa[ig].nr_stud; is++)
if (( !an.grupa[ig].stud[is].afisat) && (medmax <
an.grupa[ig].stud[is].media))
{
imax = is;
medmax = an.grupa[ig].stud[is].media;
}
an.grupa[ig].stud[imax].afisat = 1; /* la afisare se
actualizeaza */
printf ("%-*s %-*s %5.2lf\n", MAXNUME,
an.grupa[ig].stud[imax].nume,
MAXNUME,
an.grupa[ig].stud[imax].prenume,
an.grupa[ig].stud[imax].media);
contor++;
}
109
110 Programare C/C++
}
}
4)
#include <iostream.h>
#include <string.h>
void angajat::tip_angajat(void)
{ // tiparire date angajat: nume, ID, salariu
cout << "Angajat: " << nume << endl;
cout << "Id: " << angajat_id << endl;
cout << "Salariu: " << salariu << endl;
}
Programare C/C++ 111
long angajat::get_id(void)
{
return(angajat_id);
}
void main(void)
{
angajat lucrator;
6.4 Uniuni
111
112 Programare C/C++
memorie, dar nu simultan. Deci în cazul unei uniuni, aceeaşi zonă de
memorie poate fi interpretată în diferite moduri, în funcţie de tipul de date
memorat.
Spre deosebire de structură, în cazul căreia pentru fiecare câmp i se
alocă spaţiul necesar de memorie şi deci pentru structură se alocă un spaţiu
de memorie total egal cu suma spaţiilor de memorie necesare fiecărui câmp
al structurii, în cazul uniunii se alocă atâta spaţiu de memorie cât este
necesar pentru memorarea câmpului de dimensiune maximă.
O uniune mai poate fi definită ca o structură cu un singur câmp, de
un anumit tip care poate fi considerat reuniunea unor câmpuri (componente)
de diferite tipuri, dintre care este activ, la un moment dat, doar unul dintre
acestea .
De exemplu se poate defini o variabilă ce poate lua valori de diferite
tipuri: întreg, real, complex sau caracter, astfel :
struct complex
{
double real , imag ; /* memorie necesară: 16 octeţi */
};
union var_mixta
{
struct complex cmpx ; /* memorie necesară: 16 octeţi */
int n; /* memorie necesară: 2 octeţi */
double r ; /* memorie necesară: 8 octeţi */
char c ; /* memorie necesară: 1 octet */
};
Pentru a determina spaţiul de memorie necesar unei variabile, sau tip
de date, se poate utiliza operatorul sizeof :
sizeof ( struct complex ) =16 octeţi;
întrucât sunt definite două câmpuri (real , imag) fiecare de tip double, deci
ocupă fiecare 8 octeţi.
În schimb, în cazul uniunii, vom avea :
sizeof (union var_mixta) = 16 octeţi,
care reprezintă spaţiul de memorie maxim cerut de componenta cea mai
lungă (mare).
Uniunile sunt iniţializate în mod asemănător cu structurile, fie
utilizând un şablon de uniune fie o variabilă uniune, dar spre deosebire de
structuri, se va iniţializa doar un singur câmp. Accesul la un câmp al unei
uniuni se face cu acelaşi operator de selecţie (.), ca şi în cazul structurilor.
Atribuirea unei valori pentru un câmp al unei uniuni va afecta toate
câmpurile uniunii. Numele de uniuni împart acelaşi spaţiu de nume cu
numele de structuri şi de enumerări, dar separat de numele de variabile; deci
putem avea o variabilă sau o uniune cu acelaşi nume, fără a se confunda .
Programare C/C++ 113
Numele câmpurilor unei uniuni sunt locale acesteia, deci putem avea
câmpuri cu acelaşi nume în uniuni diferite, fără a se face confuzie.
Exemple de iniţializări :
union var_mixta variabila = {5};
/* iniţializare număr întreg */
union var_mixta variabila = {‘A’};
/* iniţializare caracter */
union var_mixta variabila = {{ 1.5 , 3.5 }};
/* iniţializare număr complex */
union var_mixta tablou [20] = {{41.0 , 2.0 }};
/* iniţializare primul element din tablou cu un număr complex */
113
114 Programare C/C++
} persoana [10] = { {"Ionescu", "Ion", 30, 1, inv_superior,
"Universitatea Poli Buc.", "Electrotehnica", 9.50, 1995 } } ;
Câmpurile uniunii corespunzătore acestei structuri vor fi referite astfel:
persoana [ i ].preg, pentru câmpul pregătire,
persoana [ i ].studii.stud_super.univ, pentru câmpul universitate,
persoana [ i ].studii.stud_super.facult, pentru câmpul facultate,
persoana [ i ].studii.stud_super.medief, pentru câmpul medie
facultate,
persoana [ i ].studii.stud_super.anf, pentru câmpul an absolvire
facultate etc.
115
116 Programare C/C++
/*programul listeaza angajatii, unei firme, dupa tipul pregatirii:
- fara pregatire;
- cu scoala generala, pentru care se specifica numarul de clase;
- cu liceu : anul absolvirii liceului si media;
- cu facultate: universitatea, facultatea, anul abs. si media
*/
#include <stdio.h>
#define MAXNUME 20
#define MAXPERS 50
main()
{
enum pregatire { fara , scgen , liceu , facultate };
union u_studii
{
struct /* studii: facultate */
{
char nume_univ[MAXNUME];
char nume_facult[MAXNUME];
int anf;
double mediaf;
} st_univ;
struct /* studii: liceu */
{
int anl;
double medial;
} st_liceu;
int nr_clase; /* studii: scoala generala */
};
struct
{
char nume[MAXNUME];
char prenume[MAXNUME];
int varsta, sex;
enum pregatire preg;
union u_studii studii;
} angajat[MAXPERS];
enum pregatire sel_preg;
int i, n;
char tip_pregatire[MAXNUME];
double val;
printf ("Numarul de angajati ai firmei este:");
Programare C/C++ 117
scanf ("%d", &n);while(getchar() != '\n');
for ( i = 0 ; i < n ; i++ )
{
printf ("Nume angajat:");
gets ( angajat[i].nume );
printf ("Prenume angajat:");
gets ( angajat[i].prenume );
printf ("Varsta : ");
scanf ("%d", &angajat[i].varsta);
printf ("Sex(0-femeie,1-barbat) : ");
scanf("%d", &angajat[i].sex); while( getchar() != '\n');
printf("Studii(fara, scoala generala, liceu, universitate):");
gets (tip_pregatire);
switch ( tip_pregatire[0] ) /*primul caracter din tip pregatire */
{
case 'f':
case 'F': angajat[i].preg = fara; break;
case 's':
case 'S': angajat[i].preg = scgen;
printf("Numarul de clase:");
scanf ("%d", angajat[i].studii.nr_clase);
while(getchar()!='\n');
break;
case 'l':
case 'L': angajat[i].preg = liceu;
printf ("Anul absolvirii liceului:");
scanf ("%d", &angajat[i].studii.st_liceu.anl);
printf ("Media de absolvire a liceului:");
scanf ("%lf", &val);
angajat[i].studii.st_liceu.medial = val;
while ( getchar() != '\n');
break;
case 'u':
case 'U': angajat[i].preg = facultate;
printf ("Universiatatea:");
gets (angajat[i].studii.st_univ.nume_univ);
printf ("Facultatea:");
gets (angajat[i].studii.st_univ.nume_facult);
printf ("Anul absolvirii facultatii:");
scanf("%d", &angajat[i].studii.st_univ.anf);
printf ("Media de absolviria a facultatii:");
117
118 Programare C/C++
scanf("%lf", &val);
angajat[i].studii.st_univ.mediaf = val;
while( getchar() != '\n');
break;
};
}
printf ("Angajatii cu ce fel de pregatire doriti sa fie listati?\n ");
gets (tip_pregatire); /* tipul de angajati ce se doreste a fi listati
switch ( tip_pregatire[0] )
{ /* tiparire antet lista */
case 'f':
case 'F': sel_preg = fara;
printf("Lista angajatilor fara pregatire:\n");
break;
case 's':
case 'S': sel_preg = scgen;
printf("Angajatilor cu studii scoala generala”
“(nr.clase):\n");
break;
case 'l':
case 'L': sel_preg = liceu;
printf("Lista angajatilor cu studii liceul:\n");
break;
case 'u':
case 'U': sel_preg = facultate;
printf("Lista angajatilor cu studii”
“ universitare:\n");
break;
};
for ( i = 0 ; i < n ; ++i )
if ( angajat[i].preg == sel_preg )
{
printf ("%-*s %-*s, varsta: %2d, sex:", MAXNUME,
angajat[i].nume, MAXNUME, angajat[i].prenume,
angajat[i].varsta);
switch ( angajat[i].sex ) /* tiparire sex */
{
case 0 :printf("femeie\n");
break;
case 1 :printf("barbat\n");
break;
Programare C/C++ 119
}
}
}
119
120 Programare C/C++
6.5. Pointeri
2)
// programul exemplifica utilizarea unui pointer fara tip (void) la doua
// variabile de tipuri
// diferite, motiv pentru care se face conversia explicita la tipul
necesar
// de pointer
#include<iostream.h>
int x;
float r;
void *p = &x; /* p pointer la variabila x */
int main () {
*(int *) p = 2;
121
122 Programare C/C++
p = &r; /* p pointer la variabila r */
*(float *)p = 8.8;
cout<<r<<endl; // se tiparesc valorile atribuite celor doua variabile
cout<<x<<endl; // prin intermediul variabilei pointer p
}
#include <stdio.h>
main ( )
{
char car = ‘c’ ;
char *ch_ptr = &car ; /* ch_ptr e iniţializat cu adresa variabilei car */
printf (“%c %c\n“, car, *ch_ptr);
car = ‘?’ ;
printf (“%c %c\n“, car, *ch_ptr);
*ch_ptr = ‘+‘;
printf (“%c %c\n“, car, *ch_ptr) ;
}
123
124 Programare C/C++
if ( ! ( e % 24 ) && ! ( n % 8 ) )
{e=1; /* dupa afisarea unui ecran se va tipari din nou
adresa */
afis_adr_pag = 1; /* curenta a zonei de memorie afisata
*/
if ( getchar() == EOF ) /* se testeaza daca s-a introdus
caracterul */
break; /* de sfarsit listare memorie */
}
}
}
Operaţii cu pointeri
Pentru a exemplifica operaţiile cu pointeri (cinci operaţii) să
urmărim efectul acestora, afişat, după fiecare operaţie, de următorul
program:
125
126 Programare C/C++
la tipul elementelor care sunt conţinute în tablou (adică tipul int). Deci se
declară ce tip de obiect referă variabila pointer.
Dacă definim un tablou de caractere denumit text, putem defini în
mod asemănător un pointer care va referi elemente din tabloul text, cu
declaraţia:
char * text_ptr ;
Acest mod de declarare este necesar pentru a specifica calculatorului
câţi octeţi sunt folosiţi pentru a memora un obiect (tipul int utilizează 2
octeţi, tipul char 1 octet, tipul float 4 octeţi, ş.a.m.d.). Astfel, dacă se
“adună 1 la un pointer“, de fapt, se adună o unitate de memorare pentru a
referi următorul element. Pentru tablouri aceasta înseamnă că adresa este
crescută la adresa ce referă următorul element, şi nu la următorul octet din
memorie.
Deci declaraţia unui pointer specifică tipul de date referite : ”pointer
la orice tip–de-date–obiect“.
Pentru a referi primul element al tabloului valori vom scrie:
val_ptr = &valori ; sau val_ptr = valori;
Nu trebuie utilizat operatorul de adresare (&) în cazul tablourilor,
deoarece compilatorul tratează numele unui tablou, fără indice, ca un pointer
la tablou. Un mod echivalent de iniţializare este aplicarea operatorului de
adresare primului element al tabloului:
val_ptr = &valori [0];
În continuare expresia:
* val_ptr
poate fi utilizată pentru a adresa primul element din tabloul valori, adică
valori[0]. Pentru a referi al doilea element se pot utiliza oricare dintre
expresiile:
val_ptr = &valori[1] sau val_ptr += 1;
Pentru a avea acces la elementul i, din acest tablou, se va utiliza
expresia:
* ( val_ptr + i )
Următoarele două expresii, de exemplu, sunt echivalente:
valori [ 10 ] = 100;
*( val_ptr + 10 ) = 100;
Deci, în general, dacă t este un tablou de elemente de tip x, iar px este de
tipul "pointer la x" şi i , n sunt variabile de tip întreg, atunci declaraţia:
px = t;
va pune pointerul px să refere primul element din t; expresia:
* ( px + i );
va referi valoarea din t[i]; iar declaraţia:
px + = n;
Programare C/C++ 127
va pune px să refere, mai departe, la al n-lea element din tablou, indiferent
de tipul elementului ce este conţinut de tablou.
Operatorii de incrementare şi decrementare asupra unui pointer au
acelaşi efect cu adunarea / scăderea unei unităţi din pointer, deci pointerul
va referi elementul următor / anterior dintr-un tablou. Cei doi operatori pot
fi utilizaţi şi în cazul pointerilor în cele două forme: prefixată şi postfixată.
De exemplu, expresiile :
*( ++ p x ) ; /* incrementează pointerul px, şi apoi referă elementul
din tabloul t */
*( px ++ ) ; /* referă, mai întâi, elementul din tablou, apoi
incrementează pointerul */
Alte operaţii care se pot efectua cu pointeri sunt:
- comparaţia, pentru a vedea dacă doi pointeri sunt egali sau
nu, sau care dintre ei este mai mare;
- adunarea / scăderea unei valori întregi n la un pointer
furnizează adresa obiectului respectiv situat cu n poziţii după /
înaintea celui curent;
- scăderea, care este permisă doar între doi pointeri ce
reprezintă adrese din acelaşi tablou; rezultatul scăderii celor doi
pointeri reprezintă numărul de elemente conţinute, în tablou, între
cei doi pointeri.
Alt exemplu :
127
128 Programare C/C++
în loc de: ptr < valori + 10;
din motive de optimizare a tipului de execuţie:
- în primul caz variabila ptr_sfarsit, calculată o singură dată la
începutul programului, nu îşi modifică valoarea, deci nu e nevoie să
fie reevaluată ;
- în cel de-al doilea caz, expresia valori +10, va fi evaluată, la
fiecare execuţie, cu toate că valoarea sa nu este modificată niciodată
în cadrul buclei; deci pentru această buclă expresia ar fi calculată, în
mod inutil, de 10 ori.
2. În general, procesul de indexare a unui tablou ia mai mult timp de
execuţie decât necesită accesul la conţinutul unui pointer. De fapt aceasta
este unul dintre motivele principale pentru care se utilizează pointerii
pentru a accesa elementele unui vector, întrucât codul generat este mult
mai eficient. Dacă accesul nu se face secvenţial atunci expresiile :
* ( ptr + j ) şi tablou [ j ]
vor necesita acelaşi tip de execuţie .
Dacă, însă, accesul se face secvenţial :
ptr ++ ; /* trece la elementul următor mai rapid, decât
oricare dintre */
i ++ ; /* expresiile următoare , care vor */
tablou [ i ] ; /* lua mai mult timp */
sau tablou[++i]; /* echivalentă cu precedentele două
instrucţiuni */
#include <iostream.h>
void afiseaza(float *sir, int numar_elemente)
{ // functia afiseaza elementele de tip float referite de pointerul sir
int i;
for (i = 0; i < numar_de_elemente; i++)
cout << *sir++ << endl;
}
void main(void)
{
float valori[5] = {1.1, 2.2, 3.3, 4.4, 5.5};// initializarea componentelor
tabloului
afiseaza(valori, 5);
}
Programare C/C++ 129
129
130 Programare C/C++
printf("\tAdresa primului element:\n");
printf("vect[0]=%u, &vect[0][0]=%u, *vect=%u, vect=%u\n",
vect[0], &vect[0][0], *vect, vect);
printf("\tAdresa celui de-al doilea element:\n");
printf("vect[0]+1=%u, &vect[0][0]+1=%u, *vect+1=%u,”
“&vect[0][1]=%u\n",
vect[0]+1, &vect[0][0]+1, *vect+1, &vect[0][1]);
printf("\tAdresa celui de-al treilea element:\n");
printf("vect[0]+2=%u, *(vect+1)=%u, &vect[1][0]=%u\n",
vect[0]+2, *(vect+1), &vect[1][0]);
printf("\tAdresa elementului din linia 3 coloana 2:\n");
printf("vect[0]+2*2+1=%u, *(vect+2)+1=%u, &vect[2][1]=%u\n",
vect[0]+2*2+1, *(vect+2)+1, &vect[2][1]);
printf("Adresa elementului din linia m, coloana n este:\n\t\t\t"
" *(vect + (m-1)*dimensiune_coloana) + n-1\n");
printf("\n");
printf("Moduri de referire la valorile elementelor unui tablou:\n");
printf("**vect = %u, *vect[0] = %u, vect[0][0]= %u\n",
**vect, *vect[0], vect[0][0]);
printf("**(vect+1) = %u, *(*vect+1) = %u\n", **(vect+1), *(*vect+1));
printf("vect[1][0] = %u, vect[0][1] = %u\n", vect[1][0], vect[0][1]);
printf("\tElementului din linia 3 coloana 2:\n");
printf("*(vect[0]+2*2+1)=%u, *(*(vect+2)+1)=%u, vect[2][1]=%u\n",
*(vect[0]+2*2+1),*(*(vect+2)+1), vect[2][1]);
printf("\n");
}
131
132 Programare C/C++
Nu trebuie confundat un pointer la caractere cu un tablou de
caractere. De exemplu pentru o declaraţie:
char text[80];
nu putem scrie o declaraţie de forma :
text = {“Aceasta initializare este o eroare “};
Singurul loc în care se poate face o astfel de atribuire la un tablou de
caractere este când acesta se iniţializează:
char text[80] = {“Aceasta initializare este corecta“};
Se pot defini şi tablouri care să conţină pointeri la şiruri de caractere; în
exemplul următor tabloul zile conţine 7 pointeri la cele 7 şiruri de caractere
ce conţin numele zilelor săptămânii.
main ( )
{int i;
char *zile[]= {“Luni“, ”Marti”, ”Miercuri”, ”Joi”, ”Vineri”, ”Sambata”,
”Duminica “};
/* tiparim zilele saptamanii */
for ( i = 0 ; i < 7 ; i++ )
printf(“ %s\n”, zile[i]);
}
Exemplul 3:
/*afisarea sirului incepand de dupa primul spatiu */
main ( )
{
char s[80];
char *ptrc = s;
int i;
printf (“introduceti un sir de caractere”);
gets(s); /* citeste un sir de caractere pana cand se tasteaza ‘\n’ */
/* se determina primul spatiu sau sfarsit de sir */
if ( s[0] != ‘\0’ )
{
for ( i = 0 ; s[i] && s[i] ! = ‘ ‘; i++);
/* se elimina primul spatiu intalnit */
if (s[i] == ‘ ‘)
i++;
ptrc = &s[i];
printf (ptrc); /* sau puts (ptrc), tipareste sirul de dupa
primul blanc */
}
}
sau în locul ciclului for se poate utiliza următoarea secvenţă cu acelaşi efect:
if ( *ptrc )
{
while ( *ptrc != ‘ ‘ && *ptrc )
ptrc++;
if ( *ptrc == ‘ ‘ )
ptrc++;
puts (ptrc);
}
133
134 Programare C/C++
- în unele implementări mai vechi o structură nu poate fi
transmisă ca argument al unei funcţii, în timp ce un pointer poate
fi transmis; de altfel este mai economic, din punct de vedere al
memoriei, să se transmită un pointer decât întreaga structură;
- multe reprezentări de date sunt, de fapt, structuri ce conţin
pointeri la alte structuri .
Să considerăm declaraţia unei structuri:
struct data
{int zi ;
int luna ;
int an;
};
şi declararea unei variabile şi a unui pointer la acest tip:
struct data astazi;
struct data *data_ptr;
Putem iniţializa acest pointer să refere variabila astazi, astfel:
data_ptr = &astazi ;
Odată asignat pointerul putem avea acces, indirect, la orice componentă
(membru) astfel:
(* data_ptr).zi=28;
În cazul pointerilor la structuri pe lângă operatorul de selecţie (.), utilizat
anterior, se poate utiliza pentru selecţia unui câmp şi operatorul pointer la
structură -> (linie urmată de semnul mai mare), care permite o exprimare
mult mai clară:
data_ptr -> zi =28;
Exemplul 1:
Să reprezentăm informaţiile referitoare la o persoană (nume, prenume,
vârsta, ocupaţie, venit) utilizând o structură şi să accesăm structura utilizând
un pointer:
135
136 Programare C/C++
n1 -------> n2 --------> n3
valoare valoare valoare
urmator urmator urmator
137
138 Programare C/C++
- malloc( dimensiune), alocă un spaţiu continuu de ‘dimensiune’
octeţi, returnând un pointer la începutul blocului alocat, dacă s-a
realizat alocarea cu succes, sau pointerul NULL dacă nu se poate
aloca memorie;
- calloc (n , dim), alocă spaţiu continuu pentru n elemente de date,
fiecare necesitând ‘dim’ octeţi. Spaţiul alocat este iniţializat cu zero.
Dacă s-a alocat, returnează un pointer la începutul spaţiului alocat,
altfel returnează pointerul NULL.
Când se termină operarea cu memoria ce a fost alocată dinamic, cu funcţiile
malloc() sau calloc(), spaţiul de memorie poate fi eliberat (cedat sistemului)
prin aplicarea funcţiei free():
- free (pointer), eliberează blocul de memorie referit de pointer, ce a
fost alocat de una dintre funcţiile calloc( ) sau malloc( ).
Aceste funcţii se află în fişierul antet standard <stdlib.h>.
În C++ pentru operaţii de alocare / eliberare de memorie se
utilizează funcţiile (operatorii) new şi delete.
Funcţia new încearcă să creeze un obiect de tip “nume” prin
alocarea, dacă e posibil, de sizeof ( nume ) octeţi în memoria disponibilă
(denumită “heap”). Durata de alocare e până la eliberare, cu operatorul
delete sau până la sfârşitul programului.
Dacă alocarea se face cu succes, new returnează un pointer către
această zonă, altfel un pointer nul. Trebuie testat pointerul returnat, la fel ca
la funcţia malloc. Dar, spre deosebire de aceasta, new calculează
dimensiunea spaţiului de memorie necesar (sizeof(nume)) fără a fi nevoie de
utilizarea explicită a operatorului. De asemenea pointerul returnat este de
tipul corect, “pointer la nume”, fără să fie necesară prezenţa operatorului de
conversie: nume malloc( sizeof( nume)). Sintaxa sa de utilizare este:
ptr_nume = new nume;
respectiv
delete ptr_nume;
Dacă nume este un tablou pointerul returnat de “new” referă primul
element al tabloului. Pentru tablouri multidimensionale, create cu new
trebuie specificate toate dimensiunile:
mat_ptr = new int[3][10][12];
Un alt avantaj al lui new faţă de malloc este iniţializarea opţională.
În absenţa inţializărilor explicite obiectul creat cu new va conţine date
nedefinite. Obiectele, altele decât tablouri, pot fi iniţializate cu o expresie
adecvată între paranteze:
int_ptr = new int(3);
Pentru exemplificarea acestor funcţii să considerăm următorul
exemplu. Să se aloce, dinamic, memorie pentru 40 de valori întregi, care
Programare C/C++ 139
apoi vor fi iniţializate şi tipărite, după care se eliberează memoria înapoi
sistemului.
139
140 Programare C/C++
tipărită, bineînţeles în ordinea construită. Construirea listei va lua sfârşit
când se introduce de la consolă valoarea zero.
/*
coada.c - programul construieste, apoi tipareste, o lista de valori
intregi, de tip coada
*/
#include <stdio.h>
#include <stdlib.h>
main()
{
struct intrare
{
int valoare;
struct intrare *urmator;
};
struct intrare *start, *ptr_lista;
int valoare;
start = NULL;
printf ("Introduceti un sir de valori, terminat cu 0.\n");
printf ("Valoare:");
scanf ("%d", &valoare);
while ( valoare )
{
if ( start == NULL )
{
start = ( struct intrare * ) malloc( sizeof (struct intrare ));
/* se poate utiliza si functia new in loc de malloc */
start-> valoare = valoare;
start-> urmator = NULL;
ptr_lista = start;
}
else
{
ptr_lista ->urmator=(struct intrare*)
malloc(sizeof(struct intrare));
if ( ptr_lista -> urmator != NULL ) /* se adauga la lista*/
{
ptr_lista = ptr_lista -> urmator;
ptr_lista -> valoare = valoare;
ptr_lista -> urmator = NULL;
Programare C/C++ 141
}
else
exit (0);
}
printf ("Valoare:");
scanf ("%d", &valoare);
}
/* afisare lista */
ptr_lista = start; /* initializare adresa de inceput a listei */
if ( ptr_lista != NULL )
{
do
{
printf ("%d -> ", ptr_lista -> valoare);
ptr_lista = ptr_lista -> urmator;
} while ( ptr_lista != NULL );
printf ("NULL\n");
}
else
printf ("Lista vida ( NULL )\n");
}
141
142 Programare C/C++
Accesul la valoarea referită, pe care un pointer la un pointer o referă
indirect necesită, de asemenea, aplicarea de două ori a operatorului asterisc:
#include <stdio.h>
main( )
{
int x, *p, **q;
x = 10;
p = &x;
q = &p;
printf (“%d”, **q); /*tipareste valoarea lui x referita indirect de q*/
}
Alt exemplu :
typedef float REAL ; /* redefinim tipul float ca REAL */
REAL x , y [10] , z ;
Programare C/C++ 143
Se utilizează, de obicei, litere mari în definiţia typedef pentru a reaminti
utilizatorului că numele tipului este, de fapt, o abreviere simbolică.
Scopul acestei definiţii depinde de plasarea declaraţiei typedef :
- dacă este în interiorul unei funcţii, are scop local, limitat la
aceea funcţie;
- dacă este în exteriorul unei funcţii, atunci scopul este global.
Alte exemple de utilizare:
a) typedef char STRING [81] ;
defineşte un tip denumit STRING, care este un tablou de 81 de
caractere. În consecinţă se pot declara variabile de acest tip astfel:
STRING text , linie ;
Această declaraţie este echivalentă cu următoarea:
char text [81] , linie [81] ;
De remarcat, în acest caz, STRING nu poate fi definită echivalent cu o
declaraţie preprocesor #define.
b) typedef char *STRING_PTR;
defineşte un pointer de tip char. Variabilele de acest tip vor fi
declarate astfel:
STRING_PTR buffer;
c)se poate folosi această declaraţie şi pentru structuri, astfel:
typedef struct
{
double real ;
double imag ;
} COMPLEX ;
COMPLEX numar = { 1.0 , - 1.0 } , var_compx ;
/* se declară două variabile de tip complex, prima iniţializată, cea de-a
doua nu */
d) typedef struct
{
int ziua ;
int luna ;
int anul ;
} DATA ;
DATA zile_nastere [100];
/* se declară vectorul zile_nastere, care conţine 100 de componente de
tip DATA */
Pentru programele ce conţin mai multe module, se recomandă de a plasa
definiţiile typedef utilizate de acestea, într-un fişier separat care poate fi
inclus în fiecare fişier sursă (modul) cu o declaraţie #include.
Trebuie reţinut că utilizarea declaraţiei typedef nu defineşte, de fapt, un
nou tip, ci numai asociază un nume nou tipului respectiv.
143
144 Programare C/C++
Exemplu: să se calculeze distanţa dintre două puncte specificate prin
coordonatele lor în plan.
#include <stdio.h>
#include <math.h>
main ( )
{
typedef struct
{
double x ;
double y;
}PUNCT ;
PUNCT P1, P2 ; double difx , dify;
printf (“Coordonatele punctului P1 ( x , y) : \n “) ;
scanf (“%lf%lf “, &P1.x, &P1.y);
printf (“Coordonatele punctului P2 (x , y ) : \n“);
scanf (“%lf %lf “ , &P2.x , &P2.y ) ;
difx = P1.x – P2.x ;
dify = P1.y – P2.y ;
printf (“Distanţa dintre cele două puncte este %5.2lf\n “ ,
sqrt ( pow (difx , 2 ) + pow (dify , 2 ) ) );
}
Programare C/C++ 145
7. Funcţii
145
146 Programare C/C++
Spre deosebire de Pascal, o funcţie C nu poate conţine alte funcţii. Funcţiile
definite de utilizator pot fi incluse într-unul sau mai multe fişiere – antet,
care vor fi compilate separat.
În general, o funcţie poate realiza atât acţiuni cât şi furniza rezultate.
147
148 Programare C/C++
sunt modificate, ceea ce înseamnă că programatorul nu trebuie să salveze şi
să refacă valorile argumentelor, la apelul unei funcţii.
Transferul parametrilor prin valoare nu este potrivit întotdeauna:
- când se transferă un obiect mare: necesită timp şi spaţiu
pentru a aloca şi copia obiectul în stivă, care poate fi prea mare
pentru aplicaţii în timp real;
- când valorile argumentelor trebuie să fie modificate.
Dacă un pointer (referinţă) este transmis unei funcţii, funcţia poate
modifica valorile referite de pointer, dar nu poate modifica valoarea acestui
pointer.
Utilizarea referinţei (pointerului) unui argument pentru transmiterea
acestuia se impune când funcţia trebuie să returneze rezultate. De asemenea
poate fi utilizată în situaţiile când se transmite un obiect de dimensiuni mari,
când se transmite doar adresa obiectului; în locul copierii întregului obiect,
dacă se transmite argumentul prin valoare.
Pentru a evita modificarea, din greşeală, a pointerului (referinţei), o
bună practică este aceea de a declara argumentul de tip const. Aceasta
permite compilatorului să prevină modificarea neintenţionată a acestuia, mai
ales într-un lanţ de apeluri de funcţii, la care argumentul este transmis.
Pentru început iată câteva exemple simple.
1)
#include <iostream.h>
// functie care tipareste un mesaj fix, deci nu primeste nici un
// parametru
void scrie_mesaj(void)
{
cout<<"Mesaj scris din functie"<<endl;
}
void main(void)
{
cout<<"Despre chemarea functiei"<<endl;
scrie_mesaj();
cout<<"Mesaj scris din program"<<endl;
}
2)
// functie care tipareste, de aceasta data, un sir de caractere transmis
ca
// parametru
#include <iostream.h>
Programare C/C++ 149
void main(void)
{
afis_sir("Salut, C++!");
afis_sir("Mesaje afisate de o functie C++");
}
3)
// functia determina lungimea unui sir de caractere transmis ca
// parametru
#include <iostream.h>
void main(void)
{
char titlu[] = " Introducere in C++";
char tema[] = "Despre siruri de caractere";
cout << "Sirul " << titlu << " contine " << lung_sir(titlu) <<
" caractere" << endl;
cout << "Sirul " << tema << " contine " << lung_sir(tema) <<
" caractere" << endl;
}
4)
// functie care calculeaza suma a doua numere, deci returneaza o
singura valoare
// prin numele functiei
#include<iostream.h>
149
150 Programare C/C++
int aduna(int x,int y)
{
return(x+y);
}
void main(void)
{
cout<<"300+200="<< aduna(300,200)<<endl;
cout<<"10-1="<< aduna(10,-1)<<endl;
}
Alte exemple:
1) Se va scrie un program care citeşte perechi de numere întregi şi
afişează cel mai mare divizor comun pentru fiecare pereche. Se va
utiliza o funcţie pentru determinarea celui mai mare divizor comun.
Programul se va executa ciclic până se citeşte o valoare nulă.
Transmiterea parametrilor se va face prin valoare, întrucât funcţia
primeşte doi întregi şi returnează unul singur - cel mai mare divizor
comun.
/* programul determina cel mai mare divizor comun pentru
perechi de numere intregi, introduse de la tastatura, pana se
introduce o valoare egala cu zero
*/
#include <stdio.h>
int cmmdc (int a, int b)
{
int rest;
while ( b != 0 )
{
rest = a % b;
a = b;
b = rest;
}
return ( a );
}
main ()
{
int cmmdc (int a, int b);
int x, y;
printf ("Numerele :");
scanf ("%d%d", &x, &y);
while ( ( x*y ) != 0 )
Programare C/C++ 151
{
printf ("Cel mai mare divizor comun este :%d\n",
cmmdc (x,y));
printf ("Numerele : ");
scanf ("%d%d", &x, &y);
}
}
151
152 Programare C/C++
do
{
if (valoare % baza < 10)
cifra[index] = '0' + valoare % baza;
else
cifra [index] = 'A' + valoare % baza-10;
index ++;
valoare /= baza;
} while (valoare > 0);
for (--index ; index >= 0; --index)
printf ("%c", cifra[index]);
}
153
154 Programare C/C++
#include <graphics.h>
#include <stdio.h>
int initializare( int *pxmax, int *pymax )
/* se initializeaza modul grafic, prin */
{
int gdriver = DETECT, gmode, errorcode;
/* initializare variabile grafice si locale */
initgraph( &gdriver, &gmode, "");
/* citeste rezultatul initializarii */
errorcode = graphresult();
/* se testeaza daca a aparut o eroare, la initializare, si se
tipareste un mesaj de eroare impreuna cu codul de eroare */
if (errorcode != grOk)
{
printf("Eroare de initializare a modului grafic: %s\n",
grapherrormsg( errorcode ) );
printf("Apasati o tasata pentru a termina programul.");
getchar();
return(1);
}
/* se seteaza culoarea cu care se deseneaza la valoarea maxima */
setcolor(getmaxcolor());
/* se determina dimensiunile maxime ale ecranului */
*pxmax = getmaxx();
*pymax = getmaxy();
return 0;
}
void desencub ( int xss, int yss , int xdj , int ydj )
{
line ( xss , yss , xdj , yss );
line ( xss , yss , xss , ydj );
line ( xss , ydj , xdj , ydj );
line ( xdj , yss , xdj , ydj );
}
Programare C/C++ 155
main()
{
int xmax, ymax, midx , midy , i;
char mesaj [25];
if ( initializare( &xmax, &ymax) == 0 )
{
for ( i = 0; i <= 20 ; i += 5)
desencub(11 *i, 6 *i, xmax - 11*i , ymax - 6*i);
/* se tiparesc dimensiunile ecranului, in modul grafic in centrul
acestuia*/
midx = xmax/2;
midy = ymax /2;
moveto ( midx-80 , midy );
sprintf (mesaj,"dim ecran = %d * %d",xmax,ymax);
outtext (mesaj);
/* se deseneaza o linie pe diagonala principala a ecranului */
line(0, 0, xmax, ymax);
/* se inchide modul grafic */
getchar();
closegraph();
}
}
155
156 Programare C/C++
asemenea, în unele cazuri, aceasta diminuează şi claritatea programului.
Funcţiile ce utilizează variabilele globale nu mai pot fi utilizate în cadrul
altor module, deoarece ele depind de context: nume variabile globale, tipul
lor, etc.
Variabilele declarate în cadrul unei funcţii (sau bloc) sunt variabile
locale ale funcţiei (sau blocului). Ele sunt definite numai în domeniul
funcţiei (blocului) reprezentat de corpul funcţiei sau blocului respectiv,
inclusiv blocurile incluse în acestea, exceptând blocurile interioare acestora
în care variabilele au fost redeclarate.
Variabilele definite în cadrul unei funcţii sunt denumite şi variabile
locale automatice, deoarece ele sunt “automat” create la apelul funcţiei şi
valorile lor sunt locale funcţiei.
Variabilele locale, declarate într-o funcţie (bloc) există numai pe
durata execuţiei acelei funcţii (bloc), fiind create la intrarea în funcţie, prin
alocare de memorie, şi distruse la ieşirea din funcţie, prin eliberarea
memoriei ocupate.
Variabilele declarate în exteriorul unui bloc pot fi referite din cadrul
blocului; pentru acest bloc, variabilele acestea sunt nelocale (sau globale).
Într-un bloc (al unei funcţii) sunt accesibili toţi identificatorii definiţi
în acel bloc, precum şi cei din blocurile în care acesta este inclus. Pentru
oricare din aceşti identificatori, definiţia valabilă în blocul considerat este
cea aflată în blocul cel mai apropiat.
Parametrii formali declaraţi în prototipul funcţiei pot fi folosiţi
pentru a referi argumentele funcţiei oriunde în cadrul corpului funcţiei.
Exemplu de utilizare a variabilelor globale: programul converteşte un număr
întreg într-o altă bază.
/*
Programul afiseaza un numar intreg intr-o baza ,folosind o
functie pentru conversia in baza specificata;
Se utilizeaza pentru transferul parametrilor intre functii:
VARIABILE GLOBALE
*/
#include <stdio.h>
int numar_convertit[16];
int numar, baza;
main()
{
void citeste_numar_baza(void);
void conversie (void);
citeste_numar_baza();
Programare C/C++ 157
if (baza < 2 || baza > 16)
{
printf ("Baza eronata ! Implicit baza = 10 \n");
baza = 10;
}
conversie();
}
void citeste_numar_baza(void)
{
printf ("Numar de convertit:");
scanf ("%d", &numar);
printf ("In baza:");
scanf ("%d", &baza);
}
void conversie (void)
{
char
cifra_baza[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
int index=0;
if (numar < 0)
{
printf("-");
numar = -numar;
}
do
{
numar_convertit[index] = numar % baza;
numar /= baza;
index ++;
} while (numar != 0);
for ( --index ; index >= 0 ; --index )
printf ("%c", cifra_baza[numar_convertit[index]]);
printf ("\n");
}
Efecte laterale
157
158 Programare C/C++
Orice funcţie, fie că nu este de tip void şi calculează o valoare pe
care o întoarce ca rezultat al apelului său, fie că este de tip void, poate
modifica şi variabilele globale (sau nelocale) ale programului, pe lângă
valorile transmise ca parametrii, prin referinţă (pointeri).
Acestea constituie efecte laterale ale funcţiei şi, de obicei, fac
înţelegerea programelor mai dificilă. Din acest motiv se evită modificarea
variabilelor globale în cadrul funcţiilor.
De asemenea, dacă dorim ca funcţia să nu modifice valorile
parametrilor, chiar dacă sunt transmişi prin referinţă, parametrii respectivi
trebuie declaraţi de tip const în antetul funcţiei. În acest fel se evită unele
erori de programare, dar şi prelucrările realizate de funcţie devin mai clare.
/*
Programare C/C++ 159
programul determina valoarea minima dintr-un sir de valori
utilizand pentru determinarea valorii minime o functie
*/
#include<stdio.h>
double minim (double vector[], int nr)
{
int i;
double val_min;
val_min = vector[0];
for ( i = 1 ; i < nr ; i++)
if ( vector[i] < val_min)
val_min = vector[i];
return ( val_min );
}
main()
{
double sir[100];
int ne, i;
printf ("Numar elemente:");
scanf ("%d",&ne);
for ( i = 0 ; i < ne ; i++)
{
printf ("sir(%d)=", i+1);
scanf ("%lf", &sir[i]);
}
printf ("Valoarea minima este:%lf\n", minim(sir,ne));
}
159
160 Programare C/C++
for( j = i+1;j < nrelem ; ++j)
if ( sir[i] > sir[j] )
{
aux = sir[i];
sir[i] = sir[j];
sir[j] = aux;
}
}
main()
{
double sir[1000];
int ne, i;
printf ("Numarul de elemente: ");
scanf ("%d",&ne);
for ( i = 0 ; i < ne ; ++i)
{
printf (" sir(%d)=", i+1);
scanf ("%lf", &sir[i]);
}
sort(sir,ne);
for( i = 0 ; i < ne ; ++i )
printf (" sir(%d)=%lf ", i+1, sir[i]);
}
Tablouri multidimensionale
Un element al unui tablou multidimensional poate fi transmis unei
funcţii la fel ca o variabilă obişnuită sau ca un element al unui tablou
unidimensional:
nume_funcţie (matrice [i][j]);
În acest exemplu se transmite ca argument valoarea elementului matrice[i]
[j].
Pentru a transmite un întreg tablou multidimensional se procedează
la fel ca la tablourile unidimensionale, adică se specifică numele tabloului:
multiplica_scalar (matrice, constanta);
Aceasta implică că, însăşi, funcţia poate modifica valorile conţinute
în tabloul matrice, întrucât se transmite adresa tabloului.
În declararea ca parametru formal al unei funcţii, a unui tablou
unidimensional nu este necesar să se precizeze dimensiunea tabloului, ci se
puteau specifica doar parantezele [] pentru a informa compilatorul de C că
parametrul este, de fapt, un tablou.
Programare C/C++ 161
În schimb, pentru un tablou bidimensional, de exemplu, se poate
omite prima dimensiune (numărul de linii), dar declaraţia trebuie să conţină
numărul de coloane din tablou. Motivul se datorează modului de adresare,
de exemplu, a unui element din linia l, coloana c:
matrice [l][c] = *(*(matrice+l)+c) sau *( matrice + maxc*l + c )
Deci pentru a determina corect adresa elementului trebuie să se
cunoască valoarea maximă a lui c (maxc) – numărul maxim de elemente
dintr-o linie (ştiut fiind că elementele sunt dispuse, în memorie, pe linii).
În general, pentru un tablou multidimensional, se poate omite prima
dimensiune, dar celelalte sunt obligatorii. Astfel, declaraţii de forma:
double matrice [10][10];
sau
double matrice [][10];
sunt corecte pentru un parametru formal de tip matrice (10*10); pe când
declaraţiile:
double matrice [10][];
sau
double matrice [][];
reprezintă erori, deoarece nu se specifică numărul de coloane.
161
162 Programare C/C++
for ( j = 0 ; j < ncoloane ; j++)
matm[i][j] *= valoare;
}
void afisare_matrice ( double mata[][8], int linii, int coloane )
{
int i, j, nr_elem_pe_linie = 0;
for ( i = 0 ; i < linii ; i++)
for ( j = 0; j < coloane ; j++, nr_elem_pe_linie++ )
{
printf (" mat(%i,%i)=%6.2f ", i+1, j+1, mata[i][j]);
if ( nr_elem_pe_linie % 4 == 0 )
printf("\n"); /* se afiseaza cate 4 valori/linie */
}
}
void main()
{
double matrice [8][8], scalar;
int l, c;
printf ("Introduceti matricea :\n");
citire_matrice ( matrice, &l, &c );
printf ("Scalarul cu care se inmultesc elementele matricii:\n");
scanf ("%lf", &scalar );
multiplica_scalar ( matrice, l, c, scalar );
printf ("Matricea multiplicata cu scalarul %f este :\n", scalar );
afisare_matrice( matrice, l, c );
}
163
164 Programare C/C++
int x = 5 , y = 10;
printf ("Valori initiale: x = %d, y = %d\n", x, y);
interschimba (&x, &y ); /* se transmit adresele variabilelor la functie
*/
printf ("Noile valori: x = %d, y = %d\n", x, y);
}
void interschimba ( int *u , int *v )/* u, v sunt pointeri */
{
int temp;
temp = *u;
*u = *v;
*v = temp;
}
165
166 Programare C/C++
exit (0);
}
printf ("Valoare = ");
scanf ("%d", &numar);
}
return ( pstart );
}
void display_lista ( struct s_lista *start )
{
struct s_lista *ptrel = start;
int nr_val_linie = 0;
while ( ptrel != NULL )
{
printf ("%d -> ", ptrel->valoare);
ptrel = ptrel -> urmator;
nr_val_linie++; /* contor pentru numarul de valori afisate pe
liinie */
if ( nr_val_linie % 10 == 0 )
printf ("\n");
}
printf ("\n");
}
struct s_lista *afla_element ( struct s_lista *ptr_lista,
int numar_cautat, int *p_pozitie )
{
struct s_lista *p_lista = ptr_lista;
while ( p_lista != NULL )
{
(*p_pozitie)++;
if ( p_lista ->valoare == numar_cautat )
return ( p_lista );
else
p_lista = p_lista -> urmator;
}
return (NULL);
}
void main()
{
struct s_lista *constructor();
void display_lista ( struct s_lista *start );
struct s_lista *afla_element (struct s_lista *ptr_lista, int numar_cautat,
Programare C/C++ 167
int *p_pozitie );
struct s_lista *start_lista, * ptr_valoare;
int *pozitie, val_cautata;
start_lista = constructor();
display_lista ( start_lista );
if ( start_lista == NULL )
printf ("Lista vida de valori\n");
else
{
printf ("Valoarea de cautat in lista :");
scanf ("%d", &val_cautata);
*pozitie = 0;
ptr_valoare = afla_element(start_lista, val_cautata, pozitie );
if ( ptr_valoare != NULL )
printf ("Valoarea: %d, este in lista pe pozitia %d\n",
ptr_valoare -> valoare, *pozitie);
else
printf ("Valoarea %d nu este in lista \n", val_cautata);
}
}
Probleme propuse:
- construirea unei liste ordonate de numere;
- construirea unei liste de tip stivă ce conţine numere;
- organizarea unui dicţionar de cuvinte, ca o listă înlănţuită.
Deoarece utilizarea pointerilor la tablouri are avantajele unei notaţii
mai uşoare şi o eficienţă a programului (rezultând cod mai scurt, şi deci un
timp de execuţie mai mic) aceştia sunt utilizaţi şi pentru transmiterea de
tablouri, ca parametrii la funcţie.
Să considerăm, de exemplu, o funcţie care furnizează suma
elementelor unui tablou, utilizând ambele metode de transmitere a
parametrilor:
167
168 Programare C/C++
int nr_elem, i;
printf ("Numarul de elemente din vector:");
scanf ("%d", &nr_elem);
for ( i = 0 ; i < nr_elem ; i++)
{
printf ("numar(%d)=", i+1);
scanf ("%lf", &tablou[i]);
}
suma_elemente = suma(tablou, nr_elem);
printf ("Suma elementelor vectorului este;%lf\n", suma_elemente);
}
double suma ( double tab[], int n )
{
int i;
double sum = 0;
for ( i = 0 ; i < n ; i++)
sum += tab[i];
return sum;
}
#include <stdio.h>
#define MAXL 10
#define MAXC 10
double suma ( double **pmat , int nrl, int nrc )
{
double s = 0;
int i, j ;
for ( i = 0 ; i < nrl ; i++ )
for ( j = 0 ; j < nrc ; j++ )
s += (( double *) pmat ) [ i * MAXC + j ];
return ( s );
}
void main (void)
{
double m [ ][ 3 ] = {{1, 2, 3 }, { 2, 4, 6 }, { 3, 6, 9 }};
printf ("Suma elementelor matricii : %lf\n", suma ( m , 3 , 3 ) );
}
169
170 Programare C/C++
Este posibil să se declare pointeri la funcţii. De obicei, un pointer la
funcţie este utilizat ca un argument pentru altă funcţie, pentru a specifica
acesteia ce funcţie să utilizeze.
Pentru a exemplifica utilizarea pointerilor la funcţii, în exemplul
următor vom utiliza pointeri la funcţii pentru a tipări un argument din linia
de comandă în ordine normală şi inversă:
#include <stdio.h>
#include <math.h>
double fct1 ( double x )
{
double val_funct;
val_funct = sqrt ( pow ( x , 2 ) + log ( pow ( x , 2 ) + 2 ) - 1 );
return ( val_funct );
}
double fct2 ( double y )
{
double val_funct;
val_funct = y + y * log ( pow ( y , 2 ) + 2 ) - 1;
return ( val_funct );
}
double integrala ( double (* fp ) (), double li, double ls, int n )
{
double pas, suma = 0 , arg ;
pas = ( ls - li ) / n;
171
172 Programare C/C++
arg = li + pas;
while ( arg < ls )
{
suma += ( *fp ) (arg);
arg += pas;
}
suma *= 2;
suma += ( *fp ) (li) + ( *fp ) (ls);
suma = suma * pas / 2;
return suma;
}
void main ()
{
double a = 1, m = 3, b = 5;
int n =100;
printf ("valoarea integralei este: %lf\n", integrala (fct1, a, m, n)
+ integrala(fct2, m, b, n);
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>
Programare C/C++ 173
#define NUM 40
void umple_siruri ( char (*tab)[LEN], int n);
void display_sir ( char (*tab)[LEN], int n);
int compara ( const void *str1, const void *str2);
char litere [NUM][LEN];
void main()
{
umple_siruri ( litere, NUM ); /* se genereaza cuvinte */
display_sir ( litere,NUM ); /* se afiseaza in forma initiala */
putchar ( '\n' );
qsort ( (void*)litere, NUM, LEN, compara );/*sunt ordonate alfabetic
*/
display_sir ( litere, NUM); /* sunt afisate cuvintele astfel ordonate */
}
void umple_siruri ( char (*tab)[LEN], int n)
{ /* genereaza cuvinte aleatoare */
int cuvant, lit;
for ( cuvant = 0 ; cuvant < n ; cuvant++)
{
for ( lit = 0 ; lit < LEN-1;lit++)
tab[cuvant][lit] = 'a'+rand() % 26;
tab[cuvant][lit-1] = '\0';
}
}
void display_sir ( char (*tab)[LEN], int n)
{
int cuv;
for ( cuv = 0 ; cuv < n ; cuv++)
{
if ( cuv % 8 == 7 ) /* sunt afisate cate 8 cuvinte pe o linie de
ecran */
putchar ( '\n' );
printf ("%s ", tab[cuv]);
}
putchar ('\n');
}
int compara ( const void *str1, const void *str2 )
{ /* compara doua siruri de caractere */
return strcmp ( str1, str2 );
}
173
174 Programare C/C++
Acest exemplu mai utilizează funcţiile:
int rand ( void );
care returneză un număr aleator în intervalul [ 0 , RAND_MAX], unde
RAND_MAX este definit în <stdlib.h>, şi are valoarea minimă 32767;
int strcmp( s1 , s2 );
definită în <stdio.h> şi care compară două şiruri de caractere s1, s2 şi
returnează o valoare negativă, zero sau pozitivă, după cum s1<s2, s1=s2 sau
s1>s2. (size_t este tipul întreg furnizat de operatorul sizeof).
Alte două exemple simple în care funcţia main() are argumente.
#include <iostream.h>
void main(int argc, char *argv[])
// programul citeste parametrii din linia de comanda
{ // si ii afiseaza, fiecare pe cate o linie
int i;
for (i = 0; i < argc; i++)
cout << "argv[" << i << "] contine " << argv[i] << endl;
}
Programare C/C++ 175
175
176 Programare C/C++
char *strchr ( const char *s, int c ) - se va căuta prima apariţie a caracterului
c în şirul s; dacă este găsit va returna pointerul către acesta,
altfel returnează pointerul nul;
int strcmp ( const char *s1, const char *s2 ) - compară s1 cu s2 şi
returnează o valoare negativă dacă s1<s2, returnează 0 dacă
s1 = s2, şi returnează o valoare pozitivă dacă s1<s2;
char *strcpy ( char *s1, const char *s2) - copiază şirul s2 în s1 şi returnează
s1;
size_t strlen ( const char *s) - returnează numărul de caractere din s ,
exclusiv caracterul nul;
char *strncat (char *s1, const char *s2, size_t n) - concatenează şirul de
caractere s2 la sfârşitul lui s1, până când se întâlneşte
caracterul nul, dar nu copiază mai mult de n caractere;
funcţia returnează şirul rezultat în şirul s1;
int strncmp ( const char *s1, const char *s2, size_t n ) - realizează aceeaşi
funcţie cu strcmp cu deosebirea că se compară cel mult n
caractere;
char *strncpy ( char *s1, const char *s2, size_t n) - copiază şirul s2 în s1
până la caracterul nul, dar nu mai mult de n caractere şi
returnează s1;
char *strrchr ( const char *s, int c) - caută în şirul s ultima apariţie a
caracterului c; dacă este găsit returnează un pointer la acest
caracter, altfel returnează caracterul nul;
char *strstr ( const char *s1, const char *s2 ) - caută în s1 prima apariţie a
şirului s2; dacă este găsit va returna un pointer la începutul
şirului s2, din s1, altfel returnează un pointer nul.
void *memchr ( const void *s1 , int c, size_t n ) - caută prima apariţie a lui c
în s1, de n octeţi; returnează adresa lui c sau dacă nu este
găsit returnează NULL;
int memcmp ( const void *s1, const *void *s2, size_t n ) - compară blocurile
de octeţi s1 şi s2; rezultatul este negativ, zero sau pozitiv
după cum, lexicografic, s1<s2 , s1 = s2 sau s1>s2;
void *memcpy ( void *s1, const void *s2, size_t n) - copiază un bloc de n
octeţi din s2 în s1 şi returnează s1; blocurile nu se
suprapun;
Programare C/C++ 177
void *memmove ( void *s1, const void *s2, size_t n ) - copiază un bloc de n
octeţi din s2 în s1 şi returnează s1, chiar dacă blocurile se
suprapun;
void *memset ( void *s1, int c, size_t n ) - memorează în cei n octeţi ai lui s1
valoarea c;
177
178 Programare C/C++
Funcţiile din acest fişier operează asupra unui singur caracter; aceste
funcţii au ca argument un întreg (c) şi returnează valoarea "adevărat" (diferit
de 0) dacă testul este satisfăcut sau "fals" (=0), dacă testul nu este satisfăcut.
isalnum ( c) - este c un caracter alfanumeric ?
isalpha ( c) - este c un caracter alfabetic ?
iscntrl ( c) - este c un caracter de control (0-1FH sau 7F-"del") ?
isdigit ( c) - este c un caracter numeric (cifră) ?
isgraph ( c) - este c un caracter grafic (21H-7EH)?
islower ( c) - este c o literă mică ?
isprint ( c) - este c un caracter tipăribil (20H-7EH) ?
ispunct ( c) - este c un caracter de punctuaţie ? (nu este spaţiu
sau alt caracter alfanumeric)
isspace ( c) - este c un spaţiu alb (spaţiu, linie nouă, tab, return)?
isupper ( c) - este c o literă mare ?
isxdigit ( c) - este c o cifră hexazecimală?
Exemple:
179
180 Programare C/C++
}
181
182 Programare C/C++
else
printf ("Argumentul %d nu este o valoare”
“ numerica\n",i);
}
}
Programare C/C++ 183
183
184 Programare C/C++
Prelucrarea unui fişier presupune, mai întâi, deschiderea sa, care face
asocierea acestuia cu un flux (stream) sau canal de intrare/ieşire. Biblioteca
funcţiilor de intrare/ieşire standard păstrează starea fiecărui flux într-o
entitate de tip FILE. Aceasta conţine informaţii referitoare la adresa şi
lungimea zonei tampon pentru date, modul de utilizare a fişierului (citire,
scriere, actualizare) precum şi :
- un indicator de eroare care e setat la o valoare nenulă, de o
funcţie care întâlneşte o eroare de citire sau scriere;
- un indicator de sfârşit de fişier, care e setat la o valoare
nenulă, dacă se determină sfârşitul fişierului, la citire;
- un indicator de poziţie al fişierului, care precizează
următorul octet din flux, de citit sau scris, fiind avansat după
fiecare operaţie de citire sau scriere, sau este actualizat explicit prin
operaţii de poziţionare.
Tipul FILE este definit în fişierul antet <stdio.h> şi este un tip utilizator
derivat, o structură, pe baza tipurilor fundamentale de următoarea formă :
typedef struct
{ short level; /* nivel buffer : plin/gol */
unsigned flags; /* indicatori stare fişier */
char fd; /* descriptor fişier */
unsigned char hold; /* nu este buffer */
short bsize; /* dimensiune buffer */
unsigned char * buffer; /* buffer transfer date */
unsigned char *curp; /* pointerul curent */
unsigned istemp; /* indicator de fişier temporar */
unsigned token; /* folosit pentru verificări de validitate */
} FILE;
Programele C, la lansare, prin apelul funcţiei main, iniţializează variabilele
statice şi deschid, în mod automat, trei fişiere text predefinite :
- stdin, fişier standard de intrare date, tastatură;
- stdout, fişier standard de ieşire date, display-ul;
- stderr, fişier standard de ieşire pentru erori, display-ul; la apariţia
unei erori pe durata execuţiei programului vi se afişează un mesaj de
eroare pe acest display.
Celelalte fişiere utilizate de program trebuie deschise explicit
utilizând funcţia fopen.
Funcţia fopen
Această funcţie specifică numele fişierului (un pointer la un şir de
caractere ce conţine numele) şi modul de acces la conţinutul său (citire,
scriere sau actualizare):
FILE * fopen (const char *nume_fişier, const char * mod );
Programare C/C++ 185
Se va deschide fişierul “nume_fişier“ şi i se asociază un canal
identificat printr-un pointer, folosit în operaţiile ulterioare asupra fişierului:
FILE * fişier_intrare;
fişier_intrare = fopen ( “date “, “ r “ );
Deschiderea este însoţită de stocarea întregii structuri, descrisă
anterior deci inclusiv a buffer-ului utilizat pentru transfer. Cel de-al doilea
parametru, un şir de caractere, specifică modul în care e deschis fişierul :
“ r “ – deschide un fişier pentru citire;
“ w “ – deschide un fişier pentru scriere;
“ a “ – deschide pentru scriere, adăugând la sfârşitul fişierului
sau creează fişierul dacă nu există încă;
“ r+ “ – deschide pentru actualizare, atât pentru citire cât şi pentru scriere;
“ w+ “ – deschide pentru actualizare (citire şi scriere) prin trunchierea
fişierului la lungime zero, dacă există, sau creează fişier dacă nu
există;
“ a+ “ – deschide pentru actualizare (citire şi scriere) adăugând la sfârşitul
fişierului existent, sau îl creează, dacă nu există; poate fi citit tot
fişierul, dar scrierea se poate face doar prin adăugare.
Funcţia fclose
După realizarea prelucrărilor asupra fişierului (scriere, citire,
actualizare) acesta trebuie închis; pentru închiderea unui fişier se utilizează
funcţia: int fclose ( FILE * ptr_fişier )
care returnează valoarea 0, dacă fişierul a fost închis cu succes, sau
returnează EOF dacă a apărut o eroare.
185
186 Programare C/C++
Dacă un program se termină în mod normal, sistemul de operare va
închide (prin apelarea funcţiei fclose), în mod automat, toate fişierele
deschise.
Această operaţie de închidere a fişierelor trebuie făcută din două
motive :
- numărul de fişiere deschise simultan este limitat de sistemul
de operare;
- se eliberează bufferul de memorie asociat fişierului prin
transferarea tuturor datelor acumulate în acesta, dar încă
netransmise, prin scrierea lor în fişier.
Alte funcţii utilizate în prelucrarea fişierelor sunt prezentate în continuare,
grupate pe categorii.
Funcţii generale
int feof ( FILE * ptr_fişier ) - determină dacă, la ultima operaţie de citire, s-a
ajuns la sfârşitul fişierului; dacă s-a ajuns la sfârşit de fişier
returnează o valoare nenulă, altfel returnează valoarea 0;
int ferror ( FILE * ptr_fişer ) - verifică producerea unei erori şi returnează 0,
dacă a apărut o eroare, altfel returnează o valoare nenulă;
void clearerr ( FILE * ptr_fişier ) - şterge sfârşitul de fişier şi indicatorii de
erori asociaţi fişierului, identificat de ptr_fişier;
int fflush ( FILE * ptr_fişier ) - scrie (goleşte) datele din bufferul intern,
asociat fişierului, referit de ptr_fişier, şi returnează 0 dacă
operaţia a reuşit, iar dacă a apărut o eroare returnează EOF;
int remove ( FILE * ptr_fişier ) - şterge fişierul specificat şi returnează o
valoare nenulă dacă apare o eroare;
int rename ( const char * nume_vechi, const char * nume_nou ) -
redenumeşte fişierul nume_vechi cu nume_nou, şi returnează
o valoare nenulă dacă apare eroare;
int fgetpos (FILE * ptr_fişier, fpos_t * pos ) - memorează la locaţia
specificată de pointerul pos, poziţia curentă a indicatorului de
fişier asociat cu ptr_fişier; tipul fpos_t este definit în
<stdio.h>, ca typedef long fpos_t; funcţia returnează zero în
caz de succes, altfel returnează o valoare nenulă;
int fsetpos ( FILE * ptr_fişier, const fpos_t * pos ) - actualizează indicatorul
de fişier la valoarea referită de pos şi returnează 0 în caz de
succes sau o valoare nenulă în caz de eroare;
int fseek (FILE * ptr_fişier, long offset, int mod ); - poziţionează indicatorul
fişierului, referit de ptr_fişier, la valoarea, în octeţi,
specificată de offset; poziţionarea se face pentru fişierele
binare:
- faţă de începutul fişierului, dacă mod = SEEK_SET;
Programare C/C++ 187
- faţă de poziţia curentă, dacă mod = SEEK_CUR;
- faţă de sfârşitul fişierului, dacă mod = SEEK_END;
SEEK_SET, SEEK_CUR şi SEEK_END sunt definite în
<stdio.h>.
În cazul fişierului text fie offset trebuie să fie 0, fie trebuie să
fie valoarea returnată de un apel anterior al funcţiei ftell; în
cel de-al doilea caz, mod trebuie să fie SEEK_SET. Deci
pentru fişiere text avem situaţiile :
- indicatorul ia valoarea offsetului, care trebuie să fie 0
sau cea returnată de funcţia ftell, dacă mod =
SEEK_SET;
- indicatorul ia valoarea curentă (offset = 0 ), dacă mod
= SEEK_CUR;
- indicatorul ia valoarea de sfârşit de fişier (offset =0),
dacă mod = SEEK_END;
Dacă apare o eroare fseek returnează o valoare nenulă;
long int ftell ( FILE * ptr_fişier ); - furnizează poziţia relativă în octeţi, a
indicatorului fişierului sau – 1L dacă apare o eroare;
void rewind ( FILE * ptr_fişier ); - pune indicatorul fişierului la începutul
acestuia.
int setvbuf ( FILE * ptr_fişier, char * buffer, int mod, size_t dim ) - această
funcţie iniţializează un buffer alternativ pentru a fi utilizat de
funcţiile standard de I/O. Ea este apelată după deschiderea
fişierului şi înainte de prima operaţie asupra fişierului. Se va
utiliza zona de memorie referită de buffer, a cărei dimensiune
este precizată de “dim”; modul selectat poate fi :
_IOFBF – pentru utilizare buffer complet;
_IOLBF – pentru utilizare buffer pentru fiecare linie;
_IONBF – fără buffer-are.
Pointerul buffer nu este NULL; dacă totuşi este NULL, funcţia
va aloca totuşi un buffer. Funcţia va returna zero dacă a avut
succes, altfel va returna o valoare nenulă.
Dacă un program lucrează cu date obiect având, de exemplu,
o dimensiune de 1000 octeţi fiecare, se poate utiliza setvbuf
() pentru a crea un buffer a cărei dimensiune se potriveşte cu
data obiect.
FILE * tmpfile ( void ) - creează şi deschide un fişier binar, temporar, în
modul de actualizare / scriere (“r+b“), sau returnează
pointerul NULL, dacă apare o eroare. Fişierul temporar este
automat şters când se termină execuţia programului. O
funcţie tmpnam poate fi utilizată pentru asocierea de nume
fişierului temporar, unic.
187
188 Programare C/C++
Transferul caracterelor
int fgetc ( FILE * ptr_fişier ) - citeşte următorul caracter din fişier, sau
returnează EOF dacă întâlneşte sfârşit de fişier;
int fputc ( int c, FILE * ptr _fişier ) - scrie caracterul c (convertit la unsigned
char) în fişier şi returnează caracterul c, dacă a fost scris sau
valoarea EOF, dacă nu a fost scris;
int getc ( FILE * ptr_fişier ) - citeşte şi returnează următorul caracter din
fişier; returnează valoarea EOF dacă a întâlnit sfârşit de fişier
sau în caz de eroare;
int getchar ( ) - citeşte următorul caracter stdin; este echivalentă cu fgetc
(stdin);
int putc ( int c, FILE * ptr_fişier ) scrie caracterul c în fişier şi returnează
caracterul, dacă a fost scris, sau EOF, dacă nu.
int putchar ( int c ) echivalentă cu fputc (c, stdout);
int ungetc ( int c, FILE * ptr_fişier ) - pune înapoi în fişier un caracter.
Caracterul nu e scris în fişier ci e pus în bufferul asociat
fişierului. Următorul apel getc va returna acest caracter. Deci
pentru a “pune înapoi“ un caracter trebuie să se fi executat,
anterior, o operaţie de citire din fişier (getc). Funcţia
returnează c, dacă caracterul a fost “ pus înapoi “, altfel
returnează EOF.
189
190 Programare C/C++
Toate componentele arg_i, sunt pointeri. Funcţia returnează
numărul de elemente citite şi asignate cu succes, sau poate
returna valoarea EOF dacă s-a întâlnit sfârşit de fişier înainte
de conversia vreunui element.
int fscanf ( FILE * ptr_fişier, const char *format, arg_1, arg_2, …, arg_n ) -
citeşte date la fel ca funcţia scanf(), cu deosebirea că în loc să
citească stdin citeşte date din fişierul transmis ca prim
parametru.
int sscanf ( const char *s, const char *format, arg_1, arg_2, …, arg_n ) -
preia date, conform format-ului, din şirul s; este
asemănătoare cu funcţiile anterioare cu deosebirea că în loc
să preia elementele dintr-un fişier le preia dintr-un şir de
caractere s.
De exemplu secvenţa :
char data [ ] = “ 15 iunie, 1997 “, luna [ 10 ];
int an, zi;
-----------
sscanf ( data, “%d %s, %d “, &zi, &luna, &an );
va memora : luna = “ iunie “; zi = 16; an = 1997.
Alt exemplu :
if ( sscanf ( arg [ 1 ], “%lf “, &val ) != 1 )
{ fprintf ( stderr, “ numar gresit : %s\n “, argv [1] );
exit ( 1 );
}
va converti primul argument din linia de comandă (referit
argv[1]) la o valoare reală (double) şi verifică dacă a fost
memorată cu succes în s (deci dacă s-a făcut conversia); dacă
nu va tipări mesajul de eroare.
int printf ( const char * format, arg_1, arg_2. . . , arg_n ); - scrie
argumentele specificate în stdout, conform formatului
specificat de şirul de caractere “format“. Returnează numărul
de caractere scrise.
int fprintf ( FILE * ptr_fisier, const char * format, arg_1, arg_n . . . ,
arg_n ); scrie date, la fel ca funcţia anterioară, dar nu în
stdout ci în fişierul referit de primul argument. Dacă apare o
eroare returnează o valoare negativă, altfel returnează
numărul de caractere scrise.
int sprintf ( char * s, const char * format, arg_1, arg_2,. . . , arg_n ); -
argumentele specificate, arg_i, sunt convertite conform
formatului specificat, format, şi sunt depuse în tabloul de
caractere referit de s. Este depus în mod automat, caracterul
Programare C/C++ 191
nul la sfârşitul şirului s. Funcţia returnează numărul de
caractere depuse în s, mai puţin caracterul nul.
De exemplu :
int var = 2;
char nume [ 120 ];
----------------
sprintf ( nume, “ / usr / data % i / 1997 “, var );
va avea efectul ca şirul de caractere :
“ / usr / data2 / 1997 “
să fie memorat în nume.
191
192 Programare C/C++
193
194 Programare C/C++
Pentru a copia un text din intrarea standard stdin în fişierul referit de pf se va
utiliza următoarea secvenţă:
char c;
while (( c = getchar()) != EOF)
putc (c, pf);
fclose (pf);
195
196 Programare C/C++
exit(2);
}
/*Se initializeaza un buffer de 1024 octeti pt acest fisier*/
if ( setvbuf ( pfa, NULL, _IOFBF, DIMBUF ) != 0 )
{
fputs ( "Nu se poate crea bufferul de iesire\n", stderr );
exit(3);
}
/* pentru fiecare fisier din linia de comanda:
- daca are acelasi nume cu destinatia -> trece la fisierul urmator;
- daca nu poate fi deschis pentru citire -> urmatorul fisier;
- adauga continutul fisierului la fisierul destinatie */
for ( nrfisier = 1 ; nrfisier < argc-1 ; nrfisier++ )
{
if ( strcmp ( argv[argc-1], argv[nrfisier] ) == 0 )
printf ( "Nu se poate adauga fisierul la el insusi\n" );
else if ( ( pfr = fopen ( argv[nrfisier], "r" ) ) == NULL )
printf ( "Nu se poate deschide fisierul: %s\n",
argv[nrfisier] );
else
{
if ( setvbuf ( pfr, NULL, _IOFBF, DIMBUF ) != 0 )
{
printf ( "Nu se poate crea buffer de iesire\n" );
continue;
}
adauga ( pfr, pfa );
fclose ( pfr );
}
}
fclose(pfa);
}
197
198 Programare C/C++
5. b) Actualizarea fişierului creat anterior, pentru un produs, al cărui număr
(cod) se citeşte (introduce) de la tastatură.
199
200 Programare C/C++
201
202 Programare C/C++
Funcţii generale
void exit (int n) - termină execuţia programului, închizând orice fişier
deschis şi returnând starea ieşirii specificată de argumentul
n. EXIT_SUCCES şi EXIT_FAILURE, definite în <stdlib.h>
pot fi utilizate pentru a returna o stare de ieşire cu succes sau
cu eroare, respectiv. Mai pot fi utilizate rutinele abort sau
atexit.
char *getenv (char*s) - returnează un pointer la variabila referita de s sau
un pointer nul, dacă variabila nu există. Funcţia operează
dependent de sistem. De exemplu secvenţa:
char *homedir;
............
homedir = getenv("HOME");
poate fi utilizată pentru a lua valoarea variabilei utilizator
HOME, memorând un pointer la ea în "homedir".
void qsort (const void *tab, size_t n, size_t din , int funct_comp()); -
această funcţie sortează (ordonează) un tablou de date referit
de tab, care are n componente, fiecare de dimensiune dim
octeţi. Cel de-al patrulea argument este de tip "pointer la
funcţia ce returnează int şi ce ia două argumente pointer
void". Funcţia qsort apelează această funcţie când trebuie sa
compare două elemente din vector transmiţându-i pointeri la
elementele de comparat. Această funcţie e furnizată de
utilizator şi compară două elemente; ea va returna o valoare
mai mică ca zero, egală cu zero sau mai mare ca zero după
cum relaţia între cele 2 elemente e1 respectiv e2 este: e1 <
e2 , e1 = e2 sau e1 > e2.
de exemplu:
203
204 Programare C/C++
int data[1000], comp_int ( void *, void * );
......
qsort ( data, 1000, sizeof ( int ) , comp_int);
......
int comp_int ( void *pe1, void *pe2 )
{
int e1 = *( int *) pe1;
int e2 = *( int *) pe2;
return ( e1-e2 );
}
Funcţia qsort() sortează tabloul utilizând algoritmul
quicksort(); se poate utiliza şi funcţia bserach(), care are
aceleaşi argumente cu qsort(), dar realizează sortarea unui
tablou utilizând metoda de selecţie binară.
int system (char *s); - furnizează sistemului pentru execuţie comanda
conţinută în şirul de caractere referit de s; funcţia returnează
o valoare definită de sistem; dacă s este pointerul nul, funcţia
returnează o valoare nenulă dacă un procesor de control
(comandă) este capabil să execute comanda utilizatorului. De
exemplu, o comandă pentru crearea unui director:
system ( "md /usr/tmp/data");
Exemple de utilizare a funcţiilor matematice au fost prezentate pe
parcurs, în capitolele anterioare. Vom mai prezenta, totuşi, câteva simple
exemple.
205
206 Programare C/C++
return ( valf );
}
void main()
{
double a, b, mijloc, precizia, modul;
printf ("Introduceti intervalul a,b: ");
scanf ("%lf", &a);
scanf ("%lf", &b);
printf ("introduceti precizia de localizare a radacinii:");
scanf ("%lf", &precizia);
do
{
mijloc = (a+b)/2;
if ( f (a) * f (mijloc) < 0 )
b = mijloc;
else if ( f ( mijloc ) * f(b) < 0 )
a=mijloc;
else goto gata;
modul =a-b;
if ( modul < 0 )
modul = -modul;
} while( modul > precizia );
gata : printf ("Solutia : %lf\n\n", ( a+ b)/2 );
}
Programare C/C++ 207
Variabile globale
Memorie
disponibilă pentru
alocare
207
208 Programare C/C++
Funcţiile pentru alocarea - eliberarea memoriei dinamice se afla în
fişierul <stdlib.h>.
Pentru a aloca dinamic memorie, adică în timpul execuţiei programului, se
utilizează funcţia malloc(), care alocă memorie în spaţiul de după variabilele
globale şi până la stivă. Funcţia malloc() este declarată astfel:
void * malloc (size_t număr_octeţi);
Funcţia returnează un pointer de tipul void, care înseamnă că trebuie utilizat
un operator explicit de conversie de tip (cast), când pointerul returnat de
această funcţie se asociază un pointer de un anumit tip .Dacă alocarea s-a
putut face funcţia returnează un pointer valid, iar dacă nu, va returna
pointer nul (0).
Deci după fiecare utilizare a acestei funcţii trebuie testat pointerul returnat
de malloc() pentru a fi siguri că alocarea s-a făcut cu succes; altfel se va
utiliza un pointer cu adresa 0 care va scrie peste tabela vectorilor de
întrerupere şi va conduce la blocarea calculatorului; zona alocată are
dimensiunea număr_octeţi.
Cealaltă funcţie free() returnează (eliberează) sistemului blocul de
memorie alocat anterior de funcţia malloc(). Bineînţeles că după eliberarea
memoriei se poate utiliza din nou funcţia malloc(). Această funcţie este
declarată astfel: free ( void * ptr)
Ori de câte ori este apelată această funcţie, ea trebuie apelată cu un
argument (pointer) valid, altfel se poate pierde controlul asupra
calculatorului. Aceste funcţii de alocare dinamică precum şi altele (calloc,
realloc) se află în fişierul antet standard <stdlib.h>. De asemenea mai sunt
disponibile funcţiile :
void * calloc ( size_t n, size_t dim ); - aloca un spaţiu continuu (bloc) de n
componente, unde fiecare componentă ocupă dim octeţi.
Spaţiul alocat este iniţializat cu 0. Funcţia returnează un
pointer la blocul de memorie alocat sau un pointer NULL
dacă nu există suficientă memorie pentru alocare.
void * realloc ( void *ptr, size_t dim ); - modifică (realocă) dimensiunea
unui bloc alocat anterior la "dim" octeţi, returnând un
pointer la noul bloc (care poate fi mutat în altă zonă de
memorie şi atunci se copiază vechiul conţinut) sau un pointer
NULL dacă apare o eroare.
if (float_sir != NULL)
{
for (i = 0; i < 200; i++)
float_sir[i] = (i + 1) * 1.0;
209
210 Programare C/C++
adauga_coada, si apoi tipareste lista de valori intregi,
de tip coada
*/
#include <stdio.h>
#include <stdlib.h>
struct s_coada
{
int valoare;
int nr_aparitii; /* numar aparitii ale numarului in coada */
struct s_coada *urmator;
};
int adauga_coada ( struct s_coada *primul, int val )
{
struct s_coada *ptr_coada;
if ( primul == NULL )
{
primul = (struct s_coada *) malloc (sizeof(struct s_coada));
if ( primul == NULL )
{
printf ("Memorie plina\n");
return ( 0 );
}
else
{
primul -> valoare = val;
primul -> nr_aparitii = 1;
primul -> urmator = NULL;
return ( 1 );
}
}
else
{
ptr_coada = primul ;
while ( ptr_coada -> valoare != val
&& ptr_coada -> urmator != NULL )
ptr_coada = ptr_coada -> urmator;
if ( ptr_coada -> valoare == val )
ptr_coada -> nr_aparitii ++;
else
{
ptr_coada -> urmator =
Programare C/C++ 211
( struct s_coada *) malloc(sizeof (struct s_coada));
ptr_coada = ptr_coada -> urmator;
if ( ptr_coada == NULL )
{
printf ("Memorie plina!\n");
return ( 0 );
}
else
{
ptr_coada -> valoare = val;
ptr_coada -> nr_aparitii = 1;
ptr_coada -> urmator = NULL;
return ( 1 );
}
}
}
}
void afisare_coada ( struct s_coada * primul )
{
struct s_coada *ptr;
ptr = primul;
if (ptr == NULL)
printf("Coada goala!!\n");
while ( ptr != NULL )
{
printf ( "Numar: %d, numar de aparitii: %d\n",
ptr -> valoare, ptr -> nr_aparitii );
ptr = ptr -> urmator;
}
}
void main()
{
struct s_coada *prim = (struct s_coada *)
malloc (sizeof(struct s_coada));
/* initializarea adresei variabilei 'prim'; altfel daca prim = NULL
functia 'adauga_coada' nu primeste o adresa valida la care incepe
lista si la care se face actualizarea (adaugare element sau daca
acesta exista se incrementeaza campul 'nr_aparitii'
*/
int numar;
prim -> valoare = 0;
211
212 Programare C/C++
prim -> nr_aparitii = 0;
prim -> urmator = NULL;
printf ("Programul construieste si afiseaza o coada (lista) de”
“ numere\n");
printf ("Introduceti un numar:");
while ( scanf ("%d", &numar ) != EOF )
{
if (adauga_coada (prim , numar) == 0)
break;
printf ( "Introduceti numar:");
}
afisare_coada ( prim );
free ( prim );
}
Programare C/C++ 213
213
214 Programare C/C++
creat. Deşi noile variabile au aceleaşi nume cu cele existente înainte de
apelul recursiv al funcţiei, valorile lor sunt distincte deoarece au domenii de
valabilitate diferite: identificatorii se referă întotdeauna la ultimul set de
variabile creat. Aceleaşi reguli sunt valabile şi pentru parametrii funcţiei,
care sunt înglobaţi prin definiţie în procedură.
Să considerăm un alt exemplu: să tipărim în ordine inversă un şir de
numere strict pozitive, de lungime arbitrară, care se termină cu o valoare
nulă sau negativă. Varianta recursivă a acestei funcţii este următoarea:
/* funcţia main ( ) */
.........
invers ( ); { apelul funcţiei invers ( ) }
inv ( 1 ) ; - primul apel al funcţiei
citeşte n(1) , n(1) > 0 ;
apel invers ;
inv ( 2 ) ;
citeşte n ( 2 ) , n( 2 ) > 0 ;
apel invers ;
inv ( 3 ) ;
citeşte n ( 3 ) , n( 3 ) > 0 ;
Programare C/C++ 215
apel invers ;
inv (4 ) ;
citeşte n (4 ) , n( 4 ) <=0;
scrie n ( 4 );
revenire din apel;
scrie n ( 3 ) ;
revenire din apel;
scrie n ( 2 ) ;
revenire din apel;
scrie n ( 1 ) ;
revenire din apel;
/* se continuă funcţia main ( ) */
.......
215
216 Programare C/C++
if ( n <= 0 )
rezultat = 1;
else
rezultat = n * factorial ( n-1 );
return ( rezultat );
}
void main()
{
long int i;
printf ( "Valoarea pentru care se calculeaza factorial, i = " );
scanf ( "%li", &i );
printf ("\nFactorial ( %li ) = %li\n", i , factorial (i) );
}
217
218 Programare C/C++
- dacă n=1 , se mută un singur disc;
- dacă n >1 se mută primele n-1 discuri în poziţia mijloc, prin
intermediul poziţiei dreapta; se mută apoi discul rămas în poziţia
dreapta, după care se mută cele n-1 discuri din poziţia mijloc în
poziţia dreapta, prin intermediul poziţiei stânga.
De exemplu deplasarea celor n discuri din poziţia stânga în poziţia dreapta,
muta (n, stanga, dreapta) este echivalentă cu următoarea secvenţă de sub-
probleme :
muta ( n-1 , stanga , mijloc );
deplaseză un disc din poziţia "stânga" în "dreapta";
muta ( n-1 , mijloc , dreapta );
Am redus problema la a deplasa n-1 discuri, iar acest procedeu poate fi
repetat pentru a rezolva problema. De exemplu operaţia muta ( n-1 , stanga ,
mijloc ) poate fi exprimată astfel:
muta ( n-2 , stanga , dreapta );
deplasează un disc din poziţia stânga în mijloc;
muta ( n-2 , dreapta , mijloc );
Algoritmul acesta de descompunere a problemei mutării a k discuri
în subprobleme de mutare a k-1 discuri poate continua până când se ajunge
la k-1=1, adică deplasarea unui singur disc. În algoritmul general de
rezolvare a acestei probleme va trebui să specificăm, pentru mutarea
discurilor de la poziţia sursă la cea destinaţie, şi poziţia intermediară care
este utilizată pentru a realiza un turn provizoriu. Vom nota această funcţie
astfel :
muta ( n , sursa , mijloc , destinatie );
care înseamnă că se mută n discuri de la poziţia "sursă" în poziţia
"destinaţie", utilizând poziţia intermediară "mijloc" pentru un turn
provizoriu.
Această mutare poate fi rezolvată prin descompunerea în
subprobleme astfel:
muta ( n - 1 , stanga , dreapta , mijloc );
deplasează un disc de la stânga la dreapta;
muta ( n - 1 , mijloc , stanga , dreapta );
Acest algoritm este valabil cât timp n>=1. Se observă că această
funcţie este recursivă, apelându-se pe ea însăşi cât timp n>=1. Întregul
program pentru rezolvarea problemei "Turnurilor din Hanoi" este
următorul:
Recursivitate şi iteraţie
219
220 Programare C/C++
#include <stdio.h>
221
222 Programare C/C++
void main ( )
{
void apel ( );
char car;
printf ( "Introduceti un caracter (Q-pentru sfarsit):\n");
car = getchar ( );
if ( car != 'Q' )
apel ( );
}
void apel ( )
{
main ( );
}