137309474 Invata Limbajul de Programare C

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 către Dennis Ritchie în perioada 1968-1973 și a fost dezvoltat în strânsă 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 aplicaţii de sistem la aplicaţii generale. Deşi în prezent, pentru dezvoltarea aplicaţiilor 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 aplicațiilor 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 (GNU‟s Not Unix) de către Free Software Foundation. Deși GCC se traducea iniţial prin GNU C Compiler, acesta a devenit între timp un compilator multifrontend, multi-backend, având 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 opțiuni, în funcție de rezultatul care se dorește (specificarea de căi suplimentare de căutare a bibliotecilor/fișierelor antet, link -area unor biblioteci specifice, opțiuni de optimizare, controlul stagiilor de compilare, al avertisementelor, etc.). Pentru exemplificare vom considera următorul program foarte simplu:

description

Manual de programare in limbajul C++, in limba Romana.

Transcript of 137309474 Invata Limbajul de Programare C

Page 1: 137309474 Invata Limbajul de Programare C

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 către Dennis Ritchie în perioada 1968-1973 și a fost dezvoltat în strânsă 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 aplicaţii de sistem la aplicaţii generale.

Deşi în prezent, pentru dezvoltarea aplicaţiilor 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 aplicațiilor 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 (GNU‟s Not Unix) de

către Free Software Foundation. Deși GCC se traducea iniţial prin GNU C Compiler, acesta a devenit

între timp un compilator multifrontend, multi-backend, având 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 opțiuni, în funcție de rezultatul

care se dorește (specificarea de căi suplimentare de căutare a bibliotecilor/fișierelor antet, link-area unor

biblioteci specifice, opțiuni de optimizare, controlul stagiilor de compilare, al avertisementelor, etc.).

Pentru exemplificare vom considera următorul program foarte simplu:

Page 2: 137309474 Invata Limbajul de Programare C

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

presupunând că fișierul sursă se numește hello.c (este esențial ca extensia să fie c și nu C (C mare – așa

cum este cazul fișierelor produse de Borland C++ 3.1), deoarece aceasta din urmă este interpretată de

către compilator ca fiind extenesie de fișier C++).

În funcţie de sistemul de operare folosit, pentru executarea programului astfel obținut 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 funcții) fișierului

sursăhello.c, generându-se un fișier executabil, al cărui nume implicit este a.out în cazul sistemelor Linux

și a.exeîn cazul sistemelor Windows. Pentru un control mai fin al comportării compilatorului, sunt

prezentate în tabelul următor cele mai folosite opţiuni (pentru lista completă studiaţi pagina de manual

pentru GCC – man gcc):

Opțiune Efect

-o nume_fișier Numele fișierului de ieşire va fi nume_fişier. În cazul în care această opțiune nu este setată, se va folosi numele implicit (pentru fișiere executabile: a.out – pentru Linux și a.exe – pentru Windows)

-Icale_catre_fisiere_antet

Caută fișiere antet și în calea specificată

-L cale_catre_biblioteci Caută fișiere bibliotecă și în calea specificată

Page 3: 137309474 Invata Limbajul de Programare C

-l nume_biblioteca

Link-editează librăria nume_biblioteca. Atenție!!! nume_bibliotecă nu este întotdeauna același cu numele fișierului antet prin care se include această bibliotecă. Spre exemplu, pentru includerea bibliotecii de funcții matematice, fișierul antet este math.h, iar biblioteca este m

-W tip_warning

Afișează 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

-c Compilează și asamblează, dar nu link-editează. Rezultă fișiere obiect, cu extensia .o

-S Se opreste după faza de compilare, fară să asambleze. Rezultă cod assembler in fișiere cu extensia .s

Spre exemplu:

gcc -o exemplu exemplu.c -lm -Wall

are ca efect compilarea și link-editarea fişierului exemplu.c, cu includerea bibliotecii matematice, afişând

toate avertismentele. Fişierul de ieşire se va numi exemplu.exe (extensia e determinată de sistemul de

operare, dar şi de conţinutul fişierului sursă).

Utilitarul Make

Utilitarul make determină automat care sunt părțile unui proiect care trebuie recompilate ca urmare a

operării unor modificări și declanşează comenzile necesare pentru recompilarea lor. Pentru a putea utiliza

make, este necesar un fișier de tip makefile numit de obicei Makefile (sau makefile) care descrie relațiile

de dependenţă între diferitele fișiere din care se compune programul şi care specifică regulile de

actualizare pentru fiecare fişier în parte.

În mod normal, într-un program, fişierul executabil este actualizat (recompilat) pe baza fișierelor-obiect,

care la rândul lor sunt obținute prin compilarea fișierelor sursă. Totuși, acest utilitar poate fi folosit pentru

orice proiect care conţine dependenţe şi cu orice compilator/utilitar care poate rula în linia de comandă.

Odată creat fișierul makefile, de fiecare dată când apare vreo modificare în fișierele sursă, este suficient

să rulăm utilitarul make pentru ca toate recompilările necesare să fie efectuate.

Programul make utilizează fișierul Makefile ca bază de date şi pe baza timpilor ultimei modificări a

Page 4: 137309474 Invata Limbajul de Programare C

fișierelor din Makefile decide care sunt fișierele care trebuie actualizate. Pentru fiecare din aceste fișiere,

sunt executate comenzile precizate in Makefile.

În continuare prezentăm un exemplu simplu de makefile.

# Declarațiile de variabile

CC = gcc

CCFLAGS = -Wall -lm

SRC = radical.c

PROGRAM = radical

# Regulă de compilare

all:

$(CC) -o $(PROGRAM) $(SRC) $(CCFLAGS)

# Regulile de "curațenie" (se folosesc pentru ștergerea fișierelor intermediare si/sau

rezultate)

.PHONY : clean

clean :

rm -f $(PROGRAM) core *~

Pachetul MinGW

Compilatorul GCC este disponibil pentru Windows în pachetul MinGW. Pentru instalare folosiţi fişierul

.exe. Antenţie! Installer-ul are nevoie de acces la Internet pentru a putea descărca diversele componente.

Tot de la această adresă se poate descărca si pachetul MSYS care conține 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 funcționarea corectă a pachetului MinGW/MSYS,

este însă necesară adăugarea 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

adaugați la sfarșitul șirului de la Variable value calea către directorul care conține binarele

compilatorului GCC (vezi imaginea).

Page 5: 137309474 Invata Limbajul de Programare C

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 menționa:

Linux: vi(m), pico, joe, nano, emacs, mcedit – cu interfața în mod text si Kate, KWrite, GEdit,

Scribes – cu interfață grafică.

Windows: Crimson Editor, Notepad++, Textpad, etc.

Java: jEdit

Page 6: 137309474 Invata Limbajul de Programare C

Deși lista nu este completă, editoarele specificate au pe langă facilitățile 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.

Interacțiunea 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 afișate pe ecran (alte variante sunt

folosirea fișierelor sau preluarea datelor de la un hardware periferic).

Programul dat ca exemplu mai sus folosește funcția de afișare printf. Această funcție realizează transferul

și conversia de reprezentare a valorii întregi / reale in șir de caractere sub controlul unui format (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

Semnificația câmpurilor din descriptor este descrisă în tabelul următor:

Câmp Descriere

- Indică o aliniere la stânga în câmpul de lungime Lung (implicit alinierea se face la dreapta).

Lung

Dacă expresia conține mai puțin de Lung caractere, ea este precedată de spații sau zerouri, dacă Lung începe printr-un zero. Dacă expresia conține mai mult de Lung caractere, câmpul de afișare este extins. În absența lui Lung, expresia va fi afișată cu atâtea caractere câte conține.

frac Indică numărul de cifre după virgulă (precizia) cu care se face afișarea.

l Marchează un long int, în timp ce pentru reali l determină afișarea unei valori double.

h Marchează un short int

L Precede unul din descriptorii f,e,E,g,G pentru afișarea unei valori de tip long double.

Page 7: 137309474 Invata Limbajul de Programare C

Tabelul următor prezintă descriptorii și conversiile care au loc:

Descriptor Descriere

d Întreg cu semn în baza 10

u Întreg fără semn în baza 10

o Întreg fără semn în baza 8

x sau X Întreg fără 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 notație exponențială. Se folosește e mic, respectiv E mare

g La fel ca și e, E și f dar afișarea se face cu număr minim de cifre zecimale

Citirea cu format se realizează cu ajutorul funcției scanf() astfel:

scanf("format", &var_1, &var_2, ..., &var_n)

care citește valorile de la intrarea standard în formatul precizat și le depune în variabilele var_i, returnând

numarul de valori citite.

Atenție! Funcția scanf primește adresele variabilelor în care are loc citirea. Pentru tipuri fundamentale

și/sau structuri, aceasta se obține folosind operatorul de adresă – &.

Sintaxa descriptorului de format în acest caz este:

% [*] [ Lung ] [ l ] descriptor

Semnificația campurilor din descriptor este descrisă în tabelul următor:

Câmp Descriere

* Indică faptul că valoarea citită nu se atribuie unei variabile. (valoarea citită poate fi folosită pentru specificarea lungimii câmpului)

Lung Indică lungimea câmpului din care se face citirea. În cazul în care e nespecificat, citirea are loc până la primul caracter care nu face parte din număr, sau până la „n‟ (linie nouă/enter)

d Întreg în baza 10

o Întreg în baza 8

x Întreg în baza 16

f Real

c Caracter

Page 8: 137309474 Invata Limbajul de Programare C

s Șir de caractere

L Indică un întreg long sau un real double

h Indică un întreg short

Pentru scrierea și citirea unui singur caracter, biblioteca stdio.h mai definește și

funcțiile 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, până la acționarea tastei ENTER,

moment în care în zona tampon se introduce caracterul rând nou. Fiecare apel getchar() preia

următorul caracter din zona tampon.

putchar() afișează caracterul având codul ASCII egal cu valoarea expresiei parametru.

Nota: getchar() și putchar() nu sunt de fapt funcții, ci niște macroinstrucțiuni definite în stdio.h

Pentru citirea și scrierea unei linii biblioteca stdio.h definește funcțiile gets() și puts():

gets(zona) - introduce de la terminalul standard un șir de caractere terminat prin acționarea

tastei ENTER. Funcția are ca parametru adresa zonei de memorie în care se introduc caracterele

citite. Funcția returnează adresa de început a zonei de memorie; la întalnirea sfarșitului de fișier

(CTRL+Z) funcția returnează NULL.

puts(zona) – afișează la terminalul standard șirul de caractere din zona dată ca parametru, până

la caracterul null (), care va fi înlocuit prin caracterul linie nouă. Funcția returnează codul ultimului

caracter din șirul de caractere afișate 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.

Noţiuni teoretice – Tipuri fundamentale de date

Tipurile de date reprezintă tipul de informație care poate fi stocat într-o variabilă. Un tip de data definește

atât gama de valori pe care o poate lua o variabilă de un anume tip cât și operațiile care se pot efectua

Page 9: 137309474 Invata Limbajul de Programare C

asupra ei. În continuare sunt prezentate tipurile fundamentale ale limbajului C, împreună cu o scurtă

descriere a acestora:

char – reprezentat printr-un număr pe 8 biți (un byte), stochează un caracter, definit în C printr-un

număr î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 biți

(deci 4 bytes). În acest caz, poate memora numere între –2.147.483.648 și 2.147.483.647

float - reprezintă un număr real stocat în virgulă mobilă, simplă precizie (7 cifre), în gama de

valori 3.4E +/- 38(reprezentat pe 4 bytes)

double - reprezinta un număr 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 adăuga un număr de calificatori, după cum urmează:

short - aplicabil doar pentru int, rezultând, de obicei, un întreg pe 2 octeți.

long – aplicabil doar pentru int. Rezultă un întreg pe 32 de biți (4 octeți), 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 atenție a documentației compilatorului. Compilatorul GCC pune în acest sens la dispoziția

programatorului, următoarele 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 biți int_8, int_16, int_32, int_64 – pentru întregi cu

semn reprezentați pe 8, 16, 32 respectiv 64 de biți.

Determinarea corectă a tipurilor de date care vor fi folosite este esențială pentru securitatea și buna

funcționare a aplicațiilor pe care le scrieți. În cazul în care valoarea conținută de o variabilă depașește

limitele impuse de tipul de date folosit, se produce așa-numit-ul over-flow care poate cauza erori aparent

inexplicabile. (Ca o anecdotă, în fiecare an (până acum trei sau patru ani), Bill Gates primea de la FISC o

scrisoare prin care era somat să iși platească taxele, deoarece apărea in evidențele lor ca având datorii

Page 10: 137309474 Invata Limbajul de Programare C

însemnate. Asta deoarece valoarea averii lui (mult peste 4.000.000.000$) producea un overflow în softul

folosit de către FISC. În final situația a fost soluționată, introducând un câmp 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 cetațeni ai SUA.) )

Operatori

Operatorii limbajului C pot fi unari, binari sau ternari, fiecare având o precedenţă şi o asociativitate bine

definite. Tabelul următor sintetizează operatorii limbajului C. Operatorii sunt prezentaţi în ordine

descrescătoare a priorităţii.

Precedenţă Operator Descriere Asociativitate

1

[] Indexare stanga-dreapta

. şi -> Selecţie membru (prin structură, respectiv pointer) stânga-dreapta

++ şi – Postincrementare/postdecrementare stânga-dreapta

2

! Negare logică dreapta-stânga

~ Complement faţă de 1 pe biţi dreapta-stânga

++ şi – Preincrementare/predecrementare dreapta-stânga

+ şi - + şi – unari dreapta-stânga

* Dereferenţiere dreapta-stânga

& Operator adresă dreapta-stânga

(tip) Conversie de tip dreapta-stânga

sizeof() Mărimea în octeţi dreapta-stânga

3

* Înmulţire stânga-dreapta

/ Împărţire stânga-dreapta

% Restul împărţirii stânga-dreapta

4 + şi - Adunare/scădere stânga-dreapta

5 << si >> Deplasare stânga/dreapta a biţilor stânga-dreapta

6

< Mai mic stânga-dreapta

<= Mai mic sau egal stânga-dreapta

> Mai mare stânga-dreapta

>= Mai mare sau egal stânga-dreapta

7 == Egal stânga-dreapta

!= Diferit stânga-dreapta

8 & ŞI pe biţi stanga-dreapta

9 ^ SAU-EXCLUSIV pe biţi stânga-dreapta

10 | SAU pe biţi stânga-dreapta

11 && ŞI logic stânga-dreapta

Page 11: 137309474 Invata Limbajul de Programare C

12 | | SAU logic stânga-dreapta

13 :? Operator condiţional dreapta-stânga

14

= Atribuire dreapta-stânga

+= şi -= Atribuire cu adunare/scădere dreapta-stânga

*= şi /= Atribuire cu multiplicare/împărţire dreapta-stânga

%= Atribuire cu modulo dreapta-stânga

&= si |= Atribuire cu ŞI/SAU dreapta-stânga

^= Atribuire cu SAU-EXCLUSIV dreapta-stânga

<<= şi >>= Atribuire cu deplasare de biţi dreapta-stânga

15 , Operator secvenţa stânga-dreapta

Trebuie avută în vedere precedenţa operatorilor pentru obţinerea 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 găsit. De exemplu, următorul 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 biți între falgs și 1.

Pentru a obţine rezultatul dorit se vor folosi parantezele:

if ( (flags & MASK) == 0)

{ ... }

acum mai întâi se va face ȘI pe biți î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 funcțiile: calculul unei valori, desemnarea unui obiect (variabilă) sau funcţii sau

generarea unui efect lateral.

O altă greşeală frecventă este utilizarea greşită a operatorilor = şi ==. Primul reprezintă atribuire, al doilea

comparaţie de egalitate. Apar deseori erori ca:

if ( a = 2 )

{ ... }

Page 12: 137309474 Invata Limbajul de Programare C

Compilatorul consideră condiţia corectă, deoarece este o expresie validă în limbajul C care face atribuire,

care se evaluează mereu la o valoare nenulă.

Măsurarea timpului de execuție a programelor

Uneori este utilă măsurarea timpului de execuție a unei anumite parți a unui program sau chiar a

întregului program. În acest scop putem folosi funcția clock() din fișierul antet time.h. Această funcție

întoarce o aproximare a numărului de cicluri de ceas trecute de la pornirea programului. Pentru a obţine o

valoare în secunde, împărțim această valoare la constanta CLOCKS_PER_SEC. Funcţia are antetul:

clock_t clock( void );

Următorul fragment este un exemplu de utilizare a acestei funcții:

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

Următorul fragment este un exemplu de funcție 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) {}

}

Page 13: 137309474 Invata Limbajul de Programare C

Funcții matematice

Fișierul antet math.h conține un set de funcții matematice des utilizate în programe. Câteva dintre

acestea sunt:

Antet Descriere

double asin( double arg );

double acos( double arg ); Calculează arcsinusul/arccosinusul valorii arg; rezultatul este măsurat în radiani

double atan( double arg );

double atan2( double y,

double x ); Calculează arctangenta valorii arg, respectiv a fracției y/x

double floor( double num

); Întoarce cel mai mare întreg mai mic sau egal cu num (partea întreagă inferioară)

double ceil( double num ); Întoarce cel mai mic întreg mai mare sau egal cu num (partea întreagă superioară)

double sin( double arg );

double cos( double arg );

double tan( double arg );

Calculează sinusul/cosinusul/tangenta parametrului arg, considerată în radiani

double sinh( double arg );

double cosh( double arg );

double tanh( double arg );

Calculează sinusul/cosinusul/tangenta hiperbolică a parametrului arg

double exp( double arg ); Întoarce valoarea earg

double pow( double base,

double exp ); Întoarce valoarea basearg

double log( double num ); Calculează logaritmul natural (de bază e) al valorii arg

double log10( double num

); Calculează logaritmul în baza 10 al parametrului

double sqrt( double num ); Calculează radăcina pătrată a parametrului

double fmod( double x,

double y ); Întoarce restul împarțirii lui x la y

double fabs( double arg ); Întoarce valoarea absolută a lui arg

Generarea numerelor aleatoare

Valorile aleatoare (a căror valoare nu poate fi prezisă dinaintea rulării programului şi care diferă între 2

rulări) pot fi generate în C cu funcţia:

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

Page 14: 137309474 Invata Limbajul de Programare C

Numerele generate nu sunt cu adevărat aleatoare, ci pseudo-aleatoare; aceste numere sunt uniform

distribuite pe orice interval, dar șirul de numere aleatoare generate este dependent de prima valoare,

care trebuie aleasă de utilizator sau programator. Această valoare, numită seed, se selectează cu

funcţia:

void srand( unsigned int seed );

Cea mai întalnită utilizare a funcției de inițializare presupune setarea unui seed egal cu valoarea ceasului

sistemului de la pornirea programului, prin instrucțiunea:

srand( (unsigned) time( NULL ) );

d = rand(); //generează valori random.

Funcția time() din fişierul antet time.h întoarce numărul de secunde trecute de la ora 00:00, din data de 1

ianuarie 1970. Funcția primește ş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

if…else este cea mai simplă instrucţiune condiţională. Poate fi folosită în mai multe forme:

if( condiţie )

{

// instrucţiuni

//...

}

if( condiţie )

{

//instrucţiuni

//...

}

else

{

Page 15: 137309474 Invata Limbajul de Programare C

//alte instrucţiuni

//...

}

if( condiţie1 )

{

...

}

else if( condiţie2 )

{

...

}

...

else if( condiţieN )

{

...

}

Instrucţiunea evaluează expresia condiţie şi execută instrucţiunile dintre acolade doar dacă rezultatul

este nenul. În varianta cu else, pentru rezultat nul este executat blocul de instrucţiuni aflat după else.

În al treilea caz, sunt evaluate pe rând condiţiile şi este executat blocul corespunzător primei condiţii

adevărate. 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 instrucţiune menită să simplifice structurile condiţionale cu mai multe condiţii.

switch( expresie )

{

case constanta1:

//instrucţiuni1

case constanta2:

//instrucţiuni2

...

default:

//instrucţiuni

}

Page 16: 137309474 Invata Limbajul de Programare C

Valoarea expresie este evaluată la un tip intreg, apoi această valoare este comparată cu fiecare

constantă; este rulat blocul de instrucţiuni al valorii găsite. În caz ca numărul nu este egal cu nici una

dintre constante, este executat blocul aflat după default.

int main()

{

char c;

printf("Alegeţi o opţiune:\n\t[a] afişare\n\t[s] ştergere\n\t[e] ieşire\n");

scanf("%c", &c);

printf("Aţi ales: ");

switch(c)

{

case 'a':

printf("afişare");

break;

case 's':

printf("ştergere");

break;

case 'e':

printf("ieşire");

break;

default:

printf("O opţiune inexistentă");

break;

}

return 0;

}

int main( )

{

int n, n2;

printf( "Introduceţi o valoare între 0 şi 5:" );

scanf( "%d", &n );

n2 = 1;

switch( n )

{

case 5:

n2 *= 2;

/* fără break, continuă la următoarea instrucţiune */

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;

Page 17: 137309474 Invata Limbajul de Programare C

default:

printf( "Valoare invalidă\n" );

}

return 0;

}

Instrucţiuni de repetiţie

while

while execută un bloc de instrucţiuni atâta timp cât o anumită condiţie este adevărată. Forma generală a

unui ciclu while este:

while (expresie)

{

//instrucţiuni

}

Câtă vreme expresie are o valoare nenulă, instrucţiunile 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 funcţie de valoarea la care se evaluează expresia.

do … while

do … while este o instrucţiune repetitivă similara cu cea precedentă, singura diferenţa fiind că expresia

este evaluată după executarea instrucţiunilor, nu înainte. Astfel, blocul va fi executat cel puţin o dată.

do

{

//instrucţiuni

} while (expresie);

for

for reprezintă o formă mai simplă de a scrie un while însotit de o expresie iniţiala şi de o expresie de

incrementare. Forma sa este:

for( expresie1 ; expresie2 ; expresie3 )

{

//instrucţiuni

}

Page 18: 137309474 Invata Limbajul de Programare C

Secvenţa de cod de mai sus este echivalentă cu:

expresie1

while (expresie2)

{

instrucţiuni

expresie3

}

În cazul instrucţiunii for, oricare dintre cele 3 expresii poate lipsi. Lipsa expresiei condiţionale este

echivalentă cu o buclă infinită, cum ar fi:

for( ; ; )

{

/* instrucţiunile de aici sunt intr-o buclă infinită */

}

Exemplul următor prezintă un ciclu cu funcţionalitate identică (tipărirea primelor 10 numere naturale),

folosind cele 3 instrucţiuni 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 instrucţiunea executată de for. Pentru blocuri de o singură

instrucţiune, nu este nevoie sa folosim acoladele.

Page 19: 137309474 Invata Limbajul de Programare C

Instrucţiuni speciale

break

break, pe lângă utilizarea descrisă la instrucţiunea switch, poate fi folosită pentru a ieşi forţat dintr-o

instrucţiune de repetiţie. Secventa următoare este echivalentă cu cele de mai sus:

i = 0;

for( ; ; )

{

i++;

if( i > 10 )

break; /* ieşire forţată din bucla */

printf( “i=%d\n”, i );

}

continue

continue forţează terminarea iteraţiei curente a buclei si trecerea la iteraţia următoare. În cazul

instrucţiuniifor, acest lucru presupune executarea instrucţiunii de incrementare; apoi se evaluează

condiţia de continuare a buclei. Exemplul următor demonstrează implementarea unei bucle infinite cu

ajutorul instrucţiunii continue:

for( i = 0 ; i < 10 ; )

{

if( i == 0 )

continue;

i++;

}

return

return este instrucţiunea de terminare a funcţiei curente. Aceasta poate fi apelată in forma return; în cazul

funcţiilor care returnează void şi în forma return rezultat; pentru funcţiile care întorc o valoare.

goto

goto este o instrucţiune de salt a execuţiei. Instrucţiunea primeşte ca parametru o etichetă; următoarea

instrucţiune executată după goto este cea de la eticheta dată.

Page 20: 137309474 Invata Limbajul de Programare C

int main( )

{

goto et;

printf( “Asta nu apare la executie\n” );

et:

printf( “Asta apare la rulare\n” );

return 0;

}

Atenţie! În majoritatea cazurilor, utilizarea instrucţiunii goto nu este recomandată şi poate fi evitată

folosind alte instrucţiuni de control şi funcţii. Programele care folosesc această instrucţiune pentru a sări

între secvenţe îndepărtate 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ă. Funcţii în limbajul C. Dezvoltarea algoritmilor folosind funcţii

Funcţiile împart taskuri complexe în bucăţi mici mai uşor de înţeles şi de programat. Acestea pot fi

refolosite cu alte ocazii, în loc să fie rescrise de la zero. De asemenea, funcţiile sunt utile pentru a

ascunde detalii de funcţionare ale anumitor părţi ale programului, ajutând la modul de lucru al acestuia.

Utilizând funcţii, care reprezintă unitatea fundamentală de execuţie a programelor C, se obţine o divizare

logică a programelor mari şi complexe.

Împărţirea programelor în funcţii este arbitrară şi depinde de modul de gândire a celui care le creează. De

obicei, funcţiile cuprind o serie de instrucţiuni care efectuează un calcul, realizează o acţiune,

implementează un algoritm, etc. Crearea funcţiilor trebuie să se bazeze pe următoarele

principii: claritate, lizibilitate, uşurinţă în întreţinere, reutilizabilitate.

Definirea şi apelul unei funcţii în C

Caracteristicile definitorii ale unei funcţii în C sunt: numele, parametrii de apel şi valorea returnată.

Sintaxa standard de declarare a unei funcţii este:

Page 21: 137309474 Invata Limbajul de Programare C

tip_returnat nume_functie (tip_param1 [nume_param1] [, tip_param2 [nume_param2],

...]);

Această declarare poartă numele de antetul funcţiei.

Odată declarată, o funcţie trebuie definită, în sensul că trebuie expandat corpul acesteia cu instrucţiunile

pe care trebuie să le execute.

Definirea unei funcţii 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 declaraţiei unei funcţii de definiţia acesteia (codul care o implementează).

Pentru ca funcţia să poată fi folosită, este obligatorie doar declararea acesteia înainte de codul care o

apelează. Definiţia poate apărea mai departe în fişierul sursă, sau chiar într-un alt fişier sursă sau

bibliotecă.

Diferite părţi din definirea unei funcţii pot lipsi. Astfel, o funcţie minimală este:

dummy() {}

Funcţia de mai sus nu face absolut nimic, nu întoarce nici o valoare şi nu primeşte nici un argument, însă

din punct de vedere al limbajului C este perfect validă.

Tipul returnat de o funcţie poate fi orice tip standard sau definit de utilizator. Dacă nu se specifică tipul

returnat de funcţie se consideră implicit tipul int.

Orice funcţie care întoare un rezultat trebuie să conţină instrucţiunea:

return expresie;

Expresia este evaluată şi convertită la tipul de date care trebuie returnat de funcţie. Această instrucţiune

termină şi execuţia funcţiei, indiferent dacă după aceasta mai urmează sau nu alte instrucţiuni. Dacă este

Page 22: 137309474 Invata Limbajul de Programare C

cazul, se pot folosi mai multe instrucţiuni return pentru a determina mai multe puncte de ieşire din funcţie,

în raport cu evoluţia funcţiei.

Exemplu:

int min(int x, int y)

{

if(x<y)

return x;

return y;

}

Apelul unei funcţii se face specificând parametrii efectivi (parametrii care apar în declararea funcţiei 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 funcţii se face specificând 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

Funcţia va lucra cu o copie a variabilei pe care a primit-o şi orice modificare din cadrul funcţiei va opera

asupra aceste copii. La sfârşitul execuţiei funcţiei, copia va fi distrusă şi astfel se va pierde orice

modificare efectuată. Pentru a nu pierde modificările făcute se foloseşte instrucţiunea return, care poate

întoarce, la terminarea funcţiei, noua valoare a variabilei. Problema apare în cazul în care funcţia

modifică mai multe variabile şi se doreşte ca rezultatul lor să fie disponibil şi la terminarea execuţiei

funcţiei.

Page 23: 137309474 Invata Limbajul de Programare C

Transmiterea parametrilor prin referinţă

Rezolvarea problemei anterioare se face transmiţând funcţiei adresa variabilei cu care urmează să

lucreze. Astfel, funcţia nu prelucrează o copie a valorii variabilei, ci chiar valoarea aflată la adresa

furnizată. Orice parametru care se doreşte a fi prelucrat de funcţie, şi noua sa valoare utilizată chiar şi

după terminarea funcţiei, se va transmite funcţiei prin referinţă.

Exemplu de transmitere a parametrilor prin valoare:

min(x,4)

Exemplu de transmitere a parametrilor prin referinţă:

suma(&x,&y,&sum)

Funcţii recursive

O funcţie poate să apeleze la rândul ei alte funcţii. Dacă o funcţie se apelează pe sine însăşi, atunci

funcţia este recursivă. Pentru a evita un număr infinit de apeluri recursive, trebuie ca funcţia să includă în

corpul ei ocondiţie de oprire, astfel ca, la un moment dat, recurenţa să se oprească şi să se revină

succesiv din apeluri. Condiţia trebuie să fie una generică, şi să oprească recurenţa în orice situaţie.

Această condiţie se referă în general la parametrii de intrare, pentru care la un anumit moment, răspunsul

poate fi returnat direct, fără 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;

}

Page 24: 137309474 Invata Limbajul de Programare C

ntotdeauna trebuie avut grijă în lucrul cu funcţii recursive deoarece, la fiecare apel recursiv, contextul este

salvat pe stivă pentru a putea fi refăcut la revenirea din recursivitate. În acest fel, în funcţie de numărul

apelurilor recursive şi de dimensiunea contextului (variabile, descriptori de fişier, etc.) stiva se poate

umple foarte rapid, generând o eroare de tip stack overflow.

Funcţia main

Orice program C conţine cel puţin o funcţie, şi anume cea principală, numită main(). Aceasta are un

format special de definire:

int main(int argc, char **argv);

Primul parametru, argc, reprezintă numărul de argumente primite de către program la linia de comandă,

incluzând numele cu care a fost apelat programul. Al doilea parametru, argv, este un pointer către

conţinutul listei de parametri al căror număr este dat de argc.

Atunci când nu este necesară procesarea parametrilor de la linia de comandă, se poate folosi forma

prescurtată a definiţiei funcţiei main, şi anume:

int main();

În ambele cazuri, standardele impun ca main să întoarcă o valoare de tip întreg, care să reprezinte codul

execuţiei programului şi care va fi pasată înapoi sistemului de operare, la încheierea execuţiei

programului. Astfel, instrucţiunea return în funcţia main va însemna şi terminarea execuţiei 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 întrebuinţări.

Atunci când este folosit ca tip returnat de o funcţie, specifică faptul că funcţia nu întoarce nici o valoare.

Exemplu:

Page 25: 137309474 Invata Limbajul de Programare C

void print_nr(int numar)

{

printf("Numarul este %d", numar);

}

Atunci când este folosit în declaraţia unei funcţii, void semnifică faptul că funcţia nu primeşte nici un

parametru. Exemplu:

int init(void)

{

return 1;

}

Pointerii pot fi de asemenea declaraţi void, însă nu pot fi dereferenţiaţi fără o operaţie de cast explicit.

Clase de stocare. Fişiere antet vs. biblioteci

După cum se ştie, într-un fişier sursă (.c) pot fi definite un număr oarecare de funcţii. În momentul în care

programul este compilat, din fiecare fişier sursă se generează un fişier obiect (.o), care conţine codul

compilat al funcţiilor respective. Aceste funcţii pot apela la rândul lor alte funcţii, care pot fi definite în

acelaşi fişier sursă, sau în alt fişier sursă. În orice caz, compilatorul nu are nevoie să ştie care este

definiţia funcţiilor apelate, ci numai semnătura acestora (cu alte cuvinte, declaraţia lor), pentru a şti cum

să realizeze instrucţiunile de apel din fişierul obiect. Acest lucru explică de ce, pentru a putea folosi o

funcţie, trebuie declarată înaintea codului în care este folosită.

Fişierele antet conţin o colecţie de declaraţii de funcţii, grupate după funcţionalitatea pe care acestea o

oferă. Atunci când includem un fişier antet (.h) într-un fişier sursă (.c), compilatorul va cunoaşte toate

semnăturile funcţiilor de care are nevoie, şi va fi în stare să genereze codul obiect pentru fiecare fişier

sursă în parte. (NOTĂ: Astfel nu are sens includerea unui fişier .c în alt fişier .c; se vor genera două

fişiere obiect care vor conţine definiţii comune, şi astfel va apărea un conflict de nume la editarea

legăturilor).

Cu toate acestea, pentru a realiza un fişier executabil, trebuie ca fiecare funcţie să fie definită. Acest lucru

este realizat de către editorul de legături; cu alte cuvinte, fiecare funcţie folosită în program trebuie să fie

conţinută în fişierul executabil. Acesta caută în fişierele obiect ale programului definiţiile funcţiilor de care

are nevoie fiecare funcţie care le apelează, şi construieşte un singur fişier executabil care conţine toate

Page 26: 137309474 Invata Limbajul de Programare C

aceste informaţii. Bibliotecile sunt fişiere obiect speciale, al căror unic scop este să conţină definiţiile

funcţiilor oferite de către compilator, pentru a fi integrate în executabil de către editorul de legături.

Clasele de stocare intervin în acest pas al editării de legături. O clasă de stocare aplicată unei funcţii

indică dacă funcţia respectivă poate fi folosită şi de către alte fişiere obiect (adică este externă), sau

numai în cadrul fişierului obiect generat din fişierul sursă în care este definită (în acest caz funcţia este

statică). Dacă nu este specificată nici o clasă de stocare, o funcţie este implicit externă.

Cuvintele cheie extern şi static, puse în faţa definiţiei funcţiei, îi specifică clasa de stocare. De exemplu,

pentru a defini o funcţie internă, se poate scrie:

static int compute_internally(int, int)

Funcţia compute_internally nu va putea fi folosită decât de către funcţiile definite în acelaşi fişier sursă şi

nu va fi vizibilă de către alte fişiere sursă, în momentul editării legăturilor.

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 înţelege o colecţie 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 acelaşi tip. În limbajul C, indexul este un număr întreg pozitiv şi

indexarea se face începând cu 0.

Declaraţia unei variabile de tip vector se face în felul următor:

<tip_elemente> <nume_vector>[<dimensiune>];

De exemplu, avem următoarele declaraţii de vectori:

Page 27: 137309474 Invata Limbajul de Programare C

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 execuţiei programului. Astfel, programatorul trebuie să

estimeze o dimensiune maximă pentru vector, şi aceasta va fi o limitare a programului.

De obicei, se folosesc constante simbolice (ca în ultimul exemplu) pentru aceste dimensiuni maxime,

pentru ca ele să poată fi ajustate uşor la nevoie. De asemenea, în cadrul unei declaraţii, se pot iniţializa

cu valori constante componente ale vectorului, iar în acest caz, dimensiunea vectorului poate rămâne

neprecizată (compilatorul o va determina din numărul 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 vectorului

*/

unsigned short vect[1000] = {0, 2, 4, 6}; /* Sunt initializate doar primele 4 elemente */

În cazul special în care specificăm dimensiunea şi doar un singur element la initializare, primul element

va fi cel specificat, iar toate celelalte elemente ale vectorului vor fi iniţializate la 0:

char sir[100] = {97}; /* Sirul va fi initializat cu: 97 (caracterul 'a') pe prima poziţie şi 99 de 0

*/

Este important de remarcat faptul că elementele neiniţializate pot avea valori oarecare. La alocarea unui

vector, compilatorul nu efectuează nici un fel de iniţializare şi nu furnizează nici un mesaj de eroare dacă

un element este folosit înainte de a fi iniţializat. Un program corect va iniţializa, î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;

Page 28: 137309474 Invata Limbajul de Programare C

vect[0] = 1;

vect[5] = 10;

vect[i] = 15;

vect[i+1] = 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

Depăşirea 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 încadrării în intervalul

valid.

Page 29: 137309474 Invata Limbajul de Programare C

Indici folosiţi greşit în bucle imbricate (index cross-talk). Sunt multe cazuri în care pe un nivel al

buclei se foloseşte, de exemplu vect[i], şi pe nivelul imbricat vect[j], când de fapt se dorea

folosirea lui i. Mare atenţie şi în astfel de cazuri!

#define MAX 100

int vect[MAX]

va fi de preferat în locul lui

int vect[100]

Sfat: Verificaţi că indicii se încadrează între marginile superioară şi inferioară a intervalului de valori

valide. Acest lucru trebuie în general făcut în cazul în care datele provin dintr-o sursă externă: citite de la

tastatură sau pasate ca parametri efectivi unei funcţii, de exemplu.

Exemplu:

// program care citeşte un index şi o valoare, şi atribuie valoarea elementului din vector care se găseşte la poziţia

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: Folosiţi comentarii pentru a explica ce reprezintă diverse variabile. Acest lucru vă va ajuta atât pe

voi să nu încurcaţi indici, de exemplu, cât şi pe ceilalţi care folosesc sau extind codul vostru.

Exemplu:

Page 30: 137309474 Invata Limbajul de Programare C

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

}

Aplicaţii cu vectori

Căutări

Căutare secvenţială

Când avem de a face cu un vector nesortat (şi nu numai în acest caz), cea mai simplă abordare pentru a

găsi o valoare, este căutarea secvenţială. Cu alte cuvinte, se compară, la rând, fiecare valoare din vector

cu valoarea căutată. Dacă valoarea a fost găsită, căutarea se poate opri (nu mai are sens să parcugem

vectorul până la capăt, 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;

}

Page 31: 137309474 Invata Limbajul de Programare C

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

...

Căutare binară iterativă

Dacă vectorul pe care se face căutarea este sortat, algoritmul mai eficient de folosit în acest caz este

căutarea binară. Presupunem că vectorul este sortat crescător (pentru vectori sortaţi descrescător,

raţionamentul este similar). Valoarea căutată, x, se compară cu valoarea cu indexul N/2 din vector, unde

N este numărul de elemente. Dacă x este mai mic decât valoarea din vector, se caută în prima jumătate a

vectorului, iar dacă este mai mare, în cea de-a doua jumătate.

Căutarea în una dintre cele două jumătăţi se face după acelaşi algoritm. Conceptual, căutarea binară

este un algoritm recursiv, dar poate fi implementat la fel de bine într-un mod iterativ, folosind indecşii

corespunzători bucăţii din vector în care se face căutarea. Aceşti indecşi se modifică pe parcursul

algoritmului, într-o buclă, în funcţie de comparaţiile făcute.

Evoluţia algoritmului este ilustrată în imaginea de mai jos.

Pseudocodul pentru căutarea binară:

Page 32: 137309474 Invata Limbajul de Programare C

căutare_binară (v[0..N], x)

{

low = 0

high = N - 1

cât timp (low <= high)

{

mid = (low + high) / 2

dacă v[mid] > value

high = mid - 1

altfel

dacă v[mid] < value

low = mid + 1

altfel

găsit x pe poziţia mid

}

x nu a fost găsit

}

Sortări

Bubble Sort

Metoda bulelor este cea mai simplă modalitate de sortare a unui vector, dar şi cea mai ineficientă. Ea

funcţionează pe principiul parcurgerii vectorului şi comparării elementului curent cu elementul următor.

Dacă cele două nu respectă ordinea, sunt interschimbate. Această parcurgere este repetată de suficiente

ori până când nu mai există nici o interschimbare în vector.

Sortarea prin selecţie

Sortarea prin selecţie oferă unele îmbunătăţiri în ceea ce priveşte complexitatea, însă este departe de a fi

considerat un algoritm eficient. Presupunând că se doreşte sortarea crescătoare a vectorului, se caută

minimul din vector, şi se interschimbă cu primul element – cel cu indexul 0. Apoi se reia acelaşi procedeu

pentru restul vectorului.

Motivul pentru care algoritmul de sortare prin selecţie este mai eficient este acela că vectorul în care se

caută minimul devine din ce în ce mai mic, şi, evident, căutarea 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.

Page 33: 137309474 Invata Limbajul de Programare C

Matrice. Operaţii cu matrice: adunare, înmulţire. Reprezentarea în memorie.

Matrice

Matricea este o colecţie omogenă şi bidimensională de elemente. Acestea pot fi accesate prin intermediul

a doi indici, numerotaţi, ca şi în cazul vectorilor, începand de la 0. Declaraţia 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]

Numărul de elemente ale unei matrice va fi dim_1*dim_2, şi semnificaţia fiecărei 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 totuşi, pentru

funcţionarea corectă a programului, să se respecte semnificaţiile alese pe întreg parcursul codului sursă.

Tablouri multidimensionale

Vectorii şi matricele se pot extrapola la noţiunea generală de tablou cu mai multe dimensiuni, care se

declară în modul următor:

<tip_elemente> <nume_tablou>[<dim_1>][<dim_2>]...[<dim_n>];

De exemplu:

int cub[3][3][3]

Deşi, î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 situaţii.

Adunarea si înmulţirea matricelor

Suma matricelor:

Page 35: 137309474 Invata Limbajul de Programare C

fiecare matrice conform săgeţilor. Elementele din acestea sunt înmulţite câte 2 conform înmulţirii pe

vectori, apoi suma produselor constituie elementul din matricea finală.

Reprezentarea în memorie

Cunoaşterea reprezentării în memorie a tablourilor vă ajută să înţelegeţi mai bine cum se lucrează cu

aceste tipuri de date şi să evitaţi atât erorile comune, cât şi pe cele mai subtile. Aşa cum se ştie, fiecare

variabilă are asociata o anumită adresă în memorie şi ocupă o anumită lungime, măsurată în octeţi.

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 când întalneşte numele vect, compilatorul va intelege “adresa în

memorie de la care începe vectorul vect”. Operatorul de indexare [] aplicat numelui vect instruieşte

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 observaţi ca nu mai intervine sub nici o formă dimensiunea vectorului dată la

declarare. Aceea a fost necesară doar compilatorului, ca sa ştie câtă memorie să aloce pentru

reprezentarea acestuia.

De asemenea, observaţi că sunt permise indexari în afara spaţiului de memorie alocat, şi astfel

programul va putea, din greşeala, 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 rău caz întreg sistemul va fi blocat (în cazul sistemelor care nu au implementate spaţii virtuale de

memorie proprii fiecărei aplicaţii – platformele Windows NT si Linux).

Faptul că graniţa dintre vectori şi adrese de memorie este atât de fină în limbajul C, sintaxa acestuia

permite expresii ciudate, de forma:

char a[100];

a[0] = 1;

Page 36: 137309474 Invata Limbajul de Programare C

3[a] = 5

Instrucţiunea din urmă înseamna pur şi simplu “asignează 5 variabilei de tip char de la adresa 3 + a *

sizeof(char) = 3 + a“. Observaţi că aceasta este echivalentă cu a[3] = 5;

De asemenea, un alt avantaj apare la definirea unui parametru al unei funcţii, 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 fiecărui element atunci când

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

Page 37: 137309474 Invata Limbajul de Programare C

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.

Noţiunea de pointer

Un pointer este o variabilă care reţine 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,

operaţiile cu pointeri sunt determinate de dimensiunea tipului de date (numărul de bytes ocupaţi

în memorie).

Page 38: 137309474 Invata Limbajul de Programare C

Adresa unei funcţii (adresa la care punctul curent de execuţie va sări, în cazul în care acea

funcţie este apelată).

Adresa unei adrese de memorie; acest tip de pointer poate fi redus la prima situaţie, în cazul în

care se consideră pointerul un tip de date numeric de lungime 32 de biţi (cantitatea maximă

adresabilă de memorie pentru programul nostru este de 4 GB, pentru arhitecturi pe 32 de biţi).

Adresa unei zone cu conţinut necunoscut (pointer către void); de exemplu, putem avea operaţii

cu blocuri mari de memorie, al cărui conţinut nu ne interesează (de exemplu citirea în memorie a

unui fişier, sau folosirea memoriei pe post de buffer) şi în acest caz adresele blocurilor sunt

reprezentate prin pointeri către void.

Notă:

Dimensiunea unui pointer nu este întotdeauna de 32 de biţi, ci depinde de arhitectura şi sistemul de

operare pe care programul este compilat. Pe sisteme pe 64 de biţi, un pointer va avea 64 de biţi. 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) (deşi sunt egale în cele mai multe situaţii), şi de aceea nu trebuie să vă bazaţi pe acest fapt în

programele voastre.

În lucrul cu pointeri sunt folosiţi asupra variabilelor doi operatori: operatorul * (de dereferenţiere) şi

operatorul & (de referenţiere). Amândoi apar în faţa variabilei asupra căreia acţionează şi au efecte

complementare.

Operatorul * este aplicat unei variabile de tip pointer şi are funcţia de a obţine valoarea stocată la adresa

respectivă. Utilizat în declaraţia unei variabile, acest operator are funcţia 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 obţine adresa în memorie a variabilei respective.

Atenţie!

Când este declarat un pointer, nu este alocată şi zona de memorie în care să fie stocată valoarea către

care indică pointerul respectiv! Întotdeauna când folosiţi (dereferenţiaţi) un pointer, trebuie ca acesta să

puncteze către 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).

Page 39: 137309474 Invata Limbajul de Programare C

De asemenea, un pointer poate fi iniţializat la constanta NULL (valoarea 0), compatibilă cu orice tip de

pointer, şi care indică, prin convenţie, un pointer neiniţializat. Utilizarea unui pointer asignat la o adresă

oarecare (invalidă), poate avea consecinţe imprevizibile la executia programului, de la comportamente

ciudate la blocarea sistemului!

Exemplu:

int *a; // Pointer

int b = 5; // Variabila

char *c; // Pointer catre un caracter (sau sir de caractere) void *buff = NULL; // Pointer catre void, initializat la NULL *a = 1; // Asignare INVALIDA; a nu este initializat la o adresa de memorie

a = &b; // Asignare valida; a ia adresa variabilei b *a = 5; // Asignare valida; continutul memoriei de la adresa a (care a fost initializata mai sus) ia

valoarea 5 // Acest lucru este echivalent cu "b = 5;"

Atenţie!

În cazul declaraţiilor de pointeri, operatorul * este asociat numelui variabilei, şi nu numelui tipului, astfel

că, pentru o declaraţie 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

Atenţie!

Un pointer către void nu poate fi folosit direct în operaţii cu pointeri, ci trebuie convertit mai întâi la un

pointer către un tip de date. De exemplu:

void *mem;

//[...] *mem = 10; // Operatie ILEGALA

((int*)mem) = 10; // Operatie legala, dar riscanta

Page 40: 137309474 Invata Limbajul de Programare C

Tipuri de pointeri

Pointeri la date

Operaţiile cu pointeri la date pot fi rezumate la următoarele 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 acelaşi 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 următor:

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 scăderea a două variabile pointer de acelaşi tip. Operaţia de scădere returnează

diferenţa în elemente dintre cele două adrese de memorie (cu alte cuvinte, diferenţa în octeţi

dintre două adrese de tip*, împărţită la sizeof(tip)).

Adunarea sau scăderea unui întreg la un pointer, incrementarea sau decrementarea unui pointer.

Aceste operaţii 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);

Page 41: 137309474 Invata Limbajul de Programare C

Pointeri la tablouri

O variabilă vector conţine 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 alocaţi dinamic. De exemplu, pentru

declararea unei funcţii care primeşte un vector de întregi şi dimensiunea lui, avem două posibilităţi:

void printVec(int a[], int n);

sau:

void printVec(int *a, int n);

Interesant este că în interiorul funcţiei 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ă următoarele echivalenţe de notaţii 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

Diferenţa 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 către compilatorul C şi nu mai poate fi modificată la execuţie),

deci nu poate apărea în stânga unei atribuiri, în timp ce o variabilă pointer are un conţinut modificabil prin

atribuire sau prin operaţii aritmetice. De exemplu:

Page 42: 137309474 Invata Limbajul de Programare C

int a[100], *p;

p = a; ++p; //corect a = p; ++a; //EROARE

De asemenea, o variabilă de tip vector conţine şi informaţii legate de lungimea vectorului şi dimensiunea

totală ocupată în memorie, în timp ce un pointer doar descrie o poziţie î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 funcţie poate avea ca rezultat un pointer, dar nu poate

avea ca rezultat un vector.

Pointeri în funcţii

În cadrul funcţiilor, pointerii pot fi folosiţi, printre altele, pentru:

Transmiterea de rezultate prin argumente

Transmiterea unei adrese prin rezultatul funcţiei

Utilizarea unor funcţii cu nume diferite (date prin adresele acestora)

O funcţie care trebuie să modifice mai multe valori primite prin argumente sau care trebuie să transmită

mai multe rezultate calculate în cadrul funcţiei trebuie să folosească argumente de tip pointer. De

exemplu, o funcţie care primeşte ca parametru un număr, 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 funcţie care trebuie să modifice două sau mai multe argumente, le va specifica pe acestea individual,

prin câte un pointer, sau într-un mod unificat, printr-un vector, ca în exemplul următor:

Page 43: 137309474 Invata Limbajul de Programare C

void inctime (int *h, int *m, int *s); // sau

void inctime (int t[3]); // t[0]=h, t[1]=m, t[2]=s

O funcţie poate avea ca rezultat un pointer, dar acest pointer nu trebuie să conţină adresa unei variabile

locale. De obicei, rezultatul pointer este egal cu unul din argumente, eventual modificat în funcţie. De

exemplu:

// Incrementare pointer p

char *incptr(char *p) { return ++p;

}

O variabila locală are o existenţă temporară, garantată numai pe durata execuţiei funcţiei în care este

definită (cu excepţia variabilelor locale statice), şi de aceea adresa unei astfel de variabile nu trebuie

transmisă în afara funcţiei, pentru a fi folosită ulterior. De exemplu, următoarea secvenţă de cod

estegreşită:

// 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 funcţie care trebuie să transmită ca rezultat un vector poate fi scrisă corect în două feluri:

Primeşte ca argument adresa vectorului (definit şi alocat în altă funcţie) şi depune rezultatele la

adresa primită (soluţia recomandată!);

Alocă dinamic memoria pentru vector (folosind malloc), iar această alocare se menţine şi la

ieşirea din funcţie.

Pointeri la funcţii

Page 44: 137309474 Invata Limbajul de Programare C

Anumite aplicaţii numerice necesită scrierea unei funcţii care să poată apela o funcţie cu nume

necunoscut, dar cu prototip şi efect cunoscut. De exemplu, o funcţie care să calculeze integrala definită a

oricărei funcţii cu un singur argument sau care să determine o radăcină reala a oricărei ecuaţii (neliniare).

Aici vom lua ca exemplu o funcţie listf care poate afişa (lista) valorile unei alte funcţii cu un singur

argument, într-un interval dat şi cu un pas dat. Exemple de utilizare a funcţiei listf pentru afişarea valorilor

unor funcţii 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 funcţii, care primeşte ca argument numele (adresa) unei

funcţii. Prin convenţie, în limbajul C, numele unei funcţii neînsoţit de o listă de argumente şi de

parantezele () specifice unui apel este interpretat ca un pointer către funcţia respectivă (fără a se folosi

operatorul de adresare &).

Deci sin este adresa funcţiei sin(x) în apelul funcţiei listf. Declararea unui argument formal (sau a unei

variabile) de tip pointer la o funcţie are forma următoare:

tip (*pf) (lista_arg_formale);

unde:

pf este numele argumentului (variabilei) pointer la funcţie

tip este tipul rezultatului funcţiei

Parantezele sunt importante, deoarece absenţa lor modifică interpretarea declaraţiei. De exemplu,

putem avea:

tip * f(lista_arg_formale) // functie cu rezultat pointer, si NU pointer

În concluzie, definirea funcţiei listf este:

void listf (double (*fp)(double), double min, double max, double pas) { double x,y;

for (x=min; x<=max; x=x+pas) {

Page 45: 137309474 Invata Limbajul de Programare C

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 execuţie este apelarea unei funcţii

fără paranteze; compilatorul nu apelează funcţia şi consideră că programatorul vrea să folosească adresa

funcţiei. De exemplu:

if (kbhit) break; // echivalent cu if(1) break;

if (kbhit()) break; // iesire din ciclu la apasarea unei taste

Expresii complexe cu pointeri

Deşi sunt întâlnite 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 precedenţa în faţa * ş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 funcţie

3. fără nici un parametru

4. şi care întoarce un pointer

5. la un vector de 10 elemente

6. de tip pointer

7. către tipul char

Folosind acest procedeu, se pot rezolva şi alte situaţii aparent extrem de complexe:

unsigned int *(* const *name[5][10] ) ( void );

Page 46: 137309474 Invata Limbajul de Programare C

care semnifică: “o matrice de 5×10 de pointeri către pointeri constanţi la o funcţie, care nu ia nici un

parametru, şi care întoarce un pointer către 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

Funcţii de alocare şi eliberare a memoriei

Aceste funcţii standard sunt declarate în fişierul antet stdlib.h. Cele trei funcţii de alocare au ca rezultat

adresa zonei de memorie alocate (de tip void*) şi ca argument comun dimensiunea, în octeţi, a zonei de

memorie alocate (de tip size_t ).

Dacă cererea de alocare nu poate fi satisfăcută pentru că nu mai există un bloc continuu de dimensiunea

solicitată, atunci funcţiile de alocare au rezultat NULL (ce reprezintă un pointer de tip void* la adresa de

memorie 0, care prin convenţie este o adresă invalidă – nu există date stocate în acea zonă).

La apelarea funcţiilor de alocare se folosesc:

Operatorul sizeof pentru a determina numărul de octeţi 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

Atenţie! Dimensiunea memoriei luată ca parametru de malloc() este specificată în octeţi, indiferent de

tipul de date care va fi stocat în acea regiune de memorie! Din acest motiv, pentru a aloca suficientă

memorie, numărul dorit de elemente trebuie înmulţit cu dimensiunea unui element, atunci când are loc un

apelmalloc()

Page 47: 137309474 Invata Limbajul de Programare C

Alocarea de memorie pentru un vector şi iniţializarea zonei alocate cu zerouri se poate face cu

funcţia 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 următoarea secvenţă de instrucţiuni:

int i;

int *a = (int*) malloc(n * sizeof(int));

for (i = 0; i < n; i++) {

a[i] = 0;

}

Notă: În timp ce funcţia malloc() ia un singur parametru (o dimensiune în octeţi), funcţia calloc() primeşte

două argumente, o lungime de vector şi o dimensiune a fiecărui element. Astfel, această funcţie este

specializată pentru memorie organizată ca un vector, în timp ce malloc() nu ţine cont de structura

memoriei.

Realocarea unui vector care creşte (sau scade) faţă de dimensiunea estimată anterior se poate face cu

funcţia realloc, care primeşte 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ă). Funcţia realloc() realizează următoarele

operaţii:

Alocă o zonă de dimensiunea specificată ca al doilea argument

Copiază la noua adresă datele de la adresa veche (primul argument al funcţiei)

Eliberează memoria de la adresa veche.

Funcţia free() are ca argument o adresă (un pointer) şi eliberează zona de la adresa respectivă (alocată

prin apelul unei funcţii de tipul …alloc). Dimensiunea zonei nu mai trebuie specificată deoarece este

ţinută minte de sistemul de alocare de memorie în nişte structuri interne.

Vectori alocaţi dinamic

Page 48: 137309474 Invata Limbajul de Programare C

Structura de vector are avantajul simplităţii şi economiei de memorie faţă de alte structuri de date folosite

pentru memorarea unei colectii de informaţii între care există anumite relaţii. Între cerinţa de

dimensionare constantă a unui vector şi generalitatea programelor care folosesc astfel de vectori există o

contradicţie.

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 aceşti vectori. Aceasta este o solutie mai flexibilă, care

foloseşte mai bine memoria disponibilă şi nu impune limitări arbitrare asupra utilizării 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

funcţie (alta decat main()) nu este static deoarece nu ocupă memorie pe toata durata de execuţie a

programului, deşi 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;

}

Page 49: 137309474 Invata Limbajul de Programare C

Există şi cazuri în care datele memorate într-un vector rezultă din anumite prelucrări, iar numărul lor nu

poate fi cunoscut de la începutul execuţiei. 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 următor se citeşte un număr 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 capacităţii sale anterioare. Astfel numărul de

realocări pe parcursul execuţiei programului va deveni foarte mic – un calcul matematic simplu arată că

acest număr este proporţional cu logaritmul dimensiunii maxime (finale) a vectorului.

Matrice alocate dinamic

Alocarea dinamică pentru o matrice este importantă deoarece:

Foloseşte economic memoria şi evită alocări acoperitoare, estimative.

Page 50: 137309474 Invata Limbajul de Programare C

Permite matrice cu linii de lungimi diferite (denumite uneori ragged arrays, datorită formelor

“zimţate” din reprezentările grafice)

Reprezintă o soluţie bună la problema argumentelor de funcţii de tip matrice.

Daca programul poate afla numărul efectiv de linii şi de coloane al unei matrice (cu dimensiuni diferite de

la o execuţie la alta), atunci se va aloca memorie pentru un vector de pointeri (funcţie de numărul liniilor)

şi apoi se va aloca memorie pentru fiecare linie (funcţie de numărul 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; }

Funcţia de afişare 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]);

Page 51: 137309474 Invata Limbajul de Programare C

printf("\n"); }

}

Notaţia a[i][j] este interpretată astfel pentru o matrice alocată dinamic:

a[i] conţine un pointer (o adresă b)

b[j] sau b+j conţine întregul din poziţia j a vectorului cu adresa b.

Astfel, a[i][j] este echivalent semantic cu expresia cu pointeri *(*(a + i) + j).

Totuşi, funcţia printmat() dată anterior nu poate fi apelată dintr-un program care declară argumentul

efectiv ca o matrice cu dimensiuni constante.

Exemplul următor 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;

}

Explicaţia este interpretarea diferită a conţinutului zonei de la adresa aflată în primul argument:

funcţiaprintmat() 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 funcţie pentru alocarea de memorie la execuţie 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.

Page 52: 137309474 Invata Limbajul de Programare C

Exemplul 1: Funcţie echivalentă cu funcţia 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 execuţie)

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

Page 53: 137309474 Invata Limbajul de Programare C

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 execuţie)

#include <stdio.h>

#include <stdlib.h>

int main() { int n, i, j;

Page 54: 137309474 Invata Limbajul de Programare C

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 matrice

for (i = 0; i < n; i++)

for (j = 0; j < n; j++) mat[i][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];

Page 55: 137309474 Invata Limbajul de Programare C

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

Aveţi grijă ca variabilele de tip pointer să indice către adrese de memorie valide înainte de a fi

folosite; consecinţele adresării unei zone de memorie aleatoare sau invalide (NULL) pot fi dintre

cele mai imprevizibile.

Utilizaţi o formatare a codului care să sugereze asocierea operatorului * cu variabila asupra

căreia operează; acest lucru este în special valabil pentru declaraţiile de pointeri.

Nu returnaţi pointeri la variabile sau tablouri definite în cadrul funcţiilor, întrucât valabilitatea

acestora încetează odată cu ieşirea din corpul funcţiei.

Verificaţi rezultatul funcţiilor de alocare a memoriei, chiar dacă dimensiunea pe care doriţi s-o

rezervaţi este mică.

Atunci când 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 aplicaţiei).

Page 56: 137309474 Invata Limbajul de Programare C

Nu uitaţi să eliberaţi memoria alocată dinamic, folosind funcţia free(). Memoria rămasă

neeliberată încetineşte performanţele sistemului şi poate conduce la erori (bug-uri) greu de

depistat.

Clase de stocare

Clasa de stocare (memorare) arată când, cum şi unde se alocă memorie pentru o variabilă (vector).

Orice variabilă C are o clasă de memorare care rezultă fie dintr-o declaraţie 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 execuţiei. Variabilele externe, definite în afara funcţiilor, sunt implicit

statice, dar pot fi declarate static şi variabile locale, definite în cadrul funcţiilor.

Automat: memoria este alocată automat, la activarea unei funcţii, în zona stivă alocată unui

program şi este eliberată automat la terminarea funcţiei. Variabilele locale unui bloc (unei funcţii)

şi argumentele formale sunt implicit din clasa auto.

Dinamic: memoria se alocă la execuţie în zona heap alocată programului, dar numai la cererea

explicită a programatorului, prin apelarea unor funcţii de bibliotecă (malloc, calloc, realloc).

Memoria este eliberată numai la cerere, prin apelarea funcţiei free. Variabilele dinamice nu au

nume şi deci nu se pune problema clasei de memorare (atribut al variabilelor cu nume).

Variabilele statice pot fi iniţializate numai cu valori constante (pentru că se face la compilare), dar

variabilele auto pot fi iniţializate cu rezultatul unor expresii (pentru că se face la execuţie). Toate

variabilele externe (şi statice) sunt automat iniţializate 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 funcţiilor de

alocare.

O a treia clasă de memorare este clasa register pentru variabile cărora, teoretic, li se alocă registre ale

procesorului şi nu locaţii de memorie, pentru un timp de acces mai bun. În practică nici un compilator

modern nu mai ţine cont de acest cuvânt cheie, folosind automat registre atunci când codul poate fi

optimizat în acest fel (de exemplu când observă că nu se accesează niciodata adresa variabilei în

program).

Page 57: 137309474 Invata Limbajul de Programare C

Memoria neocupată de datele statice şi de instrucţiunile unui program este împărţită între stivă şi heap.

Consumul de memorie pe stivă este mai mare în programele cu funcţii recursive şi număr 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. Funcţii.

Şiruri de caractere

Un caracter se declară în C de forma: char a=‟a‟; Pentru initializarea lui, se observă că am pus un

caracter între apostroafe.

Un şir de caractere presupune practic un vector de caractere. Cea mai simplă declaraţie fiind: char

a[10]= “cuvant”;

Pentru iniţializarea unui şir de caractere, spre deosebire de un singur caracter am folosit ghilimelele.

Cum s-a prezentat anterior, o variabilă vector conţine adresa de început a vectorului(adresa primei

componente a vectorului), şi de aceea este echivalentă cu un pointer la tipul elementelor din vector. Deci

declaraţiile de mai jos vor declara fiecare cate un şir de caractere:

char a[5]; char *b="unsir";

char *c;

Diferenţa între ele este însa că primele două declaraţii vor aloca 5 pozitii în memorie, pe când ultima nu

va aloca nici o zona de memorie, necesitând sa fie ulterior alocată, folosind funcţiile 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;

Page 58: 137309474 Invata Limbajul de Programare C

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 afişarea

cuprintf. Deasemenea biblioteca stdio.h defineşte funcţiile gets() şi puts() pentru lucrul cu şiruri de

caractere.

gets(zona) – citeşte de la terminalul standard un şir de caractere terminat cu linie noua (enter).

Funcţia are ca parametru adresa zonei de memorie în care se introduc caracterele citite.

Funcţia returneaza adresa de început a zonei de memorie;

puts(zona) – afişeaza la terminalul standard şirul de caractere din zona data ca parametru, până

la caracterul terminator de şir, care va fi înlocuit prin caracterul linie noua.

Funcţia are ca parametru adresa zonei de memorie de unde începe afişarea caracterelor.

Funcţia returneaza codul ultimului caracter din şirul de caractere afişat şi -1 daca a aparut o

eroare.

Atenţie! Funcţia gets() va citi de la tastatura câte 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 afişare vom vedea ca s-a reţinut tot sirul!(nu

doar primele 5 caractere). Nimic deosebit până acum. Dar dacă luăm î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 nimic important pentru programul nostru, poate ceva ce il va bloca

sau duce la obţinerea de date eronate.

Pentru a evita aceasta se recomandă utilizarea fgets()

fgets(zona, lung_zona, stdin) – citeşte 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

Page 59: 137309474 Invata Limbajul de Programare C

standard de intrare. În cazul în care şirul dorit are lungime mai mică decât cea maximă, înaintea

terminatorului de şir, în zona de memorie va fi reţinut şi enter-ul dat.

Funcţii din string.h

Pentru manipularea şirurilor de caractere în limbajul C se folosesc funcţii declarate în fişierul 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

până 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 poziţii la valoarea dată de val.

Funcţia 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;

}

Page 60: 137309474 Invata Limbajul de Programare C

Iesire:

------- vreau vacanta!

memmove() void * memmove ( void * destinatie, const void * sursa, size_t num );

Copiază un număr de num caractere de la sursă, la zona de memorie indicată de destinaţie. Copierea

are loc ca şi cum ar exista un buffer intermediar, deci sursa si destinatia se pot suprapune. Funcţia nu

verifică terminatorul de şir la sursă, copiază mereu num bytes, deci pentru a evita depăsirea trebuie ca

dimensiunea sursei sa fie mai mare ca num. Funcţia returnează destinaţia.

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 număr de num caractere din şirul sursă in şirul destinaţie. Funcţia returnează şirul destinaţie.

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

Page 61: 137309474 Invata Limbajul de Programare C

return 0;

}

Iesire:

str1: Exemplu

str2: Exemplu

str3: un sir

strcpy() char * strcpy ( char * destinatie, const char * sursa );

Copiază şirul sursă in şirul destinaţie. Şirul destinaţie va fi suprascris. Funcţia asigură plasarea

terminatorului de şir în şirul destinaţie după copiere. Funcţia returneaza şirul destinaţie.

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 destinaţie. Funcţia returnează şirul destinaţie.

Page 62: 137309474 Invata Limbajul de Programare C

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, verificându-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;

Page 63: 137309474 Invata Limbajul de Programare C

}

strchr() char * strchr (const char * str, int character );

Caută caracterul c în şirul str şi returnează un pointer la prima sa apariţie.

strrchr() char * strrchr (const char * str, int character );

Caută caracterul c în şirul str şi returnează un pointer la ultima sa apariţie.

strstr() char * strstr (const char *str1, const char *str2 );

Caută şirul str2 în şirul str1 şi returnează un pointer la prima sa apariţie, sau NULL dacă nu a fost găsit.

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

Page 64: 137309474 Invata Limbajul de Programare C

Funcţia are rolul de a împarţi şirul str în tokens(subşiruri 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 conţinuând împarţirea aceluiaşi şir.

Funcţia va returna la fiecare apel un token(un subsir), ignorând caracterele cu rol de separator aflate în

şirul de delimitatori. O dată terminat şirul, funcţia 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

Page 65: 137309474 Invata Limbajul de Programare C

Structurile sunt tipuri de date în care putem grupa mai multe variabile eventual de tipuri diferite (spre

deosebire de vectori, care conţin 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 număr complex cu dublă precizie */

double re;

double im;

};

Declararea şi iniţializarea 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 declaraţiilor, 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;

Page 66: 137309474 Invata Limbajul de Programare C

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 când 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;

}

Atenţie! Dacă declaraţi pointeri la structuri, nu uitaţi să alocaţi memorie pentru aceştia înainte de a

accesa câmpurile structurii. Nu uitaţi să alocaţi şi câmpurile structurii, care sunt pointeri, înainte de

utilizare, dacă este cazul. De asemenea fiţi atenţi şi la modul de accesare al câmpurilor.

Diferenţa dintre copierea structurilor şi copierea pointerilor

Pentru exemplificarea diferenţei dintre copierea structurilor şi copierea pointerilor să considerăm

urmatorul exemplu:

Page 67: 137309474 Invata Limbajul de Programare C

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 aceeaşi adresa de memorie ca şi s1.s. Deci, după modificarea celui

de-al doilea caracter din s2.s, atat s2.s cât 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.

Totuşi, putem atribui ulterior lui s2.s o altă valoare (o altă adresă), iar ca urmare a acestei operaţii,

stringurile vor fi distincte din nou.

Un alt caz (diferit de cel expus anterior) este cel al atribuirii aceleiaşi structuri către 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 observăm că din nou p1->s şi p2->s indică către acelaşi şir de caractere, dar aici adresa

către şirul de caractere apare memorată o singura dată; spre deosebire de cazul anterior, dacă

modificăm adresa din p2->s, ea se va modifica automat şi în p1->s.

Uniuni

Uniunile sunt asemănătoare structurilor, dar lor li se rezervă o zonă de memorie ce poate conţine, la

momente de timp diferite, variabile de tipuri diferite. Sunt utilizate pentru a economisi memoria (se

refoloseşte aceeaşi zonă de memorie pentru a stoca mai multe variabile).

Page 68: 137309474 Invata Limbajul de Programare C

Uniunile se pot declara astfel:

union numere {

int i;

float f;

double v;

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

union numere u1, u2;

Când scriem ceva într-o uniune (de exemplu când facem o atribuire de genul u1.f = 7.4), ceea ce citim

apoi trebuie să fie de acelaşi tip, altfel vom obţine rezultate eronate (adică trebuie să utilizam u1.f, nu u1.v

sau u1.i).

Programatorul trebuie să ţină evidenţa tipului variabilei care este memorată în uniune în momentul curent

pentru a evita astfel de greşeli. Operaţiile care se pot face cu structuri se pot face şi cu uniuni; o structura

poate conţine uniuni şi o uniune poate conţine 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:

Page 69: 137309474 Invata Limbajul de Programare C

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 păstrată economic sub forma unei structuri, care

conţine următoarele câmpuri:

int L,C – numărul de linii/coloane al matricei rare

int N – numărul de elemente nenule

int LIN[] – vectorul ce păstrează liniile în care se află elemente nenule

int COL[] – vectorul ce păstrează coloanele în care se află elemente nenule

float X[] – vectorul ce păstrează 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 fişiere. Aplicaţii folosind fişiere.

Un fişier este o structură dinamică, situată în memoria secundară (pe disk-uri). Limbajul C permite

operarea cu fişiere:

de tip text – un astfel de fişier conţine o succesiune de linii, separate prin NL („n‟)

de tip binar – un astfel de fişier conţine o succesiune de octeti, fără nici o structură.

Prelucrarea unui fişier presupune asocierea acestuia cu un canal de I/E (numit flux sau stream).

Există trei canale predefinite, care se deschid automat la lansarea unui program:

stdin - fişier de intrare, text, este intrarea standard – tastatura

stdout - fişier de iesire, text, este ieşirea standard – ecranul monitorului.

stderr – fişier de iesire, text, este ieşirea standard unde sunt scris mesajele de eroare – ecran.

Pentru a prelucra un fişier, trebuie parcurse următoarele etape:

Page 70: 137309474 Invata Limbajul de Programare C

se defineşte o variabilă de tip FILE * pentru accesarea fişierului; FILE * este un tip structură

definit înstdio.h, care conţine informaţii referitoare la fişier şi la tamponul de transfer de date între

memoria centrală şi fişier (adresa, lungimea tamponului, modul de utilizare a fişierului, indicator

de sfârsit, de poziţie în fişier)

se deschide fişierul pentru un anumit mod de acces, folosind funcţia de bibliotecă fopen, care

realizează şi asocierea între variabila fişier şi numele extern al fişierului

se prelucrează fişierul în citire/scriere

cu funcţiile specifice

se inchide fişierul folosind funcţia de bibliotecă fclose.

Mai jos se prezintă restul funcţiilor de prelucrare a fişierelor:

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

deschide fişierul cu numele dat pentru acces de tip mod.

Returnează pointer la fişier sau NULL dacă fişierul nu poate fi deschis; valoarea returnată este memorată

în variabila fişier, care a fost declarată pentru accesarea lui.

Modul de deschidere poate fi:

“r” - readonly , este permisă doar citirea dintr-un fişier existent

“w” - write, crează un nou fişier, sau dacă există deja, distruge vechiul continut

“a” - append, deschide pentru scriere un fişier existent ( scrierea se va face în continuarea

informaţiei deja existente în fişier, deci pointerul de acces se plasează la sfârşitul fişierului )

“+” - permite scrierea şi citirea – actualizare (ex: “r+”, “w+”, “a+”). O citire nu poate fi direct

urmată

de o scriere şi reciproc. Întâi trebuie repoziţionat cursorul de acces printr-un apel la fseek.

“b” - specifică fişier de tip binar

“t” - specifică fişier de tip text (implicit), la care se face automat conversia CR-LF(“nf”) în sau din

CR („n‟).

int fclose(FILE *fp);

închide fişierul şi eliberează zona tampon; returnează 0 la succes, EOF la eroare

Page 71: 137309474 Invata Limbajul de Programare C

int fseek(FILE *fp, long offset, int whence);

repoziţionează pointerul asociat unui fişier . Offset – numărul de octeţi între poziţia dată de whence şi

noua poziţie.

whence - are una din cele trei valori posibile:

SEEK_SET = 0 – Căutarea se face de la începutul fişierului

SEEK_CUR = 1 - Căutare din poziţia curentă

SEEK_END = 2 - Căutare de la sfârşitul fişierului

long ftell(FILE* fp);

întoarce poziţia curentă în cadrul lui fp

int fgetpos(FILE* fp, fpos_t* ptr);

această funcţie memorează în variabila fptr poziţia curentă în cadrul fişierului fp (ptr va putea fi folosit

ulterior cu funcţia fsetpos).

int fsetpos(FILE* fp, const fpos_t* ptr);

această funcţie setează poziţia curentă în fişierul fp la valoarea ptr, obţinută anterior prin funcţia fgetpos.

int feof(FILE *fis);

returnează 0 dacă nu s-a detectat sfârşit de fişier la ultima operaţie de citire, respectiv o valoare nenulă

(adevărată) pentru sfârşit de fişier.

FILE* freopen(const char* filename, const char* mode, FILE* fp);

se închide fişierul fp, se deschide fişierul 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ă funcţie se utilizează pentru fişierele deschise pentru scriere şi are ca efect scrierea în fişier a

datelor din bufferul asociat acestuia, care înca nu au fost puse în fişier.

Page 72: 137309474 Invata Limbajul de Programare C

Citirea şi scrierea în/din fişiere

Citirea/scrierea în fişiere se poate face în doua moduri (în functie de tipul fişierului): în mod text sau în

mod binar. Principalele diferenţe dintre cele doua moduri sunt:

în modul text, la sfarsitul fişierului se pune un caracter suplimentar, care indica sfârşitul de fişier.

Î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 fişier în mod text, citirea se va opri la intâlnirea acestui

caracter, chiar dacă mai există şi alte caractere după el. În modul binar nu există caracter de

sfârşit de fişier (mai precis, caracterul cu codul 26, respectiv 4, este tratat la fel ca şi celelalte

caractere).

în DOS şi Windows, în modul text, sfârşitul de linie este reprezentat prin două caractere, CR

(Carriage Return, cod ASCII 13) şi LF (Line Feed, cod ASCII 10). Atunci când în modul text

scriem un caracter „n‟ (LF) în fişier, acesta va fi convertit într-o secventă de 2 caractere CR şi LF.

Când citim în mod text dintr-un fişier, secvenţa CR, LF este convertită într-un „n‟ (LF). În Unix,

sfârşitul de linie este reprezentat doar prin caracterul LF. În mod binar, atât în DOS-Windows cât

şi în Unix, sfârşitul de linie este reprezentat doar prin caracterul LF.

Modul binar se utilizează de obicei pentru a scrie în fişier datele exact aşa cum sunt reprezentate în

memorie (cu functiile fread, fwrite) – de exemplu pentru un număr 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 funcţiile fprintf, fscanf) – în cazul acesta

pentru un număr î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, ...);

Funcţiile sunt utilizate pentru citire/scriere în mod text şi sunt asemănătoare cu printf/scanf (diferenţa fiind

că trebuie dat pointerul la fişier ca prim parametru).

Citire/scriere la nivel de caracter

int fgetc(FILE* fp); // întoarce următorul caracter din

Page 73: 137309474 Invata Limbajul de Programare C

fişier, EOF la sfârşit de fişier

char *fgets(char* s, int n, FILE* fp); // întoarce următoarele n caractere de

la pointer sau pâna la sfârşitul de linie

int fputc(int c, FILE* fp); //pune caracterul c in fişier

int ungetc(int c, FILE* fp); // pune c în bufferul asociat lui fp (c va fi

următorul caracter citit din fp)

Citire/scriere fără 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 funcţii lucrăm cand deschidem fişierul în mod binar; citirea/scrierea se face fără nici un fel de

conversie sau interpretare. Se lucrează cu “înregistrări”, adică zone compacte de memorie: funcţia fread

citeşte nrec înregistrări începănd de la poziţia curentă din fişierul fp, o înregistrare având dimensiunea

size.

Acestea sunt depuse în tabloul ptr. “Înregistrările” pot fi asociate cu structurile din C – adică în mod uzual,

tabloul ptr este un tablou de structuri (dar în loc de structuri putem avea ş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 execuţia unui program, de multe ori este de dorit furnizarea datelor de lucru înaintea

lansării în execuţie a programului, acesta urmând să se execute apoi fără intervenţia utilizatorului (aşa-

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 spaţii. Argumentele care conţin spaţii 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.

Page 74: 137309474 Invata Limbajul de Programare C

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 funcţiei main(). Astfel, când se doreşte folosirea argumentelor liniei de comandă, funcţia

main() se va defini astfel:

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

Astfel, funcţia main() primeşte, î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. Semnificaţia lor este

următoarea:

int argc (argument count) – reprezintă numărul de parametrii ai liniei de comandă. După cum

se vede din exemplul anterior, există cel puţin un parametru, acesta fiind chiar numele

programului (numele care a fost folosit pentru a lansa în execuţie 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, având argc

elemente (indexate de la 0 la argc – 1). Întotdeauna argv[0] conţine numele programului.

Parametrii liniei de comandă se pot accesa prin intermediul vectorului argv şi pot fi prelucraţi cu funcţiile

standard de prelucrare a şirurilor de caractere.

Funcţii cu număr variabil de parametri

Page 75: 137309474 Invata Limbajul de Programare C

Exemplele discutate pana acum primeau ca parametrii un număr 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 întrucât compilatorul va sesiza de la compilare situaţiile în care se

încearcă pasarea unui număr eronat de parametrii sau a unor parametrii de tipuri necorespunzătoare.

Limbajul C permite totuşi şi declararea şi folosirea funcţiilor cu un număr (şi eventual tip) variabil de

parametri. Astfel, numărul şi tipul tuturor parametrilor va fi cunoscut doar la rulare, biblioteca standard

Cpunând la dispoziţie o serie de definiţii de tipuri şi macro definiţii care permit parcurgerea listei de

parametri a unei funcţii cu număr variabil de parametri. Exemplul cel mai comun de astfel de funcţii sunt

funcţiile din familia printf(), scanf().

Limbajul C – Definirea funcţiilor cu număr variabil de parametri

Prototipul funcţiilor cu număr variabil de parametrii arată în felul următor:

tip_rezultat nume_funcţie(listă_parametrii_fixaţi, ...);

Notaţia „ ,… ” comunică compilatorului faptul că funcţia poate primi un număr arbitrar de parametrii

începând cu poziţia în care apare. De exemplu, prototipul funcţiei fprintf() arată în felul următor:

void fprintf(FILE*, const char*, ...);

Astfel, funcţia 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 conţinutul variabilei de tip const char* (acesta este motivul pentru care pot apărea

erori la rulare în condiţiile în care încercăm să tipărim un număr întreg cu %s ).

Page 76: 137309474 Invata Limbajul de Programare C

Implementarea funcţiilor cu număr variabil de parametri

Pentru prelucrarea listei de parametrii variabili este necesară includerea fişierului antet stdarg.h. Acesta

conţine declaraţii pentru tipul de date variable argument list (va_list) şi o serie de macrodefiniţii 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. Iniţializarea variabilei de tip va_list cu lista de parametri cu care a fost apelată funcţia se realizează cu

macrodefiniţia va_start(arg_list, last_argument) unde:

arg_list reprezintă variabila de tip va_list prin intermediul căreia vom accesa lista de parametri a

funcţiei.

last_argument reprezintă numele ultimei variabile fixate din lista de parametri a funcţiei (în

exemplul următor, aceasta este şi prima variabilă, numită first).

3. Accesarea variabilelor din lista de parametri se realizează cu macro-definiţia va_arg(arg_list, type),

unde

arg_list are aceeaşi semnificaţie 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 doreşte întoarcerea în listă, aceasta trebuie reiniţializată iar

elementul dorit este accesat prin apeluri succesive de va_arg()

4. Eliberarea memoriei folosite de lista de parametri se realizează prin intermediul macro-

definiţieiva_end(arg_list), unde arg_list are aceeaşi semnificaţie ca mai sus.

Exemplu: Afişarea unui număr variabil de numere naturale. Lista este terminată cu un număr 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;

Page 77: 137309474 Invata Limbajul de Programare C

}

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 instrucţiunile de preprocesare sunt înlocuite (substituite), pentru a genera cod

C „pur”. Preprocesarea este o prelucrare exclusiv textuală a fişierului sursă. În acest pas nu se fac nici un

fel de verificări sintactice sau semantice asupra codului sursă, ci doar sunt efectuate substituţiile din text.

Astfel, preprocesorul va prelucra şi fişiere fără nici un sens în C.

Spre exemplu, fiind considerat fişierul rubbish.c cu următorul conţinut:

#define EA Ana

#define si C

#ifdef CIFRE

#define CINCI 5

#define DOUA 2

EA are mere. Mara are DOUA pere shi CINCI cirese.

Page 78: 137309474 Invata Limbajul de Programare C

#endif

Vasilica vrea sa cante o melodie in si bemol.

La rularea comenzii

gcc -E -DCIFRE rubbish.c

se va obţine următoare ieşire (se cere compilatorului să execute doar pasul de preprocesare (-E),

definind în acelaşi 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 instrucţiuni de preprocesare sunt prezentate în continuare.

Incluziune

Probabil cea mai des folosită instrucţiune de preprocesare este cea de incluziune, de forma

#include <nume_fişier>

sau

#include "nume_fisier"

care are ca rezultat înlocuirea sa cu conţinutul fişierului specificat de nume_fişier. Diferenţa dintre cele

două versiuni este că cea cu paranteze unghiulare caută nume_fişier doar în directorul standard de

fişiere antet (numit deobicei include), iar cea cu ghilimele caută atât în directorul include cât şi în

directorul curent.

Page 79: 137309474 Invata Limbajul de Programare C

C – Definirea de simboluri

Definirea de simboluri este cel mai des folosită în conjuncţie cu instrucţiunile de procesare condiţionată,

fiind folosită pentru activarea şi dezactivarea unor segmente de cod în funcţie de prezenţa unor simboluri.

Definirea unui simbol se face în cod cu instrucţiunea

#define SIMBOL

sau se poate realiza şi la compilare, prin folosirea flagului -D al compilatorului (după cum am văzut în

exemplul precedent).

Un simbol poate fi de asemenea „şters” folosind instrucţiunea

#undef SIMBOL

în cazul în care nu se mai doreşte prezenţa simbolului de preprocesor ulterior definirii sale.

C – Definirea de macro-uri

Instrucţiunile de preprocesare mai pot fi folosite şi pentru definirea de constante simbolice şi

macroinstrucţiuni. 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 totuşi în interiorul şirurilor de caractere.

O macroinstrucţiune este similară unei constante simbolice, ca definire, dar acceptă parametrii. Este

folosită în program în mod asemănător unei funcţii, 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.

Page 80: 137309474 Invata Limbajul de Programare C

Atenţie! Deoarece preprocesarea este o prelucrare textuală a codului sursă, în cazul exemplului de mai

sus, macroinstrucţiunea în forma prezentată nu va calcula întotdeauna dublul unui număr.

Astfel, la un apel de forma:

DUBLU(a + 3)

în pasul de preprocesare se va genera expresia

2*a+3

care bineînţeles că nu realizează funcţia 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 – Instrucţiuni de compilare condiţionată

Instrucţiunile de compilare condiţionată sunt folosite pentru a „ascunde” fragmente de cod în funcţie

de anumite condiţii. Formatul este următorul:

#if conditie

....

#else

....

#endif

unde conditie este este o expresie constantă întreagă. Pentru realizarea de expresii cu mai multe opţiuni

se poate folosi şi forma #elif:

#if conditie

...

#elif conditie2

...

#elif conditie3

...

#else

...

#endif

De obicei condiţia testează existenţa unui simbol. Scenariile tipice de folosire sunt:

Page 81: 137309474 Invata Limbajul de Programare C

dezactivarea codului de debug o dată ce problemele au fost remediate

compilare condiţionată în funcţie de platforma de rulare

prevenirea includerii multiple a fişierelor antet

În aceste cazuri se foloseşte forma

#ifdef SIMBOL

sau

#ifndef SIMBOL

care testează dacă simbolul SIMBOL este definit, respectiv nu este definit.

Prevenirea includerii multiple a fişierelor 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 fişierului antet, simbolul _NUME_FISIER_ANTET_ nu este definit.

Preprocesorul execută ramura #ifndef în care este definit simbolul _NUME_FISIER_ANTET_ şi care

conţine şi corpul – conţinutul util – al fişierului antet.

La următoarele includeri ale fişierului antet simbolul _NUME_FISIER_ANTET_ va fi definit

iar preprocesorulva sări direct la sfârşitul fişierului antet, după #endif.

Alte instrucţiuni #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

opţiuni #pragma aveţi la dispoziţie consultaţi manualul compilatorului.

#error MESSAGE

La întâlnirea acestei instrucţiuni de preprocesare compilatorul va raporta o eroare, având ca text

explicativ mesajul MESSAGE.

Page 82: 137309474 Invata Limbajul de Programare C

#line NUMBER FILENAME

Această instrucţiune de preprocesare modifică numărul liniei curente în valoarea specificată

de NUMBER. În cazul în care este prezent şi parametru opţional FILENAME este modificat şi numele

fişierului sursă curent.

Astfel, mesajele de eroare şi avertismentele produse de compilator vor folosi numere de linie (şi eventual

nume de fişiere) inexistente, „imaginare”, conform acestei instrucţiuni.