Sunteți pe pagina 1din 82

Invata Limbajul de Programare C Partea 1

Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul
de programare C si conceptele Programarii Structurata.

Introducere
C este un limbaj de programare structurat menit s simplifice scrierea programelor apropiate de masin. A fost creat de ctre Dennis Ritchie n perioada 1968-1973 i a fost dezvoltat n strns legatur cu sistemul de operare Unix, care a fost rescris n ntregime n C. Utilizarea limbajului s-a extins cu trecerea timpului de la sisteme de operare i aplicaii de sistem la aplicaii generale. Dei n prezent, pentru dezvoltarea aplicaiilor complexe au fost create limbaje de nivel mai nalt ( Java, C#, Python), C este n continuare foarte folosit la scrierea sistemelor de operare i a aplicaiilor de performan mare sau dimensiune mic (n lumea dispozitivelor embedded). Nucleele sistemelor Windows i Linux sunt scrise n C.

Compilatorul GCC
GCC este unul dintre primele pachete software dezvoltate n cadrul Proiectului GNU (GNUs Not Unix) de ctre Free Software Foundation. Dei GCC se traducea iniial prin GNU C Compiler, acesta a devenit ntre timp un compilator multifrontend, multi-backend, avnd suport pentru o serie larg de limbaje, ca C, C++, Objective-C, Ada, Java, etc, astfel c denumirea curent a devenit GNU Compiler Collection. Compilatorul GCC ruleaz pe o gam larg de echipamente hardware (procesoare din familia: i386, alpha, vax, m68k, sparc, HPPA, arm, MIPS, PowerPC, etc.) i de sisteme de operare (GNU/Linux, DOS, Windows 9x/NT/2000, Solaris, Tru64, VMS, Ultrix, Aix ), fiind la ora actual cel mai portat compilator. Compilatorul GCC se apeleaz din linia de comand, folosind diferite opiuni, n funcie de rezultatul care se dorete (specificarea de ci suplimentare de cutare a bibliotecilor/fiierelor antet, link -area unor biblioteci specifice, opiuni de optimizare, controlul stagiilor de compilare, al avertisementelor, etc.). Pentru exemplificare vom considera urmtorul program foarte simplu:

/*hello.c*/ #include <stdio.h> int main() { printf("Hello from your first program!"); return 0; }

Pentru compilarea programului se va lansa comanda (n linia de comand): gcc hello.c

presupunnd c fiierul surs se numete hello.c (este esenial ca extensia s fie c i nu C (C mare aa cum este cazul fiierelor produse de Borland C++ 3.1), deoarece aceasta din urm este interpretat de ctre compilator ca fiind extenesie de fiier C++). n funcie de sistemul de operare folosit, pentru executarea programului astfel obinut se va lansa fie comanda

pentru Linux a

pentru Windows, fie comanda ./a.out

Prima comand are ca efect compilarea i link -editarea (rezolvarea apelurilor de funcii) fiierului surshello.c, generndu-se un fiier executabil, al crui nume implicit este a.out n cazul sistemelor Linux i a.exen cazul sistemelor Windows. Pentru un control mai fin al comportrii compilatorului, sunt prezentate n tabelul urmtor cele mai folosite opiuni (pentru lista complet studiai pagina de manual pentru GCC man gcc):

Opiune -o nume_fiier

Efect Numele fiierului de ieire va fi nume_fiier. n cazul n care aceast opiune nu este setat, se va folosi numele implicit (pentru fiiere executabile: a.out pentru Linux i a.exe pentru Windows)

Caut fiiere antet i n calea specificat Icale_catre_fisiere_antet -L cale_catre_biblioteci Caut fiiere bibliotec i n calea specificat

-l nume_biblioteca

-W tip_warning

-c -S

Link-editeaz librria nume_biblioteca. Atenie!!! nume_bibliotec nu este ntotdeauna acelai cu numele fiierului antet prin care se include aceast bibliotec. Spre exemplu, pentru includerea bibliotecii de funcii matematice, fiierul antet este math.h, iar biblioteca este m Afieaz tipurile de avertismente specificate (Pentru mai multe detalii man gcc saugcc --help). Cel mai folosit tip este all. Este indicat ca la compilarea cu -Wall s nu apar nici un fel de avertismente Compileaz i asambleaz, dar nu link-editeaz. Rezult fiiere obiect, cu extensia .o Se opreste dup faza de compilare, far s asambleze. Rezult cod assembler in fiiere cu extensia .s

Spre exemplu: gcc -o exemplu exemplu.c -lm -Wall

are ca efect compilarea i link-editarea fiierului exemplu.c, cu includerea bibliotecii matematice, afind toate avertismentele. Fiierul de ieire se va numi exemplu.exe (extensia e determinat de sistemul de operare, dar i de coninutul fiierului surs).

Utilitarul Make
Utilitarul make determin automat care sunt prile unui proiect care trebuie recompilate ca urmare a operrii unor modificri i declaneaz comenzile necesare pentru recompilarea lor. Pentru a putea utiliza make, este necesar un fiier de tip makefile numit de obicei Makefile (sau makefile) care descrie relaiile de dependen ntre diferitele fiiere din care se compune programul i care specific regulile de actualizare pentru fiecare fiier n parte. n mod normal, ntr-un program, fiierul executabil este actualizat (recompilat) pe baza fiierelor -obiect, care la rndul lor sunt obinute prin compilarea fiierelor surs. Totui, acest utilitar poate fi folosit pentru orice proiect care conine dependene i cu orice compilator/utilitar care poate rula n linia de comand. Odat creat fiierul makefile, de fiecare dat cnd apare vreo modificare n fiierele surs, este suficient s rulm utilitarul make pentru ca toate recompilrile necesare s fie efectuate. Programul make utilizeaz fiierul Makefile ca baz de date i pe baza timpilor ultimei modificri a

fiierelor din Makefile decide care sunt fiierele care trebuie actualizate. Pentru fiecare din aceste fiiere, sunt executate comenzile precizate in Makefile. n continuare prezentm un exemplu simplu de makefile. # Declaraiile de variabile CC = gcc CCFLAGS = -Wall -lm SRC = radical.c PROGRAM = radical # Regul de compilare all: $(CC) -o $(PROGRAM) $(SRC) $(CCFLAGS) # Regulile de "curaenie" (se folosesc pentru tergerea fiierelor intermediare si/sau rezultate) .PHONY : clean clean : rm -f $(PROGRAM) core *~

Pachetul MinGW
Compilatorul GCC este disponibil pentru Windows n pachetul MinGW . Pentru instalare folosii fiierul .exe. Antenie! Installer-ul are nevoie de acces la Internet pentru a putea descrca diversele componen te. Tot de la aceast adres se poate descrca si pachetul MSYS care conine variante portate ale utilitaruluimake i ale programelor folosite cel mai frecvent n makefile-uri (rm, cd, cat, etc.). Instalarea este simpl, folosind un setup intuitiv. Pentru funcionarea corect a pachetului MinGW/MSYS, este ns necesar adugarea directorului unde se afl binarele instalate la variabila de mediu PATH (n mediul Windows). Pentru GCC, aceasta este de obicei C:MinGWbin. Pentru sistemele Windows 2000 i mai recente setarea variabilei de mediu PATH se realizeaz astfel:

click dreapta pe My Computer > Properties din tabul Advanced se selecteaz Environment Variables aici dublu click pe elementul Path din System Variable adaugai la sfaritul irului de la Variable value calea ctre directorul care conine binarele compilatorului GCC (vezi imaginea).

Pentru Ubuntu puteti folosi urmatoarea comanda pentru a instalat make apt-get install build-essential

Editoare
Pentru editarea surselor se poate folosi orice editor de text. Astfel, putem meniona: Linux: vi(m), pico, joe, nano, emacs, mcedit cu interfaa n mod text si Kate, KWrite, GEdit, Scribes cu interfa grafic. Windows: Crimson Editor, Notepad++, Textpad, etc. Java: jEdit

Dei lista nu este complet, editoarele specificate au pe lang facilitile standard (Cut/Copy, wordwrap) si suport pentru syntax highlight, auto-indent, etc. ceea ce le face s fie mai prietenoase i s ajute n scrierea codului.

Interaciunea program-utilizator
Majoritatea algoritmilor presupun introducerea unor date de intrare i calcularea unor rezultate. n cazul programelor de consol, datele sunt introduse de la tastatur i afiate pe ecran (alte variante sunt folosirea fiierelor sau preluarea datelor de la un hardware periferic). Programul dat ca exemplu mai sus folosete funcia de afiare printf. Aceast funcie realizeaz transferul i conversia de reprezentare a valorii ntregi / reale in ir de caractere sub controlul unui fo rmat (specificat ca un ir de caractere): printf("format", expr_1, expr_2, ..., expr_n);

unde expr_i este o expresie care se evalueaz la unul din tipurile fundamentale ale limbajului. Este necesar ca pentru fiecare expresie s existe un specificator de format, i viceversa.

n caz contrar, compilatorul va returna o eroare (n afara cazului n care formatul este obtinut la rulare). Sintaxa unui descriptor de format este: % [ - ] [ Lung ] [ .frac ] [ h|l|L ] descriptor

Semnificaia cmpurilor din descriptor este descris n tabelul urmtor:

Cmp

Descriere Indic o aliniere la stnga n cmpul de lungime Lung (implicit alinierea se face la dreapta). Dac expresia conine mai puin de Lung caractere, ea este precedat de spaii sau zerouri, dac Lung ncepe printr-un zero. Dac expresia conine mai mult de Lung Lung caractere, cmpul de afiare este extins. n absena lui Lung, expresia va fi afiat cu attea caractere cte conine. frac Indic numrul de cifre dup virgul (precizia) cu care se face afiarea. l Marcheaz un long int, n timp ce pentru reali l determin afiarea unei valori double. h Marcheaz un short int L Precede unul din descriptorii f,e,E,g,G pentru afiarea unei valori de tip long double.

Tabelul urmtor prezint descriptorii i conversiile care au loc:

Descriptor Descriere d ntreg cu semn n baza 10 u ntreg fr semn n baza 10 o ntreg fr semn n baza 8 x sau X ntreg fr semn n baza 16. Se folosesc literele a, b, c, d, e, f mici, respectiv mari c Caracter s ir de caractere f Real zecimal de forma [-]xxx.yyyyyy (implicit 6 cifre dup virgul) e sau E Real zecimal n notaie exponenial. Se folosete e mic, respectiv E mare g La fel ca i e, E i f dar afiarea se face cu numr minim de cifre zecimale
Citirea cu format se realizeaz cu ajutorul funciei scanf() astfel: scanf("format", &var_1, &var_2, ..., &var_n)

care citete valorile de la intrarea standard n formatul precizat i le depune n variabilele var_i, returnnd numarul de valori citite. Atenie! Funcia scanf primete adresele variabilelor n care are loc citirea. Pentru tipuri fundamentale i/sau structuri, aceasta se obine folosind operatorul de adres &.

Sintaxa descriptorului de format n acest caz este: % [*] [ Lung ] [ l ] descriptor

Semnificaia campurilor din descriptor este descris n tabelul urmtor:

Cmp

Descriere Indic faptul c valoarea citit nu se atribuie unei variabile. (valoarea citit poate fi * folosit pentru specificarea lungimii cmpului) Indic lungimea cmpului din care se face citirea. n cazul n care e nespecificat, citirea Lung are loc pn la primul caracter care nu face parte din numr, sau pn la n (linie nou/enter) d ntreg n baza 10 o ntreg n baza 8 x ntreg n baza 16 f Real c Caracter

s L h

ir de caractere Indic un ntreg long sau un real double Indic un ntreg short

Pentru scrierea i citirea unui singur caracter, biblioteca stdio.h mai definete i funciile getchar() iputchar():

getchar() are ca efect citirea cu ecou a unui caracter de la terminalul standard. Caracterele introduse de la tastatur sunt puse ntr-o zon tampon, pn la acionarea tastei ENTER, moment n care n zona tampon se introduce caracterul rnd nou. Fiecare apel getchar() preia urmtorul caracter din zona tampon.

putchar() afieaz caracterul avnd codul ASCII egal cu valoarea expresiei parametru.

Nota: getchar() i putchar() nu sunt de fapt funcii, ci nite macroinstruciuni definite n stdio.h Pentru citirea i scrierea unei linii biblioteca stdio.h definete funciile gets() i puts(): gets(zona) - introduce de la terminalul standard un ir de caractere terminat prin acionarea tastei ENTER. Funcia are ca parametru adresa zonei de memorie n care se introduc caracterele citite. Funcia returneaz adresa de nceput a zonei de memorie; la ntalnirea sfaritului de fiier (CTRL+Z) funcia returneaz NULL. puts(zona) afieaz la terminalul standard irul de caractere din zona dat ca parametru, pn la caracterul null (), care va fi nlocuit prin caracterul linie nou. Funcia returneaz codul ultimului caracter din irul de caractere afiate sau -1 n caz de eroare.

Invata Limbajul de Programare C Partea 2


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul
de programare C si conceptele Programarii Structurata.

Noiuni teoretice Tipuri fundamentale de date


Tipurile de date reprezint tipul de informaie care poate fi stocat ntr -o variabil. Un tip de data definete att gama de valori pe care o poate lua o variabil de un anume tip ct i operaiile care se pot efectua

asupra ei. n continuare sunt prezentate tipurile fundamentale ale limbajului C, mpreun cu o scurt descriere a acestora: char reprezentat printr-un numr pe 8 bii (un byte), stocheaz un caracter, definit n C printr -un numr n intervalul [-128; +127]. De observat c valorile pozitive codific caracterele standard ASCII int - stocheaz numere ntregi. Lungimea sa (i implicit plaja de valori) este dependent de compilator si sistemul de operare considerat. De obicei, pe Linux, int se reprezint pe 32 de bii (deci 4 bytes). n acest caz, poate memora numere ntre 2.147.483.648 i 2.147.483.647 float - reprezint un numr real stocat n virgul mobil, simpl precizie (7 cifre), n gama de valori 3.4E +/- 38(reprezentat pe 4 bytes) double - reprezinta un numr real stocat n virgul mobil, dubl precizie (15 cifre), n gama de valori 1.7E +/- 308 (reprezentat pe 8 bytes) Acestor tipuri fundamentale li se mai pot aduga un numr de calificatori, dup cum urmeaz: short - aplicabil doar pentru int, rezultnd, de obicei, un ntreg pe 2 octei. long aplicabil doar pentru int. Rezult un ntreg pe 32 de bii (4 octei), schimbare semnificativa doar pentru Windows) unsigned - precizeaz faptul c valoarea variabilei este pozitiv. Aplicabil doar tipurilor ntregi.

n cazul n care este absolut necesar ca tipul ntreg s aib o anumit lungime, este indicat consultarea cu atenie a documentaiei compilatorului. Compilatorul GCC pune n acest sens la dispoziia programatorului, urmtoarele tipuri de ntregi cu lungime clar specificat: uint_8, uint_16, uint_32, uint_64 pentru ntregi far semn pe 8, 16, 32 respectiv 64 de bii int_8, int_16, int_32, int_64 pentru ntregi cu semn reprezentai pe 8, 16, 32 respectiv 64 de bii. Determinarea corect a tipurilor de date care vor fi folosite este esenial pentru securitatea i buna funcionare a aplicaiilor pe care le scriei. n cazul n care valoarea coninut de o variabil depaete limitele impuse de tipul de date folosit, se produce aa -numit-ul over-flow care poate cauza erori aparent inexplicabile. (Ca o anecdot, n fiecare an (pn acum trei sau patru ani), Bill Gates primea de la FISC o scrisoare prin care era somat s ii plateasc taxele, deoarece aprea in evidenele lor ca avnd datorii

nsemnate. Asta deoarece valoarea averii lui (mult peste 4.000.000.000$) producea un overflow n softul folosit de ctre FISC. n final situaia a fost soluionat, introducnd un cmp special pentru el n softul folosit. (A modifica softul peste tot ar fi introdus un plus de stocare nejustificat pentru fiecare din cei aproximativ 300.000.000 de cetaeni ai SUA.) )

Operatori
Operatorii limbajului C pot fi unari, binari sau ternari, fiecare avnd o preceden i o asociativitate bine definite. Tabelul urmtor sintetizeaz operatorii limbajului C. Operatorii sunt prezentai n ordine descresctoare a prioritii.

Preceden Operator [] 1 . i -> ++ i ! ~ ++ i + i 2 * & (tip) sizeof() * 3 / % 4 + i 5 << si >> < <= 6 > >= == 7 != 8 & 9 ^ 10 | 11 &&

Asociativitate Indexare stanga-dreapta Selecie membru (prin structur, respectiv pointer) stnga-dreapta Postincrementare/postdecrementare stnga-dreapta Negare logic dreapta-stnga Complement fa de 1 pe bii dreapta-stnga Preincrementare/predecrementare dreapta-stnga + i unari dreapta-stnga Derefereniere dreapta-stnga Operator adres dreapta-stnga Conversie de tip dreapta-stnga Mrimea n octei dreapta-stnga nmulire stnga-dreapta mprire stnga-dreapta Restul mpririi stnga-dreapta Adunare/scdere stnga-dreapta Deplasare stnga/dreapta a biilor stnga-dreapta Mai mic stnga-dreapta Mai mic sau egal stnga-dreapta Mai mare stnga-dreapta Mai mare sau egal stnga-dreapta Egal stnga-dreapta Diferit stnga-dreapta I pe bii stanga-dreapta SAU-EXCLUSIV pe bii stnga-dreapta SAU pe bii stnga-dreapta I logic stnga-dreapta

Descriere

12 13

14

15

|| SAU logic :? Operator condiional = Atribuire += i -= Atribuire cu adunare/scdere *= i /= Atribuire cu multiplicare/mprire %= Atribuire cu modulo &= si |= Atribuire cu I/SAU ^= Atribuire cu SAU-EXCLUSIV <<= i >>= Atribuire cu deplasare de bii , Operator secvena

stnga-dreapta dreapta-stnga dreapta-stnga dreapta-stnga dreapta-stnga dreapta-stnga dreapta-stnga dreapta-stnga dreapta-stnga stnga-dreapta

Trebuie avut n vedere precedena operatorilor pentru obinerea rezultatelor scontate. Dac unele tipuri de preceden (cum ar fi cea a operatorilor artimetici) sunt evidente i nu prezint (aparent) probleme (i datorit folosirii lor dese), altele pot duce la erori greu de gsit. De exemplu, urmtorul fragment de cod nu produce rezultatul dorit, deoarece: if ( flags & MASK == 0) { ... }

se evalueaz mai ntai egalitatea care produce ca rezultat (0 pentru False, i 1 pentru True) dup care se aplic i pe bii ntre falgs i 1. Pentru a obine rezultatul dorit se vor folosi parantezele: if ( (flags & MASK) == 0) { ... }

acum mai nti se va face I pe bii ntre flags i MASK, dup care se verific egalitatea. O expresie este o secvent de operanzi i operatori (valid din punct de vedere al sintaxei limbajului C) care realizeaz una din funciile: calculul unei valori, desemnarea unui obiect (variabil) sau funcii sau generarea unui efect lateral. O alt greeal frecvent este utilizarea greit a operatorilor = i ==. Primul reprezint atribuire, al doilea comparaie de egalitate. Apar deseori erori ca: if ( a = 2 ) { ... }

Compilatorul consider condiia corect, deoarece este o expresie valid n limbajul C care face atribuire, care se evalueaz mereu la o valoare nenul.

Msurarea timpului de execuie a programelor


Uneori este util msurarea timpului de execuie a unei anumite pari a unui program sau chiar a ntregului program. n acest scop putem folosi funcia clock() din fiierul antet time.h. Aceast funcie ntoarce o aproximare a numrului de cicluri de ceas trecute de la pornirea programului. Pentru a obine o valoare n secunde, mprim aceast valoare la constanta CLOCKS_PER_SEC. Funcia are antetul: clock_t clock( void );

Urmtorul fragment este un exemplu de utilizare a acestei funcii: #include <stdio.h> #include <time.h> clock_t t_start, t_stop; float seconds; // Marcam momentul de inceput t_start = clock(); // Executam operatia pentru care masuram timpul de executie // [....] // Marcam momentul de sfarsit t_stop = clock(); seconds = ((float)(t_stop - t_start))/ CLOCKS_PER_SEC; printf("Timp de executie: %.3f sec.\n", seconds);

Urmtorul fragment este un exemplu de funcie care are ca scop oprirea programului pentru un anumit timp: void wait ( int seconds ) { clock_t endwait; endwait = clock () + seconds * CLOCKS_PER_SEC ; while (clock() < endwait) {} }

Funcii matematice
Fiierul antet math.h conine un set de funcii matematice des utilizate n programe. Cteva dintre acestea sunt:

Descriere double asin( double arg ); Calculeaz arcsinusul/arccosinusul valorii arg; rezultatul este double acos( double arg ); msurat n radiani
double atan( double arg ); double atan2( double y, Calculeaz arctangenta valorii arg, respectiv a fraciei y/x double x ); double floor( double num );

Antet

ntoarce cel mai mare ntreg mai mic sau egal cu num (partea ntreag inferioar) ntoarce cel mai mic ntreg mai mare sau egal cu num (partea double ceil( double num ); ntreag superioar)
double double double double double double sin( double arg ); cos( double arg ); tan( double arg ); sinh( double arg ); cosh( double arg ); tanh( double arg );

Calculeaz sinusul/cosinusul/tangenta parametrului arg, considerat n radiani Calculeaz sinusul/cosinusul/tangenta hiperbolic a parametrului arg ntoarce valoarea earg ntoarce valoarea basearg Calculeaz logaritmul natural (de baz e) al valorii arg Calculeaz logaritmul n baza 10 al parametrului

double exp( double arg ); double pow( double base, double exp ); double log( double num ); double log10( double num );

double sqrt( double num ); Calculeaz radcina ptrat a parametrului double fmod( double x, ntoarce restul mparirii lui x la y double y ); double fabs( double arg ); ntoarce valoarea absolut a lui arg

Generarea numerelor aleatoare


Valorile aleatoare (a cror valoare nu poate fi prezis dinaintea rulrii programului i care difer ntre 2 rulri) pot fi generate n C cu funcia: int rand( void );

care face parte din antetul stdlib.h. Aceast ntoarce o valoare cuprins ntre 0 i RAND_MAX (valoare care este dependenta de librariile folosite, dar care se garanteaz a fi minim 32767).

Numerele generate nu sunt cu adevrat aleatoare, ci pseudo-aleatoare; aceste numere sunt uniform distribuite pe orice interval, dar irul de numere ale atoare generate este dependent de prima valoare, care trebuie aleas de utilizator sau programator. Aceast valoare, numit seed, se selecteaz cu funcia: void srand( unsigned int seed );

Cea mai ntalnit utilizare a funciei de iniializare presupune setarea unui seed egal cu valoarea ceasului sistemului de la pornirea programului, prin instruciunea: srand( (unsigned) time( NULL ) ); d = rand(); //genereaz valori random.

Funcia time() din fiierul antet time.h ntoarce numrul de secunde trecute de la ora 00:00, din data de 1 ianuarie 1970. Funcia primete i un parametru de tip pointer, care reprezint adresa unei variabile n care se salveaz valoarea returnat.

Invata Limbajul de Programare C Partea 3


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul
de programare C si conceptele Programarii Structurata.

Instructiunile limbajului C If-else


ifelse este cea mai simpl instruciune condiional. Poate fi folosit n mai multe forme: if( condiie ) { // instruciuni //... } if( condiie ) { //instruciuni //... } else {

//alte instruciuni //... } if( condiie1 ) { ... } else if( condiie2 ) { ... } ... else if( condiieN ) { ... }

Instruciunea evalueaz expresia condiie i execut instruciunile dintre acolade doar dac rezultatul este nenul. n varianta cu else, pentru rezultat nul este executat blocul de instruciuni aflat dup else. n al treilea caz, sunt evaluate pe rnd condiiile i este executat blocul corespunztor primei condiii adevrate. Un exemplu de folosire este: if( a == b ) printf( "Numerele sunt egale" ); else if( a > b ) printf( "A este mai mare" )

Switch
Switch este o instruciune menit s simplifice structurile condiionale cu mai multe condiii. switch( expresie ) { case constanta1: //instruciuni1 case constanta2: //instruciuni2 ... default: //instruciuni }

Valoarea expresie este evaluat la un tip intreg, apoi aceast valoare este comparat cu fiecare constant; este rulat blocul de instruciuni al valorii gsite. n caz ca numrul nu este egal cu nici una dintre constante, este executat blocul aflat dup default. int main() { char c; printf("Alegei o opiune:\n\t[a] afiare\n\t[s] tergere\n\t[e] ieire\n"); scanf("%c", &c); printf("Ai ales: "); switch(c) { case 'a': printf("afiare"); break; case 's': printf("tergere"); break; case 'e': printf("ieire"); break; default: printf("O opiune inexistent"); break; } return 0; } int main( ) { int n, n2; printf( "Introducei o valoare ntre 0 i 5:" ); scanf( "%d", &n ); n2 = 1; switch( n ) { case 5: n2 *= 2; /* fr break, continu la urmtoarea instruciune */ case 4: n2 *= 2; case 3: n2 *= 2; case 2: n2 *= 2; case 1: n2 *= 2; case 0: printf( "2 la puterea %d este %d\n", n, n2 ); break;

default: printf( "Valoare invalid\n" ); } return 0; }

Instruciuni de repetiie while


while execut un bloc de instruciuni atta timp ct o anumit condiie este adevrat. Forma general a unui ciclu while este: while (expresie) { //instruciuni }

Ct vreme expresie are o valoare nenul, instruciunile din blocul de dup while sunt executate. Expresia este reevaluat dup fiecare ciclu. Un astfel de ciclu poate s se execute o dat, de mai multe ori sau niciodat, n funcie de valoarea la care se evalueaz expresia.

do while
do while este o instruciune repetitiv similara cu cea precedent, singura diferena fiind c expresia este evaluat dup executarea instruciunilor, nu nainte. Astfel, blocul va fi executat cel puin o dat. do { //instruciuni } while (expresie);

for
for reprezint o form mai simpl de a scrie un while nsotit de o expresie iniiala i de o expresie de incrementare. Forma sa este: for( expresie1 ; expresie2 ; expresie3 ) { //instruciuni }

Secvena de cod de mai sus este echivalent cu: expresie1 while (expresie2) { instruciuni expresie3 }

n cazul instruciunii for, oricare dintre cele 3 expresii poate lipsi. Lipsa expresiei condiionale este echivalent cu o bucl infinit, cum ar fi: for( ; ; ) { /* instruciunile de aici sunt intr-o bucl infinit */ }

Exemplul urmtor prezint un ciclu cu funcionalitate identic (tiprirea primelor 10 numere naturale), folosind cele 3 instruciuni repetitive: int main() { short i; printf("Ciclu for\n"); for (i=1;i<=10;i++) printf("i=%d\n", i); printf("Ciclu while\n"); i=1; while (i <= 10) { printf("i=%d\n", i); i++; } printf("Ciclu do while\n"); i=0; do { i++; printf("i=%d\n", i); } while(i < 10); }

n exemplu nu am mai pus acolade la instruciunea executat de for. Pentru blocuri de o singur instruciune, nu este nevoie sa folosim acoladele.

Instruciuni speciale break


break, pe lng utilizarea descris la instruciunea switch, poate fi folosit pentru a iei for at dintr-o instruciune de repetiie. Secventa urmtoare este echivalent cu cele de mai sus: i = 0; for( ; ; ) { i++; if( i > 10 ) break; /* ieire forat din bucla */ printf( i=%d\n, i ); }

continue
continue foreaz terminarea iteraiei curente a buclei si trecerea la iteraia urmtoare. n cazul instruciuniifor, acest lucru presupune executarea instruciunii de incrementare; apoi se evalueaz condiia de continuare a buclei. Exemplul urmtor demonstreaz im plementarea unei bucle infinite cu ajutorul instruciunii continue: for( i = 0 ; i < 10 ; ) { if( i == 0 ) continue; i++; }

return
return este instruciunea de terminare a funciei curente. Aceasta poate fi apelat in forma return; n cazul funciilor care returneaz void i n forma return rezultat; pentru funciile care ntorc o valoare.

goto
goto este o instruciune de salt a execuiei. Instruciunea primete ca parametru o etichet; urmtoarea instruciune executat dup goto este cea de la eticheta dat.

int main( ) { goto et; printf( Asta nu apare la executie\n ); et: printf( Asta apare la rulare\n ); return 0; }

Atenie! n majoritatea cazurilor, utilizarea instruciunii goto nu este recomandat i poate fi evitat folosind alte instruciuni de control i funcii. Programele care folosesc aceast instruciune pentru a sri ntre secvene ndeprtate de cod sunt dificil de depanat i analizat.

Invata Limbajul de Programare C Partea 4


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul
de programare C si conceptele Programarii Structurata.

Programare modular. Funcii n limbajul C. Dezvoltarea algoritmilor folosind funcii


Funciile mpart taskuri complexe n buci mici mai uor de neles i de programat. Acestea pot fi refolosite cu alte ocazii, n loc s fie rescrise de la zero. De asemenea, funciile sunt utile pentru a ascunde detalii de funcionare ale anumitor pri ale programului, ajutnd la modul de lucru al acestuia. Utiliznd funcii, care reprezint unitatea fundamental de execuie a programelor C, se obine o divizare logic a programelor mari i complexe. mprirea programelor n funcii este arbitrar i depinde de modul de gndire a celui care le creeaz. De obicei, funciile cuprind o serie de instruciuni care efectueaz un calcul, realizeaz o aciune, implementeaz un algoritm, etc. Crearea funciilor trebuie s se bazeze pe urmtoarele principii: claritate, lizibilitate, uurin n ntreinere, reutilizabilitate.

Definirea i apelul unei funcii n C


Caracteristicile definitorii ale unei funcii n C sunt: numele, parametrii de apel i valorea returnat. Sintaxa standard de declarare a unei funcii este:

tip_returnat nume_functie (tip_param1 [nume_param1] [, tip_param2 [nume_param2], ...]);

Aceast declarare poart numele de antetul funciei. Odat declarat, o funcie trebuie definit, n sensul c trebuie expandat corpul acesteia cu instruciunile pe care trebuie s le execute. Definirea unei funcii are forma: tip_returnat nume_functie (tip_param1 [nume_param1] [, tip_param2 [nume_param2], ...]) { declaratii de variabile si instructiuni return expresie; }

Limbajul C permite separarea declaraiei unei funcii de definiia acesteia (codul care o implementeaz ). Pentru ca funcia s poat fi folosit, este obligatorie doar declararea acesteia nainte de codul care o apeleaz. Definiia poate aprea mai departe n fiierul surs, sau chiar ntr -un alt fiier surs sau bibliotec. Diferite pri din definirea unei funcii pot lipsi. Astfel, o funcie minimal este: dummy() {}

Funcia de mai sus nu face absolut nimic, nu ntoarce nici o valoare i nu primete nici un argument, ns din punct de vedere al limbajului C este perfect valid. Tipul returnat de o funcie poate fi orice tip standard sau definit de utilizator. Dac nu se specific tipul returnat de funcie se consider implicit tipul int. Orice funcie care ntoare un rezultat trebuie s conin instruciunea: return expresie;

Expresia este evaluat i convertit la tipul de date care trebuie returnat de funcie. Aceast instruciune termin i execuia funciei, indiferent dac dup aceasta mai urmeaz sau nu alte instruciuni. Dac este

cazul, se pot folosi mai multe instruciuni return pentru a determina mai multe puncte de ieire din funcie, n raport cu evoluia funciei.

Exemplu: int min(int x, int y) { if(x<y) return x; return y; }

Apelul unei funcii se face specificnd parametrii efectivi (parametrii care apar n declararea funciei se numesc parametri formali). int main() { int a, b, minim; //........... x=2; y=5; minim=min(x,4); printf("Minimul dintre %d si 4 este: %d",x,minim); printf("Minimul dintre %d si %d este: %d",x,y,min(x,y)); }

Transmiterea parametrilor
Apelul unei funcii se face specificnd parametrii care se transmit acesteia. n limbajul C, dar i n alte limbaje de programare exist dou moduri de transmitere a parametrilor.

Transmiterea parametrilor prin valoare


Funcia va lucra cu o copie a variabilei pe care a primit-o i orice modificare din cadrul funciei va opera asupra aceste copii. La sfritul execuiei funciei, copia va fi distrus i astfel se va pierde orice modificare efectuat. Pentru a nu pierde modificrile fcute se folosete instruciunea return, care poate ntoarce, la terminarea funciei, noua valoare a variabilei. Problema apare n cazul n care funcia modific mai multe variabile i se dorete ca rezultatul lor s fie disponibil i la terminarea execuiei funciei.

Transmiterea parametrilor prin referin


Rezolvarea problemei anterioare se face transmind funciei adresa variabilei cu care urmeaz s lucreze. Astfel, funcia nu prelucreaz o copie a valorii variabilei, ci chiar valoarea aflat la adresa furnizat. Orice parametru care se dorete a fi prelucrat de funcie, i noua sa valoare utilizat chiar i dup terminarea funciei, se va transmite funciei prin referin.

Exemplu de transmitere a parametrilor prin valoare: min(x,4)

Exemplu de transmitere a parametrilor prin referin: suma(&x,&y,&sum)

Funcii recursive
O funcie poate s apeleze la rndul ei alte funcii. Dac o funcie se apeleaz pe sine nsi, atunci funcia este recursiv. Pentru a evita un numr infinit de apeluri recursive, trebuie ca funcia s includ n corpul ei ocondiie de oprire, astfel ca, la un moment dat, recurena s se opreasc i s se revin succesiv din apeluri. Condiia trebuie s fie una generic, i s opreasc recurena n orice situaie. Aceast condiie se refer n general la parametrii de intrare, pentru care la un anumit moment, rspunsul poate fi returnat direct, fr a mai fi necesar un apel recursiv suplimentar.

Exemplu: Calculul recursiv al factorialului long fact(int n) { if(n==0) return 1; else return n*fact(n-1); }

sau, ntr-o form mai compact: long fact(int n) { return (n>=1) ? n*fact(n-1) : 1; }

ntotdeauna trebuie avut grij n lucrul cu funcii recursive deoarece, la fiecare apel recursiv, contextul este salvat pe stiv pentru a putea fi refcut la revenirea din recursivitate. n acest fel, n funcie de numrul apelurilor recursive i de dimensiunea contextului (variabile, descriptori de fiier, etc.) stiva se poate umple foarte rapid, genernd o eroare de tip stack overflow.

Funcia main
Orice program C conine cel puin o funcie, i anume cea principal, numit main(). Aceasta are un format special de definire: int main(int argc, char **argv);

Primul parametru, argc, reprezint numrul de argumente primite de ctre program la linia de comand, incluznd numele cu care a fost apelat programul. Al doilea parametru, argv, este un pointer ctre coninutul listei de parametri al cror numr este dat de argc. Atunci cnd nu este necesar procesarea parametrilor de la linia de comand, se poate folosi forma prescurtat a definiiei funciei main, i anume: int main();

n ambele cazuri, standardele impun ca main s ntoarc o valoare de tip ntreg, care s reprezinte codul execuiei programului i care va fi pasat napoi sistemului de operare, la ncheierea execuiei programului. Astfel, instruciunea return n funcia main va nsemna i terminarea execuiei programului. n mod normal, orice program care se execut corect va ntoarce 0, i o valoare diferit de 0 n cazul n care apar erori. Aceste coduri ar trebui documentate pentru ca apelantul programului s tie cum s adreseze eroarea respectiv.

Tipul de date void


Tipul de date void are mai multe ntrebuinri. Atunci cnd este folosit ca tip returnat de o funcie, specific faptul c funcia nu ntoarce nici o valoare. Exemplu:

void print_nr(int numar) { printf("Numarul este %d", numar); }

Atunci cnd este folosit n declaraia unei funcii, void semnific faptul c funcia nu primete nici un parametru. Exemplu: int init(void) { return 1; }

Pointerii pot fi de asemenea declarai void, ns nu pot fi derefereniai fr o operaie de cast explicit.

Clase de stocare. Fiiere antet vs. biblioteci


Dup cum se tie, ntr-un fiier surs (.c) pot fi definite un numr oarecare de funcii. n momentul n care programul este compilat, din fiecare fiier surs se genereaz un fiier obiect ( .o), care conine codul compilat al funciilor respective. Aceste funcii pot apela la rndul lor alte funcii, care pot fi definite n acelai fiier surs, sau n alt fiier surs. n orice caz, compilatorul nu are nevoie s tie care este definiia funciilor apelate, ci numai semntura acestora (cu alte cuvinte, declaraia lor), pentru a ti cum s realizeze instruciunile de apel din fiierul obiect. Acest lucru explic de ce, pentru a putea folosi o funcie, trebuie declarat naintea codului n care este folosit. Fiierele antet conin o colecie de declaraii de funcii, grupate dup funcionalitatea pe care acestea o ofer. Atunci cnd includem un fiier antet (.h) ntr-un fiier surs (.c), compilatorul va cunoate toate semnturile funciilor de care are nevoie, i va fi n stare s genereze codul obiect pentru fiecare fiier surs n parte. (NOT: Astfel nu are sens includerea unui fiier .c n alt fiier .c; se vor genera dou fiiere obiect care vor conine definiii comune, i astfel va aprea un conflict de nume la editarea legturilor). Cu toate acestea, pentru a realiza un fiier executabil, trebuie ca fiecare funcie s fie definit. Acest lucru este realizat de ctre editorul de legturi; cu alte cuvinte, fiecare funcie folosit n program trebuie s fie coninut n fiierul executabil. Acesta caut n fiierele obiect ale programului definiiile funciilor de care are nevoie fiecare funcie care le apeleaz, i construiete un singur fiier executabil care conine toate

aceste informaii. Bibliotecile sunt fiiere obiect speciale, al cror unic scop este s conin definiiile funciilor oferite de ctre compilator, pentru a fi integrate n executabil de ctre editorul de legturi. Clasele de stocare intervin n acest pas al editrii de legturi. O clas de stocare aplicat unei funcii indic dac funcia respectiv poate fi folosit i de ctre alte fiiere obiect (adic este extern), sau numai n cadrul fiierului obiect generat din fiierul surs n care este definit (n acest caz funcia este static). Dac nu este specificat nici o clas de stocare, o funcie este implicit extern. Cuvintele cheie extern i static, puse n faa definiiei funciei, i specific clasa de stocare. De exemplu, pentru a defini o funcie intern, se poate scrie: static int compute_internally(int, int)

Funcia compute_internally nu va putea fi folosit dect de ctre funciile definite n acelai fiier surs i nu va fi vizibil de ctre alte fiiere surs, n momentul editrii legturilor.

nvata Limbajul de Programare C Partea 5


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul
de programare C si conceptele Programarii Structurata.

Tablouri. Particularizare vectori. Vectori


Printr-un vector se nelege o colecie liniar i omogen de date. Un vector este liniar pentru c datele(elementele) pot fi accesate n mod unic printr-un index. Un vector este, de asemenea, omogen, pentru c toate elementele sunt de acelai tip. n limbajul C, indexul este un numr ntreg pozitiv i indexarea se face ncepnd cu 0. Declaraia unei variabile de tip vector se face n felul urmtor: <tip_elemente> <nume_vector>[<dimensiune>];

De exemplu, avem urmtoarele declaraii de vectori:

int a[100]; float vect [50]; #define MAX 100 ... unsigned long numere[MAX

Este de remarcat c vectorul este o structur static: dimensiunea acestuia trebuie s fie o constant la compilare i nu poate fi modificat n cursul execuiei programului. Astfel, programatorul trebuie s estimeze o dimensiune maxim pentru vector, i aceasta va fi o limitare a program ului.

De obicei, se folosesc constante simbolice (ca n ultimul exemplu) pentru aceste dimensiuni maxime, pentru ca ele s poat fi ajustate uor la nevoie. De asemenea, n cadrul unei declaraii, se pot iniializa cu valori constante componente ale vectorului, iar n acest caz, dimensiunea vectorului poate rmne neprecizat (compilatorul o va determina din numrul elementelor din list).

De exemplu:

int a[3] = {1, 5, 6}; /* Toate cele 3 elemente sunt initializate */ float num[] = {1.5, 2.3, 0.2, -1.3}; /* Compilatorul determina dimensiunea - 4 - a vectoru */ unsigned short vect[1000] = {0, 2, 4, 6}; /* Sunt initializate doar primele 4 elemente */

n cazul special n care specificm dimensiunea i doar un singur element la initializare, primul element va fi cel specificat, iar toate celelalte elemente ale vectorului vor fi iniializate la 0: char sir[100] = {97}; /* Sirul va fi initializat cu: 97 (caracterul 'a') pe prima poziie */

Este important de remarcat faptul c elementele neiniializate pot avea valori oarecare. La alocarea unui vector, compilatorul nu efectueaz nici un fel de iniializare i nu furnizeaz nici un mesaj de eroare dac un element este folosit nainte de a fi iniializat. Un program corect va iniializa, n orice caz, fiecare element nainte de a-l folosi. Elementele se acceseaz prin expresii de forma <nume_vector>[<indice>].

De exemplu, putem avea: char vect[100]; int i = 90;

vect[0] = vect[5] = vect[i] = vect[i+1]

1; 10; 15; = 20;

Stil de programare. Exemple de programe


Citirea unui vector de intregi de la tastatura: int main() { int a[100],n,i; /* vectorul a are maxim 100 de intregi */ scanf ("%d",&n); /* citeste nr de elemente vector */ for (i=0; i<n; i++) scanf ("%d", &a[i]); /* citire elemente vector */ for (i=0;i<n;i++) printf ("%d ", a[i]); /* scrie elemente vector */ return 0; }

Generarea unui vector cu primele n numere Fibonacci: #include <stdio.h> int main () { long fib[100]={1,1}; int n,i; printf ("n="); scanf("%d",&n); for (i=2; i<n ;i++) fib[i]=fib[i-1]+fib[i-2]; for (i=0; i<n;i++) printf ("%ld ",fib[i]); return 0; }

Erori comune Depirea limitelor indicilor (index out of bounds) este o eroare frecvent, ce poate duce la blocarea programului sau a sistemului i poate fi evitat prin verificarea ncadrrii n intervalul valid.

Indici folosii greit n bucle imbricate (index cross -talk). Sunt multe cazuri n care pe un nivel al buclei se folosete, de exemplu vect[i], i pe nivelul imbricat vect[j], cnd de fapt se dorea folosirea lui i. Mare atenie i n astfel de cazuri!

#define MAX int vect[MAX]

100

va fi de preferat n locul lui int vect[100]

Sfat: Verificai c indicii se ncadreaz ntre marginile superioar i inferioar a intervalului de valori valide. Acest lucru trebuie n general fcut n cazul n care datele provin dintr -o surs extern: citite de la tastatur sau pasate ca parametri efectivi unei funcii, de exemplu.

Exemplu: // program care citete un index i o valoare, i atribuie valoarea elementului din vector respectiv #include <stdio.h> int main() { int i, val; int v[10]; scanf("%d %d",&i,&val); // !!! Verific daca indexul este valid if(i >=0 && i < 10) v[i] = val; else { printf("Introduceti un index >= 0 si < 10"); } return 0; }

Sfat: Folosii comentarii pentru a explica ce reprezint diverse variabile. Acest lucru v va ajuta att pe voi s nu ncurcai indici, de exemplu, ct i pe ceilali care folosesc sau extind codul vostru.

Exemplu:

#include <stdio.h> #define N 100 int main() { int v[N]; int i,j; // indecsii elementelor ce vor fi interschimbate int aux; // variabila ajutatoare pentru interschimbare ... // initializari /* Interschimb */ aux = v[i]; v[i] = v[j]; v[j] = aux; return 0; }

Aplicaii cu vectori
Cutri Cutare secvenial
Cnd avem de a face cu un vector nesortat (i nu numai n acest caz), cea mai simpl abordare pentru a gsi o valoare, este cutarea secvenial. Cu alte cuvinte, se compar, la rnd, fiecare valoare din vector cu valoarea cutat. Dac valoarea a fost gsit, cutarea se poate opri (nu mai are sens s parcugem vectorul pn la capt, dac nu se cere acest lucru explicit).

Exemplu: #define MAX 100 ... int v[MAX],x,i; /*initializari*/ ... for(i=0;i<MAX;i++) if(x==v[i]) { printf("Valoarea %d a fost gasita in vector\n",x); break; }

printf("Valoarea %d nu a fost gasita in vector\n",x); ...

Cutare binar iterativ


Dac vectorul pe care se face cutarea este sortat, algoritmul mai eficient de folosit n acest caz este cutarea binar. Presupunem c vectorul este sortat cresctor (pentru vectori sortai descresctor, raionamentul este similar). Valoarea cutat, x, se compar cu valoarea cu indexul N/2 din vector, unde N este numrul de elemente. Dac x este mai mic dect valoarea din vector, se caut n prima jumtate a vectorului, iar dac este mai mare, n cea de-a doua jumtate. Cutarea n una dintre cele dou jumti se face dup acelai algoritm. Conceptual, cutarea binar este un algoritm recursiv, dar poate fi implementat la fel de bine ntr-un mod iterativ, folosind indecii corespunztori bucii din vector n care se face cutarea. Aceti indeci se modific pe parcursul algoritmului, ntr-o bucl, n funcie de comparaiile fcute. Evoluia algoritmului este ilustrat n imaginea de mai jos.

Pseudocodul pentru cutarea binar:

cutare_binar (v[0..N], x) { low = 0 high = N - 1 ct timp (low <= high) { mid = (low + high) / 2 dac v[mid] > value high = mid - 1 altfel dac v[mid] < value low = mid + 1 altfel gsit x pe poziia mid } x nu a fost gsit }

Sortri
Bubble Sort
Metoda bulelor este cea mai simpl modalitate de sortare a unui vector, dar i cea mai ineficient. Ea funcioneaz pe principiul parcurgerii vectorului i comparrii elementului curent cu elementul urmtor. Dac cele dou nu respect ordinea, sunt interschimbate. Aceast parcurgere este repetat de suficiente ori pn cnd nu mai exist nici o interschimbare n vector.

Sortarea prin selecie


Sortarea prin selecie ofer unele mbuntiri n ceea ce privete complexitatea, ns este departe de a fi considerat un algoritm eficient. Presupunnd c se dorete sortarea cresctoare a vectorului, se caut minimul din vector, i se interschimb cu primul element cel cu indexul 0. Apoi se reia acelai procedeu pentru restul vectorului. Motivul pentru care algoritmul de sortare prin selecie este mai eficient este acela c vectorul n care se caut minimul devine din ce n ce mai mic, i, evident, cutarea se face mai repede la fiecare pas.

nvata Limbajul de Programare C Partea 6


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Matrice. Operaii cu matrice: adunare, nmulire. Reprezentarea n memorie.


Matrice
Matricea este o colecie omogen i bidimensional de elemente. Acestea pot fi accesate prin intermediul a doi indici, numerotai, ca i n cazul vectorilor, ncepand de la 0. Declaraia unei matrice este de forma: <tip_elemente> <nume_matrice>[<dim_1>][<dim_2>];

De exemplu, avem: int mat[5][10] #define MAX_ELEM 100 float a[MAX_ELEM][MAX_ELEM] Numrul de elemente ale unei matrice va fi dim_1*dim_2, i semnificaia fiecrei dimensiuni este o chestiune ce ine de logica programului. n matematic, prima dimensiune poate s nsemne linia i a doua coloana pentru fiecare element, nsa acest lucru nu este obligatoriu. Este necesar totui, pentru funcionarea corect a programului, s se respecte semnificaiile alese pe ntreg parcursul codului surs.

Tablouri multidimensionale
Vectorii i matricele se pot extrapola la noiunea general de tablou cu mai multe dimensiuni, care se declar n modul urmtor: <tip_elemente> <nume_tablou>[<dim_1>][<dim_2>]...[<dim_n>];

De exemplu: int cub[3][3][3] Dei, n cazul a mai mult de 3 dimensiuni, tablourile pot s nu mai aib sens concret sau fizic, acestea pot fi deosebit de utile n multe situaii.

Adunarea si nmulirea matricelor Suma matricelor:

Exemplu:

nmulirea matricelor:

Exemplu:

Exemplul de mai sus prezint cum se calculeaz valorile (1,2) si (3,3) ale AB daca A este o matrice 32, si B o matrice 23. Pentru calculul unui element din matrice se consider o linie respectiv o coloan din

fiecare matrice conform sgeilor. Elementele din acestea sunt nmulite cte 2 conform nmulirii pe vectori, apoi suma produselor constituie elementul din matricea final.

Reprezentarea n memorie
Cunoaterea reprezentrii n memorie a tablourilor v ajut s nelegei mai bine cum se lucreaz cu aceste tipuri de date i s evitai att erorile comune, ct i pe cele mai subtile. Aa cum se tie, fiecare variabil are asociata o anumit adres n memorie i ocup o anumit lungime, msurat n octei. Standardul C impune ca un tablou s fie memorat ntr-o zon continu de memorie, astfel ca pentru un tabloul de forma: T tab[dim1][dim2][dimn]; dimensiunea ocupat n memorie va fi sizeof(T)*dim1*dim2**dimn. Vom considera n continuare cazul particular al unui vector vect de lungime n, i al unui element oarecare al acestuia, de pe pozitia i. Atunci cnd ntalnete numele vect, compilatorul va intelege adresa n memorie de la care ncepe vectorul vect. Operatorul de indexare [] aplicat numelui vect instruiete compilatorul s evalueze acel element de tipul T, care se afl pe pozitia i n vectorul care ncepe de la adresa vect. Acest lucru se poate exprima direct: evaluarea variabilei de tip T de la adresa vect + i * sizeof(T). n ultima formulare observai ca nu mai intervine sub nici o form dimensiunea vectorului dat la declarare. Aceea a fost necesar doar compilatorului, ca sa tie ct memorie s aloce pentru reprezentarea acestuia. De asemenea, observai c sunt permise indexari n afara spaiului de memorie alocat, i astfel programul va putea, din greeala, accesa alte zone de memorie, lucru care poate avea repercursiuni grave. n cel mai bun caz programul nostru se va comporta foarte ciudat (erori n locuri total imprevizibile), i n cel mai ru caz ntreg sistemul va fi blocat (n cazul sistemelor care nu au implementate spaii virtuale de memorie proprii fiecrei aplicaii platformele Windows NT si Linux). Faptul c grania dintre vectori i adrese de memorie este att de fin n limbajul C, sintaxa acestuia permite expresii ciudate, de forma: char a[100]; a[0] = 1;

3[a] = 5 Instruciunea din urm nseamna pur i simplu asigneaz 5 variabilei de tip char de la adresa 3 + a * sizeof(char) = 3 + a. Observai c aceasta este echivalent cu a[3] = 5; De asemenea, un alt avantaj apare la definirea unui parametru al unei funcii, de tip vector, caz n care nu este necesar precizarea dimensiunii acestuia: void sort(int[] vect, n); Este de remarcat faptul c pentru tablouri de dimensiuni m > 1, este necesar precizarea lungimilor primelorm 1 dimensiuni, pentru ca compilatorul s poat calcula adresa fiecrui element atunci cnd acesta este referit n program.

Stil de programare
Declararea unei matrici unitate: #define M 20 /* nr maxim de linii si de coloane */ int main () { float unit[M][M]; int i,j,n; printf("nr.linii/coloane: "); scanf("%d",&n); if (n > M) return; for (i=0;i<n;i++) for (j=0;j<n;j++) if (i !=j) unit[i][j]=0; else unit[i][j]=1; return 0; }

Citire/scriere de matrice de reali: int main () { int nl,nc,i,j; float a[20][20]; /* Citire de matrice */ printf("nr.linii: "); scanf("%d",&nl); printf("nr.coloane: "); scanf("%d",&nc);

if (nl >20 || nc >20) { printf("Eroare: dimensiuni > 20 \n"); return; } for (i=0;i<nl;i++) for (j=0;j<nc;j++) scanf("%f", &a[i][j]); /* Afisare matrice */ for (i=0;i<nl;i++) { for (j=0;j<nc;j++) printf("%f ",a[i][j]); printf ("\n"); } return 0; }

Erori comune
Inversarea indicilor pentru elementele unei matrice sau tablou. E usor sa-l inversezi pe i cu j in expresia A[i][j] astfel ca trebuie sa fiti atenti cand scrieti astfel de cod. Luati in considerare si folosirea de nume mai sugestive pentru variabile.

Invata Limbajul de Programare C Partea 7


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Pointeri. Abordarea lucrului cu tablouri folosind pointeri. Noiunea de pointer


Un pointer este o variabil care reine o adres de memorie. n C, aceste adrese de memorie pot fi de mai multe feluri: Adresa unor date de un anumit tip (tip elementar, structur, ir de caractere, etc.). n acest caz, operaiile cu pointeri sunt determinate de dimensiunea tipului de date (numrul de bytes ocupai n memorie).

Adresa unei funcii (adresa la care punctul curent de execuie va s ri, n cazul n care acea funcie este apelat).

Adresa unei adrese de memorie; acest tip de pointer poate fi redus la prima situaie, n cazul n care se consider pointerul un tip de date numeric de lungime 32 de bii (cantitatea maxim adresabil de memorie pentru programul nostru este de 4 GB, pentru arhitecturi pe 32 de bii).

Adresa unei zone cu coninut necunoscut (pointer ctre void); de exemplu, putem avea operaii cu blocuri mari de memorie, al crui coninut nu ne intereseaz (de exemplu citirea n memorie a unui fiier, sau folosirea memoriei pe post de buffer) i n acest caz adresele blocurilor sunt reprezentate prin pointeri ctre void.

Not: Dimensiunea unui pointer nu este ntotdeauna de 32 de bii, ci depinde de arhitectura i sistemul de operare pe care programul este compilat. Pe sisteme pe 64 de bii, un pointer va avea 64 de bii. De asemenea, dimensiunea n memorie pentru o variabil de tip pointer nu este n mod necesar egal cu dimensiunea unui tip de date ntreg. Cu alte cuvinte, sizeof(void*) nu este in mod necesar egal cu sizeof(int) (dei sunt egale n cele mai multe situaii), i de aceea nu trebuie s v bazai pe acest fapt n programele voastre. n lucrul cu pointeri sunt folosii asupra variabilelor doi operatori: operatorul * (de derefereniere) i operatorul & (de refereniere). Amndoi apar n faa variabilei asupra creia acioneaz i au efecte complementare. Operatorul * este aplicat unei variabile de tip pointer i are funcia de a obine valoarea stocat la adresa respectiv. Utilizat n declaraia unei variabile, acest operator are funcia de a specifica faptul c avem de-a face cu o variabil pointer la tipul respectiv. Operatorul & este aplicat unei variabile de un anumit tip i obine adresa n memorie a variabilei respective. Atenie! Cnd este declarat un pointer, nu este alocat i zona de memorie n care s fie stocat valoarea ctre care indic pointerul respectiv! ntotdeauna cnd folosii (derefereniai) un pointer, trebuie ca acesta s puncteze ctre o zona valid de memorie a programului vostru. Acesta poate fi, de exemplu, adresa unei variabile declarate n prealabil, sau adresa unui bloc de memorie alocat dinamic (dup cum vom vedea mai departe).

De asemenea, un pointer poate fi iniializat la constanta NULL (valoarea 0), compatibil cu orice tip de pointer, i care indic, prin convenie, un pointer neiniializat. Utilizarea unui pointer asignat la o adres oarecare (invalid), poate avea consecine imprevizibile la executia programului, de l a comportamente ciudate la blocarea sistemului! Exemplu: int *a; int b = 5; char *c; void *buff = NULL; *a = 1; a = &b; *a = 5; valoarea 5

// Pointer // Variabila // Pointer catre un caracter (sau sir de caractere) // Pointer catre void, initializat la NULL // Asignare INVALIDA; a nu este initializat la o adresa de memorie // Asignare valida; a ia adresa variabilei b // Asignare valida; continutul memoriei de la adresa a (care a fost in // Acest lucru este echivalent cu "b = 5;"

Atenie! n cazul declaraiilor de pointeri, operatorul * este asociat numelui variabilei, i nu numelui tipului, astfel c, pentru o declaraie de mai multe variabile, operatorul * trebuie s apar pentru fiecare variabil n parte i este recomandat ca i formatarea codului s indice aceast asociere. De exemplu: char *sir1, sir2, sir3; // sir1 e pointer, sir2 si sir3 sunt caractere int *a, *b, *c; // a, b si c sunt pointeri char* a, b; // Doar a este pointer; formatarea codului este nerecomandata Operatorul * poate fi folosit i n specificarea numelui unui tip (de exemplu n cazul unui cast), i n acest caz el apare dup numele tipului. De exemplu: void *var = NULL; int *num = (int *)var; // Operatie valida, dar riscanta Atenie! Un pointer ctre void nu poate fi folosit direct n operaii cu pointeri, ci trebuie convertit mai nti la un pointer ctre un tip de date. De exemplu: void *mem; //[...] *mem = 10; // Operatie ILEGALA ((int*)mem) = 10; // Operatie legala, dar riscanta

Tipuri de pointeri
Pointeri la date
Operaiile cu pointeri la date pot fi rezumate la urmtoarele categorii: Indirectarea folosind operatorul *, pentru acces la datele acelui pointer. De exemplu: *p = y; // Ia valoarea y si pune-o la adresa indicata de p x = *p; // Ia valoarea de la adresa indicata de p si pune-o in variabila x *s1++ = *s2++; Atribuirea la un pointer; n partea dreapt poate fi un pointer de acelai tip (eventual cu conversie de tip), constanta NULL, sau o expresie cu rezultat pointer. De exemplu: p1 = p2; p = NULL; p = (int*)malloc(n); Atribuirea ntre tipuri diferite de pointeri se poate face numai cu cast explicit i, printre altele, permite interpretarea diferit a unor date din memorie, ca n exemplul urmtor: int n; short s1, s2; s1 = *( (short*)&n); // Extrage primul cuvant din intregul n s2 = *( (short*)&n + 1); // Extrage cel de-al doilea cuvant din intregul n Compararea sau scderea a dou variabile pointer de acelai tip. Operaia de scdere returneaz diferena n elemente dintre cele dou adrese de memorie (cu alte cuvinte, diferena n octei dintre dou adrese de tip*, mprit la sizeof(tip)). Adunarea sau scderea unui ntreg la un pointer, incrementarea sau decrementarea unui pointer. Aceste operaii lucreaz n multipli de dimensiunea tipului de date la care pointerii se refer, pentru a permite accesul la memorie ca ntr-un vector. De exemplu: int *num; num++; // Aduna la adresa initiala pe sizeof(num), dand acces // la urmatorul intreg care ar fi stocat daca zona aceea de memorie ar fi organizata // sub forma unui vector num = num + 5; // Incrementeaza adresa cu 5*sizeof(num);

Pointeri la tablouri
O variabil vector conine adresa de nceput a vectorului (adresa primei componente a vectorului), i de aceea este echivalent cu un pointer la tipul elementelor din vector. Aceast echivalen este exploatat, de obicei, n argumentele de tip vector i n lucrul cu vectori alocai dinamic. De exemplu, pentru declararea unei funcii care primete un vector de ntregi i dimensiunea lui, avem dou posibiliti: void printVec(int a[], int n);

sau: void printVec(int *a, int n); Interesant este c n interiorul funciei ne putem referi la elementele vectorului a fie prin indici, fie prin indirectare, indiferent de felul cum a fost declarat parametrul vector a: void printVec (int a[], int n) { int i; for (i=0;i<n;i++) printf ("%6d",a[i]); // Indexare } void printVec (int *a, int n) { int i; for (i=0;i<n;i++) printf ("%6d", *a++); // Indirectare } Astfel, exist urmtoarele echivalene de notaii pentru un vector a: a[0] == *a a[1] == *(a+1) a[k] == *(a+k) &a[0] == a &a[1] == a+1 &a[k] == a+k Diferena dintre o variabil pointer i un nume de vector este aceea c un nume de vector este un pointer constant (adresa sa este alocat de ctre compilatorul C i nu mai poate fi modificat la execuie), deci nu poate aprea n stnga unei atribuiri, n timp ce o variabil pointer are un coninut modificabil prin atribuire sau prin operaii aritmetice. De exemplu:

int a[100], *p; p = a; ++p; //corect a = p; ++a; //EROARE De asemenea, o variabil de tip vector conine i informaii legate de lungimea vectorului i dimensiunea total ocupat n memorie, n timp ce un pointer doar descrie o poziie n memorie (e o valoarea punctual). Operatorul sizeof(v) pentru un vector v[N] de tipul T va fi N*sizeof(T), n timp ce sizeof(v) pentru o variabila v de tipul T* va fi sizeof(T*), adic dimensiunea unui pointer. Ca o ultim not, este importat de remarcat c o funcie poate avea ca rezultat un pointer, dar nu poate avea ca rezultat un vector.

Pointeri n funcii
n cadrul funciilor, pointerii pot fi folosii, printre altele, pentru: Transmiterea de rezultate prin argumente Transmiterea unei adrese prin rezultatul funciei Utilizarea unor funcii cu nume diferite (date prin adresele acestora)

O funcie care trebuie s modifice mai multe valori primite prin argumente sau care trebuie s transmit mai multe rezultate calculate n cadrul funciei trebuie s foloseasc argumente de tip pointer. De exemplu, o funcie care primete ca parametru un numr, pe care il modifica: // Functie care incrementeaza un intreg n modulo m int incmod (int *n, int m) { return ++(*n) % m; } // Utilizarea functiei int main() { int n = 10; int m = 15; incmod(&n, m); // Afisam noua valoare a lui n printf("n: %d", n); return 0; } O funcie care trebuie s modifice dou sau mai multe argumente, le va specifica pe acestea individual, prin cte un pointer, sau ntr-un mod unificat, printr-un vector, ca n exemplul urmtor:

void inctime (int *h, int *m, int *s); // sau void inctime (int t[3]); // t[0]=h, t[1]=m, t[2]=s O funcie poate avea ca rezultat un pointer, dar acest pointer nu trebuie s conin adresa unei variabile locale. De obicei, rezultatul pointer este egal cu unul din argumente, eventual modificat n funcie. De exemplu: // Incrementare pointer p char *incptr(char *p) { return ++p; } O variabila local are o existen temporar, garantat numai pe durata execuiei funciei n care este definit (cu excepia variabilelor locale statice), i de aceea adresa unei astfel de variabile nu trebuie transmis n afara funciei, pentru a fi folosit ulterior. De exemplu, urmtoarea secven de cod estegreit: // Vector cu cifrele unui nr intreg int *cifre (int n) { int k, c[5]; // Vector local for (k=4;k>=0;k--) { c[k]=n%10; n=n/10; } return c; // Aici este eroarea ! } Astfel, o funcie care trebuie s transmit ca rezultat un vector poate fi scris corect n dou feluri: Primete ca argument adresa vectorului (definit i alocat n alt funcie) i depune rezultatele la adresa primit (soluia recomandat!); Aloc dinamic memoria pentru vector (folosind malloc), iar aceast alocare se menine i la ieirea din funcie.

Pointeri la funcii

Anumite aplicaii numerice necesit scrierea unei funcii care s poat apela o funcie cu nume necunoscut, dar cu prototip i efect cunoscut. De exemplu, o funcie care s calculeze integrala definit a oricrei funcii cu un singur argument sau care s determine o radcin reala a oricrei ecuaii (neliniare). Aici vom lua ca exemplu o funcie listf care poate afia (lista) valorile unei alte funcii cu un singur argument, ntr-un interval dat i cu un pas dat. Exemple de utilizare a funciei listf pentru afiarea valorilor unor funcii de bibliotec: int main() { listf (sin, 0.0, 2.0*M_PI, M_PI/10.0); listf (exp, 1.0, 20.0, 1.0); return 0; } Problemele apar la definirea unei astfel de funcii, care primete ca argument numele (adresa) unei funcii. Prin convenie, n limbajul C, numele unei funcii nensoit de o list de argumente i de parantezele () specifice unui apel este interpretat ca un pointer ctre funcia respectiv (fr a se folosi operatorul de adresare &). Deci sin este adresa funciei sin(x) n apelul funciei listf. Declararea unui argument formal (sau a unei variabile) de tip pointer la o funcie are forma urmtoare: tip (*pf) (lista_arg_formale); unde: pf este numele argumentului (variabilei) pointer la funcie tip este tipul rezultatului funciei

Parantezele sunt importante, deoarece absena lor modific interpretarea declaraiei. De exemplu, putem avea: tip * f(lista_arg_formale) // functie cu rezultat pointer, si NU pointer n concluzie, definirea funciei listf este: void listf (double (*fp)(double), double min, double max, double pas) { double x,y; for (x=min; x<=max; x=x+pas) {

y=(*fp)(x); // apel functie de la adresa din "fp" printf ("\n%20.10lf %20.10lf", x, y); } } O eroare de programare care trece de compilare i se manifest la execuie este apelarea unei funcii fr paranteze; compilatorul nu apeleaz funcia i consider c programatorul vrea s foloseasc adresa funciei. De exemplu: if (kbhit) break; // echivalent cu if(1) break; if (kbhit()) break; // iesire din ciclu la apasarea unei taste

Expresii complexe cu pointeri


Dei sunt ntlnite mai rar n practic, limbajul C permite declararea unor tipuri de date complexe, precum: char *( *(*var)() )[10]; //7 6 4 2 1 3 5 n interpretarea acestor expresii, operatorii () i [] au precedena n faa * i modul de interpretare al acestor expresii este pornind din interior spre exterior. Astfel expresia dat ca exemplu mai sus este (numerele de sub expresie reprezint ordinea de interpretare): 1. o variabila var 2. care este un pointer la o funcie 3. fr nici un parametru 4. i care ntoarce un pointer 5. la un vector de 10 elemente 6. de tip pointer 7. ctre tipul char Folosind acest procedeu, se pot rezolva i alte situaii aparent extrem de complexe: unsigned int *(* const *name[5][10] ) ( void );

care semnific: o matrice de 510 de pointeri ctre pointeri constani la o funcie, care nu ia nici un parametru, i care ntoarce un pointer ctre tipul unsigned int.

Invata Limbajul de Programare C Partea 8


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Alocarea dinamic a memoriei Funcii de alocare i eliberare a memoriei


Aceste funcii standard sunt declarate n fiierul antet stdlib.h. Cele trei funcii de alocare au ca rezultat adresa zonei de memorie alocate (de tip void*) i ca argument comun dimensiunea, n octei, a zonei de memorie alocate (de tip size_t ). Dac cererea de alocare nu poate fi satisfcut pentru c nu mai exist un bloc continuu de dimensiunea solicitat, atunci funciile de alocare au rezultat NULL (ce reprezint un pointer de tip void* la adresa de memorie 0, care prin convenie este o adres invalid nu exist date stocate n acea zon). La apelarea funciilor de alocare se folosesc: Operatorul sizeof pentru a determina numrul de octei necesar unui tip de date (variabile); Operatorul de conversie (cast) pentru adaptarea adresei primite la tipul datelor memorate la adresa respectiv (conversie necesar atribuirii ntre pointeri de tipuri diferite). De exemplu: char *str = (char*) malloc(30); // Aloca memorie pentru 30 de caractere int *a = (int*) malloc ( n * sizeof(int)); // Aloca memorie pt. n numere intregi Atenie! Dimensiunea memoriei luat ca parametru de malloc() este specificat n octei, indiferent de tipul de date care va fi stocat n acea regiune de memorie! Din acest motiv, pentru a aloca suficient memorie, numrul dorit de elemente trebuie nmulit cu dimensiunea unui element, atunci cnd are loc un apelmalloc()

Alocarea de memorie pentru un vector i iniializarea zonei alocate cu zerouri se poate face cu funcia calloc. Exemplu: int *a= (int*) calloc(n, sizeof(int) ); // Aloca memorie pentru n numere intregi Codul de mai sus este perfect echivalent (dar mai rapid) cu urmtoarea secven de instruciuni: int i; int *a = (int*) malloc(n * sizeof(int)); for (i = 0; i < n; i++) { a[i] = 0; } Not: n timp ce funcia malloc() ia un singur parametru (o dimensiune n octei), funcia calloc() primete dou argumente, o lungime de vector i o dimensiune a fiecrui element. Astfel, aceast funcie este specializat pentru memorie organizat ca un vector, n timp ce malloc() nu ine cont de structura memoriei. Realocarea unui vector care crete (sau scade) fa de dimensiunea estimat anterior se poate face cu funcia realloc, care primete adresa veche i noua dimensiune i ntoarce noua adres: a = (int *) realloc (a, 2*n* sizeof(int)); // Dublare dimensiune anterioara (n) n exemplul anterior, noua adres este memorat tot n variabila pointer a, nlocuind vechea adres (care nu mai este necesar i nici nu mai trebuie folosit). Funcia realloc() realizeaz urmtoarele operaii: Aloc o zon de dimensiunea specificat ca al doilea argument Copiaz la noua adres datele de la adresa veche (primul argument al funciei) Elibereaz memoria de la adresa veche.

Funcia free() are ca argument o adres (un pointer) i elibereaz zona de la adresa respectiv (alocat prin apelul unei funcii de tipul alloc). Dimensiunea zonei nu mai trebuie specificat deoarece este inut minte de sistemul de alocare de memorie n nite structuri interne.

Vectori alocai dinamic

Structura de vector are avantajul simplitii i economiei de memorie fa de alte structuri de date folosite pentru memorarea unei colectii de informaii ntre care exist anumite relaii. ntre cerina de dimensionare constant a unui vector i generalitatea programelor care folosesc astfel de vectori exist o contradicie. De cele mai multe ori programele pot afla (din datele citite) dimensiunile vectorilor cu care lucreaz i deci pot face o alocare dinamic a memoriei pentru aceti vectori. Aceasta este o solutie mai flexibil, care folosete mai bine memoria disponibil i nu impune limitri arbitrare asupra utilizrii unor programe. n limbajul C nu exist practic nici o diferen ntre utilizarea unui vector cu dimensiune fix i utilizarea unui vector alocat dinamic, ceea ce ncurajeaz i mai mult utilizarea unor vectori cu dimensiune variabil. De observat c nu orice vector cu dimensiune constant este un vector static; un vector definit ntr -o funcie (alta decat main()) nu este static deoarece nu ocup memorie pe toata durata de execuie a programului, dei dimensiunea sa este stabilit la scrierea programului. Exemplul urmator arata cum se poate defini si utiliza un vector alocat dinamic: int main() { int n,i; int *a; // Adresa vector alocat dinamic printf ("n="); scanf ("%d", &n); // Dimensiune vector a = (int *) calloc(n, sizeof(int)); // Alternativ: a = (int*) malloc (n*sizeof(int)); printf ("Componente vector: \n"); for (i = 0; i < n; i++) scanf ("%d", &a[i]); // Sau scanf (%d, a+i); for (i = 0; i < n; i++) // Afisare vector printf ("%d ",a[i]); free(a); // Nu uitam sa eliberam memoria return 0; }

Exist i cazuri n care datele memorate ntr-un vector rezult din anumite prelucrri, iar numrul lor nu poate fi cunoscut de la nceputul execuiei. Un exemplu poate fi un vector cu toate numerele prime mai mici ca o valoare dat. n acest caz se poate recurge la o realocare dinamic a memoriei. n exemplul urmtor se citete un numr necunoscut de valori ntregi ntr -un vector extensibil: #define INCR 100 // cu cat creste vectorul la fiecare realocare int main() { int n,i,m; float x, *v; // v = adresa vector n = INCR; i = 0; v = (float *)malloc (n*sizeof(float)); // Dimensiune initiala vector while ( scanf("%f",&x) != EOF) { if (++i == n) { // Daca este necesar... n = n + INCR; // ... creste dimensiune vector v = (float*) realloc(vector, n*sizeof(float)); } v[i] = x; // Memorare in vector numar citit } for (i = 0; i < n; i++) // Afisare vector printf ("%f ",v[i]); free(vector); return 0; } Realocarea repetat de memorie poate conduce la fragmentarea memoriei heap, adic la crearea unor blocuri de memorie libere dar neadiacente i prea mici pentru a mai fi reutilizate ulterior. De aceea, politica de realocare pentru un vector este uneori dublarea capacitii sale anterioare. Astfel numrul de realocri pe parcursul execuiei programului va deveni foarte mic un calcul matematic simplu arat c acest numr este proporional cu logaritmul dimensiunii maxime (finale) a vectorului.

Matrice alocate dinamic


Alocarea dinamic pentru o matrice este important deoarece: Folosete economic memoria i evit alocri acoperitoare, estimative.

Permite matrice cu linii de lungimi diferite (denumite uneori ragged arrays, datorit formelor zimate din reprezentrile grafice)

Reprezint o soluie bun la problema argumentelor de funcii de tip matrice.

Daca programul poate afla numrul efectiv de linii i de coloane al unei matrice (cu dimen siuni diferite de la o execuie la alta), atunci se va aloca memorie pentru un vector de pointeri (funcie de numrul liniilor) i apoi se va aloca memorie pentru fiecare linie (funcie de numrul coloanelor) cu memorarea adreselor liniilor n vectorul de pointeri. O astfel de matrice se poate folosi la fel ca o matrice declarat cu dimensiuni constante. Exemplu: int main () { int **a; int i, j, nl, nc; printf ("nr. linii="); scanf (%d,&nl); printf ("nr. col. ="); scanf (%d,&nc); a = (int**) malloc (nl * sizeof(int*)); // Alocare pentru vector de pointeri for (i = 0; i < n; i++) a[i] = (int*) calloc (nc, sizeof(int)); // Alocare pentru o linie si initializare la zero // Completare diagonala matrice unitate for (i = 0; i < nl; i++) a[i][i]=1; // a[i][j]=0 pentru i != j // Afisare matrice printmat(a, nl, nc); free(a); // Nu uitam sa eliberam! return 0; } Funcia de afiare a matricei se poate defini astfel: void printmat(int **a, int nl, int nc) { for (i = 0; i < nl; i++) { for (j = 0; j < nc; j++) printf("%2d", a[i][j]);

printf("\n"); } } Notaia a[i][j] este interpretat astfel pentru o matrice alocat dinamic:

a[i] conine un pointer (o adres b)


b[j] sau b+j conine ntregul din poziia j a vectorului cu adresa b.

Astfel, a[i][j] este echivalent semantic cu expresia cu pointeri *(*(a + i) + j). Totui, funcia printmat() dat anterior nu poate fi apelat dintr-un program care declar argumentul efectiv ca o matrice cu dimensiuni constante. Exemplul urmtor este corect sintactic dar nu se execut corect: int main() { int x[2][2] = { {1, 2}, {3, 4} }; // O matrice patratica cu 2 linii si 2 coloane printmat((int**)x, 2, 2); return 0; } Explicaia este interpretarea diferit a coninutului zonei de la adresa aflat n primul argument: funciaprintmat() consider c este adresa unui vector de pointeri (int *a[]), iar programul principal consider c este adresa unui vector de vectori (int x[][2]), care este reprezentat liniar in memorie. Se poate defini i o funcie pentru alocarea de memorie la execuie pentru o matrice: int **newmat(int nl, int nc) { // Rezultat adresa matrice int i; int **p = (int **)malloc(nl * sizeof(int*)); for (i = 0; i < n; i++) p[i] = (int*)calloc(nc, sizeof(int)); return p; }

Stil de programare. Exemple de programe.

Exemplul 1: Funcie echivalent cu funcia de bibliotec strdup(): #include <string.h> #include <alloc.h> // Alocare memorie si copiere sir char *strdup(char* adr) { int len = strlen(adr); char *rez = (char *)malloc(len); strcpy(rez, adr); return adr; } // Utilizare "strdup" #include <stdio.h> int main() { char s[80], *d; do { if (gets(s) == 0) break; d = strdup(s); puts(d); free(d); } while (1); return 0; } Exemplul 2: Vector alocat dinamic (cu dimensiune cunoscut la execuie) #include <stdio.h> #include <stdlib.h> int main() { int n, i; int *a; // Adresa vector printf("n="); scanf("%d",&n); // Dimensiune vector a = (int*)malloc(n * sizeof(int)); printf("componente vector: \n"); for (i = 0; i < n; i++) // Citire vector scanf("%d", &a[i]);

for (i = 0; i < n; i++) // Afisare vector printf("%d", a[i]); free(a); return 0; } Exemplul 3: Vector realocat dinamic (cu dimensiune necunoscut) #include <stdio.h> #include <stdlib.h> #define INCR 4 int main() { int n, i, m; float x, *v; n = INCR; i = 0; v = (float *)malloc(n * sizeof(float)); while (scanf("%f", &x) != EOF) { if (i == n) { n = n + INCR; v = (float *)realloc(v, n * sizeof(float)); } v[i++] = x; } m = i; for (i = 0; i < m; i++) printf("%.2f ", v[i]); free(v); return 0; } Exemplul 4: Matrice alocat dinamic (cu dimensiuni cunoscute la execuie) #include <stdio.h> #include <stdlib.h> int main() { int n, i, j;

int **mat; // Adresa matrice // Citire dimensiuni matrice printf("n="); scanf("%d",&n); // Alocare memorie ptr matrice mat = (int **)malloc(n * sizeof(int *)); for (i = 0; i < n; i++) mat[i] = (int *)calloc(n, sizeof(int)); // Completare for (i = 0; i for (j = 0; mat[i][j] matrice < n; i++) j < n; j++) = n * i + j + 1;

// Afisare matrice for (i = 0; i < n; i++) { for (j = 0;j < n; j++) printf("%6d", mat[i][j]); printf("\n"); } return 0; } Exemplul 5: Vector de pointeri la iruri alocate dinamic /* Creare / afisare vector de pointeri la siruri */ #include <stdio.h> #include <stdlib.h> #include <string.h> // Afisare siruri reunite in vector de pointeri void printstr(char *vp[], int n) { int i; for(i = 0; i < n; i++) printf("%s\n", vp[i]); } // Ordonare vector de pointeri la siruri void sort(char *vp[], int n) { int i, j; char *tmp; for (j = 1; j < n; j++) for (i = 0; i < n - 1; i++) if (strcmp(vp[i], vp[i+1]) > 0) { tmp = vp[i];

vp[i] = vp[i+1]; vp[i+1] = tmp; } } // Citire siruri si creare vector de pointeri int readstr (char * vp[]) { int n = 0; char *p, sir[80]; while (scanf("%s", sir) == 1) { p = (char *)malloc(strlen(sir) + 1); strcpy(p, sir); vp[n] = p; ++n; } return n; } int main() { int n; char *vp[1000]; // vector de pointeri, cu dimensiune fixa n = readstr(vp); // citire siruri si creare vector sort(vp, n); // ordonare vector printstr(vp, n); // afisare siruri retrun 0; }

Practici recomandate
Avei grij ca variabilele de tip pointer s indice ctre adrese de memorie valide nainte de a fi folosite; consecinele adresrii unei zone de memorie aleatoare sau invalide ( NULL) pot fi dintre cele mai imprevizibile. Utilizai o formatare a codului care s sugereze asocierea operatorului * cu variabila asupra creia opereaz; acest lucru este n special valabil pentru declaraiile de pointeri. Nu returnai pointeri la variabile sau tablouri definite n cadrul funciilor, ntruct valabilitatea acestora nceteaz odat cu ieirea din corpul funciei. Verificai rezultatul funciilor de alocare a memoriei, chiar dac dimensiunea pe care dorii s -o rezervai este mic. Atunci cnd memoria nu poate fi alocat rezultatul este NULL iar programul vostru ar trebui s trateze explicit acest caz (finalizat, de obicei, prin nchiderea curat a aplicaiei).

Nu uitai s eliberai memoria alocat dinamic, folosind funcia free(). Memoria rmas neeliberat ncetinete performanele sistemului i poate conduce la erori (bug -uri) greu de depistat.

Clase de stocare
Clasa de stocare (memorare) arat cnd, cum i unde se aloc memorie pentru o variabil (vector). Orice variabil C are o clas de memorare care rezult fie dintr-o declaraie explicit, fie implicit din locul unde este definit variabila. Exist trei moduri de alocare a memoriei, dar numai dou corespund unor clase de memorare: Static: memoria este alocat la compilare n segmentul de date din cadrul programului i nu se mai poate modifica n cursul execuiei. Variabilele externe, definite n afara funciilor, sunt implicit statice, dar pot fi declarate static i variabile locale, definite n cadrul funciilor. Automat: memoria este alocat automat, la activarea unei funcii, n zona stiv alocat unui program i este eliberat automat la terminarea funciei. Variabilele locale unui bloc (unei funcii) i argumentele formale sunt implicit din clasa auto. Dinamic: memoria se aloc la execuie n zona heap alocat programului, dar numai la cererea explicit a programatorului, prin apelarea unor funcii de bibliotec (malloc, calloc, realloc). Memoria este eliberat numai la cerere, prin apelarea funciei free. Variabilele dinamice nu au nume i deci nu se pune problema clasei de memorare (atribut al variabilelor cu nume). Variabilele statice pot fi iniializate numai cu valori constante (pentru c se face la compilare), dar variabilele auto pot fi iniializate cu rezultatul unor expresii (pentru c se face la execuie). Toate variabilele externe (i statice) sunt automat iniializate cu valori zero (inclusiv vectorii). Cantitatea de memorie alocat pentru variabilele cu nume rezult automat din tipul variabilei i din dimensiunea declarat pentru vectori. Memoria alocat dinamic este specificat explicit ca parametru al funciilor de alocare. O a treia clas de memorare este clasa register pentru variabile crora, teoretic, li se aloc registre ale procesorului i nu locaii de memorie, pentru un timp de acces mai bun. n practic nici un compilator modern nu mai ine cont de acest cuvnt cheie, folosind automat registre atunci cnd codul poate fi optimizat n acest fel (de exemplu cnd observ c nu se acceseaz niciodata adresa variabilei n program).

Memoria neocupat de datele statice i de instruciunile unui program este mprit ntre stiv i heap. Consumul de memorie pe stiv este mai mare n programele cu funcii recursive i numr mare de apeluri recursive, iar consumul de memorie heap este mare n programele cu vectori i matrice alocate (i realocate) dinamic.

Invata Limbajul de Programare C Partea 9


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Prelucrarea irurilor de caractere. Funcii.


iruri de caractere
Un caracter se declar n C de forma: char caracter ntre apostroafe. Un ir de caractere presupune practic un vector de caractere. Cea mai simpl declaraie fiind: char

a=a; Pentru initializarea lui, se observ c am pus un

a[10]= cuvant;
Pentru iniializarea unui ir de caractere, spre deosebire de un singur caracter am folosit ghilimelele. Cum s-a prezentat anterior, o variabil vector conine adresa de nceput a vectorului(adresa primei componente a vectorului), i de aceea este echivalent cu un pointer la tipul elementelor din vector. Deci declaraiile de mai jos vor declara fiecare cate un ir de caractere: char a[5]; char *b="unsir"; char *c; Diferena ntre ele este nsa c primele dou declaraii vor aloca 5 pozitii n memorie, pe cnd ultima nu va aloca nici o zona de memorie, necesitnd sa fie ulterior alocat, folosind funciile de alocare dinamic (malloc(),

calloc(), realloc()). Un mic exemplu de citire a unui ir, caracter cu caracter pana la 0:

#include <stdio.h> #include <string.h> int main () { char s[30],c; int n=0;

do{ scanf("%c", &c) if(c=='0') break; s[n++]=c; }while(1); s[n]='\0'; printf("%s",s); return 0; } Pentru citirea si afisarea unui ir de caractere se poate folosi flagul s la citirea cu scanf sau afiarea cuprintf. Deasemenea biblioteca stdio.h definete funciile gets() i puts() pentru lucrul cu iruri de caractere. gets(zona) citete de la terminalul standard un ir de caractere terminat cu linie noua (enter). Funcia are ca parametru adresa zonei de memorie n care se introduc caracterele citite. Funcia returneaza adresa de nceput a zonei de memorie; puts(zona) afieaza la terminalul standard irul de caractere din zona data ca parametru, pn la caracterul terminator de ir, care va fi nlocuit prin caracterul linie noua. Funcia are ca parametru adresa zonei de memorie de unde ncepe afiarea caracterelor. Funcia returneaza codul ultimului caracter din irul de caractere afiat i -1 daca a aparut o eroare. Atenie! Funcia gets() va citi de la tastatura cte caractere sunt introduse, chiar daca irul declarat are o lungime mai mic. Presupunem un ir declarat: char a[]=unsir , care va avea deci 5 caractere. Citind un ir de lungime mai mare ca 5 de la tastatura, n irul a, la afiare vom vedea ca s -a reinut tot sirul!(nu doar primele 5 caractere). Nimic deosebit pn acum. Dar dac lum n considerare c citirea caracterelor auxiliare se face n continuare n zona de memorie, ne punem problema ce se va suprascrie?! Raspunsul este: nu se tie poate nim ic important pentru programul nostru, poate ceva ce il va bloca sau duce la obinerea de date eronate. Pentru a evita aceasta se recomand utilizarea fgets() fgets(zona, lung_zona, stdin) citete de la stdin un ir de caractere terminat printr-o linie nou dac lungimea lui este mai mic decat lung_zona sau primele lung_zona caractere n caz contrar. Parametrii sunt: zona de memorie, lungimea maxima admis a irului, i terminalul

standard de intrare. n cazul n care irul dorit are lungime mai mic dect cea maxim, naintea terminatorului de ir, n zona de memorie va fi reinut i enter-ul dat.

Funcii din string.h


Pentru manipularea irurilor de caractere n limbajul C se folosesc funcii declarate n fiierul string.h. Vom ncerca s le detaliem putin pe cele mai des folosite.

strlen()
size_t strlen ( const char * str );

Returneaza lungimea unui ir dat ca parametru. (numarul de caractere pn la ntalnirea terminatorului de ir) Exemplu:
#include <stdio.h> #include <string.h> int main () { char text[256]; printf ("Introduceti un text: "); gets (text); printf ("Textul are %u caractere.\n",strlen(text)); return 0; }

memset()
void * memset ( void * ptr, int val, size_t num ); n zona de memorie dat de pointerul ptr, sunt setate primele num poziii la valoarea dat de val. Funcia returneaz irul ptr. Exemplu: #include <stdio.h> #include <string.h> int main () { char str[] = "nu prea vreau vacanta!"; memset (str,'-',7); puts (str); return 0; }

Iesire: ------- vreau vacanta!

memmove()
void * memmove ( void * destinatie, const void * sursa, size_t num ); Copiaz un numr de num caractere de la surs, la zona de memorie indicat de destinaie. Copierea are loc ca i cum ar exista un buffer intermediar, deci sursa si destinatia se pot suprapune. Funcia nu verific terminatorul de ir la surs, copiaz mereu num bytes, deci pentru a evita depsirea trebuie ca dimensiunea sursei sa fie mai mare ca num. Funcia returneaz destinaia. Exemplu: #include <stdio.h> #include <string.h> int main () { char str[] = "memmove can be very useful......"; memmove (str+20,str+15,11); puts (str); return 0; }

Iesire: memmove can be very very useful.

memcpy()
void * memcpy ( void * destinatie, const void * sursa, size_t num ); Copiaz un numr de num caractere din irul surs in irul destinaie. Funcia returneaz irul destinaie. Exemplu: #include <stdio.h> #include <string.h> int main () { char str1[]="Exemplu"; char str2[40]; char str3[40]; memcpy (str2,str1,strlen(str1)+1); memcpy (str3,"un sir",7); printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3);

return 0; }

Iesire: str1: Exemplu str2: Exemplu str3: un sir

strcpy()
char * strcpy ( char * destinatie, const char * sursa ); Copiaz irul surs in irul destinaie. irul destinaie va fi suprascris. Funcia asigur plasarea terminatorului de ir n irul destinaie dup copiere. Funcia returneaza irul destinaie.

strncpy()
char * strncpy ( char * destinatie, const char * sursa, size_t num ); Asemeni cu strcpy(), dar in loc de a fi copiat toata sursa sunt copiate doar primele num caractere. Exemplu: #include <stdio.h> #include <string.h> int main () { char str1[]="Exemplu"; char str2[40]; char str3[40]; strcpy (str2,str1); strncpy (str3,"un sir",2); printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3); return 0; }

Iesire: str1: Exemplu str2: Exemplu str3: un

strcat()
char * strcat ( char * destinatie, const char * sursa ); Concatenenaza irul surs la irul destinaie. Funcia returneaz irul destinaie.

strncat()
char * strncat ( char * destinatie, const char * sursa, size_t num ); Asemeni cu strcat(), dar n loc de a fi concatenat toat sursa sunt concatenate doar primele num caractere. Exemplu: #include <stdio.h> #include <string.h> int main () { char str[80]; strcpy (str,"ana "); strcat (str,"are "); strcat (str,"mere "); puts (str); strncat (str,"si pere si prune", 7); puts (str); return 0; }

Iesire: ana are mere ana are mere si pere

strcmp()
int strcmp ( const char * str1, const char * str2 ); Compar irul str1 cu irul str2, verificndu-le caracter cu caracter. Valoarea returnat este 0 daca cele iruri sunt identice, mai mare ca 0 daca str1 este mai mare(alfabetic) i mai mic ca zero altfel. Exemplu: #include <stdio.h> #include <string.h> int main () { char cuv[] = "rosu"; char cuv_citit[80]; do { printf ("Ghiceste culoarea..."); gets (cuv_citit); } while (strcmp (cuv,cuv_citit) != 0); puts ("OK"); return 0;

strchr()
char * strchr (const char * str, int character ); Caut caracterul c n irul str i returneaz un pointer la prima sa apariie.

strrchr()
char * strrchr (const char * str, int character ); Caut caracterul c n irul str i returneaz un pointer la ultima sa apariie.

strstr()
char * strstr (const char *str1, const char *str2 ); Caut irul str2 n irul str1 i returneaz un pointer la prima sa apariie, sau NULL dac nu a fost gsit.

strdup()
char * strdup (const char *str); Realizeaz un duplicat al irului str, pe care l i returneaz. Exemplu: #include <stdio.h> #include <string.h> int main () { char str[80], * d; do { if (gets(str)==0) break; d=strdup(str); puts(d); } while (1); return 0; }

strtok()
char * strtok ( char * str, const char * delimitatori );

Funcia are rolul de a mpari irul str n tokens(subiruri separate de orice caracter aflat n lista de delimitatori), prin apelarea ei succesiv. La primul apel, parametrul str trebuie sa fie un ir de caractere, ce urmeaz a fi mpartit. Apelurile urmatoare, vor avea n loc de str, NULL coninund mparirea aceluiai ir. Funcia va returna la fiecare apel un token(un subsir), ignornd caracterele cu rol de separator aflate n irul de delimitatori. O dat terminat irul, funcia va returna NULL. Exemplu: #include <stdio.h> #include <string.h> int main () { char str[] ="- Uite, asta e un sir."; char * p; p = strtok (str," ,.-"); /* separa sirul in "tokeni" si afiseaza-i pe linii separate. */ while (p != NULL) { printf ("%s\n",p); p = strtok (NULL, " ,.-"); } return 0; }

Iesire: Uite asta e un sir

Invata Limbajul de Programare C Partea 10


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Structuri. Uniuni. Matrici rare Structuri

Structurile sunt tipuri de date n care putem grupa mai multe variabile eventual de tipuri diferite (spre deosebire de vectori, care conin numai date de acelasi tip). O structur se poate defini astfel: struct nume_structura { declaratii_de_variabile };

Exemple: struct student { char nume[40]; int an; float medie; }; (nume, an etc. se numesc campurile structurii) struct complex { /* pentru memorarea unui numr complex cu dubl precizie */ double re; double im; }; Declararea i iniializarea unor variabile de tip structur se poate face astfel: struct student s1 = {"Popescu Ionel", 3, 9.25}; struct complex c1, c2; struct complex v[10]; Pentru simplificarea declaraiilor, putem asocia unei structuri un nume de tip de date: typedef struct student Student; ... Student s1, s2, s3; Accesul la membrii unei structuri se face prin operatorul .: s1.nume = "Ionescu Raluca";

n cazul pointerilor la structuri, accesul la membri se poate face astfel: Student *stud = (Student *)malloc(sizeof(Student)); (*stud).medie = 9.31; /* alt modalitate mai simpl i mai des folosit: */ stud -> medie = 9.31;

Atribuirile de structuri se pot face astfel: struct complex n1, n2; ... n2 = n1; Prin aceast atribuire se realizeaz o copiere bit cu bit a elementelor lui

n1 n n2.

Alt exemplu de utilizare: Dup cum se vede mai jos trebuie facut diferenta cnd definim un tip i cand declaram o variabila de tip struct sau typedef

struct.

typedef struct { int data; int text; } S1; // este un typedef pentru S1, functional in C si C++ struct S2 { int data; int text; }; // este un typedef pentru S2, functional numai in C++ struct { int data; int text; } S3; // este o declaratie a lui S3, variabila de tip struct nu defineste un tip // spune compilatorului sa aloce memorie pentru variablia S3 int main(){ // ce se intampla la declarare variabile de tip S1,S2,S3 S1 mine1; // este un typedef si va merge S2 mine2; // este un typedef si va merge S3 mine3; // nu va merge pt ca S3 nu este un typedef. // ce se intampla la utilizare variabile S1,S2,s3 S1.data = 5; // da eroare deoarece S1 este numai un typedef. S2.data = 5; // da eroare deoarece S2 este numai un typedef. S3.data = 5; // merge doarece S3 e o variabila return 0; } Atenie! Dac declarai pointeri la structuri, nu uitai s alocai memorie pentru acetia nainte de a accesa cmpurile structurii. Nu uitai s alocai i cmpurile structurii, care sunt pointeri, nainte de utilizare, dac este cazul. De asemenea fii ateni i la modul de accesare al cmpurilor.

Diferena dintre copierea structurilor i copierea pointerilor


Pentru exemplificarea diferenei dintre copierea structurilor i copierea pointerilor s considerm urmatorul exemplu:

struct exemplu { int n; char *s; } struct exemplu s1, s2; char *litere = "abcdef"; s1.n = 5; s1.s = strdup(litere); s2 = s1; s2.s[1]='x'; Dup atribuirea s2

= s1;, s2.s va avea o valoare identic cu s1.s. Deoarece s este un pointer (o

adres de memorie), s2.s va indica aceeai adresa de memorie ca i s1.s. Deci, dup modificarea celui de-al doilea caracter din s2.s, atat s2.s ct si s1.s vor fi axcdef. De obicei acest efect nu este dorit i nu se recomand atribuirea de structuri atunci cand acestea contin pointeri. Totui, putem atribui ulterior lui s2.s o alt valoare (o alt adres), iar ca urmare a acestei operaii, stringurile vor fi distincte din nou. Un alt caz (diferit de cel expus anterior) este cel al atribuirii aceleiai structuri ctre dou variabile pointer diferite: struct exemplu { int n; char * s; } struct exemplu s1; struct exemplu* p1; struct exemplu* p2; p1 = &s1; p2 = &s2; n acest caz observm c din nou p1->s i p2->s indic ctre acelai ir de caractere, dar aici adresa ctre irul de caractere apare memorat o singura dat; spre deosebire de cazul anterior, dac modificm adresa din p2->s, ea se va modifica automat i n p1->s.

Uniuni
Uniunile sunt asemntoare structurilor, dar lor li se rezerv o zon de memorie ce poate conine, la momente de timp diferite, variabile de tipuri diferite. Sunt utilizate pentru a economisi memoria (se refolosete aceeai zon de memorie pentru a stoca mai multe variabile).

Uniunile se pot declara astfel: union numere { int i; float f; double v; };

/* se poate utiliza si typedef... */

union numere u1, u2;

Cnd scriem ceva ntr-o uniune (de exemplu cnd facem o atribuire de genul u1.f = 7.4), ceea ce citim apoi trebuie s fie de acelai tip, altfel vom obine rezultate eronate (adic trebuie s utilizam u1.f, nu u1.v sau u1.i). Programatorul trebuie s in evidena tipului variabilei care este memorat n uniune n momentul curent pentru a evita astfel de greeli. Operaiile care se pot face cu structuri se pot face i cu uniuni; o structura poate conine uniuni i o uniune poate conine structuri. Exemplu: #include <stdio.h> #include <stdlib.h> typedef union { int Wind_Chill; char Heat_Index; } Condition; typedef struct{ float temp; Condition feels_like; } Temperature; int main(){ Temperature *tmp; tmp = (Temperature *)malloc(sizeof(Temperature)); printf("\nAddress of Temperature = %u", tmp); printf("\nAddress of temp = %u, feels_like = %u", &(*tmp).temp, &(*tmp).feels_like); printf("\nWind_Chill = %u, Heat_Index= %u\n", &((*tmp).feels_like).Wind_Chill, &((*tmp).feels_like).Heat_Index); return 0; }

La rulare va afisa:

Address of Temperature = 165496 Address of temp = 165496, feels_like = 165500 Wind_Chill = 165500, Heat_Index= 16550

Ce este o matrice rara?


O matrice rar (cu circa 90% din elemente 0) este pstrat economic sub forma unei structuri, care conine urmtoarele cmpuri: int L,C numrul de linii/coloane al matricei rare int N numrul de elemente nenule int LIN[] vectorul ce pstreaz liniile n care se afl elemente nenule int COL[] vectorul ce pstreaz coloanele n care se afl elemente nenule float X[] vectorul ce pstreaz elementele nenule

Invata Limbajul de Programare C Partea 11


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Operatii cu fiiere. Aplicaii folosind fiiere.


Un fiier este o structur dinamic, situat n memoria secundar (pe disk -uri). Limbajul C permite operarea cu fiiere: de tip text un astfel de fiier conine o succesiune de linii, separate prin NL ( n) de tip binar un astfel de fiier conine o succesiune de octeti, fr nici o structur.

Prelucrarea unui fiier presupune asocierea acestuia cu un canal de I/E (numit flux sau stream). Exist trei canale predefinite, care se deschid automat la lansarea unu i program: stdin - fiier de intrare, text, este intrarea standard tastatura stdout - fiier de iesire, text, este ieirea standard ecranul monitorului. stderr fiier de iesire, text, este ieirea standard unde sunt scris mesajele de eroare ecran.

Pentru a prelucra un fiier, trebuie parcurse urmtoarele etape:

se definete o variabil de tip FILE * pentru accesarea fiierului; FILE * este un tip structur definit nstdio.h, care conine informaii referitoare la fiier i la tamponul de transfer de date ntre memoria central i fiier (adresa, lungimea tamponului, modul de utilizare a fiierului, indicator de sfrsit, de poziie n fiier)

se deschide fiierul pentru un anumit mod de acces, folosind funcia de bibliotec fopen, care realizeaz i asocierea ntre variabila fiier i numele extern al fiierului

se prelucreaz fiierul n citire/scriere cu funciile specifice

se inchide fiierul folosind funcia de bibliotec fclose.

Mai jos se prezint restul funciilor de prelucrare a fiierelor: FILE *fopen(const char *nume_fis, const char *mod); deschide fiierul cu numele dat pentru acces de tip mod. Returneaz pointer la fiier sau NULL dac fiierul nu poate fi deschis; valoarea returnat este memorat n variabila fiier, care a fost declarat pentru accesarea lui. Modul de deschidere poate fi: r - readonly , este permis doar citirea dintr-un fiier existent w - write, creaz un nou fiier, sau dac exist deja, distruge vechiul continut a - append, deschide pentru scriere un fiier existent ( scrierea se va face n continuarea informaiei deja existente n fiier, deci pointerul de acces se plaseaz la sfritul fiierului ) + - permite scrierea i citirea actualizare (ex: r+, w+, a+). O citire nu poate fi direct urmat de o scriere i reciproc. nti trebuie repoziionat cursorul de acces printr -un apel la fseek. b - specific fiier de tip binar t - specific fiier de tip text (implicit), la care se face automat conversia CR-LF(nf) n sau din CR (n). int fclose(FILE *fp); nchide fiierul i elibereaz zona tampon; returneaz 0 la succes, EOF la eroare

int fseek(FILE *fp, long offset, int whence); repoziioneaz pointerul asociat unui fiier . Offset numrul de octei ntre poziia dat de whence i noua poziie. whence - are una din cele trei valori posibile: SEEK_SET = 0 Cutarea se face de la nceputul fiierului SEEK_CUR = 1 - Cutare din poziia curent SEEK_END = 2 - Cutare de la sfritul fiierului

long ftell(FILE* fp); ntoarce poziia curent n cadrul lui fp int fgetpos(FILE* fp, fpos_t* ptr); aceast funcie memoreaz n variabila fptr poziia curent n cadrul fiierului fp (ptr va putea fi folosit ulterior cu funcia fsetpos). int fsetpos(FILE* fp, const fpos_t* ptr); aceast funcie seteaz poziia curent n fiierul fp la valoarea ptr, obinut anterior prin funcia fgetpos. int feof(FILE *fis); returneaz 0 dac nu s-a detectat sfrit de fiier la ultima operaie de citire, respectiv o valoare nenul (adevrat) pentru sfrit de fiier. FILE* freopen(const char* filename, const char* mode, FILE* fp); se nchide fiierul fp, se deschide fiierul cu numele filename n modul mode i acesta se asociaz la fp; se ntoarce fp sau NULL n caz de eroare. int fflush(FILE* fp); Aceast funcie se utilizeaz pentru fiierele deschise pentru scriere i are ca efect scrierea n fiier a datelor din bufferul asociat acestuia, care nca nu au fost puse n fiier.

Citirea i scrierea n/din fiiere


Citirea/scrierea n fiiere se poate face n doua moduri (n functie de tipul fiierului): n mod text sau n mod binar. Principalele diferene dintre cele doua moduri sunt: n modul text, la sfarsitul fiierului se pune un caracter suplimentar, care indica sfritul de fiier. n DOS i Windows se utilizeaza caracterul cu codul ASCII 26 (Ctrl-Z), iar n Unix se utilizeaz caracterul cu codul ASCII 4. Dac citim un fiier n mod text, citirea se va opri la intlnirea acestui caracter, chiar dac mai exist i alte caractere dup el. n modul binar nu exist caracter de sfrit de fiier (mai precis, caracterul cu codul 26, respectiv 4, este tratat la fel ca i celelalte caractere). n DOS i Windows, n modul text, sfritul de linie este reprezentat prin dou caractere, CR (Carriage Return, cod ASCII 13) i LF (Line Feed, cod ASCII 10). Atunci cnd n modul text scriem un caracter n (LF) n fiier, acesta va fi convertit ntr -o secvent de 2 caractere CR i LF. Cnd citim n mod text dintr-un fiier, secvena CR, LF este convertit ntr-un n (LF). n Unix, sfritul de linie este reprezentat doar prin caracterul LF. n mod binar, att n DOS -Windows ct i n Unix, sfritul de linie este reprezentat doar prin caracterul LF. Modul binar se utilizeaz de obicei pentru a scrie n fiier datele exact aa cum sunt reprezentate n memorie (cu functiile fread, fwrite) de exemplu pentru un numr intreg se va scrie reprezentarea intern a acestuia, pe 2 sau pe 4 octeti. Modul text este utilizat mai ales pentru scrierea cu format (cu funciile fprintf, fscanf) n cazul acesta pentru un numr ntreg se vor scrie caracterele ASCII utilizate pentru a reprezenta cifrele acestuia (adic un ir de caractere cum ar fi 1 sau 542).

Citire/scriere cu format
int fprintf(FILE* fp, const char *format, ...); int fscanf(FILE* fp, const char *format, ...); Funciile sunt utilizate pentru citire/scriere n mod text i sunt asemntoare cu printf/scanf (diferena fiind c trebuie dat pointerul la fiier ca prim parametru).

Citire/scriere la nivel de caracter


int fgetc(FILE* fp); // ntoarce urmtorul caracter din

fiier, EOF la sfrit de fiier char *fgets(char* s, int n, FILE* fp); // ntoarce urmtoarele n caractere de la pointer sau pna la sfritul de linie int fputc(int c, FILE* fp); //pune caracterul c in fiier int ungetc(int c, FILE* fp); // pune c n bufferul asociat lui fp (c va fi urmtorul caracter citit din fp)

Citire/scriere fr conversie
size_t fread(void* ptr, size_t size, size_t nrec, FILE* fp); size_t fwrite(const void* ptr, size_t size, size_t nrec, FILE* fp); Cu aceste funcii lucrm cand deschidem fiierul n mod binar; citirea/scrierea se face fr nici un fel de conversie sau interpretare. Se lucreaz cu nregistrri, adic zone compacte de memorie: funcia fread citete nrec nregistrri ncepnd de la poziia curent din fiierul fp, o nregistrare avnd dimensiunea size. Acestea sunt depuse n tabloul ptr. nregistrrile pot fi asociate cu structurile din C adic n mod uzual, tabloul ptr este un tablou de structuri (dar n loc de structuri putem ave a i tipuri simple de date).

Invata Limbajul de Programare C Partea 12


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Limbajul C Parametrii liniei de comand


Pentru a controla execuia unui program, de multe ori este de dorit furnizarea datelor de lucru naintea lansrii n execuie a programului, acesta urmnd s se execute apoi fr intervenia utilizatorului (aa numitul batch mode). Acest lucru se realizeaz prin intermediul parametrilor liniei de comand. (Un exemplu cunoscut este lansarea compilatorului gcc n linia de comand cu diverse argumente, care i spun ce i cum sa compileze.) Din punct de vedere al utilizatorului, parametrii liniei de comand sunt simple argumente care se adaug dup numele unui program, n linia de comand, la rularea sa. Elementele acestei liste de argumente sunt iruri de caractere separate de spaii. Argumentele care conin spaii pot fi confinate ntr -un singur argument prin inchiderea acestuia ntre ghilimele. Shell-ul este cel care se ocup de parsarea liniei de comand i de crearea listei de argumente.

Exemplu de apelare a unui program cu argumente n linia de comand: gcc -Wall -I/usr/include/sys -DDEBUG -o "My Shell" myshell.c n acest caz, argumentele liniei de comand sunt n acest caz: gcc -Wall -I/usr/include/sys -DDEBUG -o My Shell myshell.c

Din punct de vedere al programatorului, parametrii liniei de comand sunt accesibili prin utilizarea parametrilor funciei main(). Astfel, cnd se dorete folosirea argumentelor liniei de comand, funcia main() se va defini astfel: int main(int argc, char *argv[]) Astfel, funcia main() primete, n mod formal, doi parametri, un ntreg i un vector de iruri de caractere. Numele celor dou variabile nu e obligatoriu s fie argc i argv, dar tipul lor, da. Semnificaia lor este urmtoarea: int argc (argument count) reprezint numrul de parametrii ai liniei de comand. Dup cum se vede din exemplul anterior, exist cel puin un parametru, acesta fiind chiar numele programului (numele care a fost folosit pentru a lansa n execuie programul i care poate fi diferit de numele executabilului de exemplu prin crearea unui symlink, n Linux). char *argv[] (arguments value) reprezint un vector de iruri de caractere, avnd argc elemente (indexate de la 0 la argc 1). ntotdeauna argv[0] conine numele programului. Parametrii liniei de comand se pot accesa prin intermediul vectorului argv i pot fi prelucrai cu funciile standard de prelucrare a irurilor de caractere.

Funcii cu numr variabil de parametri

Exemplele discutate pana acum primeau ca parametrii un numr prestabilit de parametrii, de tipuri bine precizate. Acest lucru se datoreaz faptului c limbajul C este un limbaj strong-typed. n cele mai multe cazuri acesta este un aspect pozitiv ntruct compilatorul va sesiza de la compilare situaiile n care se ncearc pasarea unui numr eronat de parametrii sau a unor parametrii de tipuri necorespunztoare. Limbajul C permite totui i declararea i folosirea funciilor cu un numr (i eventual tip) variabil de parametri. Astfel, numrul i tipul tuturor parametrilor va fi cunoscut doar la rulare, biblioteca standard Cpunnd la dispoziie o serie de definiii de tipuri i macro definiii care permit parcurgerea listei de parametri a unei funcii cu numr variabil de parametri. Exemplul cel mai comun de astfel de funcii sunt funciile din familia printf(), scanf().

Limbajul C Definirea funciilor cu numr variabil de parametri


Prototipul funciilor cu numr variabil de parametrii arat n felul urmtor: tip_rezultat nume_funcie(list_parametrii_fixai, ...); Notaia , comunic compilatorului faptul c funcia poate primi un numr arbitrar de parametrii ncepnd cu poziia n care apare. De exemplu, prototipul funciei fprintf() arat n felul urmtor: void fprintf(FILE*, const char*, ...); Astfel, funcia fprintf() trebuie s primeasc un pointer la o structur FILE i un string de format i eventual mai poate primi 0 sau mai multe argumente. Modul n care ele vor fi interpretate fiind determinat n cazul de fa de coninutul variabilei de tip const char* (acesta este motivul pentru care pot aprea erori la rulare n condiiile n care ncercm s tiprim un numr ntreg cu %s ).

Implementarea funciilor cu numr variabil de parametri


Pentru prelucrarea listei de parametrii variabili este necesar includerea fiierului antet stdarg.h. Acesta conine declaraii pentru tipul de date variable argument list (va_list) i o serie de macrodefiniii pentru manipularea unei astfel de liste. n continuare este detaliat modul de lucru cu liste variabile de parametri. 1. Declararea unei variabile de tip va_list (denumit de obicei args, arguments, params) 2. Iniializarea variabilei de tip va_list cu lista de parametri cu care a fost apelat funcia se realizeaz cu macrodefiniia va_start(arg_list, last_argument) unde: arg_list reprezint variabila de tip va_list prin intermediul creia vom accesa lista de parametri a funciei. last_argument reprezint numele ultimei variabile fixate din lista de parametri a funciei (n exemplul urmtor, aceasta este i prima variabil, numit first). 3. Accesarea variabilelor din lista de parametri se realizeaz cu macro-definiia va_arg(arg_list, type), unde arg_list are aceeai semnificaie ca mai sus. type este un nume de tip i reprezint tipul variabilei care va fi citit. La fiecare apel se avanseaz n list. n cazul n care se dorete ntoarcerea n list, aceasta trebuie reiniializat iar elementul dorit este accesat prin apeluri succesive de va_arg() 4. Eliberarea memoriei folosite de lista de parametri se realizeaz prin intermediul macrodefiniieiva_end(arg_list), unde arg_list are aceeai semnificaie ca mai sus. Exemplu: Afiarea unui numr variabil de numere naturale. Lista este terminat cu un numr negativ. #include <stdio.h> #include <stdarg.h> void list_ints(int first, ...); int main() { list_ints(-1); list_ints(128, 512, 768, 4096, -1); list_ints('b', 0xC0FFEE, 10000, 200, -2); /* apel corect deoarece castul la int este valid ;) */ list_ints(1, -1); return 0;

} void list_ints(int first, ...) { /* tratam cazul special cand nu avem nici un numar (in afara de delimitator) */ if (first < 0) { printf("No numbers present (besides terminator)\n"); return; } /*lista de parametri*/ va_list args; int current = first; /* initializam lista de parametri */ va_start(args, first); printf("These are the numbers (excluding terminator):\n"); do { printf("%d ", current); } /* parcurgem lista de parametri pana ce intalnim un numar negativ */ while ((current = va_arg(args, int)) >= 0); printf("\n"); /* curatam lista de parametrii */ va_end(args); }

Invata Limbajul de Programare C Partea 13


Bine ati venit pe ItAssistant. Aceasta noua serie de tutoriale isi propune sa va familiarizeze cu limbajul de programare C si conceptele Programarii Structurata.

Preprocesorul C
Preprocesorul este componenta din cadrul compilatorului C care realizeaz preprocesarea. n urma acestui pas, toate instruciunile de preprocesare sunt nlocuite (substituite), pentru a genera cod C pur. Preprocesarea este o prelucrare exclusiv textual a fiierului surs. n acest pas nu se fac nici un fel de verificri sintactice sau semantice asupra codului surs, ci doar sunt efectuate substituiile din text. Astfel, preprocesorul va prelucra i fiiere fr nici un sens n C. Spre exemplu, fiind considerat fiierul rubbish.c cu urmtorul coninut: #define EA Ana #define si C #ifdef CIFRE #define CINCI 5 #define DOUA 2 EA are mere. Mara are DOUA pere shi CINCI cirese.

#endif Vasilica vrea sa cante o melodie in si bemol.

La rularea comenzii gcc -E -DCIFRE rubbish.c se va obine urmtoare ieire (se cere compilatorului s execute doar pasul de preprocesare ( -E), definind n acelai timp i simbolul CIFRE (-DCIFRE) : # 1 "rubbish.c" # 1 "<built-in>" # 1 "<command line>" # 1 "rubbish.c" Ana are mere. Mara are 2 pere shi 5 cirese. Vasilica vrea sa cante o melodie in C bemol.

Cele mai importante instruciuni de preprocesare sunt prezentate n continuare.

Incluziune
Probabil cea mai des folosit instruciune de preprocesare este cea de incluziune, de forma #include <nume_fiier>

sau #include "nume_fisier" care are ca rezultat nlocuirea sa cu coninutul fiierului specificat de nume_fiier. Diferena dintre cele dou versiuni este c cea cu paranteze unghiulare caut nume_fiier doar n directorul standard de fiiere antet (numit deobicei include), iar cea cu ghilimele caut att n directorul include ct i n directorul curent.

C Definirea de simboluri
Definirea de simboluri este cel mai des folosit n conjuncie cu instruciunile de procesare condiionat, fiind folosit pentru activarea i dezactivarea unor segmente de cod n funcie de prezena unor simboluri. Definirea unui simbol se face n cod cu instruciunea #define SIMBOL sau se poate realiza i la compilare, prin folosirea flagului -D al compilatorului (dup cum am vzut n exemplul precedent). Un simbol poate fi de asemenea ters folosind instruciunea #undef SIMBOL n cazul n care nu se mai dorete prezena simbolului de preprocesor ulterior definir ii sale.

C Definirea de macro-uri
Instruciunile de preprocesare mai pot fi folosite i pentru definirea de constante simbolice i macroinstruciuni. De exemplu #define CONSTANTA valoare va duce la nlocuirea peste tot n cadrul codului surs a irului CONSTANTA cu irul valoare. nlocuirea nu se face totui n interiorul irurilor de caractere. O macroinstruciune este similar unei constante simbolice, ca definire, dar accept parametrii. Este folosit n program n mod asemntor unei funcii, dar la compilare, ea este nlocuit n mod textual cu corpul ei. n plus, nu se face nici un fel de verificare a tipurilor. Spre exemplu: #define MAX(a, b) a > b ? a : b va returna maximul dintre a i b, iar #define DUBLU(a) 2*a

va returna dublul lui a.

Atenie! Deoarece preprocesarea este o prelucrare textual a codului surs, n cazul exemplului de mai sus, macroinstruciunea n forma prezentat nu va calcula ntotdeauna dublul unui numr. Astfel, la un apel de forma: DUBLU(a + 3)

n pasul de preprocesare se va genera expresia 2*a+3 care bineneles c nu realizeaz funcia dorit. Pentru a evita astfel de probleme, este bine ca ntotdeauna n corpul unui macro, numele parametrilor s fie nchise ntre paranteze (ca de exemplu:) #define SQUARE(a) (a)*(a)

C Instruciuni de compilare condiionat


Instruciunile de compilare condiionat sunt folosite pentru a ascunde fragmente de cod n funcie de anumite condiii. Formatul este urmtorul: #if conditie .... #else .... #endif unde conditie este este o expresie constant ntreag. Pentru realizarea de expresii cu mai multe opiuni se poate folosi i forma #elif: #if conditie ... #elif conditie2 ... #elif conditie3 ... #else ... #endif De obicei condiia testeaz existena unui simbol. Scenariile tipice de folosire sunt:

dezactivarea codului de debug o dat ce problemele au fost remediate compilare condiionat n funcie de platforma de rulare prevenirea includerii multiple a fiierelor antet

n aceste cazuri se folosete forma #ifdef SIMBOL

sau #ifndef SIMBOL care testeaz dac simbolul SIMBOL este definit, respectiv nu este definit. Prevenirea includerii multiple a fiierelor antet se realizeaz astfel: #ifndef _NUME_FISIER_ANTET_ #define _NUME_FISIER_ANTET_ /* corpul fisierului antet */ /* prototipuri de functii, declaratii de tipuri si de constante */ #endif Astfel, la prima includere a fiierului antet, simbolul _NUME_FISIER_ANTET_ nu este definit. Preprocesorul execut ramura #ifndef n care este definit simbolul _NUME_FISIER_ANTET_ i care conine i corpul coninutul util al fiierului antet. La urmtoarele includeri ale fiierului antet simbolul _NUME_FISIER_ANTET_ va fi definit iar preprocesorulva sri direct la sfritul fiierului antet, dup #endif.

Alte instruciuni
#pragma expresie Sunt folosite pentru a controla din codul surs comportamentul compilatorului (modul n care genereaz cod, alinierea structurilor, etc.) iar formatul lor difer de la compilator la compilator. Pentru a determina ce opiuni #pragma avei la dispoziie consultai manualul compilatorului. #error MESSAGE La ntlnirea acestei instruciuni de preprocesare compilatorul va raporta o eroare, avnd ca text explicativ mesajul MESSAGE.

#line NUMBER FILENAME Aceast instruciune de preprocesare modific numrul liniei curente n valoarea specificat de NUMBER. n cazul n care este prezent i parametru opional FILENAME este modificat i numele fiierului surs curent. Astfel, mesajele de eroare i avertismentele produse de compilator vor folosi numere de linie (i eventual nume de fiiere) inexistente, imaginare, conform acestei instruciuni.