You are on page 1of 140

1. Cum se scrie un program in C?

1.1. Un prim program


1.2. Variabile, expresii si asignari
1.3. Un exemplu de utilizare a variabilelor de tip float
1.4. Initializarea variabilelor
1.5. Folosirea directivei #define
1.6. Folosirea functiilor printf() si scanf()
1.7. Instructiunea "while"
1.8. Stilul de redactare al programelor
1.9. Erori de programare frecvente
1.10. Redirectarea intrarii si iesirii
1.11. Exercitii propuse spre implementare

========
Capitolul 1
========
=========================
Cum se scrie un program (in C) ?
=========================
Programele sunt scrise pentru a instrui masinile sa lucreze cu taskuri specifice sau sa rezolve
probleme specifice. O procedura (descrisa pas cu pas) asociata unui task se numeste algoritm.
Programarea este activitatea de comunicare (codificare) a algoritmilor in calculatoare. Procesul de
programare are (in general) patru pasi:
1. Specificarea task-ului;
2. Descoperirea unui algoritm pentru solutia sa;
3. Codificarea algoritmului in C;
4. Testarea codului.
Un calculator este o masina electronica digitala compusa din trei componente:
1. procesor (central processing unit sau CPU);
2. memorie;
3. dispozitive de intrare/iesire.
Procesorul lucreaza cu instructiuni care sunt inregistrate in memorie. Pe langa aceste instructiuni, in
memorie sunt pastrate si date. Dispozitivele de intrare/iesire iau informatii de la agenti externi catre
masina si produc informatii pentru acesti agenti.
Dispozitivele de intrare sunt (de obicei):
1. tastatura;
2. discheta;
3. banda;
4. CD-ROM.
Dispozitivele de iesire sunt (de obicei):
1. ecranul terminalului;
2. imprimanta;
3. discheta;
4. banda;
5. CD-ROM.

Sistemul de operare consta intr-o colectie de programe speciale si are doua scopuri principale:
1. coordoneaza resursele (memoria, procesorul, imprimanta) masinii. De exemplu, daca un fisier
este creat pe disc sistemul de operare are grija de detaliile localizarii acestuia si memoreaza
numele, lungimea si data creearii;
2. produce instrumente necesare utilizatorilor, multe dintre ele sunt folositoare utilizatorilor C. De
exemplu, doua dintre acestea sunt un editor de texte si un compilator de C.
Un cod C se numeste cod sursa, iar un fisier ce contine un cod sursa se numeste fisier sursa. Dupa
ce a fost creat un fisier sursa, atunci se invoca un compilator de C. De exemplu, pentru sistemele
MS-DOS se poate da comanda:
bc ex1.c sau tcc ex1.c
iar pentru unele sisteme UNIX:
cc ex1.c
Daca nu sunt erori in ex1.c, atunci aceasta comanda produce fisierul executabil asociat (ex1.exe).
Acum acesta poate fi rulat (executat) cu numele sau (ex1 sau ex1.exe).
In continuare, vom preciza trei dintre trasaturile procesului de compilare (mentionam ca asupra
acestor notiuni, vom reveni cu detalii interesante intr-un capitol viitor):
1. invocarea preprocesorului;
2. invocarea compilatorului;
3. invocarea incarcatorului.
Preprocesorul modifica o copie a codului sursa prin includerea altor fisiere si facand alte schimbari.
Compilatorul traduce aceasta in cod obiect folosit de incarcator pentru producerea fisierului
executabil final. Fisierul care contine codul obiect se numeste fisier obiect. Fisierele obiect, spre
deosebire de fisierele sursa, nu se pot intelege asa usor. Cand spunem deci compilare, de fapt
invocam preprocesorul, compilatorul si apoi incarcatorul.
Dupa ce scriem un program, acesta trebuie compilat si testat. Daca sunt necesare modificari, atunci
codul sursa trebuie editat din nou. Asadar, partea proceselor de programare consta din ciclul:

editare ---> compilare ---> executie


^^ | |
| |__________ | |
|_______________________|
-----------------------
Un prim program
-----------------------
O sa incepem cu un exemplu de program C necesar pentru tiparirea unui sir pe ecran.

#include <stdio.h>

main()
{
printf("azi am inceput laboratoarele de C\n");
}

Folosind un editor de texte, presupunem ca am scris si salvat acest fisier numit "ex1.c". Cand
programul este compilat si rulat atunci va apare pe ecran sirul:

azi am inceput laboratoarele de C


-------------
Explicatii:
-------------
1. #include <stdio.h>
Liniile care incep cu "#" se numesc directive de preprocesare (precompilare). Acestea comunica cu
preprocesorul. Aceasta directiva "#include" determina preprocesorul sa includa o copie a fisierului
header "stdio.h" in acest punct al codului. Parantezele unghiulare din "<stdio.h>" indica ca acest
fisier se gaseste in biblioteca C (pentru compilatorul Borland 3.1 pentru MS-DOS, acesta se gaseste
in subdirectorul BC31/INCLUDE). Am inclus acest fisier deoarece acesta contine informatii despre
functia "printf()".
2. main()
Fiecare program are o functie numita "main", care se executa intai. Parantezele ce urmeaza dupa
"main" indica compilatorului ca aceasta este o functie.

3. {
Acolada stanga incepe corpul fiecarei functii. O acolada dreapta corespunzatoare trebuie sa fie la
sfarsitul functiei.

4. printf()
Sistemul C contine o biblioteca standard de functii care poate fi utilizata in programe. "printf()" este
o functie din biblioteca care tipareste pe ecran. Aceasta este o functie descrisa in biblioteca
"stdio.h" (care se numeste prototipul functiei "printf()").

5. "azi am inceput laboratoarele de C\n"


Un sir constant in C consta dintr-un numar de caractere incadrate intre ghilimele. Acest sir este un
argument al functiei "printf()". Caracterele \n de la sfarsitul sirului (se citesc "backslash n")
reprezinta, de fapt, un singur caracter numit "newline".

O forma echivalenta a programului de mai sus:

#include <stdio.h>
main()
{
printf("azi am inceput ");
printf("laboratoarele de C\n");
}

--------------
Observatii:
--------------
1. Primul "printf" contine la sfarsit un spatiu.
--------------------------------------
Variabile, expresii si asignari
--------------------------------------
In urmatorul exemplu vom ilustra folosirea variabilelor pentru manipularea valorilor intregi.
Variabilele sunt folosite sa memoreze valori. Din moment ce diferite tipuri de variabile sunt folosite
sa memoreze diferite tipuri de date, tipul fiecarei variabile trebuie specificat.
Pentru a ilustra aceasta idee vom calcula cate ore si minute contin un anumit numar de zile.
Algoritmul ar fi:
1. asigneaza un numar de zile unei variabile;
2. calculeaza numarul de ore si memoreaza-l intr-o variabila;
3. calculeaza numarul de minute si memoreaza-l intr-o variabila;
4. afiseaza numarul de zile, ore si minute pe ecran.
Urmatorul program scris in C reprezinta implementarea algoritmului precedent:

#include <stdio.h>
main()
{
int zile, ore, minute;
zile=7;
ore=24*zile;
minute=60*ore;
printf("O saptamana are %d ore, %d minute.\n",ore, minute);
}
Cand compilam si rulam acest program, pe ecran va apare mesajul:

O saptamana are 168 ore, 10080 minute.


-------------
Explicatii:
-------------
1. Linia
int zile, ore, minute;
reprezinta o declaratie de variabile. Variabilele zile, ore, minute sunt declarate de tip "int", unul
dintre cele mai importante tipuri din C. O variabila de tip "int" poate lua o valoare intreaga intre
-32678 si 32677. Toate variabilele dintr-un program trebuie declarate inainte de a fi utilizate.
Declaratiile, la fel ca si instructiunile, au la sfarsit ";".

2. Linia
zile=7;
reprezinta o instructiune de atribuire (sau asignare). Semnul "=" este operatorul de asignare de baza
in C. Valoarea expresiei din partea dreapta a simbolului "=" este atribuita variabilei din partea
stanga.

3. Instructiunea
printf("O saptamana are %d ore, %d minute.\n",ore, minute);
este similara celei prezentate in exemplul precedent, dar are trei argumente. Primul argument,
intotdeauna un sir de caractere, se numeste sir de control. Aici, intalnim specificarea de conversie
(care se mai numeste format) "%d". Formatele "%d" determina tiparirea valorilor expresiilor
corespunzatoare (al doilea si al treilea argument) in formatul intregilor zecimali. Asadar, primul
format "%d" corespunde cu valoarea variabilei "ore", iar cel de-al doilea format "%d" cu valoarea
variabilei "minute".

In C, toate variabilele trebuie declarate inainte de a fi utilizate in expresii si instructiuni. Forma


generala a unui program simplu este:

directive de precompilare

main()
{
declaratii
instructiuni
}

Un nume de variabila, numit si identificator, consta dintr-o secventa de litere, cifre si "underscore",
dar fara a incepe cu cifra. Cuvintele cheie, numite si cuvinte rezervate, nu pot fi utilizate ca nume de
variabile. Exemple de cuvinte cheie: char, int, float.

Operatorii binari
+ - * / %
sunt folositi pentru adunare, scadere, inmultire, impartire sau modul.

Exemple: 5 % 2 = 1 si 7 % 4 = 3.
Evident, in expresia a % b, b nu trebuie sa fie zero, din moment ce nu se poate face impartirea cu
zero.
----------------------------------------------------------------
Un exemplu de utilizare a variabilelor de tip float
----------------------------------------------------------------
#include <stdio.h>

main()
{
float x, y;

x = 1.0;
y = 2.0;
printf("Suma dintre x si y este %f.\n", x+y);
}

Pe ecran se va afisa

Suma dintre x si y este 3.000000.


--------------
Observatii:
--------------
1. Linia
float x, y;
semnifica declararea variabilelor x si y de tip "float" (deci de tip real). In realitate sunt numere
rationale din intervalul
[-10^{308},-10^{-308}] U [10^{-308},10^{308}]
2. Linia
printf("Suma dintre x si y este %f.\n", x+y);
are doua argumente si reprezinta o tiparire la ecran. De remarcat, ca spre deosebire de exemplele
precedente (unde foloseam formatul %d), aici folosim formatul pentru numere reale, care este "%f".
3. Precizarea tipului variabilelor este esential. De exemplu, daca, sa zicem, ca x=7.0 si y=2.0, sunt
declarati ca fiind de tip float, atunci x/y se evalueaza la 3.5. Daca, insa x=7 si y=2, sunt declarati ca
fiind de tip int, atunci x/y se evalueaza la 3 (ramane doar partea intreaga).
----------------
Initializarea
----------------
Initializarea variabilelor se poate face si cand se declara acestea. De exemplu, putem scrie:
char c='A';
int i=1;
Putem astfel modifica programul precedent, inlocuind liniile
int zile, ore, minute;
zile=7;
cu
int zile=7, ore, minute;
--------------
Observatii:
--------------
1. De obicei, pentru initializarea unei variabile se folosesc constante sau expresii constante. Se pot
insa folosi si variabile care au deja valoarea precizata. De exemplu, urmatoarea secventa de
program este corecta:
int zile=7, ore=zile * 24, minute=ore * 60;
------------------------------------
Folosirea directivei #define
------------------------------------
Reamintim ca in procesul de compilare a programelor C, intai este invocat preprocesorul. De
exemplu, pot fi incluse fisiere, sau anumite siruri de caractere specificate pot fi modificate in alte
siruri. Directivele de preprocesare incep cu caracterul # (in C traditional, acesta trebuie pus pe
prima coloana, pe cand in ANSI C poate fi precedat de spatii). Se recomanda scrierea # pe prima
coloana, iar a directivelor de precompilare la inceputul programului. Iata cateva exemple de folosire
a directivei "#define":

#define LIMIT 100


#define PI 3.14159
--------------
Observatii:
--------------
1. Daca aceste directive de preprocesare apar la inceputul fisierului, atunci in momentul compilarii
preprocesorul schimba toate aparitiile identificatorului LIMIT la 100 si a lui PI cu 3.14159.
Singurele care raman neschimbate sunt sirurile constante. De exemplu, preprocesorul va schimba
printf("PI = %f\n", PI); in printf("PI = %f\n", 3.14159);
Deoarece identificatorul PI se va inlocui peste tot (cu exceptia sirurilor constante) in 3.14159, atunci
acesta se va numi constanta simbolica.
2. Directiva #define poate aparea oriunde in program, dar ea afecteaza numai liniile care urmeaza
acesteia.
3. Prin conventie, identificatorii care trebuie schimbati de preprocesor se scriu cu majuscule.
--------------------------------------------------
Avantaje ale folosirii directivei #define
--------------------------------------------------
1. Lizibilitate marita. Se refera la citirea si intelegerea rapida a fisierului sursa (PI stim ce inseamna
si ne amintim ca este 3.ceva, deci nu trebuie sa scriem de fiecare data valoarea sa);
2. Schimbarile ulterioare ale unor valori constante se face foarte usor. De exemplu, vrem sa
modificam valoarea lui LIMIT la 10000. In locul liniei
#define LIMIT 100
scriem
#define LIMIT 10000
Daca nu am fi folosit acest mod de definire a constantei LIMIT, atunci ar fi trebuit sa modificam
peste tot in program 100 cu 10000.
-----------------------------------------------
Folosirea functiilor printf() si scanf()
-----------------------------------------------
Functiile de tiparire, respectiv de citire, "printf()" si "scanf()" au urmatoarele argumente:
sir_de_control si celelalte_argumente
unde sir_de_control este un sir care poate contine specificatii de conversie, sau formate. O
specificare de conversie incepe cu caracterul % si se termina cu caracterul de conversie. De
exemplu, in formatul %d, litera "d" este caracterul de conversie.
---------------------------
Folosirea lui printf()
---------------------------
Reamintim ca formatul %d este folosit pentru scrierea valorii unei expresii ca un intreg zecimal. In
mod similar:
- %c este folosit pentru tiparirea unei expresii ca un caracter
- %f este folosit pentru tiparirea unei expresii reale
- %s este folosit pentru tiparirea unui sir de caractere
-----------
Exemplu:: Fie instructiunea:
-----------
printf("Multime de argumente: %s %d %f %c%c\n","one",2,2.33,'G','O');

Argumentele lui "printf()" sunt separate de virgula, deci avem sase argumente. Primul argument
este sirul de control. Obtinem corespondenta:
%s <---> "one"
%d <---> 2
%f <---> 2.33
%c <---> 'G'
%c <---> 'O'

Cand se executa programul ce contine aceasta instructiune, obtinem:


Multime de argumente: one 2 2.330000 GO
--------------
Observatii:
--------------
1. Daca instructiunea "printf()" contine prea multe caractere, atunci se poate scrie aceasta pe mai
multe linii, separate prin virgula. De exemplu, putem scrie:
printf("%s%s\n",
"Aceasta instructiune se va scrie ",
"pe o linie de text foarte lunga.\n");
Tabelul de mai jos descrie cum caracterele de conversie afecteaza argumentele corespunzatoare.
printf()
|--------------------------------------------------------------------------------|
| Caracter | |
| de | Cum este tiparit argumentul corespunzator ? |
| conversie | |
|--------------------------------------------------------------------------------|
| c | ca un caracter |
| d | ca un intreg zecimal |
| e | ca un numar in virgula flotanta in notatia stiintifica |
| f | ca un numar in virgula flotanta |
| g | in format e sau f (alegand cel mai scurt dintre ele) |
| s | ca un sir |
|---------------------------------------------------------------------------------|
Cand un argument este tiparit, locul unde este tiparit se numeste campul sau, iar numarul de
caractere ale acestui camp se numeste lungimea campului. Aceasta lungime poate fi specificata intr-
un format ca un intreg plasat intre caracterul % si caracterul de conversie. De exemplu,
instructiunea
printf("%c%3c%7c\n", 'A', 'B', 'C');
va tipari
A B C
Pentru numerele in virgula flotanta, putem controla precizia (numarul de cifre zecimale), la fel ca
lungimea campului. Forma generala a formatului este
%m.nf
si semnifica ca m este lungimea campului, iar n precizia. Formatul %mf specifica doar lungimea
campului, iar formatul %.nf numai precizia. De exemplu, instructiunile:
printf("Numere1: %.1f %.2f %.3f\n", 1.0, 2.0, 3.0);
printf("Numere2: %7.1f %7.2f %7.3f\n", 4.0, 5.0, 6.0);
vor avea ca efect afisarea:
Numere1: 1.0 2.00 3.000
Numere2: 4.0 5.00 6.000
--------------------------
Folosirea lui scanf()
--------------------------
Functia "scanf()" este asemanatoare cu "printf()", dar este folosita pentru intrari in loc de iesiri.
Primul sau argument este un sir de control care are formatele corespunzatoare cu variatele moduri
de interpretare a sirurilor de intrare. Dupa sirul de control urmeaza adresele variabilelor. Adresa
unei variabile este locul din memorie unde este memorata variabila (vom reveni in capitolele
viitoare). Simbolul "&" reprezinta operatorul de adresa. De exemplu,
scanf("%d", &x);
formatul %d implica interpretarea caracterelor tiparite la intrare ca un intreg zecimal, si apoi
memorarea valorii variabilei la adresa lui x.
Tabelul de mai jos descrie efectele caracterelor de conversie din formate folosite de functia scanf().
scanf()
|-------------------------------------------------------------------------|
| Caracter | |
| de | La ce caractere se face convertirea ? |
| conversie | |
|-------------------------------------------------------------------------|
| c | la un caracter |
| d | la un intreg zecimal |
| f | la un numar in virgula flotanta (float) |
| lf | la un numar in virgula flotanta (double) |
| Lf | la un numar in virgula flotanta (long double) |
| s | la un sir |
|-------------------------------------------------------------------------|
-----------
Atentie ! Functia "prinf()" foloseste formatul %f pentru tiparirea numerelor
----------- float si double, pe cand "scanf()" foloseste formatul %f pentru
citirea unui float si %lf pentru citirea unui double.
------------
Exemplu:: Aria unui cerc.
------------
#include <stdio.h>
#define PI 3.141592653589793
main()
{
double raza;
printf("\n%s\n\n%s",
"Acest program calculeaza aria cercului",
"Dati raza:");
scanf("%lf", &raza);
printf("\n%s\n%s%.2f%s%.2f%s%.2f\n%s%.5f\n\n",
"Aria = PI * raza * raza",
" = ", PI, " * ", raza, " * ", raza,
" = ", PI * raza * raza);
}

Presupunem ca la executia programului introducem raza egala cu 2.333. Atunci vor apare pe ecran:
Acest program calculeaza aria cercului

Dati raza: 2.333

Aria = PI * raza * raza


= 3.14 * 2.33 * 2.33
= 17.09934

Daca am calcula separat (pe hartie), am obtine Aria = 3.14 * 2.33 * 2.33 = 17.046746, numar care
nu coincide cu cel furnizat de calculator. Justificarea este aceea ca PI si raza sunt tiparite doar cu
doua zecimale, pe cand valorile lor sunt pastrate in memorie cu precizie mai mare.
-----------------------------
Instructiunea "while"
-----------------------------
Instructiunea while face parte din categoria actiunilor repetitive. Pentru a intelege aceasta
instructiune, vom face un exemplu de adunare a numerelor de la 1 la 10.
-------------
Exemplu::
-------------
#include <stdio.h>

main()
{
int i=1, suma=0;

while (i<=10)
{
suma = suma + i;
i = i + 1;
}
printf("Suma primelor 10 numere este %d\n",suma);
}
-------------
Explicatii:
-------------
1. La linia
int i=1, suma=0;
se declara variabilele i si sum de tip int si sunt initializate cu 1 si 0, respectiv.
2. Constructia
while (i<=10)
{
suma = suma + i;
i = i + 1;
}
reprezinta o instructiune while (sau iteratie while). Mai intai, se evalueaza expresia i<=10. Cum
valoarea initiala a lui i este egala cu 1, rezulta ca se vor executa instructiunile dintre acolade. Astfel,
variabila suma va fi asignata cu vechea valoare a lui suma la care se adauga valoarea lui i. Deci,
suma se evalueaza la 1. Apoi, variabila I se evalueaza la suma dintre vechea valoare a lui i (i=1) si
1, deci este egala cu 2. In acest moment, executia revine la inceput adica evaluam expresia i<=10.
Cum valoarea lui i este 2, rezulta ca se va executa iar corpul lui while. La sfarsitul acestei iteratii,
suma este evaluata la 1+2, iar i la 3. Se observa usor ca i<=10 este tot adevarata, deci se va executa
din nou corpul lui while. La sfarsitul celei de-a treia iteratii, sum este evaluata la 1+2+3, iar i la 4.
Procesul continua pana cand valoarea lui i este 11, care implica falsitatea expresiei i<=10. Astfel se
iese din bucla while.
3. Instructiunea
printf("Suma primelor 10 numere este %d\n", suma);
va afisa mesajul Suma primelor 10 numere este 55.
4. Formatul general al instructiunii while este
while (expresie)
instructiune
unde instructiune poate fi o singura instructiune sau un grup de instructiuni delimitate prin { si }
(care se mai numeste si instructiune compusa).
-----------------------------------------------
Generalizarea problemei precedente
-----------------------------------------------
Dorim sa citim mai multe numere (fara a sti aprioric numarul lor si care sunt acestea) si dorim sa
afisam suma lor.
Consideram urmatorul algoritm cu patru pasi:
1. Initializarea a doua variabile contor si suma;
2. Afisarea unor mesaje utile citirii numerelor;
3. Citirea repetata a numerelor, incrementarea variabilei contor si adunarea numarului citit la suma;
4. Afisarea celor doua valori ale variabilelor.
Instructiunea while este una din cele trei constructii existente in C menite sa descrie actiuni
repetitive. In solutia noastra, utilizam valoarea returnata de functia "scanf()" pentru a controla
instructiunea while. Consideram urmatorul cod C:
#include <stdio.h>
main()
{
int contor = 0;
float suma = 0.0, x;

printf("Suma urmatoarelor numere va fi calculata\n");


printf("Dati numerele:\n");
while (scanf("%f", &x)==1)
{
contor = contor +1;
suma = suma +x;
}
printf("\n%s%5d\n%s%12f\n\n",
"Numarul de numere: ", contor,
"Suma lor: ", suma);
}
--------------
Explicatii:
--------------
1. scanf("%f", &x)==1
Simbolul == reprezinta operatorul de egalitate. Expresia a==b intoarce true daca valoarea lui a este
egala cu valoarea lui b. De exemplu, 1==1 intoarce true, 2==3 intoarce false. Functia "scanf()" are
rolul de a citi caractere scrise de utilizator, sa le converteasca la float, si sa plaseze aceasta valoare
la adresa lui x. Daca totul se desfasoara cu succes, atunci scanf() intoarce valoarea 1, adica true.
Daca din anumite motive, procesul de conversie esueaza, atunci se intoarce valoarea 0 (deci false).
Daca nu mai introducem nici o data (Control^z in MD-DOS, CR urmat de Control^d in UNIX),
atunci scanf() va intoarce valoarea -1 (deci tot false).
2. while (scanf("%f", &x)==1)
{
contor = contor +1;
suma = suma +x;
}
Dupa cum am vazut mai sus, bucla while se executa atata timp cat scanf("%f", &x)==1 se evalueaza
la true. Iteratia se incheie cand tastam Control^z sau ceva ce nu se poate converti la float, de
exemplu 'a' sau CR. La fiecare executie a buclei se incrementeza variabila contor, iar valoarea
variabilei suma creste cu valoarea lui x (citita de la tastatura).
3. printf("\n%s%5d\n%s%12f\n\n",
"Numarul de numere: ", contor,
"Suma lor: ", suma);
Presupunem ca executam acest program pentru numerele
1.1 2.02 3.003 4.0004 5.00005
Pe ecran va apare rezultatul:
Numarul de numere: 5
Suma lor: 15.123449
---------------
Observatii:
---------------
1. Daca numaram spatiile, observam ca valoarea lui contor a fost tiparita pe un camp de 5 caractere,
iar suma pe 12 caractere. Aceasta este cauzata de formatele "%5d" si "%12f". Retineti ca tiparirea
zecimalelor pentru suma este gresita de la a treia zecimala.
---------------------------------------------
Stilul de redactare al programelor
---------------------------------------------
Un stil bun de scriere a codului este esential pentru arta programarii. Aceasta faciliteaza citirea,
scrierea si intretinerea programelor. Un stil bun foloseste:
1. spatii goale si comentarii, astfel incat codul este usor de citit si de inteles;
2. utilizarea indentarii este cruciala, care indica cu precizie structurile de control. De exemplu,
in constructia
while (expresie)
instructiune
indentarea instructiunii indica ca executia acesteia este sub controlul iteratiei while;
3. alegerea de nume sugestive pentru variabile;
4. corespondenta dintre acolade. De exemplu, urmatorul program este scris in stilul "Bell Labs
industrial programming style" (#,{,},m pe prima coloana).

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

#define GO "Start"

main()
{

}
---------------
Observatii:
---------------
1. Programatorii incepatori uneori cred ca vor "sparge" piata cu stilul lor propriu de redactare a
programelor. Atentie ! Utilizati strategia care este deja in uz.
-----------------------------------------
Erori de programare frecvente
-----------------------------------------
Mai ales la inceputul invatarii programarii pe calculator, se fac multe erori simple, cum ar fi:
1. neinchiderea sirului cu ghilimele drepte. Cand compilatorul intalneste ghilimeaua stanga, atunci
incepe sa colecteze toate caracterele care urmeaza spre a forma un sir de caractere, pana intalneste
ghilimeaua dreapta. Daca aceasta nu exista, atunci compilatorul da mesajul de eroare, de tipul:
Unterminated string or character constant
2. scrierea gresita a numelui variabilelor;
3. nedeclararea lor.
In general, compilatorul da un mesaj de eroare potrivit, alteori din cauza interpretarii separate, nu.
De exemplu, scriem gresit "prinf()" in loc de "printf()". Atunci compilatorul va da un mesaj prin
care ne informeaza ca nu gaseste functia "prinf()". Deci, el nu isi da seama de omiterea unei litere,
ci o interpreteaza in mod diferit.
4. daca dorim sa citim o variabila de tip double, atunci in functia "scanf()" trebuie sa specificam
formatul "%lf";
5. faptul ca uneori uitam ca in formatul "%m.nf" pentru afisarea cu virgula mobila, m semnifica
lungimea campului. De exemplu, pentru a specifica doua zecimale la dreapta si trei la stanga, nu
trebuie sa scriem %2.3f, ci %6.3f deoarece se numara si punctul zecimal;
6. poate cea mai frecventa greseala cand se foloseste scanf() este uitarea operatorului de adresa.
Daca, de exemplu, scriem
scanf("%d%d", a, b) in loc de scanf("%d%d", &a, &b)
atunci compilatorul nu se va "prinde" de eroare. Bineinteles, ca vom obtine rezultate eronate (run-
time error) care sunt ceva mai greu de corectat.
---------------------------------------
Redirectarea intrarii si iesirii
---------------------------------------
Putem citi valorile variabilelor dintr-un fisier, nu neaparat de la tastatura. De exemplu, fisierul
nostru executabil se numeste "ex1.exe". Pentru a extrage datele din fisierul de intrare "in.dat", dam
comanda
ex1.exe < in.dat
Putem, de asemenea, sa scriem valorile unor variabile intr-un fisier extern, sa zicem "out.dat".
Astfel dam comanda
ex1.exe > out.dat
Evident, daca se doreste atat citirea unor variabile din fisier, cat si scrierea rezultatelor in alt fisier,
atunci se va da comanda
ex1.exe < in.dat > out.dat
-------------
Exemplu::
-------------
#include <stdio.h>

main()
{
char ch;

while (scanf("%c", &ch) ==1)


{
printf("%c", ch);
printf("%c", ch);
}
}
-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Presupunem ca dispunem de rezultatele din anul curent relative la cursul de schimb valutar dintre
leu, dolar, marca si franc. Sa se scrie un program C care deseneaza (cu caractere ASCII) graficul
evolutiei raportului leu/valuta, unde valuta={dolar, marca, franc}. Apoi, desenati graficul raportului
dolar/marca si marca/franc.
2. Scrieti un program C care primeste la intrare un numar de secunde, si intoarce numar maxim de
ore, de minute, de secunde care este echivalent ca timp. De exemplu, 7384 secunde este echivalent
cu 2 ore, 3 minute si 4 secunde.
3. Folosind o bucla while, scrieti un program C care calculeaza al n-lea termen din sirul din
Fibonacci. Reamintim ca sirul lui Fibonacci este dat de recurenta liniara de ordin 2:
a_1=a_2=1;
a_{n+2}=a_{n+1}+a_n, n>=1
Atomi lexicali, operatori, sistemul C
2.1. Caractere si atomi lexicali
2.2. Avantajele folosirii comentariilor
2.3. Cuvinte rezervate
2.4. Identificatori
2.5. Constante
2.6. Siruri constante
2.7. Operatori si semne de punctuatie
2.8. Operatorii de precedenta si asociativitate
2.9. Operatorii de incrementare si decrementare
2.10. Operatori de asignare
2.11. Sistemul C
2.12. Exercitii propuse spre implementare

=========
Capitolul 2
=========
===========================
Atomi lexicali, operatori, sistemul C
===========================

Ca si alte limbaje, C are un alfabet si reguli pentru scrierea programelor corecte folosind semne de
punctuatie. Aceste reguli formeaza sintaxa limbajului C. Compilatorul C are rolul de a testa daca un
program C este corect. Daca sunt erori, atunci va afisa o lista de mesaje de eroare si se va opri. Daca
nu sunt erori, atunci compilatorul va "traduce" acest program in cod obiect, folosit de incarcator
pentru producerea codului executabil.
Mai intai compilatorul imparte multimea caracterelor (programul sursa) in atomi lexicali, care
reprezinta vocabularul de baza al limbajului. In ANSI C (ANSI = American National Standards
Institute) sunt sase tipuri de atomi lexicali (care se mai numesc si elemente lexicale sau unitati
lexicale):
1. cuvinte rezervate (sau cheie);
2. identificatori;
3. constante;
4. siruri constante;
5. operatori;
6. semne de punctuatie.

----------------------------------
Caractere si atomi lexicali
----------------------------------
In fapt, un program C este o secventa de caractere. Caracterele permise in programele C sunt:
1. litere mici : a b ... z
2. litere mari : A B ... Z
3. cifre : 0 1 ... 9
4. alte caractere: d f * / = ( ) { } [ ] < > ' "
!@#$%&_|^~\.,;:?
5. spatii : blank, newline si tab
---------------
Comentarii
---------------
Comentariile sunt siruri de caractere cuprinse intre /* si */. Comentariile nu reprezinta atomi
lexicali. Compilatorul va traduce comentariile intr-un singur caracter spatiu, de aceea comentariile
nu fac parte din codul executabil.
-----------
Atentie ! Pentru a verifica aceasta, puteti citi lungimea unui cod executabil (fara comentarii) si
----------- apoi sa comparati lungimea codului executabil obtinut dupa o noua compilare (cu
comentarii).

-------------------------------
Exemple: de comentarii:
-------------------------------
1. /* un comentariu */
2. /** al doilea comentariu **/
3. /*****/
4. /*
* Al patrulea
* comentariu
*/
5. /**************
* Al cincilea *
* comentariu *
**************/

--------------------------------------------
Avantajele folosirii comentariilor:
--------------------------------------------
1. Principalul scop este usurarea documentarii ulterioare. Scopul documentarii este explicarea clara
a folosirii programelor;
2. Uneori un comentariu poate contine informatii ce argumenteaza demonstratia corectitudinii
acelui algoritm;
3. Sfat ! Folositi comentariile in timpul introducerii textului programului.

-----------------------
Cuvinte rezervate
-----------------------
Cuvintele rezervate (cheie) au un inteles strict insemnand un atom individual. Ele nu pot fi
redefinite sau utilizate in alte contexte. Iata lista lor:

auto do goto signed unsigned


break double if sizeof void
case else int static volatile
char enum long struct while
const extern register switch
continue float return typedef
default for short union
Anumite implementari pot contine si alte cuvinte rezervate:
asm cdecl far huge interrupt near pascal
Comparativ cu alte limbaje de programare, C are un numar mic de cuvinte rezervate. Ada, de
exemplu, are 62 cuvinte rezervate. Aceasta este o caracteristica a limbajului C de a avea doar cateva
simboluri speciale si cuvinte rezervate.

-----------------
Identificatori
-----------------
Un identificator este un atom lexical compus din secventa de litere, cifre sau underscore ("_") cu
restrictia ca primul caracter este o litera sau underscore. In multe implementari C, se face distinctie
dintre litere mici si mari. In general, se obisnuieste ca identificatorii sa fie scrisi cu nume sugestive
care sa usureze citirea si documentarea programului.
------------
Exemple::
------------
1. k, _id, contor, un_identificator sunt identificatori;
2. gresit#unu, 100_gresit_doi, -plus nu sunt identificatori.
Identificatorii sunt creati pentru a da nume unice pentru diverse obiecte dintr-un program.
Cuvintele rezervate pot fi privite ca fiind identificatori. Identificatori precum "printf()" sau "scanf()"
sunt deja cunoscuti sistemului C ca fiind functii de intrare/iesire.
O diferenta majora dintre sistemele de operare si sistemele C o reprezinta lungimea admisa pentru
numele identificatorilor. Astfel, pentru unele sisteme vechi, este acceptat un identificator al carui
nume are mai mult de 8 caractere, dar numai primele 8 sunt semnificative. De exemplu,
identificatorul _23456781 este privit la fel ca _23456782.
In ANSI C, primele 31 de caractere sunt luate in considerare.

---------
Atentie !
---------
Identificatorii care incep cu underscore pot fi confundati cu numele variabilelor sistem. De
exemplu, identificatorul _iob declarat in biblioteca <stdio.h> este folosit pentru numele unui vector
de structuri. Daca un programator foloseste un identificator cu acelasi nume, dar pentru alte scopuri,
atunci ori se va semnala o eroare aparent necunoscuta, ori (si mai rau) compilatorul se va comporta
ciudat. Recomandarea este: Nu folositi identificatori care incep cu underscore.

-------------
Constante
-------------
C manipuleaza diferite tipuri de valori. Numere precum 0 si 17 sunt exemple de constante intregi,
iar numere precum 1.0 si 3.14159 sunt exemple de constante numere zecimale. Ca si multe alte
limbaje, C trateaza constantele "int" si "float" in mod diferit. Constantele caracter sunt foarte
apropiate de tipul "int" (vom reveni). Un caracter special l-am si intalnit deja. Este vorba de '\n',
care se mai cheama "secventa escape". In traducere libera, ar insemna "evadare a lui n din intelesul
uzual". In fapt, el este folosit pentru a trece cursorul curent la linie noua (newline).
Constantele de intregi, reali, caractere si enumerare sunt toate colectate de compilator ca fiind
atomi lexicali. Din cauza limitelor impuse de memoria masinilor, unele constante care pot fi
exprimate sintactic nu pot fi disponibile pe o masina anume. De exemplu, numarul
123456789000000000000 nu poate fi memorat ca fiind un intreg.

---------------------
Siruri constante
---------------------
O secventa de caractere incadrate intre ghilimele, de exemplu "abc", este un sir constant. Este
inteles de compilator ca fiind un singur atom lexical. In capitolele ulterioare, vom vedea ca de fapt
sirurile constante se memoreaza ca siruri de caractere. Sirurile constante sunt tratate mereu diferit
fata de constantele de tip caracter. De exemplu, "a" nu este totuna cu 'a'.
De mentionat ca ghilimeaua " reprezinta un singur caracter, nu doua. De aceea, daca dorim sa apara
intr-un sir constant, atunci ea trebuie precedata de \ (backslash). Daca dorim ca intr-un sir sa apara \,
atunci trebuie sa-l precedam tot cu \ (devenind astfel \\).

------------
Exemple::
------------
1. "sir text"
2. "" /* sirul vid */
3. " " /* sir de spatii */
4. " a = b + c " /* nu se executa nimic */
5. " /* acesta nu este un comantariu */ "
6. " un sir ce contine ghilimea \" "
7. " un sir ce contine backslash \\ "
8. /* "gresit" */ /* nu este un sir */
9. "gresit
doi" /* nici asta nu este sir */
Doua siruri constante care sunt separate doar printr-un spatiu vor fi concatenate de compilator intr-
unul singur. De exemplu,
"abc" "def" este echivalent cu "abcdef"
Aceasta este o trasatura a limbajului ANSI C, nefiind disponibil in C traditional. Sirurile constante
sunt tratate de compilator ca atomi lexicali. Ca si alte constante, compilatorul va rezerva spatiu in
memorie pentru pastrarea sirurilor constante.

-------------------------------------------
Operatori si semne de punctuatie
-------------------------------------------
In C, exista multe caractere speciale cu inteles specific. De exemplu, operatorii aritmetici
+ - * / %
reprezinta adunarea, scaderea, inmultirea, impartirea, modulul, respectiv. Reamintim (pentru
bubulici) ca a % b inseamna restul impartirii intregi a lui a la b (notatie matematica: a mod b; a nu
se confunda modul cu valoarea absoluta). De exemplu, 5 % 3 are valoarea 2. Atentie la numere
intregi negative (Vezi Exercitiul 1).
Anumite simboluri au intelesuri dependente de context. Consideram simbolul % din instructiunile
printf("%d", a); si a = b % 7;
Primul simbol % este un format de scriere, pe cand al doilea reprezinta operatorul modul.
In exemplul de mai jos, parantezele (,) se folosesc atat pentru a preciza ca () este un operator
("main" reprezinta numele unei functii), cat si ca semne de punctuatie.
main()
{
int a, b = 2, c = 3;
a = 17 * (b + c);
...
}
Anumite caractere speciale sunt folosite in multe contexte. Fie espresiile
a+b ++a a += b
Ele folosesc caracterul +, dar ++ este un singur operator, la fel ca si +=.

------------------------------------------------------
Operatorii de precedenta si asociativitate
------------------------------------------------------
Operatorii au reguli de precedenta si asociativitate care implica evaluarea expresiilor. Din moment
ce expresiile din interiorul parantezelor se evalueaza mai intai, este clar ca parantezele sunt folosite
pentru a preciza care operatii se fac mai intai. Consideram expresia
1+2*3
In C, operatorul * are prioritate (precedenta) mai mare decat +, deci se va face intai inmultirea apoi
adunarea. Deci valoarea expresiei este 7. O expresie echivalenta este
1 + (2 * 3)
Pe de alta parte, expresia (1 + 2) *3 este diferita; ea are valoarea 9.
Consideram acum expresia 1 + 2 - 3 + 4 - 5. Operatorii + si - au aceeasi precedenta, deci se va
folosi regula de asociativitate la stanga. Astfel (((1 + 2) - 3) + 4) -5 este o expresie echivalenta.
In continuare vom prezenta un tabel in care precizam regulile de precedenta si asociativitate pentru
cativa operatori din C.
|----------------------------------------------|----------------------------|
| Operatori | Asociativitate |
|----------------------------------------------|----------------------------|
| () ++ (postfix) -- (postfix) | de la stanga la dreapta |
|----------------------------------------------|----------------------------|
| +(unar) -(unar) ++(prefix) --(prefix) | de la dreapta la stanga |
|----------------------------------------------|----------------------------|
| * / % | de la stanga la dreapta |
|----------------------------------------------|----------------------------|
| + - | de la stanga la dreapta |
|----------------------------------------------|----------------------------|
| = += -= *= /= etc. | de la dreapta la stanga |
|----------------------------------------------|----------------------------|
Toti operatorii de pe o linie (de exemplu, *, /, %) au aceeasi prioritate intre ei, dar au prioritate mai
mare decat cei ce apar in liniile de mai jos.
Operatorii + si - pot fi si binari si unari. De remarcat ca cel unar are prioritate mai mare. De
exemplu, in expresia
-a*b-c
primul operator - este unar, pe cand al doilea binar. Folosind regulile de precedenta, se vede ca
aceasta este echivalenta cu
((- a) * b) - c

----------------------------------------------------------
Operatorii de incrementare si decrementare
---------------------------------------------------------
Operatorii de incrementare si de decrementare (++, --) au o prioritate foarte mare (dupa cum se
poate vedea in tabelul de mai sus) si se pot asocia atat de la dreapta la stanga, cat se de la stanga la
dreapta. Operatorii ++ si -- se pot aplica variabilelor, dar nu si constantelor. Mai mult, ei pot apare
ca notatie prefixata, cat si postfixata. De exemplu, putem avea ++i si contor++, dar nu putem avea
167++ sau ++(a * b - 1).
Fiecare din expresiile ++i si i++ au o valoare; mai mult fiecare cauzeaza incrementarea valorii
variabilei i cu o unitate. Diferenta este:
1. expresia ++i va implica intai incrementarea lui i, dupa care
expresia va fi evaluata la noua valoare a lui i;
2. expresia i++ va implica evaluarea sa la valoarea lui i, dupa
care se va incrementa i.

------------
Exemplu::
------------
int a, b, c = 0;
a = ++c;
b = c++;
printf("a=%d b=%d c=%d ++c=%d\n", a, b, c, ++c);

Intrebare: Ce se va tipari la ecran ?


Intr-un mod similar, --i va implica decrementarea valorii lui i cu 1, dupa care expresia --i va avea
noua valoare a lui i, pe cand i-- se va evalua la valoarea lui i, dupa care i se va decrementa cu 1.
Retineti deci ca, spre deosebire de + si -, operatorii ++ si -- vor determina schimbarea valorii
variabilei i din memorie. Se mai spune ca operatorii ++ si -- au efect lateral (side effect).
Daca nu folosim valoarea lui ++i sau a lui i++, atunci acestea sunt echivalente. Mai precis,
++i; si i++;
sunt echivalente cu
i = i + 1;

------------
Exemple::
------------
Presupunem ca avem declaratiile
int a = 1, b = 2, c = 3, d = 4;
Atunci avem:
Expresie Expresie echivalenta parantetizata Valoare

a*b/c (a * b) / c 0
a*b%c+1 ((a * b) % c) + 1 3
++ a * b - c -- ((++ a) * b) - (c --) 1
7 - - b * ++ d 7 - ((- b) * (++ d)) 17

-----------------------------
Operatori de asignare
----------------------------
Pentru schimbarea valorii unei variabile, am utilizat deja instructiunea de asignare (atribuire), cum
ar fi
a = b + c;
Spre deosebire de celelalte limbaje, C trateaza = ca un operator. Precedenta sa este cea mai mica
dintre toti operatorii si asociativitatea sa este de la dreapta la stanga. O expresie de asignare simpla
are forma:
variabila = parte_dreapta
unde "parte_dreapta" este o expresie. Daca punem ; la sfarsitul expresiei de asignare, atunci vom
obtine instructiune de asignare. Operatorul = are doua argumente, "variabila" si "parte_dreapta".
Valoarea expresiei "parte_dreapta" este asignata pentru "variabila" si aceasta valoare se returneaza
de catre expresia de asignare (ca un tot unitar).

------------
Exemplu:: Consideram instructiunile
------------
b = 2;
c = 3;
a = b + c;
unde toate variabilele sunt de tipul int. Folosind faptul ca = este un operator, putem condensa
aceasta la
a = (b = 2) + (c = 3);
Explicatia este ca expresia de asignare b = 2 atribuie valoarea 2 atat variabilei b, cat si instructiunii
intregi.
Daca exemplul de mai sus pare artificial, atunci o situatie frecvent intalnita este asignarea multipla.
De exemplu, instructiunea
a = b = c = 0;
este echivalenta cu (folosind asociativitatea de la dreapta la stanga)
a = (b = (c = 0));
Relativ la =, mai exista inca doi operatori. Este vorba de += si -=. Expresia
k=k+2
va aduna 2 la vechea valoare a lui k si va asigna rezultatul lui k si intregii expresii. Expresia
k += 2
face acelasi lucru.

----------------------------------------
Lista operatorilor de asignare: = += -= *= /= %= >>= <<= &= ^= |=
----------------------------------------
Toti acesti operatori au aceeasi precedenta si se asociaza de la dreapta la stanga. Semantica lor este
specificata de
variabila op= expresie
care este echivalent cu
variabila = variabila op (expresie)
cu exceptia faptului ca variabila sa nu fie o expresie.

------------
Exemplu::
------------
Expresia de asignare
j *= k + 3
este echivalenta cu
j = j * (k + 3)
si nu cu
j=j*k+3
Fie declaratia
int i = 1, j = 2, k = 3, m = 4;
Consideram urmatoarele exemple de evaluari ale expresiilor
Expresie Expresie echivalenta Expresie echivalenta Valoare
i += j + k i += (j + k) i = (i + (j + k)) 6
j *= k = m + 5 j *= (k = (m + 5)) j = (j * (k = (m + 5))) 18

------------
Exemple:: Calculul puterilor lui 2
-----------
#include <stdio.h>

main()
{
int i = 0, power = 1;

while (++i <= 10)


printf("%6d", power *=2);
printf("\n");
}
Iesirea acestui program va fi:
2 4 8 16 32 64 128 256 512 1024

---------------
Sistemul C
---------------
In capitolele precedente am prezentat directiva de preprocesare #include si #define. Directiva
#include avea forma generala:
#include <nume_fisier>
si insemna includerea in acest loc a fisierului header specificat din directoarele specifice C (MS-
DOS \bc\include sau \tc\include, UNIX /usr/include). O alta forma este
#include "nume_fisier"
ce are drept scop inlocuirea acestei linii cu o copie a fisierului "nume_fisier" din directorul curent.
Deci, atunci cand utilizam o functie C, trebuie sa specificam prototipul ei (scanf() si printf() au
prototipul <stdio.h>, rand() are prototipul <stdlib.h>).

------------
Exemplu::
------------
#include <stdio.h>
#include <stdlib.h>

main()
{
int i, n;

printf("\n%s\n%s",
"Vom afisa niste intregi aleatori.",
"Cati doriti sa vedeti ? ");
scanf("%d", &n);
for (i = 0; i < n; ++i)
{
if (i % 6 == 0)
printf("\n");
printf("%12d", rand());
}
printf("\n");
}
Daca de exemplu, tastam numarul 11, atunci pe ecran vor apare 11 numere intregi aleatoare.
--------------
Observatii:
--------------
1. Atentie ! ++i < n este diferit de i++ < n;
2. Operatorul == este operatorul de egalitate (test), adica a == b va fi evaluata la true daca si numai
daca valoarea lui a este egala cu valoarea lui b (in caz contrar va fi evaluata la false).
3. Functia rand() intoarce un intreg cuprins intre 0 si n, unde n este dependent de sistem. In ANSI
C, n este dat de constanta RAND_MAX.

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Investigati comportarea operatorilor / si % pentru numere intregi negative. Mentionam ca in
unele sisteme C, 7/-2 da rezultatul -3, in altele -4. Verificati daca se pastreaza identitatea din
matematica (prevazuta a fi adevarata de ANSI):
(a / b) * b + a % b = a
Sugestie: Scrieti un program C care sa contina liniile de cod
int a, b;
printf("dati doi intregi nenuli: ");
scanf("%d%d", &a, &b);
printf("%s%4d\n%s%4d\n%s%4d\n%s%4d\n%s%4d\n",
" a =",a,
" b =",b,
" a / b =", a / b,
" a % b =", a % b,
"Verif. ANSI=", (a / b) * b + a % b - a);
2. Scrieti un program C care sa calculeze cel mai mare divizor comun dintre a si b, unde a, b sunt
numere intregi, folosind algoritmul lui Euclid.
3. Din moment ce + si ++ sunt operatori, rezulta ca expresia a+++b poate fi interpretata fie ca
a++ + b fie a + ++b
depinzand de modul de grupare semnului +. Scrieti un program scurt pentru a vedea ce interpretare
face compilatorul C.
4. Inlocuiti ++i cu i++ in programul de calcul a puterilor lui 2.
5. Un patrat magic (de latura n) are proprietatea ca include in locatiile sale toate numerele intregi
din intervalul 1, ..., n^2 si sumele numerelor de pe fiecare linie, fiecare coloana sau fiecare
diagonala sunt egale. De exemplu:
6 1 8
7 5 3
2 9 4
este un patrat magic de dimensiune 3. Sa se scrie un program C care testeaza daca un patrat este
magic sau nu. De asemenea, incercati sa generati toate patratele magice de ordin n.
6. Sa se scrie un program C care sa calculeze n!, unde n>0 este un numar natural (iar n! = 1 * 2 *
... * n).

3. Controlul instructiunilor
3.1. Operatori relationali, de egalitate si logici
3.2. Operatori si expresii relationale
3.3. Operatori si expresii de egalitate
3.4. Operatori logici si expresii logice
3.5. Evaluare rapida (short-circuit)
3.6. Instructiunea compusa
3.7. Instructiunea vida
3.8. Instructiunile "if" si "if-else"
3.9. Instructiunea "while"
3.10. Instructiunea "for"
3.11. Operatorul ","
3.12. Instructiunea "do"
3.13. Instructiunea "goto"
3.14. Instructiunile "break" si "continue"
3.15. Instructiunea "switch"
3.16. Operatorul conditional
3.17. Exercitii propuse spre implementare
========
Capitolul 3
========
==================
Controlul instructiunilor
==================
------------------------------------------------------
Operatori relationali, de egalitate si logici
------------------------------------------------------
Operatori relationali : <, >, <=, >=
Operatori de egalitate: ==, !=
Operatori logici : !, &&, ||
Ca si ceilalti operatori, acesti operatori au reguli de precedenta si asociativitate care determina
precis modul de evaluare a acestor
expresii.
--------------------------------------------------------------------------------
| Operatori | Asociativitate |
--------------------------------------------------------------------------------
() ++ (postfix) -- (postfix) | de la stanga la dreapta |
+ (unar) - (unar) ++ (prefix) -- (prefix) | de la dreapta la stanga |
* / % | de la stanga la dreapta |
+ - | de la stanga la dreapta |
< <= > >= | de la stanga la dreapta |
== != | de la stanga la dreapta |
&& | de la stanga la dreapta |
|| | de la stanga la dreapta |
?: | de la dreapta la stanga |
= += -= *= /= etc | de la dreapta la stanga |
, (operatorul virgula) | de la stanga la dreapta |
-------------------------------------------------------------------------------
Operatorul ! este unar, spre deosebire de toti operatori (relationali, de egalitate si logici) care sunt
binari. Toti operatorii vor fi prezenti in expresii ce pot lua valoarea intreaga 1 sau 0. Motivul este ca
C reprezinta "false" orice expresie egala cu zero, si "true" orice expresie diferita de zero.
------------
Exemple: In continuare, dam o lista de expresii ce se evaluaza la false
------------
1. O expresie de tip int ce are valoarea 0;
2. O expresie de tip float ce are valoarea 0.0;
3. Caracterul null '\0';
4. Pointerul NULL.
-----------------------------------------
Operatori si expresii relationale
-----------------------------------------
Am vazut ca operatorii <, >, <=, >= sunt toti binari. Expresiile ce contin acesti operatori pot lua
valoarea 0 sau 1.
-----------
Exemple. Primele patru exemple sunt corecte, restul sunt gresite:
-----------
1. a < 3
2. a > b
3. -1.1 >= (2.2 * x + 3.3)
4. a < b < c (corecta, dar confuza)
5. a =< b
6. a < = b
7. a >> b
Fie expresia relationala "a < b". Daca valoarea lui a este mai mica decat valoarea lui b, atunci
expresia va avea valoarea 1, pe care o gandim ca fiind "true". Daca valoarea lui a este mai mare
decat valoarea lui b, atunci expresia va avea valoarea 0, pe care o gandim ca fiind "false".
Observam ca valoarea lui "a < b" este aceeasi cu valoarea lui "a - b < 0". Folosind precedenta
operatorilor aritmetici, aceasta este deci echivalenta cu "(a - b) < 0". De altfel, pe multe masini,
expresii cum sunt "a < b" sunt implementate ca fiind "a - b < 0".
-----------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
-----------
Presupunem ca avem declaratiile:
int i = 1, j = 2, k = 3;
double x = 5.5, y = 7.7;
----------------------------------------------------------------------
| Expresie | Expresie echivalenta | Valoare |
----------------------------------------------------------------------
i<j-k i < (j - k) 0
- i + 5 * j >= k + 1 ((- i) + (5 * j)) >= (k + 1) 1
x - y <= j - k -1 (x - y) <= ((j - k) - 1) 1
x+k+7<y/k ((x + k) + 7) < (y / k) 0
|----------------------|---------------------------------|-------------|
------------------------------------------
Operatori si expresii de egalitate
-----------------------------------------
Expresiile pot contine si operatorii de egalitate == si !=. Expresiile ce le contin au valoarea 0 sau 1.
-----------
Exemple. Primele trei exemple sunt corecte, restul sunt gresite:
-----------
1. c == 'A'
2. k != -2
3. x + y == 2 * x - 5
4. a = b
5. a = = b - 1
6. (x + y) =! 44
Intuitiv, o expresie de egalitate cum ar fi a == b este sau "true" sau "false". Mai precis, daca a este
egal cu b, atunci a == b intoarce valoarea 1 (true); altfel, aceasta intoarce valoarea 0 (false). O
expresie echivalenta este a - b == 0 (aceasta este ceea ce se implementeaza la nivel masina).
Expresia "a != b" ilustreaza folosirea operatorului "diferit de" (sau "nu este egal cu").
-----------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
-----------
Presupunem ca avem declaratiile:
int i = 1, j = 2, k = 3;
-------------------------------------------------------------------------
| Expresie | Expresie echivalenta | Valoare |
-------------------------------------------------------------------------
i == j j == i 0
i != j j != i 1
i + j + k == - 2 * - k ((i + j) + k) == ((-2) * (- k)) 1
-------------------------|------------------------------------|----------|
------------------------------------------
Operatori logici si expresii logice
------------------------------------------
Operatorul logic ! este unar, iar && si || sunt binari. Expresiile ce contin acesti operatori intorc
valoarea 0 sau 1. Negarea logica poate fi aplicata unei expresii aritmetice sau unui tip pointer. Daca
o expresie are valoarea 0, atunci expresia negata are valoarea 1. Daca expresia are o valoare diferita
de 0, atunci expresia negata intoarce valoarea 1.
-----------
Exemple. Primele trei exemple sunt corecte, restul sunt gresite:
-----------
1. !a
2. !(x + 7.7)
3. !(a < b || c < d)
4. a!
5. a != b (este corecta, dar se refera la operatorul "diferit")
Unele identitati logice (din matematica) nu se "transmit" in C. De exemplu, se stie ca "not (not s)
=s", in timp ce valoarea lui "!!5" nu este 5, ci 1. Motivul este ca operatorul "!" se asociaza de la
dreapta la stanga, si deci "!!5" este echivalent cu "!(!5)", care echivalent cu "!(0)", ce intoarce
valoarea 1.
------------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
------------
Presupunem ca avem declaratiile:
int i = 7, j = 7;
double x = 0.0, y = 999.9;
--------------------------------------------------------------------
| Expresie | Expresie echivalenta | Valoare |
--------------------------------------------------------------------
! (i - j) + 1 (! (i - j)) + 1 2
!i-j+1 ((! i) - j) + 1 -6
! ! (x + 3.3) ! (! (x + 3.3)) 1
!x*!!y (! x) * (! (! y)) 1
----------------------|------------------------------|-------------|
Operatorii logici binari && si || pot fi folositi in expresii care intorc 0 sau 1.
-----------
Exemple. Primele patru exemple sunt corecte, restul sunt gresite:
-----------
1. a && b
2. a || b
3. !(a < b) && c
4. 3 && (-2 * a + 7)
5. a &&
6. a | | b
7. a & b (corecta, dar se refera la operatii peste biti)
8. &b (corecta, dar se refera la adresa lui b)-----------
Exemple: Vom considera urmatorul tabel cu declaratii si initializari.
-----------
Presupunem ca avem declaratiile:
int i = 3, j = 3, k = 3;
double x = 0.0, y = 2.3;
--------------------------------------------------------------------
| Expresie | Expresie echivalenta | Valoare |
--------------------------------------------------------------------
i && j && k (i && j) && k 1
x || i && j - 3 x || (i && (j - 3)) 0
i < j && x < y (i < j) && (x < y) 0
i < j || x < y (i < j) || (x < y) 1
|----------------------|---------------------------------|------------|
--------------------------------------
Evaluare rapida (short-circuit)
--------------------------------------
Pentru expresiile ce contin && sau ||, evaluarea are loc cand s-a stabilit deja valoarea expresiei,
eventual fara parcurgerea intregii
expresii. Astfel, presupunem ca "expr1" se evalueaza la 0 (false). Atunci expresia
expr1 && expr2
se va evalua la 0, fara a se mai face evaluarea expresiei "expr2".
Alt exemplu, daca "expr1" se evalueaza la 1 (true), atunci expresia
expr1 || expr2
se va evalua la true fara a se mai evalua expresia "expr2".
Uneori se mai spune ca operatorii && si || sunt lazy (adica le este lene sa mai evalueze toti
operanzii din expresie).
-----------------------------
Instructiunea compusa
-----------------------------
O instructiune compusa este un sir de declaratii si instructiuni delimitate de acolade. Ceea ce
acoladele delimiteaza se numeste "bloc". O instructiune compusa este ea insasi o instructiune.
-----------
Exemplu:
-----------
{
a = 1;
{
b = 2;
c = 3;
}
}
-------------------------
Instructiunea vida
-------------------------
Instructiunea vida se reprezinta cu semnul ; (punct si virgula). Ea se foloseste cand se doreste
folosirea ei sintactica, si nu neaparat folosire semantica. Dupa cum vom vedea, aceasta se foloseste
in constructii "if-else" si "for". O expresie urmata de ; se numeste "instructiune expresie".
-----------
Exemplu:
-----------
a = b;
a + b + c;
;
printf("%d\n", a);
--------------------------------------
Instructiunile "if" si "if-else"
--------------------------------------
Forma generala a instructiunii "if" este
if (expresie)
instructiune
Semantica intuitiva este simpla. Astfel, daca valoarea expresiei este true (diferita de zero), atunci se
executa instructiunea, altfel nu.
-----------
Exemplu:
-----------
Instructiunea "if" de mai jos va testa daca se poate face impartirea cu y (ce trebuie sa fie diferit de
0):
if (y != 0.0)
x /= y;
Urmatoarele doua instructiuni
if (j < k)
min = j;
if (j < k)
printf("j este mai mic decat k\n");
se pot scrie intr-una singura
if (j < k)
{
min = j;
printf("j este mai mic decat k\n");
}
Instructiunea "if-else" de mai jos este foarte apropiata de instructiunea "if". Aceasta are forma
generala
if (expresie)
instructiune1
else
instructiune2
Semantica intuitiva este de asemenea clara. Daca valoarea expresiei este diferita de zero, atunci se
executa instructiune1 si "se sare" peste instructiune2. Daca valoarea expresiei este zero, atunci "se
sare" instructiune1, si se executa instructiune2.
------------
Exemplu:
------------
Urmatorul subprogram C de mai jos calculeaza si afiseaza minimul dintre x si y.
if (x < y)
min = x;
else
min = y;
printf("Valoarea minima = %d\n", min);
-----------------------------
Instructiunea "while"
-----------------------------
"While", "for" si "do" sunt cele trei instructiuni repetitive din limbajul C. Consideram urmatorul
format general al instructiunii "while" (iteratia sau bucla "while").
while (expresie)
instructiune
instructiune_urmatoare
Mai intai se evalueaza expresie. Daca aceasta nu este zero (deci este "true"), atunci se executa
instructiunea, si control trece la inceputul buclei "while". Astfel, corpul buclei se executa de cate ori
expresie se evalueaza la "true". Terminarea buclei are loc cand expresie ia valoarea zero (adica
"false"). In acest punct, controlul se paseaza catre "instructiune_urmatoare".
-----------
Exemplu:-----------
while (i <= 10)
{
suma += i;
++i;
}
--------------------------
Instructiunea "for"
-------------------------
Ca si instructiunea "while", instructiunea "for" se foloseste pentru descrierea structurilor iterative
(repetitive). Astfel constructia
for (expresie1; expresie2; expresie3)
instructiune
instructiune_urmatoare
este semantic echivalenta cu
expresie1;
while (expresie2)
{
instructiune;
expresie3;
}
instructiune_urmatoare;
Deci, se va evalua expresie1. De obicei, aceasta se foloseste pentru initializarea buclei. Apoi, se
evalueaza expresie2. Daca aceasta nu este zero ("true"), atunci se executa instructiune, se evalueaza
expresie3, si controlul buclei se "paseaza" la inceputul buclei (cu deosebirea ca nu se mai evalueaza
expresie1). De obicei, expresie2 este o expresie logica care controleaza bucla. Acest proces
continua pana cand expresie2 este 0 (false), punct in care se plaseaza controlul catre
instructiune_urmatoare.
------------
Exemplu: Exemplul de mai jos calculeaza factorialul numarului n.
------------
factorial=1;
for (i = 1; i <= n; i++)
factorial *= i;
Orice sau toate expresiile dintr-o instructiune "for" pot lipsi, dar nu poate lipsi ;.
-----------
Exemple:
-----------
Exemplul de mai jos calculeaza suma numerelor intregi de la 1 la 10.
i = 1;
suma = 0;
for ( ; i <= 10; ++i)
suma += i;
Acesta se poate scrie echivalent:
i = 1;
suma = 0;
for ( ; i <= 10; )
suma += i++;
Daca, in schimb, lipseste expresie2, atunci obtinem o bucla infinita.
---------------------
Operatorul ","
---------------------
Operatorul "," are cea mai mica prioritate dintre toti operatorii din C. Este un operator binar ce are
ca operanzi drept expresii si se
asociaza de la stanga la dreapta. Intr-o expresie de forma
expresie1 , expresie2
se evalueaza mai intai expresie1, apoi expresie2. Expresia "," intoarce valoarea si tipul operandului
din dreapta.
-----------
Exemplu: Presupunem ca a, b sunt de tip int. Atunci expresia ","
------------
a = 0, b = 1
intoarce valoarea 1 de tipul int.
Operatorul "," este deseori folosit in instructiunea "for".
----------
Exemplu: Exemplul de mai jos calculeaza factorialul numarului n (reluare).
-----------
for (factorial = 1, i = 1; i <= n; i++)
factorial *= i;
------------
Exemplu: Revenim asupra unui exemplu precedent (suma primelor N numere naturale)
------------
for (suma = 0, i = 1; i <= n; ++i)
suma += i;
se poate scrie, echivalent, in
for (suma = 0, i = 1; i <= n; suma += i, ++i);
-------------
Intrebare: Ce se intampla cu valoarea lui suma daca intervertim instructiunile
------------
suma += i cu ++i
-----------
Exemplu:
-----------
for (i=0, p = head; p != NULL; p=p -> next )
.....
------------------------
Instructiunea "do"
-----------------------
Instructiunea "do" poate fi considerata o varianta a instructiunii "while". Deosebirea consta in faptul
ca pentru instructiunea "while" testul se face la inceputul ciclului, iar pentru "do" la sfarsit.
Consideram constructia de forma
do
instructiune
while (expresie);
instructiune_urmatoare
La inceput se executa instructiune, apoi se evalueaza expresie. Daca valoarea lui expresie este
diferita de 0 ("true"), atunci controlul se paseaza la inceputul instructiunii "do", si procesul se
repeta. Daca expresie se evalueaza la 0 (false), atunci controlul se paseaza la
instructiune_urmatoare.
-----------
Exemplu: Suma unor numere intregi diferite de 0
-----------
suma = i = 0;
do
{
suma += i;
scanf("%d", &i);
}
while (i > 0);
--------------------------
Instructiunea "goto"
-------------------------
Instructiunea "goto" (salt neconditionat) este considerata opusa programarii structurate. Sfatul
general valabil este evitarea acestei instructiuni. Totusi, in unele cazuri se poate folosi (cand
simplifica controlul, cand face codul mai eficient). O instructiune de etichetare are forma:
eticheta : instructiune
unde eticheta este un identificator.
------------
Exemple:
------------
bye: exit(1);
eticheta1: a = b + c;

333: a = b + c; (exemplu gresit, de ce ?)


Controlul programului poate fi transferat neconditionat catre o instructiune de etichetare astfel
goto eticheta;
----------------------------------------------
Instructiunile "break" si "continue"
----------------------------------------------
Cele doua instructiuni
break; si continue;
intrerup controlul normal al programelor. Instructiunea "break" va cauza iesirea din bucla in care se
afla sau din instructiunea "switch". Instructiunea "continue" se poate afla numai in instructiuni
"for", "while" si "do". Ea are rolul de a trasmite controlul catre sfarsitul buclei respective.
-----------
Exemple:
-----------
while (1)
{
scanf("%lf", &x);
if (x < 0.0)
break; /* iesim cand x este negativ */
printf("%lf\n", sqrt(x));
}
while (contor < n)
{
scanf("%lf", &x);
if (x > -0.01 && x < =0.01)
continue; /* valorile mici nu se iau in considerare */
++contor;
suma += x;
}
------------------------------
Instructiunea "switch"
-----------------------------
"switch" este o instructiune conditionala ce generalizeaza o instructiune "if-else".
-----------
Exemplu:
-----------
switch (val)
{
case 1:
++contor_a;
break;
case 2:
case 3:
++contor_b;
break;
default:
++contor_c;
}
Corpul unei instructiuni "switch" este un exemplu de instructiune compusa. Expresia de control
dintre paranteze (ce urmeaza cuvantului switch) trebuie sa fie de tip integral (vom reveni intr-un alt
capitol). Dupa evaluarea lui val, controlul sare la eticheta corespunzatoare valorii lui val. De obicei,
ultima instructiune dintr-un "case" este de obicei "break". Daca nu exista "break", atunci se vor
executa si instructiunile din urmatoarele "case"-uri.
Atentie ! Omiterea scrierii lui "break" este foarte frecventa !!
Poate apare cel mult un "default" (in general pe ultima pozitie). Cuvintele rezervate "case" si
"default" pot apare numai in interiorul
unui "switch".
------------------------------
Operatorul conditional
------------------------------
Operatorul "?:" este mai putin obisnuit deoarece este ternar (cu trei argumente). Forma generala este

expresie1 ? expresie2 : expresie3


Mai intai, se evalueaza expresie1. Daca aceasta este diferita de 0 (true), atunci se evalueaza
expresie2, si aceasta va fi valoarea returnata de intreaga expresie conditionala. Daca expresie1 este
0 (false), atunci se evalueaza expresie3, si aceasta va fi valoarea intregii expresii conditionale.
------------
Exemplu: Instructiunea
-----------
if (y < z)
x = y;
else
x = z;
este echivalenta cu
x = (y < z) ? y : z;
Operatorul ?: are aceeasi prioritate cu operatorul de asignare si se asociaza de la dreapta la stanga.
-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Sa se scrie un program care sa calculeze minimul a trei numere (folosind o instructiune "if-then"
si una "if" sau doua "if-then" (fara variabila suplimentara)). Generalizare: Sa se gaseasca primele
doua numere (cele mai mici) dintr-un vector de n elemente (cu numar minim de comparatii).
2. Cititi n numere de la tastatura si afisati maximul lor. Incercati sa cititi un numar arbitrar de
numere (deci fara a citi acest n).
3. Folosind structura for, scrieti un program care calculeaza urmatoarele formule logice (sub forma
unei tabele de adevar):
b1 || b3 || b5 si b1 && b2 || b4 && b5
4. Fie functia lui Collatz:
{ n/2 daca n este par
f(n) =
{ 3*n+1 daca n este impar
Sa se scrie un program C care determina k natural minim astfel incat
(f o f o ... o f)(n)=1.
de k ori
5. Scrieti un program C care calculeaza suma divizorilor naturali ai unui numar natural n. Un numar
este perfect daca este egal cu suma divizorilor proprii pozitivi (ex: 28 = 1 + 2 + 4 + 7 + 14). Sa se
genereze primele k numere perfecte (k < 5 !).
6. Operatia matematica min(x,y) se poate reprezenta ca o expresie conditionala:
(x < y) ? x : y
Intr-un mod similar, descrieti operatiile aritmetice
min(x, y, z) si max(x, y, z, t)
7. Se stie ca un procedeu de interschimbare a valorii a doua variabile (a si b) se poate face folosind
o variabila auxiliara (se foloseste in metodele de sortare, arbori, sisteme de ecuatii, etc):
aux = a ;
a=b ;
b = aux;
Sa se arate ca in limbajul C se poate face acest lucru in mod echivalent fara utilizarea explicita a
unei variabile suplimentare. Asadar intervertirea valorilor a si b se poate face si astfel:
a = b + a - (b = a);
Aratati ca aceasta instructiune este echivalenta cu:
aux = b + a ;
b=a ;
a = aux - a; (sau a = aux - b;)
Echivalent, fara variabile suplimentare, se pot considera instructiunile:
a = a + b;
b = a - b;
a = a - b;

Functii si programare structurata


4.1. Apelul functiilor
4.2. Instructiunea "return"
4.3. Prototipurile functiilor
4.4. Descriere "top-down"
4.5. Invocare si apel prin valoare
4.6. Deosebirea dintre "return" si "exit"
4.7. Exercitii propuse spre implementare

========
Capitolul 4
========
=========================
Functii si programare structurata
=========================
Programarea structurata este o problema ce rezolva strategia si metodologia programarii si are
urmatoarele principii:
1. Structurile de control trebuie sa fie cat se poate de simple;
2. Constructia unui program trebuie sa fie descrisa top-down.
Descrierea top-down se refera la descompunerea problemei noastre in subprobleme. De obicei,
aceste subprobleme sunt usor de descris.

---------------------
Apelul functiilor
---------------------
Un program este compus din una sau mai multe functii, printre care si "main()". Intotdeauna
executia unui program incepe cu "main()". Cand o functie este apelata (sau invocata) atunci
controlul programului este pasat functiei apelate. Dupa ce aceasta isi termina executia, atunci se
paseaza inapoi controlul catre program.
Codul C care descrie ce face o functie se numeste "definitia functiei". Aceasta are urmatoarea
forma generala:
tip nume_functie (lista_parametri)
{
declaratii
instructiuni
}
Primul rand se numeste "header-ul" (antetul) functiei, iar ceea ce este inclus intre acolade se
numeste corpul functiei. Daca in antet
nu precizam parametri, atunci se va scrie "void" (cuvant rezervat pentru lista vida). Daca functia nu
intoarce nici o valoare, atunci se va scrie ca tip intors tot "void". Tipul intors de functie este cel
precizat in "return" (ce va fi indata explicat). Parametrii din antetul functiei sunt dati printr-o lista
cu argumente separate prin virgula. Aceste argumente sunt date de tipul argumentului urmat de un
identificator ce apartine acelui tip. Se mai spune ca acel identificator este "parametru formal".
-----------
Exemplu:
-----------
#include <stdio.h>
void tipareste_mesaj(int k)
{
int i;
printf("Iti urez:\n");
for (i = 0; i < k; ++i)
printf(" O zi buna ! \n");
}

main()
{
int n;
printf("Dati un numar natural mic: ");
scanf("%d", &n);
tipareste_mesaj(n);
}

------------------------------
Instructiunea "return"
------------------------------
Instructiunea "return" este folosita pentru doua scopuri. Cand se executa o instructiune "return",
controlul programului este pasat inapoi programului apelant. In plus, daca exista o expresie dupa
acest "return", atunci se va returna valoarea acestei expresii. Instructiunea "return" poate avea
formele:
return;
sau
return expresie;
-----------
Exemplu: Minimul a doi intregi.
-----------
#include <stdio.h>
int min(int x, int y)
{
if (x < y)
return x;
else
return y
}

main()
{
int j, k, m;

printf("Dati doi intregi: ");


scanf("%d%d", &j, &k);
m = min(j, k);
printf("\n%d este minimul dintre %d si %d.\n", m, j, k);
}
------------------------------
Prototipurile functiilor
------------------------------
In C, apelul unei functii poate apare inaintea declararii ei. Functia poate fi definita mai tarziu in
acelasi fisier, sau in alt fisier sau dintr-o biblioteca standard. In ANSI C, prototipul functiei
remediaza problema punand la dispozitie numarul si tipul argumentelor functiei. Prototipul
specifica, de asemenea, si tipul returnat de functie. Sintaxa prototipului unei functii este:
tip nume_functie (lista_tipuri_parametri);
In lista de parametri putem specifica chiar si parametrul, dar asta este optional. Daca functia nu are
parametri, atunci se foloseste "void".
-----------
Exemplu: Reluam un exemplu precedent.
-----------
#include <stdio.h>
main()
{
int n;
void tipareste_mesaj(int);

printf("Dati un numar natural mic: ");


scanf("%d", &n);
tipareste_mesaj(n);
}

void tipareste_mesaj(k)
{
int i;

printf("Iti urez:\n");
for (i = 0; i < k; ++i)
printf(" O zi buna ! \n");
}
Prototipul unei functii poate fi plasat in corpul altei functii, sau de regula, se scriu la inceputul
programelor dupa directivele #include si #define.

----------------------------
Descriere "top-down"
----------------------------
Presupunem ca avem de citit cativa intregi si trebuie sa-i afisam in ordine pe coloane (in capatul de
sus al coloanelor trebuie sa scriem numele campului), sa le afisam suma lor partiala, minimul si
maximul lor. Pentru scrierea unui program C ce face acest lucru, vom utiliza proiectarea
(descrierea) "top-down".
Astfel, descompunem problema in urmatoarele subprobleme:
1. Un antet pentru problema data;
2. Scrierea campurilor;
3. Citirea si scrierea lor pe coloane.
Toti acesti trei pasi vor fi descrisi in cate o functie ce se apeleaza din "main()". Obtinem, un prim
cod:
#include <stdio.h>

main()
{
void tipareste_antet(void);
void scrie_campurile(void);
void citeste_scrie_coloanele(void);

tipareste_antet();
scrie_campurile();
citeste_scrie_coloanele();
}

Aceasta reprezinta intr-un mod foarte simplu descrierea "top-down". Daca o problema este prea
grea, atunci o descompunem in subprobleme, si apoi le rezolvam pe acestea. Beneficiul suplimentar
al acestei metode este claritatea sa.

void tipareste_antet(void)
{
printf("\n%s%s%s\n",
"**************************************************\n",
"* Calculul sumelor, minimului si maximului *\n",
"**************************************************\n");
}

Functia ce foloseste la scrierea campurilor este la fel usor de scris:


void scrie_campurile(void)
{
printf("%5s%12s%12s%12s%12s\n\n",
"Numar", "Articol", "Suma", "Minimul", "Maximul");
}

Urmeaza apoi functia ce serveste la scrierea inregistrarilor referitoare la campurile discutate mai
sus:

void citeste_scrie_coloanele(void)
{
int contor = 0, articol, suma, minim, maxim;
int min(int, int), max(int, int);

if (scanf("%d", &articol) == 1)
{
++contor;
suma = minim = maxim = articol;
printf("%5d%12d%12d%12d%12d\n\n",
contor, articol, suma, minim, maxim);
while (scanf("%d", &articol) == 1)
{
++contor;
suma += articol;
minim = min(articol, minim);
maxim = max(articol, maxim);
printf("%5d%12d%12d%12d%12d\n\n",
contor, articol, suma, minim, maxim);
}
}
else
printf("Nici o data nu a fost citita.\n\n");
}

Daca datele se introduc de la tastatura, atunci tabelul se va afisa "intrerupt" de citirile ce au loc de la
tastatura. Astfel, se prefera citirea dintr-un fisier extern. Presupunem ca fisierul nostru executabil
(asociat fisierului sursa scris in C) se numeste "numere.exe" si am creat un fisier numit "fisier.int"
ce contine urmatoarele numere:
19 23 -7 29 -11 17
Dand comanda
numere < fisier.int
vom obtine un tabel ce contine toate datele dorite.

-------------------------------------
Invocare si apel prin valoare
-------------------------------------
O functie este invocata prin scrierea numelui sau impreuna cu lista sa de argumente intre paranteze.
De obicei, numarul si tipul acestor argumente se "potriveste" cu parametrii din lista de parametri
prezenti in definitia functiei. Toate argumentele sunt apelate prin valoare ("call-by-value"). Asta
inseamna ca fiecare argument este evaluat si valoarea sa este folosita ca valoare pentru parametrul
formal corespunzator. De aceea, daca o variabila (argument) este folosita la transmiterea unei
valori, atunci valoarea ei nu se schimba.

-----------
Exemplu:
-----------
#include <stdio.h>
main()
{
int n=3, suma, calculeaza_suma(int);

printf("%d\n", n); /* se va scrie 3 */


suma = calculeaza_suma(n);
printf("%d\n", n); /* se va scrie 3 */
printf("%d\n", suma); /* se va scrie 6 */
}

int calculeaza_suma(int n) /* suma numerelor de la 1 la n */


{
int suma = 0;
for ( ; n > 0; --n) /* n se schimba aici, dar nu si in main() */
sum += n;
printf("%d\n", n); /* se va scrie 0 */
return suma;
}
Chiar daca n este trimis ca argument in functia "calculeaza_suma()" si valoarea lui n se modifica in
aceasta functie, valoarea sa din
mediul apelant ramane neschimbata. Vom vedea mai tarziu cum se poate simula apelul prin adresa
("call-by-reference").

----------------------------------------------
Deosebirea dintre "return" si "exit"
----------------------------------------------
Exista doua procedee de a returna o valoare.
return expresie si exit(expresie)
Daca se folosesc in "main()", atunci acestea sunt echivalente, dar in orice alta functie efectul lor
este diferit (in ANSI C, functia "main()" intoarce o valoare de tip int). Un apel al lui exit() in orice
alta functie va implica terminarea executiei programului si returnarea valorii catre mediul apelant
(sistemul de operare sau mediul de programare C). Valoarea returnata se numeste stare de iesire
("exit status"). Prin conventie, starea de iesire zero indica terminare cu succes, pe cand iesire cu un
numar diferit de zero indica o situatie anormala.

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Folosind functiile "rand()", "min(,)" si "max(,)", sa se genereze n numere naturale si sa se
afiseze minimul si maximul dintre acestea.
2. (Jocul cap-pajura, simulare Monte-Carlo)
Presupunem ca dispunem de o moneda ideala (nemasluita). Doi jucatori arunca cu moneda dupa
urmatoarele reguli:
1.a. Se fac un numar total de n aruncari;
1.b. Primul jucator arunca moneda si celalalt spune "cap" sau "pajura";
1.c. Daca acesta "ghiceste" ce va pica moneda, atunci se inverseaza jucatorii (adica arunca al
doilea si primul incearca sa ghiceasca);
1.d. La sfarsit, trebuie afisat scorul (si procentul de castig al fiecaruia).
3. (Conjectura lui Goldbach)
Orice numar par mai mare decat 2 se poate scrie ca suma a doua numere prime. Scrieti un
program C care verifica aceasta conjectura pentru numere situate intre m si n. De exemplu, daca
m=700 si n=1100, atunci afisati:
700 = 17 + 683
702 = 11 + 691
704 = 3 + 701
...
1098 = 5 + 1093
1100 = 3 + 1097
Generalizare: Scrieti toate combinatiile posibile de adunare a doua numere prime egal cu un numar
dat.
Procesarea caracterelor
5.1. Tipul de data "char"
5.2. Utilizarea lui "getchar()" si "putchar()"
5.3. Biblioteca <ctype.h>
5.4. Un exemplu util: Numararea cuvintelor
5.5. Exercitii propuse spre implementare

========
Capitolul 5
========
==================
Procesarea caracterelor
==================
---------------------------
Tipul de data "char"
---------------------------
Este unul dintre tipurile fundamentale din limbajul C. Constantele si variabilele de acest tip sunt
folosite pentru reprezentarea caracterelor. Fiecare caracter este memorat pe 1 byte (octet), care (in
general) este compus din 8 biti. Un octet compus din 8 biti poate pastra 2^8=256 valori distincte.
Cand memoram un caracter intr-un octet, continutul acestuia poate fi gandit ca un caracter sau un
intreg mic (intre 0 si 255). Desi putem memora 256 valori distincte, doar o parte din ele sunt
tiparibile (litere mici, mari, cifre, semne de punctuatie, spatiu, tab, caractere speciale +, *, %).
Exemple de caractere netiparibile: newline, bell.
O constanta caracter se scrie intre apostroafe, cum ar fi: 'a', 'b'. O declaratie obisnuita a unei
variabile de tip caracter este:
char c;
Variabilele caracter se pot initializa astfel:
char c1 = 'A', c2 = '*';
Un caracter este pastrat in memorie pe un octet dupa o codificare specifica. Multe masini folosesc
codurile de caractere ASCII sau
EBCDIC. Ne vom referi numai la codul ASCII. Astfel, vom preciza constanta caracter si valoarea
corespunzatoare a sa:
de la 2^5+2^4 pana la 57, in ordine: '0', '1', ..., '9'
de la 2^6+2^0 pana la 90, in ordine: 'A', 'B', ..., 'Z'
de la 2^6+2^5+2^0 pana la 112, in ordine: 'a', 'b', ..., 'z'
De exemplu, se observa ca pentru a obtine litere mici din cele mari, schimbam doar un bit. Astfel,
caracterul 'A' are codul 65 care
inseamna numarul 01000001 in baza 2, iar caracterul 'a' are codul 01100001. Se observa ca difera
doar bitul cu numarul 3.

-----------
Exemple:
-----------
In functiile "printf()" si "scanf()", pentru formatul caracter se foloseste %c.
printf("%c", 'a'); va tipari a
printf("%c%c%c", 'A', 'B', 'C'); va tipari ABC
printf("%d", 'a'); va tipari 97
printf("%c", 97); va tipari a
Anumite caractere netiparibile necesita "secvente escape" (\ reprezinta caracterul escape). In acest
sens, dam un tabel
Numele caracterului Modul de scriere Valoarea intreaga
alert \a 7
backslash \\ 92
backspace \b 8
carriage return \r 13
ghilimea \" 34
formfeed \f 12
tab orizontal \t 9
newline \n 10
caracterul nul \0 0
apostrof \' 39
tab vertical \v 11

-----------
Exemple: Ce va fi afisat in cazul urmatoarelor instructiuni ?
-----------
1. printf("\"ABC\"");
2. printf("'ABC'");
Un alt mod de a scrie o constanta caracter este folosind una, doua sau trei cifre octale ca secvente
escape, cum ar fi '\007'. Acest
'\007' este de fapt caracterul "alert" (sau clopotel). El mai poate fi scris '\07' sau '\7' sau \a.

----------------------------------------------------
Utilizarea lui "getchar()" si "putchar()"
----------------------------------------------------
Aceste functii sunt folosite pentru citirea si scrierea caracterelor si sunt definite in <stdio.h>. Astfel
pentru citirea unui caracter de
la tastatura se foloseste "getchar()", iar pentru scrierea unui caracter pe ecran "putchar()".
Bineinteles ca daca dorim sa afisam un sir de caractere mai mare, este mai elegant cu functia
"printf()".

-----------
Exemplu:
-----------
Urmatorul program citeste un caracter din intrare (tastatura) si il atribuie unei varibile de tip char,
apoi il afiseaza pe ecran.
#include <stdio.h>
main()
{
char c;

while (1)
{
c=getchar();
putchar(c);
}
}

Singurul mod de a opri acest program este sa apasam CTRL^C. Putem reface acest program
folosind constanta EOF.
#include <stdio.h>
main()
{
int c;

while ((c = getchar()) != EOF)


{
putchar(c);
}
}

--------------
Comentarii:
--------------
1. In biblioteca <stdio.h>, exista o linie in care se declara
#define EOF (-1)
Denumirea lui EOF provine de la "end-of-file".
2. Variabila c trebuie sa fie declarata de tip int si nu de tip char. Am vazut ca sfarsitul unui fisier
este codificat cu -1, si nu cu un
caracter.
3. Subexpresia
c=getchar()
citeste o valoare de la tastatura si o asigneaza variabilei c.

--------------------------
Biblioteca <ctype.h>
--------------------------
Sistemul C pune la dispozitie fisierul header <ctype.h> care contine o multime de macro-uri
(definitii) folosite pentru testarea
caracterelor si o multime de prototipuri de functii ce sunt folosite pentru conversia caracterelor.
In tabelul de mai jos prezentam o lista de macro-uri folosite la testarea caracterelor. Aceste macro-
uri iau ca argument o variabila
de tip int si returneaza o valoare de tip int (zero=false, diferit de zero=true).
-----------------------------------------------------------------------
| Macro | Se returneaza true (diferit de zero) daca |
-----------------------------------------------------------------------
isalpha(c) c este litera
isupper(c) c este litera majuscula
islower(c) c este litera mica
isdigit(c) c este cifra
isalnum(c) c este litera sau cifra
isxdigit(c) c este cifra hexazecimala
isspace(c) c este caracter spatiu
ispunct(c) c este semn de punctuatie
isprint(c) c este caracter tiparibil
isgraph(c) c este tiparibil, dar diferit de spatiu
iscntrl(c) c este caracter de control
isascii(c) c este cod ASCII
-------------------------------------------------------------------
In tabelul urmator, vom scrie functiile "toupper()" si "tolower()", care sunt din biblioteca standard
si macro-ul "toascii()". Macro-ul
si prototipurile pentru cele doua functii sunt in <ctype.h>. Acestea au ca argument o variabila de tip
int si returneaza tipul int.
toupper(c) schimba c din litera mica in majuscula
tolower(c) schimba c din majuscula in litera mica
toascii(c) schimba c cu codul ASCII

----------------
Un exemplu (util): Numararea cuvintelor
----------------
Vrem sa numaram cate cuvinte sunt introduse de la tastatura. Ele sunt separate prin spatiu. Pentru
scrierea programului vom utiliza tot strategia "top-down".

#include <stdio.h>
#include <ctype.h>
main()
{
int numar_cuvinte = 0;
int gaseste_urmatorul_cuvant(void);

while (gaseste_urmatorul_cuvant() == 1)
++ numar_cuvinte;
printf("Numarul de cuvinte = %d\n\n", numar_cuvinte);
}

int gaseste_urmatorul_cuvant(void)
{
int c;

while (isspace(c = getchar()))


; /* sarim peste spatii */
if (c != EOF)
{
while ((c = getchar()) != EOF && !isspace(c))
; /* sarim peste orice diferit de EOF si spatii */
if (c != EOF) return 1;
else return 0;
}
return 0;
}

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Folosind functiile "getchar()" si "putchar()", sa se scrie un program C care transforma literele
mici in litere mari. Incercati si o varianta de program care foloseste functiile "islower()" si
"toupper()".
2. Utilizand functiile "getchar()" si "putchar()" creati un program C care sa copie un fisier in alt
fisier (comanda voastra proprie de
copiere). Utilizati redirectarea ! De asemeni, precizati si cazul cand dorim sa copiem un fisier la
sfarsitul unui fisier existent.
3. Scrieti in C un analizor lexical care sa recunoasca cat mai multi atomi lexicali din C. De
exemplu, cuvintele rezervate (while, do, for, ...), identificatori, operatori (relationali, logici,
artimetici, ...) si eventual alte structuri. Apoi, tipariti acelasi fisier de intrare cu exceptia spatiilor
multiple si a comentariilor.
4. Scrieti un program C care citeste caractere de la tastatura si le scrie la ecran. Scrieti toate
vocalele cu litere majuscule si consoanele cu litere mici. Apoi, scrieti un program C care citeste
caractere de la tastatura si sterge vocalele din ele (afisand doar consoanele). (Acest mod de scriere
era folosit in scrisul hieroglific al Greciei Antice).
5. ("Pretty printing")
Scrieti un program C care are la intrare un fisier sursa C si il transforma intr-un program C scris
frumos (eventual in stilul Bell Laboratoires).
Tipuri fundamentale de date
6.1. Declaratii si expresii
6.2. Tipuri fundamentale de date
6.3. Caractere si tipul "char"
6.4. Tipul de date "int"
6.5. Tipurile integrale "short", "long" si "unsigned"
6.6. Tipuri reale
6.7. Operatorul "sizeof()"
6.8. Functii matematice
6.9. Conversii implicite si explicite
6.10. Conversia la intreg
6.11. Conversiile aritmetice uzuale
6.12. Conversii explicite
6.13. Erori de programare frecvente
6.14. Exercitii propuse spre implementare

========
Capitolul 6
========
=====================
Tipuri fundamentale de date
=====================

---------------------------
Declaratii si expresii
---------------------------
Variabilele si constantele sunt obiecte cu care se lucreaza intr-un program. In C, toate variabilele
trebuie declarate inainte de a fi
folosite. Declaratiile au doua scopuri:
1. spun compilatorului cat spatiu de memorie trebuie rezervat pentru memorarea acelor variabile;
2. permit compilatorului sa instruiasca masina pentru a face operatiile specifice corect.
De exemplu, in expresia a + b, operatorul + este aplicat pentru doua variabile. Masina executa in
mod diferit adunarea pentru variabile de tip "int" si pentru variabile de tip "float". Bineinteles,
pentru programator aceste conventii sunt transparente (se mai spune ca "+" este operator de
supraincarcare). Expresiile sunt combinatii (cu inteles) de constante, variabile si apeluri de functii.
Majoritatea expresiilor (cum ar fi, de exemplu, variabilele) au si valoare si tip. In multe situatii,
valoarea returnata depinde in principal de tipul expresiei.

--------------------------------------
Tipuri fundamentale de date
--------------------------------------
Avem urmatoarele tipuri fundamentale de date (scriere intreaga - lunga):
char signed char unsigned char
signed short int signed int signed long int
unsigned short int unsigned int unsigned long int
float double long double
Toate acestea sunt cuvinte rezervate, deci nu se pot folosi ca nume de variabile. Alte tipuri de date,
cum ar fi vectorii si pointerii,
sunt derivate din tipurile fundamentale.
De obicei, cuvantul rezervat "signed" nu se mai scrie. De exemplu, "signed int" este echivalent cu
"int". De asemenea, cuvintele "short int", "long int" si "unsigned int" pot fi prescurtate, de obicei, ca
"short", "long" si "unsigned". Cu aceste conventii, tabelul de mai sus se mai poate scrie:
char signed char unsigned char
short int long
unsigned short unsigned unsigned long
float double long double
Tipurile fundamentale se pot grupa dupa functionalitate:
1. tipurile integrale sunt cele care sunt folosite pentru reprezentarea valorilor intregi;
2. tipurile reale sunt cele care sunt folosite pentru reprezentarea valorilor reale;
3. tipurile aritmetice sunt tipuri integrale sau reale.
Acestea sunt:
Tipuri integrale:
char signed char unsigned char
short int long
unsigned short unsigned unsigned long
Tipuri reale:
float double long double

---------------------------------
Caractere si tipul "char"
---------------------------------
In C, variabilele de orice tip integral pot fi folosite pentru reprezentarea caracterelor. In particular,
variabilele de tip "char" si "int" se folosesc pentru acest scop. Am vazut in capitolul precedent ca
atunci cand dorim sa comparam o variabila cu EOF, atunci trebuie sa declaram acea variabila de tip
"int", si nu de tip "char". Constante cum ar fi 'a', '+' pe care le gandim ca fiind caractere
sunt de tip "int", si nu de tip "char". Retineti ca nu exista constante de tip "char" !!!
Reamintim ca toate caracterele sunt tratate ca "intregi mici", si reciproc, intregii mici sunt tratati ca
niste caractere. In particular, orice expresie integrala poate fi afisata in format intreg sau caracter.

-----------
Exemplu: Presupunem ca avem o "bucata" de cod C:
-----------
char c = 'a'; /* 'a' are codul ASCII 97 */
int i = 65; /* 65 este codul ASCII pentru 'A' */

printf("%c", c + 1); /* este afisat b */


printf("%d", c + 2); /* este afisat 99 */
printf("%c", i + 3); /* este afisat D */
In C, fiecare caracter este memorat pe un octet de memorie. Pe aproape toate masinile, un octet este
compus din 8 biti. Fie declaratia
char c = 'a';
Putem gandi ca "c" este memorat pe un octet astfel
------------------------------
|0|1|1|0|0|0|0|1|
------------------------------
7 6 5 4 3 2 1 0
Fiecare celula reprezinta un bit si fiecare bit este numerotat (incepand cu cel mai putin
semnificativ). Bitii care formeaza un octet sunt fie "on", fie "off", aceste stari fiind reprezentate prin
1 si 0 respectiv. Acesta ne conduce sa gandim fiecare octet din memorie ca un sir de 8 cifre binare
(se mai numesc siruri de biti).
Astfel variabila "c" poate fi gandita ca sirul de biti
01100001
Mai general, fiecare cuvant masina poate fi gandit ca un sir de cifre binare grupate in octeti.
Un sir de cifre binare poate fi deci gandit ca un numar binar (adica in baza 2). Fara a intra in detalii
matematice (teorema bazei de numeratie) vom face doar un exemplu:

-----------
Exemplu:
-----------
Valoarea lui "c" este numarul 01100001 (in baza 2)
1 x 2^6 + 1 x 2^5 + 0 x 2^4 + 0 x 2^3 + 0 x 2^2 + 0 x 2^1 + 1 x 2^0
care inseamna 64 + 32 + 1 = 97 in notatia zecimala (in baza 10).
ANSI C pune la dispozitie trei tipuri referitoare la caractere:
char signed char unsigned char
De obicei, tipul "char" este echivalent cu "signed char" sau "unsigned char", depinzand de
compilator. Fiecare din aceste trei tipuri se memoreaza pe un octet (deci poate "tine" 256 valori
distincte). Pentru "signed char", valorile sunt intre -128 si 127, iar pentru "unsigned char" intre 0 si
255.

-------------------------
Tipul de date "int"
-------------------------
Tipul de date "int" este cel mai folosit tip din limbajul C. Acest tip, impreuna cu alte tipuri
integrale (cum ar fi: "char", "short" si "long") este desemnat pentru lucrul cu valori intregi
reprezentabile pe o masina.
In matematica, numerele naturale sunt 0, 1, 2, 3, ..., care impreuna cu cele negative
(corespunzatoare) formeaza numerele intregi. Pe o masina, se pot reprezenta (folosind un tip
integral) numai o submultime finita a acestor numere.
De obicei, un cuvant se memoreaza pe un cuvant masina. Anumite calculatoare folosesc cuvante de
2 octeti (=16 biti), altele 4 octeti (=32 biti).

-----------
Exemple:
-----------
1. Masini ce folosesc cuvinte memorate pe 2 octeti: PC
2. Masini ce folosesc cuvinte memorate pe 4 octeti: Apollo, Hewlett-Packard, Next, Silicon
Graphics, Sun, etc.

Presupunem ca lucram pe un calculator care lucreaza pe 4 octeti. Aceasta implica ca un cuvant are
32 biti, deci poate "tine" 2^{32}
valori distincte. Jumatate sunt folosite pentru reprezentarea numerelor negative si cealalta jumatate
pentru pozitive:
- 2^{31}, -2^{31}+1,..., -2, -1, 0, 1, 2, ..., 2^{31}-1
Daca lucram pe un calculator unde memorarea unui cuvant se face pe 2 octeti, atunci putem
memora 2^{16} valori distincte.
Valoarea cea mai mare, a tipului "int" este data de constanta MAXINT. Evident cea mai mica
valoare va fi -MAXINT-1.
Daca se incearca, de exemplu, adunarea a doua numere (si se depaseste aceasta valoare), atunci se
va primi un mesaj "integer overflow".

-------------------------------------------------------------
Tipurile integrale "short", "long" si "unsigned"
-------------------------------------------------------------
De obicei, tipul "short" se memoreaza pe doi octeti si tipul "long" pe patru octeti. Astfel, pe
masinile in care cuvintele au patru octeti, lungimea tipului "int" este aceeasi cu lungimea tipului
"long", iar pe masinile in care cuvintele au doi octeti, lungimea tipului "int" este egala cu lungimea
tipului "short". Constantele predefinite MAXSHORT si MAXLONG (in unele implementari
LONG_MAX) caracterizeaza lungimea acestor tipuri. De obicei, MAXSHORT=2^{15} si
MAXLONG=2^{31}. Astfel, daca "s" este o variabila de tip "short", atunci
- MAXSHORT <= s <= MAXSHORT-1
Daca "l" este o variabila de tip "long", atunci
- MAXLONG <= s <= MAXLONG-1
In ceea ce priveste tipul "unsigned", acesta este memorat pe acelasi numar de octeti ca si tipul "int".
Daca "u" este o variabila de tip
"unsigned", atunci
0 <= u <= 2*MAXINT-1

----------------
Tipuri reale
----------------
ANSI C contine trei tipuri reale: "float", "double" si "long double". Variabilele de acest tip vor
putea tine valori reale, cum ar fi:
0.001 2.0 3.14159
Aceasta notatie se numeste notatie zecimala, deoarece contine punctul zecimal. Mai exista si
notatia exponentiala. De exemplu,
1.234567e5 corespunde cu 1.234567 x 10^5=123456.7
Pe majoritatea masinilor, tipul "float" se memoreaza pe 4 octeti, iar tipul "double" pe 8 octeti. Asta
inseamna ca o variabila de tipul
"float" poate avea 6 zecimale, iar o variabila de tipul "double" poate avea 15 zecimale. Astfel, o
variabila de tipul "float" are forma
0,d_1 d_2 d_3 d_4 d_5 d_6 x 10^{n}
unde -38 <= n <= 38.
Asemanator, o variabila de tipul "double" are forma
0,d_1 d_2 ... d_{15} x 10^{n}
unde -308 <= n <= 308.
Astfel, instructiunea
x = 123.45123451234512345; /* 20 cifre semnificative */
va implica atribuirea lui x a valorii
0.123451234512345 x 10^3 (15 cifre semnificative)
In ANSI C, pentru varibilele de tip "long double" se aloca mai multa memorie. Insa sunt
compilatoare care trateaza acest exact tip exact ca si "double".

----------------------------
Operatorul "sizeof()"
----------------------------
C pune la dispozitie operatorul "sizeof()" pentru determinarea numarului de octeti necesari
memorarii unui obiect. Acesta are aceeasi prioritate si asociativitate ca si ceilalti operatori unari. O
expresie de forma
sizeof(obiect)
returneaza un intreg car reprezinta numarul de octeti necesari pentru memorarea obiectului in
memorie. Un obiect poate fi un tip, cum ar fi "int" sau "float", sau poate fi o expresie, cum ar fi a +
b, sau poate fi un sir sau o structura.

-----------
Exemplu: Calculul numarului de octeti pentru cateva tipuri
-----------
#include <stdio.h>
main()
{
printf("Lungimea catorva tipuri fundamentale.\n\n");
printf(" char:%3d octeti \n", sizeof(char));
printf(" short:%3d octeti \n", sizeof(short));
printf(" int:%3d octeti \n", sizeof(int));
printf(" long:%3d octeti \n", sizeof(long));
printf(" unsigned:%3d octeti \n", sizeof(unsigned));
printf(" float:%3d octeti \n", sizeof(float));
printf(" double:%3d octeti \n", sizeof(double));
printf("long double:%3d octeti \n", sizeof(long double));
}
Din moment ce limbajul C este flexibil in ceea ce priveste necesarul de memorie pentru tipurile
fundamentale, situatiile pot sa difere de la o masina la alta. Totusi, aceasta garanteaza ca:
sizeof(char) = 1
sizeof(short) <= sizeof(int) <= sizeof(long)
sizeof(signed) <= sizeof(unsigned) <= sizeof(int)
sizeof(float) <= sizeof(double) <= sizeof(long double)
"sizeof()" nu este o functie (chiar daca contine paranteze atunci cand ne referim la tipuri), ci este un
operator. De exemplu:
sizeof(a + b + 7.7) este echivalent cu sizeof a + b + 7.7

-------------------------
Functii matematice
-------------------------
Nu exista functii matematice implicite (in compilatorul C), ci acestea sunt descrise in biblioteci. De
exemplu, functiile
sqrt() pow() exp() log() sin() cos() tan()
sunt definite in biblioteca <math.h>. Toate aceste functii, cu exceptia lui "power()" au un argument
de tip "double" si returneaza o
valoare de tip "double". Functia "power()" are doua argumente de tip "double" si returneaza o
valoare de tip "double".

---------------------------------------
Conversii implicite si explicite
---------------------------------------
O expresie aritmetica, cum ar fi "x + y", are si valoare si tip. De exemplu, daca "x" si "y" au tipul
"int", atunci expresia "x + y" are
tipul "int". Dar, daca "x" si "y" au ambele tipul "short", atunci "x + y" este de tip "int", si nu
"short". Aceasta se intampla deoarece
in orice expresie, "short" se converteste la "int".

-------------------------
Conversia la intreg
------------------------
Un "char" sau "short", ori "signed" sau "unsigned", ori un tip enumerare (vom reveni) poate fi
folosit in orice expresie unde poate fi folosit "int" sau "unsigned int". Daca toate valorile tipului
original pot fi reprezentate de un "int", atunci valoarea acesteia se va converti la "int"; altfel se va
converti la "unsigned int". Aceasta se numeste "conversie la intreg".
-----------
Exemplu:
-----------
char c = 'A';
printf("%c\n", c);
Variabila "c" apare ca argument al functiei "printf()". Cu toate acestea, deoarece are loc conversia
la intreg, tipul expresiei "c" este "int", si nu "char".

--------------------------------------
Conversiile aritmetice uzuale
--------------------------------------
Conversiile aritmetice pot apare cand sunt evaluati operanzii unui operator binar.
------------
Exemplu:
------------
Presupunem ca "i" este "int" si "f" este un "float". In expresia "i + f", operandul "i" se converteste
la "float" si deci expresia "i + f"
va intoarce tipul "float".
Aceste reguli se numesc "conversii aritmetice uzuale". Iata urmatorul "algoritm":
daca un operand este de tip "long double" atunci
si celalalt operand va fi convertit la tipul "long double"
altfel
daca un operand este de tip "double" atunci
si celalalt operand va fi convertit la tipul "double"
altfel
daca un operand este de tip "float" atunci
si celalalt operand va fi convertit la tipul "float"
altfel
/***** au loc conversiile la intreg *****/
daca un operand este de tip "unsigned long" atunci
si celalalt operand va fi convertit la tipul "unsigned long"
altfel
daca un operand are tipul "long" si celalalt "unsigned" atunci
- daca un "long" poate reprezenta toate valorile "unsigned" atunci
operandul de tip "unsigned" se va converti la "long"
- daca un "long" nu poate reprezenta toate valorile "unsigned" atunci
ambii operanzi se vor converti la "unsigned long"
altfel
daca un operand are tipul "long" atunci
celalalt operand se converteste la "long"
altfel
daca un operator are tipul "unsigned" atunci
celalalt operand va fi convertit la "unsigned"
altfel
ambii operanzi vor avea tipul "int"

-----------
Exemplu: Presupunem ca avem declaratiile:
-----------
char c;
short s;
int i;
unsigned u;
unsigned long ul;
float f;
double d;
long double ld;
Atunci avem urmatoarele valori pentru tipurile expresiilor de mai jos:
-----------------------------------------------------------------------
| Expresie Tip | Expresie Tip |
-----------------------------------------------------------------------
| c-s/i int u * 7 - i unsigned |
| u * 2.0 - i double f * 7 - i float |
| c+3 int 7 * s * ul unsigned long |
| c + 5.0 double ld + c long double |
| d+s double u - ul unsigned long |
| 2*i/l long u-l dependent de sistem |
-----------------------------------------------------------------------

------------------------
Conversii explicite
------------------------
Daca "i" este de tip "int", atunci
(double) i
va converti valoarea lui "i" astfel incat expresia sa aiba tipul "double". Variabila "i" ramane
neschimbata. Conversiile se pot aplica
si expresiilor.
-----------
Exemple:
-----------
(long) ('A' + 1.0)
x = (float) ((int) y + 1)
(double) (x = 77)
Operatorul de conversie de tip (cast) este un operator unar care are aceeasi prioritate si
asociativitate (de la dreapta la stanga) ca
alti operatori unari.

------------
Exemplu:
------------
Expresia
(float) i + 3 este echivalenta cu ((float) i) + 3
pentru ca operatorul "cast" are prioritate mai mare decat "+".

---------------------------------------
Erori de programare frecvente
---------------------------------------
Presupunem ca suntem pe o masina care lucreaza folosind cuvinte memorate pe doi octeti.
Consideram urmatorul exemplu:
-----------
Exemplu:
-----------
int a = 1, b = 1776, c = 32000;
printf("%d\n", a + b + c); /* eroare: va fi afisat -31759 */
Un mod de a repara aceasta greseala este inlocuirea instructiunii "printf()" cu:
printf("%d\n", (long) a + b + c); /* va fi afisat 33777 */
-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Presupunem ca depunem o suma (depozit la termen) intr-o banca care ofera o dobanda de 38 %
(de exemplu) pe an. Sa se calculeze suma finala dupa un anumit numar de ani (se va tine cont de
"dobanda la dobanda").
2. Scrieti o functie C utilizator care sa simuleze functia "power(m, n)" pentru m intreg si n natural.
Cate inmultiri are functia ?
3. Sa se verifice care din urmatoarele numere este mai mare:
pi^e sau e^{pi}
unde "pi"=3.14159265358979324 si "e"=2.71828182845904524.
4. Sa se scrie un program C care aproximeaza "pi" si "e" cu un anumit numar de zecimale.
Idei: Pentru calculul lui "e", puteti folosi convergenta sirului
1+\sum 1/n! -> e
Pentru calculul lui "pi", puteti folosi convergenta sirului
\sum 1/k^2 -> pi^2/6
Tipurile enumerare, "typedef", si operatori pentru biti
7.1. Tipurile enumerare
7.2. Folosirea lui "typedef"
7.3. Expresii si operatori pe biti
7.4. Complement pe bit
7.5. Complement fata de doi
7.6. Operatori logici binari pe biti
7.7. Operatori de deplasare stanga si dreapta
7.8. Masti
7.9. Un program de impachetare si despachetare a cuvintelor
7.10. Litere mari -> litere mici
7.11. Recapitulare
7.12. Exercitii propuse spre implementare

========
Capitolul 7

========
=========================================
Tipurile enumerare, "typedef", si operatori pentru biti
=========================================

--------------------------
Tipurile enumerare
-------------------------

Pentru declararea tipurilor enumerare se foloseste cuvantul rezervat "enum". Acesta va implica
denumirea multimii, enumerarea elementelor (numite enumeratori), ca elemente ale multimii.

-----------
Exemplu:
-----------
enum zile {luni, marti, miercuri, joi, vineri, sambata, duminica};
Aceasta declaratie creeaza tipul utilizator "enum zile". Cuvantul rezervat "enum" este urmat de
identificatorul "zile". Enumeratorii
sunt identificatorii luni, marti, ... . Acestea sunt constante de tip "int". Prin conventie, primul este 0,
si apoi restul sunt incrementati. Declararea variabilelor de tip "enum zile" se face astfel:
enum zile zi1, zi2;
Variabilele "zi1" si "zi2" pot lua ca valori elemente ale acestui tip. De exemplu,
zi1 = miercuri;
va asigna variabilei "zi1" valoarea miercuri. Instructiunea
if (zi1 == zi2)
{
...
}
va testa daca valoarea variabilei "zi1" este egala cu valoarea variabilei "zi2".
Enumeratorii pot fi initializati. De asemeni, putem declara variabilele in timpul definirii tipului
"enum".

-----------
Exemplu:
-----------
enum carti {trefla = 1, caro, frunza, inima} a, b, c;
Din moment ce "trefla" este initializata cu 1, rezulta ca "caro", "frunza" si "inima" sunt initializate
cu 2, 3, respectiv 4
.
-----------
Exemplu:
-----------
enum fructe {mere = 7, pere, portocale = 3, lamai} nr_frct;
Este clar ca "pere" va fi initializat cu 8, iar "lamai" cu 4.
Numele tipului enumerare poate lipsi, insa atunci nu mai putem declara alte variabile de acel tip.

-----------
Exemplu:
-----------
enum {plop, molid, brad} copaci;
Singura variabila de tip "enum {plop, molid, brad}" este copaci (nu se mai poate declara alta).

-----------------------------
Folosirea lui "typedef"
-----------------------------
C pune la dispozitie facilitatea "typedef" pentru redenumirea tipurilor deja existente.
-----------
Exemplu: typedef int culoare;
----------- culoare rosu, verde, albastru;
Acesta defineste tipul "culoare" ca fiind un sinonim al lui "int". Apoi declaram trei variabile de
tipul "culoare".

-----------
Exemplu:
-----------
Vom ilustra folosirea lui "typedef" pentru un tip enumerare (creand o functie ce returneaza ziua
urmatoare).

enum zile {duminica, luni, marti, miercuri, joi, vineri, sambata};


typedef enum zile zi;

zi gaseste_ziua_urmatoare(zi z)
{
zi ziua_urmatoare;

switch(z)
{
case duminica:
ziua_urmatoare = luni;
break;
case luni:
ziua_urmatoare = marti;
break;
...
case sambata:
ziua_urmatoare = duminica;
break;
}
return ziua_urmatoare;
}

O alta versiune (mai succinta) folosind "cast" este:

enum zile {duminica, luni, marti, miercuri, joi, vineri, sambata};


typedef enum zile zi;

zi gaseste_urmatoarea_zi(zi z)
{
return ((zi)(((int) z + 1) % 7));
}

-----------------------------------
Expresii si operatori pe biti
-----------------------------------
Operatorii pe biti lucreaza cu expresii integrale reprezentate ca siruri de cifre binare. Acesti
operatori sunt dependenti de sistem.
Operatorii pe biti sunt:
Operatori logici
--------------------
1. Complement pe bit (unar): ~
2. Si pe bit : &
3. Sau exclusiv pe bit : ^
4. Sau inclusiv pe bit : |
Operatori de deplasare
----------------------------
1. Deplasare stanga : <<
2. Deplasare dreapta : >>
Ca si ceilalti operatori, operatorii pe biti au reguli de precedenta si asociativitate care determina
precis cum se evalueaza expresiile ce contin astfel de operatori. Operatorul ~ este unar, restul
operatorilor sunt binari si lucreaza cu tipuri integrale.

-------------------------
Complement pe bit
-------------------------
Operatorul ~ se numeste operator de complement (sau operator de complement pe bit). Acesta
inverseaza reprezentarea sirului pe biti, adica 0 devine 1 si 1 devine 0.
-----------
Exemplu: Fie declaratia
-----------
int a = 5171;
Reprezentarea binara a lui a este:
00010100 00110011
Expresia ~a este:
11101011 11001100
Aceasta are valoarea intreaga
- 5172
-------------------------------
Complement fata de doi
-------------------------------
Reprezentarea complementului fata de doi a unui numar natural este un sir de biti obtinut prin
complementarierea scrierii lui n in baza 2. Considerand complementul pe biti al lui n la care
adunam 1, obtinem reprezentarea complementului fata de doi a lui -n.
O masina care utilizeaza reprezentarea complementului fata de doi ca reprezentare binara in
memorie pentru valori integrale se numeste masina complement fata de doi. Reprezentarile
complement fata de doi ale lui 0 si -1 sunt speciale. Astfel valoarea 0 are toti bitii "off", pe cand
valoarea -1 are toti bitii "on". Numerele negative sunt caracterizate prin aceea ca au bitul cel mai
semnificativ 1. Pe masinile care utilizeaza complementul fata de doi, hard-ul permite implementarea
scaderii ca o adunare si un complement pe biti. Operatia a - b este aceeasi cu a + (-b), unde -b se
obtine considerand complementul pe biti al lui b la care adunam 1.

---------------------------------------
Operatori logici binari pe biti
--------------------------------------
Cei trei operatori & (si), ^ (sau exclusiv) si | (sau inclusiv) sunt binari si au operanzi integrali.
Operanzii sunt operati bit cu bit. Tabelul de mai jos contine semantica lor:
a b a&b a^b a|b
--------------------------------------------
0 0 0 0 0
1 0 0 1 1
0 1 0 1 1
1 1 1 0 1

-----------
Exemplu: Presupunem ca avem declaratiile si initializarile
-----------
int a = 3333, b = 7777;
Expresie Reprezentare Valoare
-----------------------------------------------------------
a 00001101 00000101 3333
b 00011110 01100001 7777
a&b 00001100 00000001 3073
a^b 00010011 01100100 4964
a|b 00011111 01100101 8037
~(a | b) 11100000 10011010 -8038
(~a & ~b) 11100000 10011010 -8038
----------------------------------------------------------

----------------------------------------------------
Operatori de deplasare stanga si dreapta
----------------------------------------------------
Cei doi operanzi ai unui operator de deplasare trebuie sa fie expresii integrale. Tipul returnat de
expresie este dat de operandul
din stanga. O expresie de forma
expresie1 << expresie2
implica reprezentarea pe bit a lui "expresie1" sa fie deplasata catre stanga cu un numar de pozitii
specificat de "expresie2". In capatul din dreapta, vor fi adaugate 0-uri.

-----------
Exemplu: char c = 'Z';
-----------
------------------------------------------------------------------------
Expresie Reprezentare Actiune
------------------------------------------------------------------------
c 01011010 nedeplasat
c << 1 10110100 deplasare la stanga cu 1
c << 4 10100000 deplasare la stanga cu 4
c << 15 00000000 deplasare la stanga cu 15
------------------------------------------------------------------------
Chiar daca valoarea lui "c" se memoreaza pe un octet, intr-o expresie aceasta ia tipul "int". Deci
valoarea expresiilor "c << 1", "c << 4" si "c << 15" se memoreaza pe doi octeti.
Operatorul de deplasare la dreapta ">>" nu este chiar simetric cu "<<". Pentru expresiile integrale
fara semn, din stanga se va deplasa 0, iar pentru cele cu semn 1.

-----------
Exemplu:
-----------
Presupunem ca avem declaratiile:
int a = 1 << 15;
unsigned b = 1 << 15;
---------------------------------------------------------------------------
Expresie Reprezentare Actiune
---------------------------------------------------------------------------
a 10000000 00000000 nedeplasat
a >> 3 11110000 00000000 deplasare la dreapta cu 3
b 10000000 00000000 nedeplasat
b >> 3 00010000 00000000 deplasare la dreapta cu 3
--------------------------------------------------------------------------
Ca valoare intreaga, a >> 3 este -4096, iar b >> 3 este 4096, lucru care este in concordanta cu
notiunea de numar negativ si pozitiv din matematica.
Daca operandul din dreapta a operatorului de deplasare este negativ sau are o valoare care este
egala sau depaseste numarul de biti folositi pentru reprezentarea operandului din stanga, atunci
comportarea este nedefinita.
Tabelul de mai jos ilustreaza regulile de precedenta (+ este mai prioritar) si asociativitate (de la
stanga la dreapta) referitoare la operatorii de deplasare.

-----------
Exemplu: Presupunem ca avem: unsigned a = 1, b = 2;
-----------
------------------------------------------------------------------------------------------------
Expresie Expresie echivalenta Reprezentare Valoare
------------------------------------------------------------------------------------------------
a << b >> 1 (a << b) >> 1 00000000 00000010 2
a << 1 + 2 << 3 (a << (1 + 2)) << 3 00000000 01000000 64
a + b << 12 * a >> b ((a + b)<<(12 * a))>>b 00001100 00000000 3072
------------------------------------------------------------------------------------------------

--------
Masti
--------
O "masca" este o constanta sau variabila folosita pentru extragerea bitilor doriti dintr-o alta
variabila sau expresie. Din moment ce constanta "int" 1 are reprezentarea pe biti:
00000000 00000001
aceasta poate fi folosita pentru determinarea bitului cel mai nesemnificativ dintr-o expresie "int".
Codul de mai jos foloseste aceasta masca si tipareste o secventa alternativa de 0 si 1:
int i, masca = 1;
for (i = 0; i < 10; ++ i)
printf("%d", i & masca);
Daca dorim sa gasim valoarea unui anume bit dintr-o expresie, putem folosi o masca care este 1 in
acea pozitie si 0 in rest.

-----------
Exemplu:
-----------
Putem folosi expresia 1 << 2 pentru a vedea al treilea bit (numarand de la dreapta). Expresia
(v & (1 << 2)) ? 1 : 0
are valoarea 1 sau 0 dupa cum este al treilea bit din "v".
Alt exemplu de masca este valoarea constanta 255 (adica 2^8 -1). Ea are reprezentarea
00000000 11111111
Deoarece doar octetul din dreapta este plasat pe "on", atunci expresia
v & 255
va intoarce o valoare ce are reprezentare pe biti cu toti bitii din octetul din stanga "off" si cel din
dreapta identic cu octetul din dreapta a lui "v". Spunem ca 255 este o masca pentru octetul din
dreapta.

-------------------------------------------------------------------------
Un program de impachetare si despachetare a cuvintelor
-------------------------------------------------------------------------
Stim ca un caracter se memoreaza pe un octet, pe cand un intreg pe doi octeti. Folosind operatorii
de deplasare, putem comprima doua caractere intr-un intreg.

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

void bit_print(int a)
{
int i;
int n = sizeof(int) * CHAR_BIT;
int masca = 1 << (n-1);

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


{
putchar(((a & masca) == 0) ? '0' : '1');
a <<= 1;
if (i % CHAR_BIT == 0 && i < n)
putchar(' ');
}
}

int pack(char a, char b)


{
int p = a;
p = (p << CHAR_BIT) | b;
return p;
}

char unpack(int p, int k) /* k = 0, 1 */


{
int n = k * CHAR_BIT; /* n = 0, 8 */
unsigned masca = 255;

masca <<= n;
return((p & masca) >> n);
}

void main()
{
clrscr();
bit_print(65);
printf("\nab = ");
bit_print(pack('a', 'b'));
putchar('\n');
getch();
printf("Numarul 24930 este impachetarea caracterelor %c si %c",
unpack(24930,1), unpack(24930,0));
getch();
}

--------------------------------
Litere mari -> litere mici
--------------------------------
Reluam un exemplu dintr-un capitol precedent si anume transformarea literelor mari in mici
folosind operatori de deplasare.

#include <stdio.h>
#include <conio.h>
#include <ctype.h>

void main()
{
clrscr();
int c;
while ((c = getchar()) != EOF)
{
if (isupper(c)) // sau (c>='A' && c<='Z')
putchar(c | (1 << 5));
else
putchar(c);
}
}

-----------------
Recapitulare
-----------------
In cele ce urmeaza, vom preciza cum se calculeaza valoarea unui numar pozitiv, respectiv negativ
(in memoria calculatorului).
Fie declaratia
int a;
Consideram reprezentarea sa in baza 2
_______________________________ ____________________________
|______|______|______________|___| |___|___|_________________|___|
b_{16} b_{15} . . . . . . . b_9 b_8 b_7 ....... b_1
Daca b_{16} = 0 atunci
a = suma_{j=1}^{15} b_j*2^j
altfel
consideram reprezentarea in baza 2 a complementului lui a (~a)
_______________________________ _____________________________
|_______|______|____________|____| |____|____|_______________|____|
b'_{16} b'_{15} . . . . . b'_9 b'_8 b'_7 . . . . . . b'_1
unde b'_j = 1 - b_j;
~a = suma_{j=1}^{15} b'_j*2^j;
-a = ~a + 1;
a = -(~a + 1);
sfarsit.

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. (Jocul hartie, pumn, farfece)
Presupunem ca avem doi jucatori care folosesc mana dreapta pentru reprezentarea a trei obiecte:
hartie = palma intinsa
pumn = mana stransa sub forma de pumn
foarfece = doua degete departate (semnul Victoriei)
Ei isi arata simultan mana dreapta in una din aceste configuratii (de mai multe ori). Daca ei arata
acelasi lucru, este remiza (nu castiga nimeni). Daca nu se aplica una din urmatoarele trei reguli:
a) Hartia acopera pumnul. (deci palma intinsa castiga fata de pumn)
b) Pumnul sparge foarfecele.
c) Foarfecele taie hartia.
Sa se simuleze acest joc, facand un numar arbitrar de evenimente precizand scorul final. Se cere sa
se joace persoana-calculator, si
varianta a doua calculator-calculator.
2. O ruleta este o masina care selecteaza la intamplare un numar intre 0 si 35 la intamplare.
Jucatorul poate paria pe un numar par/impar sau poate paria pe un numar oarecare. Castigul unui
pariu par/impar se premiaza cu 2/1 dolari, cu exceptia celor in care ruleta alege 0. Daca jucatorul
"prinde" numarul selectat de ruleta, atunci este platit cu 35 ... 1 dolari.
Intrebare: Considerand ca jucam pariuri de 1 dolar, cate parieri trebuie sa facem astfel incat sa
pierdem 10 dolari ?
3. Folosind functia "bit_print" construiti un tabel (cu trei coloane) care sa contina n, reprezentarea
binara a lui 2^n, reprezentarea binara a lui 2^n-1, pentru n = 0, 1, 2, ..., 32. Apoi, afisati un tabel (tot
cu trei coloane) care sa contina n, 10^n si 10^n-1 pentru n = 0, 1, 2, ..., 7 (in baza zece). Ce
asemanare observati ?
4. Zilele secolului 20 pot fi notate folosind intregi in forma "zi/luna/an". De exemplu, 1/7/93
inseamna 1 iulie 1993. Scrieti o functie care memoreaza ziua, luna si anul compact, astfel:
a) pentru zile (maxim 31) sunt suficienti 5 biti;
b) pentru luna (12) sunt suficienti 4 biti;
c) pentru ani (100) sunt suficienti 7 biti;
Functia trebuie sa aiba la intrare ziua, luna si anul ca intregi si trebuie sa returneze data
"impachetata" pe un "intreg" pe 16 biti. Scrieti inca o functie care face "despachetarea".
5. Folosind operatori pe biti, scrieti programe C care:
a) testeaza daca un numar de tip "int" sau "long" este divizibil cu 8. Generalizare (divizibilitate cu
2^n);
b) testeaza daca un numar este pozitiv sau negativ;
c) calculeaza pentru un n dat, multiplii de 2, 4, ... Generalizare;
d) calculeaza pentru un n dat, [n/2], [n/4], ... Generalizare;
e) calculeaza m^n, folosind reprezentarea in baza 2 a lui n si metoda "divide et impera" (vezi
exercitiul 2 din capitolul 6).
6. Sa se calculeze suma cifrelor unui numar cu n cifre.
Functii, pointeri si clase de memorare
8.1. Declararea si atribuirea pointerilor
8.2. Adresare si indirectare
8.3. Pointeri catre "void"
8.4. Apel prin adresa (referinta)
8.5. Reguli pentru stabilirea domeniului
8.6. Clase de memorare
8.7. Clasa de memorare "auto"
8.8. Clasa de memorare "extern"
8.9. Clasa de memorare "register"
8.10. Clasa de memorare "static"
8.11. Variabile externe statice
8.12. Initializari implicite
8.13. Definitii si declaratii de functii
8.14. Calificatorii de tip "const" si "volatile"
8.15. Exercitii propuse spre implementare

========
Capitolul 8
========
============================
Functii, pointeri si clase de memorare
============================
Va amintiti ca daca o expresie este transmisa ca argument pentru o functie, atunci se creeaza o
copie a valorii expresiei care se transmite. Acest mecanism este cunoscut sub numele de apel prin
valoare ("call-by-value") si se foloseste in limbajul C. Presupunem ca avem o variabila v si o
functie f(). Daca scriem
v = f(v);
atunci valoarea returnata de functia f va schimba valoarea lui v, altfel nu. In interiorul functiei f, nu
se modifica valoarea lui v. Aceasta se datoreaza faptului ca se transmite doar o copie a lui v catre f.
In alte limbaje de programare, un apel de functie poate schimba valoarea lui v din memorie. Acest
mecanism se mai numeste apel prin referinta ("call-by-reference"). Noi vom simula apelul prin
referinta transmitand adresele variabilelor ca argumente in apelul functiei.

----------------------------------------------
Declararea si atribuirea pointerilor
---------------------------------------------
Pointerii sunt folositi in programe pentru accesarea memoriei si manipularea adreselor. Deja ne-am
intalnit cu adresele variabilelor
ca argumente ale functiei "scanf()". De Exemplu, putem avea:
scanf("%d\n", &n);
Daca v este o variabila, atunci &v este o adresa (sau locatie) din memorie. Operatorul de adresa &
este unar si are aceeasi precedenta si asociativitate de la dreapta la stanga ca si ceilalti operatori
unari. Variabilele pointer pot fi declarate in programe si apoi folosite pentru a lua valori adrese din
memorie.

-----------
Exemplu: Declaratia
-----------
int i, *p;
defineste i de tip "int" si p "pointer catre int". Domeniul legal de valori pentru orice pointer
cuprinde adresa speciala 0 si o multime de numere naturale care sunt interpretate ca fiind adrese
masina ale sistemului C. De obicei, constanta simbolica NULL este 0 (definita in <stdio.h>).

-----------
Exemple:
-----------
1. p = &i; /* valoarea lui p este adresa lui i */
2. p = 0; /* valoarea lui p este adresa speciala 0 */
3. p = NULL; /* echivalent cu p = 0; */
4. p = (int *) 1307; /* o adresa absoluta din memorie */

------------------------------
Adresare si indirectare
------------------------------
Am vazut ca operatorul de adresa & se aplica unei variabile si intoarce valoarea adresei sale din
memorie. Operatorul de indirectare (sau de dereferentiere) se aplica unui pointer si returneaza
valoarea scrisa in memorie la adresa data de pointer. Intr-un anumit sens, acesti doi operatori sunt
inversi unul altuia. Pentru a intelege mai bine aceste notiuni, sa vedem pe un exemplu ce se
intampla in memorie:
-----------
Exemplu:
-----------
Presupunem ca avem declaratiile:

int i = 777, *p = &i;

Atunci, in memorie avem:


Nume Tip Valoare Adresa
---------------------------------------
| i | int | 777 | 3A38:0FFE |
---------------------------------------
/\ |
| |
(*p == i) * | -------- (p = &i)
| | &
| \/
------------------------------------------------
| p | int * | 3A38:0FFE | 3A38:0FFA |
------------------------------------------------
Nume Tip Valoare Adresa

Mentionam ca adresa unei variabile este dependenta de sistem (C aloca memorie acolo unde poate).

-----------
Exemplu:
-----------
float x, y, *p;
p = &x;
y = *p;
Mai intai "p" se asigneaza cu adresa lui "x". Apoi, "y" se asigneaza cu valoarea unui obiect la care
pointeaza p (adica *p). Aceste doua instructiuni de asignare se pot scrie
y = *&x;
care este echivalent cu
y = x;
Am vazut mai sus ca un pointer se poate initializa in timpul declararii sale. Trebuie sa avem totusi
grija ca variabilele din membrul drept sa fie deja declarate.

----------
Exemplu: Unde este greseala ? int *p = &a, a;
----------
Tabelul de mai jos ilustreaza modul de evaluare a expresiilor cu pointeri.

----------
Exemplu:
----------
Presupunem ca avem declaratiile:
int i = 3, j = 5, *p = &i, *q = &j, *r;
double x;
---------------------------------------------------------------------
| Expresie | Expresie echivalenta | Valoare |
---------------------------------------------------------------------
p == & i p == (& i) 1
p=i+7 p = (i + 7) gresit
**&p * (* (& p)) 3
r=&x r = (& x) gresit
7**p/*q+7 (((7 * (* p))) / (* q)) + 7 11
* (r = & j) *= * p (* (r = (& j))) *= (* p) 15
--------------------------------------------------------------------
Spre deosebire de C traditional, in ANSI C, singura valoare intreaga care poate fi asignata unui
pointer este 0 (sau constanta NULL). Pentru asignarea oricarei alte valori, trebuie facuta o conversie
explicita (cast).
In cele ce urmeaza, vom scrie un program care ilustreaza legatura dintre valoarea unui pointer si
adresa lui.

-----------
Exemplu:
-----------
#include <stdio.h>
#include <conio.h>

void main()
{
int i = 777, *p = &i;
clrscr();

printf("Valoarea lui i: %d\n", *p);


printf("Adresa lui i: %lu sau %p\n", &i, &i);
printf("Adresa lui i: %lu sau %p\n", p, p);
printf("Valoarea lui p: %lu sau %p\n", p, p);
printf("Adresa lui p: %lu sau %p\n", &p, &p);
getch();
}

Locatia curenta a unei variabile din memorie este dependenta de sistem. Operatorul * (din expresia
*p) va afisa valoarea scrisa la adresa care este egala cu valoarea lui p. Adresa lui i (valoarea lui p)
va fi afisata ca fiind ceva de genul
3A38:0FFE
care reprezinta un numar scris in baza 16 (in care cifrele sunt 0, 1, ..., 9, A, B, C, D, E, F) si are
valoarea
3*16^7+10*16^6+3*16^5+8*16^4+ 15*16^2+15*16+14 = 976752638
De observat ca un pointer se memoreaza intotdeauna pe patru octeti indiferent de tipul variabilei
catre care se face referirea. Explicati de ce ?

---------------------------
Pointeri catre "void"
---------------------------
In C traditional, pointerii de tipuri diferite sunt considerati compatibili ca asignare. In ANSI C,
totusi, un pointer poate fi asignat altuia doar daca au acelasi tip, sau cand unul dintre ei este de tipul
"void". De aceea, putem gandi "void *" ca un tip pointer generic.

----------
Exemple: Presupunem ca avem declaratiile
----------
int *p;
float *q;
void *v;
----------------------------------------------------------
| Asignari legale | Asignari ilegale |
----------------------------------------------------------
| p = 0; p = 1; |
| p = (int *) 1; v = 1; |
| p = v = q; p = q; |
| p = (int *) q; |
---------------------------------------------------------
Vom discuta in capitolele ulterioare despre functiile "calloc()" si "malloc()", care produc alocare
dinamica a memoriei pentru vectori si structuri. Ele returneaza un pointer catre "void", de aceea
putem scrie:
int *a;
a = calloc(...);
In C traditional, trebuie sa facem conversie explicita:
a = (int *) calloc(...);

-----------------------------------
Apel prin adresa (referinta)
-----------------------------------
Am vazut ca C foloseste mecanismul apelului prin valoare ("call-by-value") in cazul apelurilor
functiilor si anume se fac copii ale parametrilor actuali care se transmit functiilor. In cele ce
urmeaza, vom descrie mecanismul apelului prin adresa si astfel se va asigura modificarea valorii
variabilei transmise. Pentru aceasta, vom utiliza pointeri.
-----------
Exemplu:
-----------
#include <stdio.h>

void interschimba(int *, int *);


void main()
{
int a = 3, b = 7;

printf("%d %d\n", a, b);


interschimba(&a, &b);
printf("%d %d\n", a, b);
}

void interschimba(int *p, int *q)


{
int tmp;

tmp = *p;
*p = *q;
*q = tmp;
}
Efectul apelului prin adresa este realizat prin:
1. Declararea parametrului functiei ca fiind un pointer;
2. Folosirea unui pointer de indirectare in corpul functiei;
3. Transmiterea adresei unui argument cand functia este apelata.

----------------------------------------------
Reguli pentru stabilirea domeniului
----------------------------------------------
Domeniul unui identificator este partea din textul unui program unde identificatorul este cunoscut
sau accesibil. Aceasta idee depinde de notiunea de "bloc", care este o instructiune compusa cu
declaratii.
Regula de baza in stabilirea domeniului este aceea ca identificatorii sunt accesibili numai in blocul
unde sunt declarati si necunoscuti in afara granitelor blocului. Unii programatori folosesc acelasi
nume de identificatori prezenti in anumite blocuri.
-----------
Exemplu:
-----------
{
int a = 2;
printf("%d\n", a);
{
int a = 7;
printf("%d\n", a);
}
printf("%d\n", ++a);
}
Un program echivalent ar fi:
{
int a_afara = 2;
printf("%d\n", a_afara);
{
int a_inauntru = 7;
printf("%d\n", a_inauntru);
}
printf("%d\n", ++a_afara);
}

-------------------------
Clase de memorare
-------------------------
Orice variabila si functie are doua atribute:
tipul si clasa de memorare
Exista patru clase de memorare in C, automata, externa, registru si statica si sunt date de
urmatoarele cuvinte rezervate:
auto extern register static
Cea mai cunoscuta clasa de memorare este "auto".

----------------------------------
Clasa de memorare "auto"
----------------------------------
Variabilele declarate in interiorul functiilor sunt implicit automate. De aceea, clasa "auto" este cea
mai cunoscuta dintre toate. Daca o instructiune compusa (bloc) incepe cu declararea unor variabile,
atunci aceste variabile sunt in domeniu in timpul acestei instructiuni compuse (pana la intalnirea
semnului }).

-----------
Exemplu:
-----------
auto int a, b, c;
auto float f;
Declaratiile variabilelor in blocuri sunt implicit automate.
La executie, cand se intra intr-un bloc, se aloca memorie pentru variabilele automate. Variabilele
sunt considerate locale acestui bloc. Cand se iese din acest bloc, sistemul elibereaza zona de
memorie ocupata de acestea si deci valorile acestor variabile se pierd. Daca intram din nou in acest
bloc, atunci se aloca din nou memorie pentru aceste variabile, dar vechile valori sunt necunoscute.

-------------------------------------
Clasa de memorare "extern"
-------------------------------------
O metoda de transmitere a informatiei in blocuri si functii este folosirea variabilelor externe. Daca
o variabila este declarata inafara functiei, atunci acesteia i se aloca permanent memorie si spunem
ca ea apartine clasei de memorare "extern". O variabila externa este considerata globala tuturor
functiilor declarate dupa ea, si chiar dupa iesirea din blocuri sau functii, ea ramane permanent in
memorie.
-----------
Exemplu:
-----------
#include <stdio.h>

int a = 1, b = 2, c = 3;
int f(void);

void main()
{
printf("%3d\n", f());
printf("%3d%3d%3d\n", a, b, c);
}
int f(void)
{
int b, c; /* b si c sunt locale, deci b, c globale sunt
mascate */

a = b = c = 4; /* valoarea lui a se modifica */


return(a + b +c);
}

Explicatia este foarte simpla. La inceput se memoreaza cate 2 octeti pentru "a", "b", "c". Cand
ajungem la functia "f()", memoram inca cate doi octeti pentru "b" si "c" (notate la fel din
intamplare). La intoarcerea in functia apelanta, aceste "b" si "c" noi nu mai exista pentru ca erau
locale functiei "f()". Sa vedem mai exact ce se intampla in memorie:
Inainte de apelul functiei "f()":
Nume Tip Valoare Adresa
---------------------------------------
| a | int | 1 | 3A38:0FFE |
----------------------------------------
----------------------------------------
| b | int | 2 | 3A38:0FFC |
----------------------------------------
----------------------------------------
| c | int | 3 | 3A38:0FFA |
----------------------------------------
In timpul executiei functiei "f()" (dupa a = b = c = 4):
Nume Tip Valoare Adresa
---------------------------------------
| a | int | 4 | 3A38:0FFE |
---------------------------------------
---------------------------------------
| b | int | 2 | 3A38:0FFC |
---------------------------------------
---------------------------------------
| c | int | 3 | 3A38:0FFA |
---------------------------------------
--------------------------------------
| b | int | 4 | 3A38:0FF8 |
--------------------------------------
--------------------------------------
| c | int | 4 | 3A38:0FF6 |
--------------------------------------
La intoarcerea in functia "main()":
Nume Tip Valoare Adresa
---------------------------------------
| a | int | 4 | 3A38:0FFE |
---------------------------------------
---------------------------------------
| b | int | 2 | 3A38:0FFC |
---------------------------------------
---------------------------------------
| c | int | 3 | 3A38:0FFA |
--------------------------------------
Deci, cuvantul rezervat "extern" spune compilatorului "cauta peste tot, chiar si in alte fisiere !".
Astfel, programul precedent se poate rescrie:
in fisierul "fisier1.c":

#include <stdio.h>

int a = 1, b = 2, c = 3; /* variabile externe */


int f(void);

void main()
{
printf("%3d\n", f());
printf("%3d%3d%3d\n", a, b, c);
}
in fisierul "fisier2.c":

int f(void)
{
extern int a; /* cauta-l peste tot */
int b, c;

a = b = c = 4; /* valoarea lui a se modifica */


return(a + b +c);
}

Deci, putem conchide ca informatiile se pot transmite prin variabile globale (declarate cu extern)
sau folosind transmiterea parametrilor. De obicei se prefera al doilea procedeu.
Toate functiile au clasa de memorare externa. De Exemplu,
extern double sin(double);
este un prototip de functie valid pentru functia "sin()", iar pentru definitia functiei, putem scrie:
extern double sin(double x)
{
..
}

--------------------------------------
Clasa de memorare "register"
--------------------------------------
Clasa de memorare "register" spune compilatorului ca variabilele asociate trebuie sa fie memorate
in registri de memorie de viteza mare, cu conditia ca aceasta este fizic si semantic posibil. Daca
limitarile resurselor si restrictiile semantice (cateodata) fac aceasta imposibila, clasa de memorare
register va fi inlocuita cu clasa de memorare implicita "auto". De obicei, compilatorul are doar
cativa astfel de registri disponibili. Multi sunt folositi de sistem si deci nu pot fi alocati.
Folosirea clasei de memorare "register" este o incercare de a mari viteza de executie a programelor.
De regula, variabilele dintr-o
bucla sau parametrii functiilor se declara de tip "register".
-----------
Exemplu:
-----------
{
register int i;
for (i = 0; i < LIMIT; ++i)
{
.....
}
} /* la iesirea din bloc, se va elibera registrul i */
Declaratia
register i;
este echivalenta cu
register int i;
Daca lipseste tipul variabilei declarata intr-o clasa de memorare de tip "register", atunci tipul se
considera implicit "int".

-----------------------------------
Clasa de memorare "static"
-----------------------------------
Declaratiile "static" au doua utilizari distincte si importante:
a) permite unei variabile locale sa retina vechea valoare cand se reintra in bloc (sau functie)
(caracteristica ce este in contrast cu variabilele "auto" obisnuite);
b) folosita in declaratii externe are alta comportare (vom discuta in sectiunea urmatoare);
Pentru a ilustra a), consideram Exemplul:
-----------
Exemplu:
-----------
void f(void)
{
static int contor = 0;

++contor;
if (contor % 2 == 0)
.....
else
.....
}

Prima data cand functia este apelata, "contor" se initializeaza cu 0. Cand se paraseste functia,
valoarea lui "contor" se pastreaza in memorie. Cand se va apela din nou functia "f()", "contor" nu se
va mai initializa, ba mai mult, va avea valoarea care s-a pastrat in memorie la precedentul apel.
Declararea lui "contor" ca un "static int" in functia "f()" il pastreaza privat in "f()" (adica numai aici
i se poate modifica valoarea). Daca ar fi fost declarat in afara acestei functii, atunci si alte il puteau
accesa.

-------------------------------
Variabile externe statice
-------------------------------
Ne vom referi acum la folosirea lui "static" ca declaratie externa. Aceasta pune la dispozitie un
mecanism de "izolare" foarte important pentru modularitatea programelor. Prin "izolare" intelegem
vizibilitatea sau restrictiile de domeniu.
Deosebirea dintre variabile externe si cele externe static este ca acestea din urma sunt variabile
externe cu restrictii de domeniu. Domeniul este fisierul sursa in care ele sunt declarate. Astfel,
acestea sunt inaccesibile pentru functiile definite anterior in fisier sau definite in alte fisiere, chiar
daca functiile folosesc clasa de memorare "extern".
-----------
Exemplu:
-----------
void f(void)
{
. . . . . /* v nu este accesibil aici */
}

static int v; /* variabila externa statica */

void g(void)
{
. . . . . /* v poate fi folosit aici */
}
Vom mai prezenta un Exemplu de generare a numerelor aleatoare bazata pe metode de congruente
liniara (Knuth, D., E.: "The Art of Computer Programming", 2nd ed., vol. 2, "Seminumerical
Algorithms", Reading Mass. Addison-Wesley, 1981).

-----------
Exemplu:
-----------
#define INITIAL_SEED 17 /* SEED - samanta */
#define MULTIPLIER 25273
#define INCREMENT 13849
#define MODULUS 65536
#define FLOATING_MODULUS 65536.0

static unsigned seed = INITIAL_SEED; /* externa, dar locala acestui fisier */


unsigned random(void)
{
seed = (MULTIPLIER * seed + INCREMENT) % MODULUS;
return seed;
}

double probability(void)
{
seed = (MULTIPLIER * seed + INCREMENT) % MODULUS;
return (seed / FLOATING_MODULUS);
}

Functia "random()" produce o secventa aleatoare (aparenta) de numere intregi situate intre 0 si
MODULUS. Functia "probability()" produce o secventa aleatoare (aparenta) de valori reale intre 0
si 1.
Observam ca un apel al functiei "random()" sau "probability()" produce o noua valoare a variabilei
"seed" care depinde de cea veche. Din moment ce "seed" este o variabila externa statica, aceasta
este locala acestui fisier si valoarea sa se pastreaza de la un apel la altul. Putem acum crea functii in
alte fisiere care apeleaza aceste numere aleatoare fara sa avem grija efectelor laterale.
Prezentam, in continuare, un ultim Exemplu de utilizare a lui "static" ca specificator de clasa de
memorare pentru functii. Functiile declarate "static" sunt vizibile doar in fisierul unde au fost
declarate.
-----------
Exemplu:
-----------
void f(int a)
{
. . . . . /* g() este disponibil aici, dar nu si in alte fisiere */
}

static int g(void)


{
.....
}

--------------------------
Initializari implicite
--------------------------
In C, variabilele externe si statice care nu sunt explicit initializate de catre programator, sunt
initializate de catre sistem cu 0. Aceasta include siruri, siruri de caractere, pointeri, structuri si
inregistrari (union). Pentru siruri (de caractere), aceasta inseamna ca fiecare element se initializeaza
cu 0, iar pentru structuri si "union" fiecare membru se initializeaza tot cu 0. In contrast cu aceasta,
variabilele "registru" si "auto" nu se initializeaza de catre sistem, ci pornesc cu valori "garbage"
(adica cu ce se gaseste la momentul executiei la acea adresa).

-----------
Exemplu: Procesarea caracterelor
-----------
O functie care utilizeaza "return" poate returna o singura valoare. Daca dorim sa trasmitem mai
multe valori pentru mediul apelant, atunci trebuie sa transmitem adresele unor variabile. Vrem sa
procesam un sir de caractere (in stilul "top-down") astfel:
- citeste caractere de la intrare pana cand avem EOF;
- schimba litere mici in litere mari;
- scrie pe fiecare linie trei cuvinte separate de un singur spatiu;
- numara caracterele si literele de la intrare.

#include <stdio.h>
#include <ctype.h>

#define NR_CUVINTE 3
int procesare(int *, int *, int *);

void main()
{
int c, numar_caractere = 0, numar_litere = 0;

while ((c = getchar()) != EOF)


if (procesare(&c, &numar_caractere, &numar_litere) == 1)
putchar(c);
printf("\n%s%5d\n%s%5d\n\n",
"Numar de caractere:", numar_caractere,
"Numar de litere: ", numar_litere);
}

int procesare(int *p, int *n_c_p, int *n_l_p)


{
static int contor = 0, ultim_caracter = ' ';
if (isspace(ultim_caracter) && isspace(*p))
return 0;
if (isalpha(*p))
{
++*n_l_p;
if (islower(*p))
*p = toupper(*p);
}
else
if (isspace(*p))
if (++contor % NR_CUVINTE == 0)
*p = '\n';
else
*p = ' ';
++*n_c_p;
ultim_caracter = *p;
return 1;
}

----------------------------------------
Definitii si declaratii de functii
---------------------------------------
Pentru compilator, declaratiile functiilor sunt date in multe moduri:
- apelul functiei
- definitia functiei
- prototipuri si declaratii explicite
Daca un apel de functie cum ar fi f(x) apare inainte de a fi declarata atunci compilatorul presupune
declaratia implicita
int f();
In stilul C traditional, declararea functiilor se face astfel:
int f(x)
double x;
{
.....
}
Este responsabilitatea programatorului de a transmite o variabila de tip "double". In stilul ANSI C,
aceasta s-ar scrie:
int f(double x)
{
.....
}
In acest caz, compilatorul stie tipul argumentelor din functia "f()". De Exemplu, daca un "int" este
transmis ca parametru, atunci el va fi convertit automat la "double".
Exista cateva limitari pentru definitiile si prototipurile functiilor. Clasa de memorare a functiei,
daca este prezenta, poate fi "extern" sau "static", dar nu ambele; "auto" si "register" nu se pot folosi.
Singura clasa care se poate folosi in lista de tipuri a parametrilor este "register". Parametrii nu se pot
initializa.

--------------------------------------------------
Calificatorii de tip "const" si "volatile"
--------------------------------------------------
Comitetul ANSI a adaugat cuvintele rezervate "const" si "volatile" pentru limbajul C (acestea nu
sunt disponibile in limbajul C traditional). De obicei, "const" este plasat intre clasa de memorare si
tipul variabilei.
-----------
Exemplu: static const int k = 3;
-----------
Citim aceasta "k este o constanta de tip int cu clasa de memorare static". Deoarece "k" are tipul
"const", atunci putem initializa "k", dar nu mai poate fi reasignat (incrementat sau decrementat).
Chiar daca variabila este calificata ca fiind "const", aceasta nu se poate folosi pentru precizarea
lungimii unui sir.
-----------
Exemplu:
-----------
const int n = 3;
int v[n]; /* gresit */
Deci o variabila calificata "const" nu este echivalenta cu o constanta simbolica.
Un pointer necalificat nu poate fi asignat cu adresa unei variabile calificata "const".

----------
Exemplu:
-----------
const int a = 7;
int *p = &a; /* gresit */
Motivul este ca "p" este un pointer obisnuit catre "int" si l-am putea folosi mai tarziu in expresii de
genul "++*p". Totusi, utilizand pointeri, putem schimba valoarea lui a (ceea ce contravine
conceptului de constanta).

-----------
Exemplu:
-----------
const int a = 7;
const int *p = &a;
Nu vom putea modifica valoarea lui "a", utilizand "*p". Pointerul "p" nu este constant (putem face
p++).
Presupunem ca vrem ca "p" sa fie constant, si nu "a". Consideram declaratiile:
int a;
int * const p = &a;
Ultima declaratie spune ca "p este un pointer constant catre int, si valoarea sa initiala este adresa lui
a". Apoi, nu mai putem asigna o valoare lui p, dar putem da valori lui "*p".
Consideram acum un Exemplu si mai interesant:

------------
Exemplu:
------------
const int a = 7;
const int * const p = &a;
Ultima declaratie spune ca p este un pointer constant catre o constanta intreaga. Nici "p", nici "*p",
nu mai pot fi reasignate. In contrast cu "const", calificatorul "volatile" este rar folosit. Un obiect
"volatile" este unul ce poate fi modificat intr-un mod nespecificat de catre hard.

-----------
Exemplu: Consideram declaratia
-----------
extern const volatile int real_time_clock;
Clasa de memorare "extern" inseamna "cauta-l oriunde, in acest fisier sau in alte fisiere".
Calificatorul "volatile" presupune ca obiectul poate fi modificat de hard. Din moment ce apare si
calificatorul "const", inseamna ca obiectul nu poate fi modificat din program.

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Daca "i" si "j" sunt de tip "int", iar "p" si "q" sunt pointeri catre "int", precizati care dintre
urmatoarele asignari sunt corecte:
p = &i; p = &*&i; i = (int) p; q = &p;
*q = &j; i = (*&)j; i = *&*&j; i = (*p)++ + *q;
2. Scrieti o functie C care sa faca o permutare circulara a cinci variabile. (1,2,3,4,5) -> (2,3,4,5,1).
3. Fie codul C
int v = 7, *p = &v, **q = &p;
printf("%p\n%d\n%p\n%p\n%d\n%p\n%p\n%p\n%d\n",
&v, *&v, &p, *&p, **&p, &q, *&q, **&q, ***&q);
Explicati de ce anumite numere se repeta ! Observati ca am folosit combinatia "*&", si nu "&*".
Explicati daca exista situatii unde "&*" este corect semantic.
4. Scrieti un program C care arata pe cati octeti sunt memorati pointerii catre tipurile fundamentale
de date. Ce observati ?
Siruri si pointeri
9.1. Siruri uni-dimensionale
9.2. Initializarea sirurilor
9.3. Indexul unui sir
9.4. Relatia dintre vectori si pointeri
9.5. Pointeri aritmetici si lungimea elementelor
9.6. Trimiterea sirurilor ca argumente pentru functii
9.7. Siruri multidimensionale
9.8. Vectori 2-dimensionali
9.9. Vectori 3-dimensionali
9.10. Initializarea vectorilor
9.11. Alocarea dinamica a memoriei
9.12. Exercitii propuse spre implementare

=========
Capitolul 9
=========
=============
Siruri si pointeri
=============
Un sir (se mai spune si vector) este o secventa de date ce contine articole de acelasi tip, indexate si
memorate contiguu. De obicei, sirurile se folosesc pentru reprezentarea unui numar mare de valori
omogene (in capitolul urmator vom studia sirurile de caractere). O declaratie obisnuita de sir aloca
memorie incepand de la adresa de baza. Numele sirului este un pointer constant la aceasta adresa de
baza.
O alta notiune pe care o vom explica este transmiterea sirurilor ca argumente in functii.

-------------------------------
Siruri uni-dimensionale
-------------------------------
------------
Exemplu:
------------
Presupunem ca vrem sa lucram cu trei intregi:
int a1, a2, a3;
Totusi, daca avem mai multe numere este anevoios sa declaram numerele in acest fel. Solutia
consta in utilizarea unui sir (de lungime trei) de intregi.
int a[3];
Elementele acestui sir vor fi accesate astfel: a[0] a[1] a[2]
Deci numarul 3 reprezinta lungimea sirului, elementele sale fiind indexate incepand cu numarul 0.
Aceasta este o trasatura a limbajului C.
O declaratie de sir uni-dimensional este un tip urmat de un identificator urmat la randul lui de
paranteze patrate ce cuprind o expresie integrala constanta. Valoarea expresiei constante, care
trebuie sa fie pozitiva, se numeste lungimea sirului si ea specifica numarul de elemente ale sirului.
Pentru memorarea elementelor intr-un sir, compilatorul rezerva un spatiu de memorie
corespunzator, pornind de la adresa de baza. Dimensiunea spatiului de memorie este egala cu
numarul de elemente ale sirului inmultit cu numarul de octeti necesari memorarii unui element al
sirului.
------------
Exemplu:
------------
Vom scrie un mic program care initializeaza un sir, tipareste valorile sale si insumeaza elementele
sirului.

#include <stdio.h>
#define N 5

void main()
{
int a[N]; /* aloca spatiu de memorie pentru a[0], a[1], a[2], a[3] si a[4] */
int i, suma = 0;

for (i = 0; i < N; ++i) /* initializeaza sirul */


a[i] = 7 + i * i;
for (i = 0; i < N; ++i) /* tipareste sirul */
printf("a[%d] = %d ", i, a[i]);
for (i = 0; i < N; ++i) /* insumeaza elementele sirului */
suma += a[i];
printf("\nsuma = %d\n", suma); /* tipareste suma lor */
}

Sa vedem ce se intampla in memorie ?


Nume Tip Valoare Adresa
---------------------------------------
| a[4] | int | 23 | 3A38:0FFE |
---------------------------------------
---------------------------------------
| a[3] | int | 16 | 3A38:0FFC |
---------------------------------------
---------------------------------------
| a[2] | int | 11 | 3A38:0FFA |
---------------------------------------
-------------------------------------
| a[1] | int | 8 | 3A38:0FF8 |
-------------------------------------
-------------------------------------
| a[0] | int | 7 | 3A38:0FF6 |
-------------------------------------

Deci vectorul (sirul) "a" se va memora incepand de la adresa 3A38:0FF6. Deci "a = &a[0]".
Se recomanda definirea lungimii unui sir ca o constanta simbolica (folosind directiva "#define").

---------------------------
Initializarea sirurilor
---------------------------
Sirurile pot apartine claselor de memorare "auto", "extern", "static" sau "constant", dar nu pot fi
"register". Ca si variabilele simple,
sirurile pot fi initializate in timpul declararii lor. Initializarea sirurilor se face folosind acolade si
virgule.
------------
Exemplu:
------------
float x[7] = {-1.1, 0.2, 33.0, 4.4, 5.05, 0.0, 7.7};
Asta inseamna, echivalent:
x[0] = -1.1;
x[1] = 0.2;
.....
x[6] = 7.7;
Daca lista de valori de initializare este mai mica decat numarul de elemente ale sirului, atunci
elementele ramase se initializeaza cu 0. Daca un sir declarat "extern" sau "static" nu este initializat,
atunci sistemul initializeaza toate elementele cu 0. Vectorii declarati constanti sau automatic (cei
impliciti) sunt initializati cu valori "garbage" (adica cu valorile existente in momentul executiei in
memorie la acele adrese). C traditional permite doar initializarea vectorilor declarati "extern" sau
"static", pe cand ANSI C permite initializarea sirurilor automate si constante.
Daca un sir este declarat fara precizarea lungimii si initializat cu o serie de valori, atunci lungimea
sa se considera implicit numarul
de valori initiale.

------------
Exemplu:
------------
Declaratiile
int a[] = {3, 4, 5, 6}; si int a[4] = {3, 4, 5, 6};
sunt echivalente.

---------------------
Indexul unui sir
--------------------
Presupunem ca avem declaratia
int i, a[lungime];
Pentru accesarea unui element din sir, vom scrie "a[i]", sau mai general "a[expresie]", unde
"expresie" este o expresie integrala. "i" de mai sus se numeste index al sirului "a" si poate avea
valori intre 0 si "lungime-1". Daca indexul depaseste acest domeniu, compilatorul va da eroare in
timpul executiei programului.

-----------
Exemplu:
-----------
#include <stdio.h>
#include <ctype.h>

void main()
{
int c, i, litera[26];

for (i = 0; i < 26; ++i) /* initializarea vectorului cu 0 */


litera[i] = 0;
while ((c = getchar()) != EOF) /* numararea literelor */
if (isupper(c))
++litera[c - 'A'];
for (i = 0; i < 26; ++i) /* tiparirea rezultatelor */
{
if (i % 6 == 0)
printf("\n");
printf("%5c:%4d", 'A' + i, litera[i]);
}
printf("\n\n");
}
Acest program citeste de la tastatura sau dintr-un fisier (folosind indirectarea) un sir de caractere si
numara in vectorul "litera" fiecare aparitie (in parte) a literelor.

-----------------------------------------
Relatia dintre vectori si pointeri
-----------------------------------------
Am vazut ca numele unui sir (de exemplu "a") este o adresa, deci poate fi privit ca valoare a unui
pointer. Deci sirurile si pointerii pot fi priviti oarecum la fel in ceea ce priveste modul cum sunt
folositi pentru accesarea memoriei. Cu toate acestea, sunt cateva diferente (subtile si importante).
Cum numele unui sir este o adresa fixa (particulara), atunci aceasta o putem gandi ca un pointer
constant. Cand este declarat un sir, compilatorul trebuie sa aloce o adresa de baza si un spatiu
suficient de memorie care trebuie sa contina toate elementele sirului. Adresa de baza a unui sir este
locatia initiala din memorie unde sirul este memorat; aceasta coincide cu adresa primului element
(de index 0) al sirului.

------------
Exemplu: Presupunem ca avem declaratiile:
------------

#define N 100
int a[N], *p;
Atunci sistemul va rezerva octetii (sa zicem) numerotati 300, 302, ..., 498 ca fiind adresele
elementelor a[0], a[1], ..., a[99]
Instructiunile
p = a; si p = &a[0];
sunt echivalente si vor asigna lui "p" valoarea 300 (ca adresa de memorie). Aritmetica pointerilor
pune la dispozitie o alternativa pentru indexarea sirurilor.
Instructiunile p = a + 1; si p = &a[1]; sunt echivalente si va asigna lui "p" valoarea 302 (adresa,
bineinteles).

------------
Exemplu: Presupunem ca avem un sir ale carui elemente au deja valori. Pentru a face suma
elementelor, putem folosi pointeri.
------------
suma = 0;
for (p = a; p < &a[N]; ++p)
suma += *p;

-------------------------------------------------------
Pointeri aritmetici si lungimea elementelor
-------------------------------------------------------
Pointerii aritmetici reprezinta una din trasaturile puternice ale limbajului C. Daca variabila "p" este
pointer catre un tip particular, atunci expresia "p + 1" reprezinta adresa masina pentru memorarea
sau accesarea urmatoarei variabile de acest tip. In mod similar, expresiile
p+i ++p p += i
au sens. Daca "p" si "q" sunt pointeri catre elemente de tip vector, atunci "p - q" intoarce valoarea
"int" si reprezinta numarul de elemente dintre "p" si "q". Chiar daca expresiile pointer si expresiile
aritmetice seamana, exista diferente mari intre cele doua tipuri de expresii.

-----------
Exemplu:
-----------
void main()
{
double a[2], *p, *q;
p = &a[0]; /* pointeaza catre baza sirului */
q = p + 1; /* echivalent cu q = &a[1]; */
printf("%d\n", q - p); /* se va tipari 1 */
printf("%d\n", (int) q - (int) p)); /* se va tipari 8 */
}

--------------------------------------------------------------
Trimiterea sirurilor ca argumente pentru functii
--------------------------------------------------------------
Intr-o definitie de functie, un parametru formal care este declarat ca un sir este de fapt un pointer.
Cand este trimis un sir, atunci se
trimite de fapt adresa de baza (evident prin "call-by-value"). Elementele vectorului nu sunt copiate.
Ca o conventie de notatie, compilatorul permite folosirea parantezelor patrate ([,]) in declararea
pointerilor ca parametri.
-----------
Exemplu: Suma elementelor unui sir de tip vector
-----------
int suma(int a[], int n) /* n dimensiunea sirului */
{
int i, s = 0;
for (i = 0; i < n; ++i)
s += a[i];
return s;
}
In antetul functiei precedente, declaratia:
int a[]; este echivalenta cu int *a;
Pe de alta parte, declaratiile de mai sus nu sunt echivalente daca se utilizeaza in alta parte:
- prima se refera la creearea unui pointer constant (fara spatiu de memorie);
- a doua va crea o variabila pointer.
Presupunem ca "v" este declarat ca fiind un sir de 100 de elemente de tip "int". Dupa ce am atribuit
valori elementelor sale, putem utiliza functia "suma()" pentru a aduna anumite valori ale lui "v".
----------------------------------------------------------------
| Apel | Ce se calculeaza si se returneaza ? |
----------------------------------------------------------------
suma(v, 100) v[0] + v[1] + ... + v[99]
suma(v, 88) v[0] + v[1] + ... + v[87]
suma(&v[7], k-7) v[7] + v[8] + ... + v[k - 1]
suma(v + 7, 2 * k) v[7] + v[8] + ... + v[2 * k + 6]
----------------------------------------------------------------

-----------
Exemplu: Sortare cu bule - "Bubble sort"
-----------
Algoritmii eficienti de sortare au, de obicei, O(n*log n) operatii. Metoda sortarii cu bule este
ineficienta din acest punct de vedere
deoarece are O(n^2) operatii. Totusi, pentru siruri de lungime mica, numarul de operatii este
acceptabil. Un cod "elegant" ar fi:
void interschimba(int *, int *);

void bubble(int a[], int n) /* n este lungimea lui a[] */


{
int i, j;

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


for (j = n - 1; i < j; --j)
if (a[j - 1] > a[j])
interschimba(&a[j - 1], &a[j]);
}

--------------------------------
Siruri multidimensionale
--------------------------------
Limbajul C permite siruri de orice tip, inclusiv siruri de siruri. Putem obtine siruri de dimensiune 2,
3, ... .
-----------
Exemple:
-----------
int a[100]; <- sir de dimensiune 1
int b[2][7]; <- sir de dimensiune 2
int c[5][3][2]; <- sir de dimensiune 3
Pornind de la adresa de baza, toate elementele sirului sunt memorate contiguu in memorie. Prin
definitie un tablou bidimensional este de fapt un tablou unidimensional ale carei elemente sunt
fiecare in parte cite un tablou. Prin urmare, indicii se scriu astfel a[i][j] in loc de a[i, j] ca in
majoritatea limbajelor. In plus un tablou bidimensional poate fi tratat in mai multe moduri decat in
alte limbaje. Elementele sunt memorate pe linii, ceea ce inseamna ca indicele din dreapta variaza
primul in asa fel incit elementele sunt accesate in ordinea memoriei.

------------------------------
Vectori 2-dimensionali
-----------------------------
Presupunem ca avem un vector 2-dimensional cu elemente intregi.
int a[3][5];
Incepand cu adresa de baza, compilatorul va aloca spatiu contiguu pentru 15 intregi. Atunci putem
gandi acest vector ca o matrice,
astfel:
col1 col2 col3 col4 col5
lin1 a[0][0] a[0][1] a[0][2] a[0][3] a[0][4]
lin2 a[1][0] a[1][1] a[1][2] a[1][3] a[1][4]
lin3 a[2][0] a[2][1] a[2][2] a[2][3] a[2][4]
Pentru a[i][j] avem expresiile, de exemplu, echivalente:
*(a[i] + j)
(*(a + i))[j]
*((*(a + i)) + j)
*(&a[0][0] + 5*i + j)
Putem gandi "a[i]" ca a "i"-a coloana a lui "a" (numarand de la 0), si "a[i][j]" ca elementul din linia
"i", coloana "j" a sirului (numarand de la 0). Numele sirului ("a") este tot una cu "&a[0]"; acesta
este un pointer catre un sir de 5 intregi. Adresa de baza este "&a[0][0]", si nu "a". Ultimul exemplu
de mai sus reflecta functia de corespondenta in memorie dintre valoarea pointerului si indicele
sirului.
Cand un vector multidimensional este un parametru formal in definitia unei functii, toate
dimensiunile, exceptand prima trebuie specificate.

-----------
Exemplu:
-----------
Presupunem ca sunt date elementele vectorului "a". Functia de mai jos se poate folosi pentru suma
elementelor unui sir. Atentie ! Trebuie specificat numarul de coloane.
int suma(int a[][5])
{
int i, j, suma = 0;

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


for (j = 0; j < 5; ++j)
suma += a[i][j];
return suma;
}
In antetul functiei, urmatoarele declaratii sunt echivalente:
int a[][5] int (*a)[5] int a[3][5]
Constanta 3 actioneaza ca o reminiscenta a omului, dar compilatorul nu tine cont de ea.
Nou venitii in C sunt uneori confuzi in legatura cu deosebirea dintre un tablou bidimensional si un
tablou de pointeri cum ar fi "a" din exemplul de mai sus. Fiind date declaratiile
int a[10][10];
int *b[10];
utilizarile lui "a" si "b" pot fi similare, in sensul ca a[5][5] si b[5][5] sunt ambele referinte legale
ale aceluiasi "int".
Avantaje pentru utilizarea vectorilor (dezavantaje pentru pointeri):
- "a" este un tablou in toata regula: toate cele 100 celule de memorie trebuie alocate, iar pentru
gasirea fiecarui element
se face calculul obisnuit al indicelui;
- pentru "b", oricum prin declararea sa se aloca 10 pointeri; fiecare trebuie facut sa pointeze un
tablou de intregi.
Presupunind ca fiecare pointeaza cate 10 elemente din tablou, atunci vom obtine 100 celule de
memorie rezervate, plus cele 10
celule pentru pointeri. Astfel tabloul de pointeri utilizeaza sensibil mai mult spatiu si poate
cere un procedeu explicit de
initializare.
Avantaje pentru utilizarea pointerilor (dezavantaje pentru vectori):
- accesarea unui element se face indirect prin intermediul unui pointer, in loc sa se faca prin
inmultire si adunare;
- liniile tabloului pot fi de lungimi diferite. Aceasta inseamna ca nu orice element al lui b este
constrins sa pointeze pe un vector
de 10 elemente, unii pot pointa pe cate 2 elemente, altii pe cate 20 si altii pe niciunul.

-----------------------------
Vectori 3-dimensionali
-----------------------------
Vectorii de dimensiune mai mare decat 3 lucreaza intr-un mod similar. Daca avem declaratia
int a[7][9][2];
atunci compilatorul va aloca spatiu pentru 7*9*2 intregi. Adresa de baza a sirului este
"&a[0][0][0]", iar functia de corespondenta in memorie este specificata de
a[i][j][k] care este echivalent cu *(&a[0][0][0] + 9*2*i + 2*j + k)

-----------------------------
Initializarea vectorilor
-----------------------------
Exista mai multe moduri de a initializa un vector multidimensional.
------------
Exemplu: Urmatoarele declaratii sunt echivalente:
------------
int a[2][3] = {1, 2, 3, 4, 5, 6};
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
int a[][3] = {{1, 2, 3}, {4, 5, 6}};
Indexarea se face dupa linii. Daca nu sunt suficiente elemente care sa initializeze vectorul, atunci
restul elementelor sunt initializate cu 0. Daca prima componenta lipseste, atunci compilatorul
extrage lungimea din numarul de perechi de acolade interioare.
------------
Exemplu: Consideram initializarea:
------------
int a[2][2][3] = {
{{1, 1, 0}, {2, 0, 0}},
{{3, 0, 0}, {4, 4, 0}}
};
O initializare echivalenta poate fi data si astfel:
int a[][2][3] = {{{1, 1}, {2}}, {{3}, {4, 4}}};
De obicei, daca un sir declarat "auto" nu este explicit initializat, atunci elementele sirului vor
contine valori "garbage".
Sirurile "static" si "external" sunt initializate implicit cu 0. Iata un mod simplu de a initializa toate
valorile unui vector cu 0:
int a[2][2][3] = {0};

---------------------------------------
Alocarea dinamica a memoriei
---------------------------------------
C pune la dispozitie pentru alocarea memoriei functiile "calloc()" si "malloc()" din biblioteca
standard. Aceste functii au prototipul
declarat in <stdlib.h>. Acest lucru va permite rezervarea memoriei pentru un vector (de exemplu)
in care ii aflam dimensiunea abia la rularea in executie (pana acum declararam dimensiunea unui
vector cu #define). Un apel de tipul
calloc(n, dimensiune_tip)
va returna un pointer catre un spatiu din memorie necesar pentru memorarea a "n" obiecte, fiecare
pe "dimensiune_tip" octeti. Daca
sistemul nu poate aloca spatiul cerut, atunci acesta va returna valoarea NULL.
In ANSI C, tipul "size_t" este dat ca "typedef" in <stdlib.h>. De obicei, tipul este "unsigned".
Definitia tipului este folosita in
prototipurile functiilor "calloc()" si "malloc()":
void *calloc(size_t, size_t);
void *malloc(size_t);
Deoarece pointerul returnat de aceste functii are tipul "void", acesta poate fi asignat altor pointeri
fara conversie explicita (cast). Totusi unele sisteme nu accepta aceasta conversie, deci ea trebuie
facuta explicit. Octetii rezervati de "calloc()" sunt automat initializati cu 0, pe cand cei rezervati cu
"malloc()" nu sunt initializati (deci vor avea valori "garbage"). Numele "calloc", respectiv "malloc",
provine de la "contiguous allocation", respectiv "memory allocation".

-----------
Exemplu: Program care citeste dimensiunea unui sir interactiv
-----------
#include <stdio.h>
#include <stdlib.h>

void main()
{
int *a, i, n, suma = 0;

printf("\n%s", "Citirea dimensiunii unui sir interactiv.\n\nDati numarul de elemente a sirului: ");
scanf("%d", &n);
a = calloc(n, sizeof(int)); /* aloca spatiu pentru n intregi */
/* daca da eroare de conversie de tip, atunci adaugati dupa semnul =,
conversia (int *) */
for (i = 0; i < n; ++i)
scanf("%d", &a[i]);
for (i = 0; i < n; ++i)
suma += a[i];
free(a); /* eliberarea spatiului */
printf("\n%s%7d\n%s%7d\n\n", "Numarul de elemente: ", n, "Suma elementelor : ", suma);
}
Prototipul functiei "free()" se gaseste in <stdlib.h> si este
void free(void *ptr);
Spatiul alocat de "calloc()" si "malloc()" ramane ocupat pana cand este eliberat de catre
programator. Acesta nu se elibereaza cand se iese dintr-o functie (in care s-a facut rezervarea de
memorie).
In programul de mai sus, instructiunea
a = calloc(n, sizeof(int));
este echivalenta cu
a = malloc(n * sizeof(int));
Singura diferenta este deci initializarea cu 0 in cazul functiei "calloc()".

------------
Exemplu: Exemplu de citire interactiva a dimensiunii si a elementelor unei matrice de intregi

void main()
{
int N;
int ** a, *ptr;

printf("Introduceti dimensiunea matricii:");


scanf("%d", &N);
a = (int **) calloc(N * N, sizeof(int));

ptr=&a[0][0];
printf("\n\nIntroduceti elementele matricii:\n");
for (i = 0;i < N; ++i)
for (j = 0;j < N; ++j)
{
printf("a[%d][%d]=", i, j);
scanf("%d", ptr++);
}
}

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Scrieti o functie care insumeaza elementele de rang (index) impar, respectiv par, ale unui vector
cu elemente de tip "double".
Sugestie: functia poate incepe cam asa
void suma(double a[],
int n, /* n - lungimea sirului a */
double *impar,
double *par)
{
.....
2. Modificati programul de sortare cu bule astfel incat terminarea iteratiilor sa aiba loc cand nu se
mai fac interschimbari de
elemente.
3. Calculati valoarea unui determinant asociat unei matrice patratice. In cazul in care determinantul
este nenul, calculati
inversa matricei.
4. Calculati inversa unei permutari cu un numar constant de variabile suplimentare
Siruri de caractere si pointeri
10.1. Marcatorul "sfarsit de sir de caractere" \0
10.2. Folosirea pointerilor pentru procesarea unui sir
10.3. Trimiterea argumentelor catre "main()"
10.4. Lucrul cu sirurile din biblioteca standard
10.5. Unde este eroarea ?
10.6. Exercitii propuse spre implementare

=========
Capitolul 10
=========
======================
Siruri de caractere si pointeri
======================
Un caracter dintr-un sir de caractere "a" poate fi accesat folosind indexul sirului (a[i], de exemplu)
sau folosind pointeri la caracter.

------------------------------------------------------
Marcatorul "sfarsit de sir de caractere" \0
------------------------------------------------------
Prin conventie, un sir de caractere se termina prin marcatorul (santinela, delimitator) \0, sau
caracterul nul. De exemplu, sirul "abc" este memorat pe 4 caractere, ultimul fiind \0. Deci numarul
de elemente al sirului este 3, iar dimensiunea 4.
-----------
Exemplu:
-----------
#define MAXWORD 100
void main()
{
char w[MAXWORD];
.....
}
Initializarea (citirea) unui sir se poate face in mai multe moduri:
1. Initializarea fiecarui element cu cate un caracter:
w[0] = 'A';
w[1] = 'B';
w[2] = 'C';
w[3] = '\0';
2. Folosind functia "scanf()":
scanf("%s", w);

Formatul "%s" este folosit pentru citirea unui sir de caractere. Distingem trei pasi:
- pozitionare pe primul caracter al sirului;
- se citesc toate caracterele diferite de <Enter> si se introduc in "w";
- citirea se face pana cand intalnim EOF; acum se plaseaza la sfarsitul sirului '\0'.
Din moment ce numele unui sir este un pointer la adresa de baza a sirului, expresia "w" este
echivalenta cu "&w[0]".
Daca sirul citit are mai multe caractere decat cele rezervate, atunci se va obtine o eroare.
Atentie ! 'a' si "a" sunt diferite. Prima este o constanta caracter, iar a doua este o constanta
sir de caractere.
----- ------
"a" = | 'a' | '\0' |
----- ------
3. Sirurile se pot initializa la fel ca si caracterele
char s[] = "abc";
sau echivalent
char s[] = {'a', 'b', 'c', '\0'};
4. Putem folosi si un pointer catre un sir constant, dar interpretarea este diferita:
char *p = "abc";
Va reamintim ca numele unui sir poate fi tratat ca un pointer catre adresa de baza a sirului
din memorie.
Constanta "abc" este memorata de catre compilator. In acelasi timp, aceasta este "un nume
de sir".
Asadar, diferenta dintre un sir initializat cu o constanta sir si un pointer initializat tot cu o constanta
sir este ca sirul contine caractere individuale urmate de caracterul "\0", in timp ce pointerul este
asignat cu adresa sirului constant din memorie.

-----------
Exemplu: Utilizarea sirurilor de caractere (ca vectori).
----------- Citim o linie de caractere dintr-un sir, le tiparim in ordine inversa si adunam literele din
sir.

#include <stdio.h>
#include <ctype.h>
#define MAXSTRING 100

main()
{
char c, name[MAXSTRING];
int i, sum = 0;

printf("\nSalut! Care este numele tau? ");


for (i = 0; (c = getchar()) != '\n'; ++i)
{
name[i] = c;
if (isalpha(c))
sum += c;
}
name[i] = '\0';
printf("\n%s%s%s\n%s",
"Ma bucur ca te-am intalnit ",name,".",
"Numele tau scris invers este ");
for (--i; i >= 0; --i)
putchar(name[i]);
printf("\n%s%d%s\n\n%s\n",
"si numele tau are ", sum," litere .",
"La revedere. ");
}

------------------------------------------------------------
Folosirea pointerilor pentru procesarea unui sir
------------------------------------------------------------
Vom discuta despre folosirea pointerilor pentru procesarea unui sir si cum se pot folosi acestea
pentru a fi transmise ca parametri unei functii. Vom scrie un exemplu de program interactiv care
citeste intr-un sir o linie de caractere introdusa de utilizator. Programul va crea un nou sir si-l va
tipari.
------------
Exemplu:
------------
#include <stdio.h>
#define MAXLINE 100

void main()
{
char linie[MAXLINE], *schimba(char *);
void citeste_in(char *);

printf("\nDati un sir:");
citeste_in(linie);
printf("\n%s\n\n%s\n\n",
"Asa arata sirul dupa schimbare:", schimba(linie));
}

void citeste_in(char s[])


{
int c, i = 0;

while ((c = getchar()) != EOF && c != '\n')


s[i++] = c;
s[i] = '\0';
}

char *schimba(char *s)


{
static char sir_nou[MAXLINE];
char *p = sir_nou;

*p++ = '\t';
for ( ; *s != '\0'; ++s)
if (*s == 'e')
*p++ = 'E';
else
if (*s == ' ')
{
*p++ = '\n';
*p++ = '\t';
}
else
*p++ = *s;
*p = '\0';
return sir_nou;
}
-----------
Intrebare: De ce vectorul "sir_nou" a fost declarat static ?
-----------
Deoarece numele "sir_nou" este tratat ca un pointer catre adresa de baza a sirului. Fiind declarat
"static", acesta se pastreaza in memorie si dupa ce se iese din functia "schimba()". Acest lucru nu s-
ar fi intamplat si daca, de exemplu, sirul ar fi fost declarat "auto".
-----------
Exemplu: Functie C pentru numararea cuvintelor unui sir de caractere
-----------
#include <ctype.h>

int numarare_cuvinte(char *s)


{
int contor = 0;

while (*s != '\0')


{
while (isspace(*s)) /* sarim spatiile goale */
++s;
if (*s != '\0') /* gasim un cuvant */
{
++contor;
while (!isspace(*s) && *s != '\0') /* sarim peste cuvant */
++s;
}
}
return contor;
}

----------------------------------------------------
Trimiterea argumentelor catre "main()"
----------------------------------------------------
C pune la dispozitie siruri de orice tip, inclusiv siruri de pointeri. Pentru scrierea de programe care
folosesc argumente in linia de comanda, trebuie sa folosim siruri de pointeri catre caractere. Pentru
aceasta, functia "main()" foloseste doua argumente, numite generic "argc" si "argv".
-----------
Exemplu:
-----------
#include <stdio.h>

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


{
int i;

printf("argc = %d\n", argc);


for (i = 0; i < argc; ++i)
printf("argv[%d] = %s\n", i, argv[i]);
}

Variabila "argc" precizeaza numarul de argumente din linia de comanda. Sirul "argv" este un sir de
pointeri catre caracter si poate fi gandit ca vector de siruri de caractere. Deoarece elementul
"argv[0]" contine intotdeauna numele comenzii, rezulta ca valoarea lui "argc" va fi mai mare sau
egala cu 1.
Compilam programul de mai sus si obtinem executabilul "prog1.exe". Daca dam comanda
prog1
atunci pe ecran se va afisa
argc = 1
argv[0] = prog1
Daca dam comanda
prog1 fisier1 fisier2
atunci pe ecran se va afisa
argc = 3
argv[0] = prog1
argv[1] = fisier1
argv[2] = fisier2
Parametrul "argv" s-ar fi putut declara si astfel
char **argv;
Acesta este un pointer catre pointer catre "char" si acesta poate fi gandit ca un sir de pointeri catre
"char", care la randul lor pot fi ganditi ca vector de siruri de caractere. Observati ca nu alocam
spatiu in memorie pentru sirurile din linia de comanda. Acest lucru este facut de insusi sistemul C
cand atribuie valori pentru argumentele "argc" si "argv".

-----------------------------------------------------
Lucrul cu sirurile din biblioteca standard
-----------------------------------------------------
Biblioteca standard <string.h> contine multe functii utile pentru lucrul cu siruri de caractere.
Sirurile ce sunt argumente trebuie terminate cu '\0' si toate returneaza un intreg sau o valoare a unui
pointer catre "char".
Cateva functii utile pentru lucrul cu siruri de caractere
------------------------------------------------------------------
- char *strcat(char *s1, const char *s2);
Functia primeste doua argumente, le concateneaza si pune rezultatul in "s1". Programatorul
trebuie sa verifice daca "s1" are suficient spatiu pentru pastrarea rezultatului. Se returneaza sirul
"s1".
- int strcmp(const char *s1, const char *s2);
Sunt trimise doua siruri de caractere si se returneaza un intreg care este mai mic strict, egal
sau mai mare strict
decat 0 dupa cum "s1" este mai mic, egal sau mai mare lexicografic decat "s2".
- char *strcpy(char *s1, const char *s2);
Sirul "s2" este copiat in "s1" pana cand se intalneste '\0'. Ceea ce se gaseste in "s1" se
suprascrie. Se presupune ca "s1"
are suficient spatiu pentru pastrarea rezultatului. Se returneaza valoarea lui "s1".
- unsigned strlen(const char *s);
Pastreaza numarul de caractere inaintea lui '\0'.
----------------------------------------------------------------
Aceste functii sunt scrise in C si sunt foarte scurte. Variabilele din ele sunt de obicei declarate
"register" pentru a face executia
mai rapida.

----------
Exemplu: Functia "strlen()" (o varianta).
----------
unsigned strlen(const char *s)
{
register int n = 0;

for ( ; *s != '\0'; ++s)


++n;
return n;
}
-----------------------------------------------------------
| Declaratii si initializari |
-----------------------------------------------------------
| char s1[] = "tara noastra frumoasa si bogata", |
| s2[] = "facultatea de informatica"; |
-----------------------------------------------------------
| Expresie | Valoare |
-----------------------------------------------------------
| strlen(s1) | 31 |
| strlen(s2 + 8) | 17 |
| strcmp(s1, s2) | numar pozitiv |
-----------------------------------------------------------
| Instructiune | Ce se va tipari ? |
-----------------------------------------------------------
| printf("%s", s1 + 13); | frumoasa si bogata |
| strcpy(s2 + 11, s1 + 25);| |
| strcat(s2, "\n"); | |
| printf("%s", s2); | facultatea bogata |
-----------------------------------------------------------

-------------------------
Unde este eroarea ?
-------------------------
1. char s[14];
strcpy(s, "Ce mai faci ?\n");
2. char s[14];
scanf("%s", &s);

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Folosind "argc" si "argv" (si eventual optiunea -c) tipariti cu litere majuscule argumentele din
"argv".
2. Scrieti o functie proprie "strncmp()" (extrageti din Help definitia si prototipul).
3. Presupunem ca avem declaratia si initializarea:
char *p[2][3] = {
"abc", "defg", "hi", "jklmno", "pqrstuvw", "xyz"
};
Completati urmatorul tabel (incercati intai sa nu rulati programul C).
-----------------------------------------------------------------
| Expresie | Expresie echivalenta | Valoare |
----------------------------------------------------------------
| ***p | p[0][0][0] | 'a' |
| **p[1] | | |
| **(p[1] + 2) | | |
|*(*(p + 1) + 1)[7] | | eroare |
|(*(*(p + 1) + 1))[7]| | |
| *(p[1][2] + 2) | | |
-----------------------------------------------------------------
4. Folosind "scanf()" cititi 7 siruri de caractere, dupa care folosind "strcmp()" sortati-le alfabetic
(eventual cu "bubble sort").
5. (*) Scrieti un program similar cu exercitiul 4 care sorteaza si afiseaza argumentele din linia de
comanda.
Directive preprocesor si metodologie de programare
11.1. Folosirea lui #include
11.2. Folosirea lui #define
11.3. Sintaxa "dulce"
11.4. Macrouri cu argumente
11.5. Unde este greseala ?
11.6. Definitii de tipuri si macrouri din <stddef.h>
11.7. Sortare folosind "qsort()"
11.8. Un exemplu de utilizare a macrourilor cu argumente
11.9. Compilare conditionala
11.10. Macrouri predefinite
11.11. Operatorii # si ##
11.12. Macroul "assert()"
11.13. Folosirea lui #error si #pragma
11.14. Numerele liniilor unui program
11.15. Exercitii propuse spre implementare

=========
Capitolul 11
=========

=======================================
Directive preprocesor si metodologie de programare
=======================================
---------------------------
Folosirea lui #include
---------------------------
Am discutat deja folosirea directivelor de preprocesare
#include <stdio.h>
#include <stdlib.h>
O alta forma pentru #include este
#include "nume_fisier"
Preprocesorul va inlocui aceasta linie cu o copie a fisierului precizat. Mai intai cautarea se face in
directorul curent, apoi
in alte locuri dependente de sistem. Daca directiva este de forma
#include <nume_fisier>
atunci preprocesorul va cauta in alte locuri (deci nu in directorul curent). De exemplu, sub UNIX,
fisierele header standard (cum ar fi "stdio.h", "stdlib.h") se gasesc de obicei in directorul
/usr/include
Sub MS-DOS, aceste fisiere se gasesc in directorul
/include

--------------------------
Folosirea lui #define
--------------------------
Directivele de preprocesare declarate cu "#define" au doua forme:
- #define identificator sir_atomi
- #define identificator(id,...,id) sir_atomi
O definitie lunga (care nu dorim sa o scriem pe aceeasi linie poate fi continuata pe linia urmatoare
punand un \ (backslash) la sfarsitul liniei curente). In primul caz, compilatorul va inlocui fiecare
aparitie a "identificatorului" prin "sir_atomi" in restul fisierului (de la pozitia curenta in jos) cu
exceptia celor care sunt incadrate intre ghilimele sau apostroafe.
-----------
Exemple:
-----------
#define NR_SEC_PE_ZI (60 * 60 * 24)
#define PI 3.141592653
#define C 299792.458 /* viteza luminii in km/sec */
#define EOF (-1) /* valoarea uzuala pt sfarsit de fisier */
#define MAXINT 2147483647 /* numarul intreg maxim pe 4 octeti */
#define DIMENS 250 /* dimensiunea unui sir */
#define EPSILON 1.0e-9 /* limita numerica */
Deci, folosirea lui "#define" mareste claritatea si portabilitatea unui program.

--------------------
Sintaxa "dulce"
--------------------
Se foloseste pentru evitarea unor greseli frecvente sau ca un moft.
------------
Exemplu: #define EQ ==
------------
Aceasta declaratie ajuta programatorul sa nu mai confunde = cu ==.
------------
Exemplu: #define do /* spatiu */
------------
De exemplu, acum putem simula instructiunea "while" din C ca un "while do" din Pascal sau
Algol.
De exemplu, daca avem definitiile de sintaxa "dulce" de mai sus, putem spune ca instructiunile
while (i EQ 1) do
{
.....
}
si
while (i == 1)
{
.....
}
sunt echivalente.

------------------------------
Macrouri cu argumente
------------------------------
Revenim la forma a doua a macrourilor cu argumente:
#define identificator(id,...,id) sir_atomi
------------
Exemplu: #define SQ(x) ((x) * (x))
------------
Identificatorul x din #define este un parametru care va fi substituit in textul ce urmeaza. Substitutia
se face fara considerarea
corectitudinii sintactice. De exemplu,
SQ(7 + w) este echivalent cu ((7 + w) * (7 + w))
Intr-o maniera similara,
SQ(SQ(*p)) este echivalent cu ((((*p) * (*p))) * (((*p) * (*p))))
Observati deci ca folosirea parantezelor (de exemplu, (x)) are o importanta deosebita, altfel nu s-ar
respecta ordinea de evaluare.

-------------------------
Unde este greseala ? #define SQ(x) ((x) * (x));
-------------------------
Macrourile sunt folosite de obicei pentru a inlocui apelurile functiilor cu cod liniar (scurte si fara
variabile suplimentare).
-----------
Exemplu: Macroul de mai jos defineste minimul a doua valori:
-----------
#define min(x, y) (((x) < (y)) ? (x) : (y))
Dupa aceasta definitie, o expresie de forma
m = min(u, v)
se poate expanda de catre preprocesor la
m = (((u) < (v)) ? (u) : (v))
Folosind aceasta definitie, putem defini minimul a patru valori, astfel
#define min4(a, b, c, d) min(min(a,b), min(c, d))
O macro-definitie poate folosi functii si macrouri in corpul lor.

-----------
Exemple:
-----------
#define SQ(x) ((x) * (x))
#define CUB(x) (SQ(x) * (x))
#define F_POW(x) sqrt(sqrt(CUB(x)))
O directiva de preprocesare de forma
#undef identificator
va anula definitia precedenta a identificatorului.

---------------------------------------------------------
Definitii de tipuri si macrouri din <stddef.h>
---------------------------------------------------------
C pune la dispozitie facilitatea "typedef" pentru a asocia (redenumi) un tip cu unul specific.
-----------
Exemplu: typedef char uppercase;
-----------
Declaratia de mai sus face tipul "uppercase" sinonim cu "char". De exemplu, declaratiile de mai jos
sunt valide:
uppercase c, u[100];
Fisierul header <stddef.h> contine cateva definitii de tip:
typedef int ptrdiff_t; /* tip intors de diferenta pointerilor */
typedef short wchar_t; /* tip caracter mare */
typedef unsigned size_t; /* tipul sizeof */
Tipul "ptrdiff_t" spune care este tipul returnat de o expresie implicata in diferenta a doi pointeri. In
MS-DOS, acesta depinde de modelul de memorie ales (tiny, short, large, far, huge), pe cand in
UNIX, tipul folosit este "int".
Tipul "wchar_t" se foloseste pentru acele caractere care nu se pot reprezenta pe un octet (char ->
int).
Reamintim ca operatorul "sizeof" este folosit pentru determinarea lungimii unui tip sau a unei
expresii. De exemplu, "sizeof(double) = 8". Tipul "size_t" este returnat de operatorul "sizeof".
Un macrou definit in <stddef.h> este
#define NULL 0

---------------------------------
Sortare folosind "qsort()"
---------------------------------
Daca avem o multime relativ mica de elemente, atunci putem sa folosim sortare cu bule sau metoda
sortarii prin selectie directa (care sunt de ordinul O(n^2)). Daca insa avem multe elemente, atunci
este convenabil sa folosim metoda sortarii rapide ("quick sort"). Prototipul functiei "qsort()" se
gaseste in <stdlib.h>. Acesta este
void qsort(void *array, size_t n_els, size_t el_size, int compare(const void *, const void *));
Argumentele acestei functii au rolul:
array - sirul care va fi sortat;
n_els - numarul de elemente ale sirului;
el_size - numarul de octeti necesar memorarii unui element;
compare - functia de comparare, ce se declara ca fiind int compare(const void *, const void
*)
Functia de comparare are ca argumente doi pointeri catre void. Aceasta returneaza un intreg care
este mai mic, egal sau mai mare
decat zero dupa cum primul argument este mai mic, egal sau mai mare decat al doilea argument.

-----------
Exemplu:
-----------
Vom scrie un program ce foloseste "qsort()". Initializam un vector, il tiparim, il sortam cu
"qsort()", apoi il tiparim din nou.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define N 11 /* dimensiunea sirului */

int cmp(const void *vp, const void *vq); /* functia de comparare */


void init(double *a, int n);
void tipareste_sir(double *a, int n);
void main()
{
double a[N];
init(a, N);
tipareste_sir(a, N);
qsort(a, N, sizeof(double), cmp);
tipareste_sir(a, N);
}
int cmp(const void *vp, const void *vq)
{
const double *p = (const double *)vp;
const double *q = (const double *)vq;
double diff = *p - *q;
return ((diff >= 0.0) ? ((diff > 0.0) ? -1 : 0) : +1);
}
void init(double *a, int n)
{
int i;
srand(time(NULL)); /* vezi rand() */
for (i = 0; i < n; ++i)
a[i] = (rand() % 1001) / 10.0;
}
void tipareste_sir(double *a, int n)
{
int i;
for (i = 0; i < n; ++i)
{
if (i % 6 == 0)
putchar('\n');
printf("%12.1f", a[i]);
}
putchar('\n');
}
----------
Intrebari: 1. Ce trebuie sa modificati pentru a obtine ordinea crescatoare a sirului ?
---------- 2. Ce rol are "const" din declaratia lui "cmp()" ?

-------------------------------------------------------------------
Un exemplu de utilizare a macrourilor cu argumente
-------------------------------------------------------------------
Vom relua problema de mai sus, dar vom folosi macrouri cu argumente. Vom scrie programul in
doua fisiere, un fisier header "sort.h" si un fisier "sort.c". Fisierul header va contine directive de
precompilare (#include, #define), precum si prototipuri pentru functiile noastre. Fisierul "sort.h"
este:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define M 32
#define N 11
#define parte_fractionara(x) (x - (int) x)
#define caracter_aleator() (rand() % 26 + 'a')
#define real_aleator() (rand() % 100 / 10.0)
#define INIT(array, sz, type) \
if (strcmp(type, "char") == 0) \
for (i = 0; i < sz; ++i) \
array[i] = caracter_aleator(); \
else \
for (i = 0; i < sz; ++i) \
array[i] = real_aleator();

#define PRINT(array, sz, sir_control) \


for (i = 0; i < sz; ++i) \
printf(sir_control, array[i]); \
putchar('\n')

int compara_partea_fractionara(const void *, const void *);


int lexico(const void *, const void *);
Acum, vom scrie restul codului pentru programul nostru, si anume fisierul "sort.c".
#include "sort.h"

void main()
{
char a[M];
float b[N];
int i;
srand(time(NULL));
INIT(a, M, "char");
PRINT(a, M, "%-2c");
qsort(a, M, sizeof(char), lexico);
PRINT(a, M, "%-2c");
printf("---\n");
INIT(b, N, "float");
PRINT(b, N, "%-6.1f");
qsort(b, N, sizeof(float), compara_partea_fractionara);
PRINT(b, N, "%-6.1f");
}

int compara_partea_fractionara(const void *vp, const void *vq)


{
const float *p = (const float *)vp, *q = (const float *)vq;
float x;
x = parte_fractionara(*p) - parte_fractionara(*q);
return((x < 0.0) ? -1 : (x == 0.0) ? 0 : +1);
}

int lexico(const void *vp, const void *vq)


{
const char *p = (const char *)vp, *q = (const char *)vq;
return(*p - *q);
}

------------------------------
Compilare conditionata
------------------------------
Preprocesorul are directive pentru compilare conditionata. Acestea pot fi folosite pentru
dezvoltarea programelor si pentru scrierea
codului mai portabil de la o masina la alta. Fiecare directiva de forma
#if expresie_integrala_constanta
#ifdef identificator
#ifndef identificator
implica compilarea conditionata a codului care urmeaza pana la directiva de precompilare
#endif
Pentru compilarea codului de mai sus, in cazul lui #if trebuie ca expresia constanta sa fie diferita de
zero (true), in cazul lui
#ifdef sau #ifdefined numele identificatorului trebuie sa fie definit anterior intr-o linie #define, fara
interventia directivei
#undef identificator
In cazul lui #ifndef, numele identificatorului trebuie sa nu fie curent definit.
Expresia constanta integrala folosita intr-o directiva de precompilare nu poate contine operatorul
"sizeof" sau un cast. Poate
insa, folosi operatorul de precompilare "defined" (valabil in ANSI C, dar nu si C traditional).
Expresia
defined identificator
este echivalenta cu
defined(identificator)
Acesta se evalueaza la 1 daca identificatorul este definit, si 0 in caz contrar.

------------
Exemplu:
------------
#if defined(HP9000) || defined(SUN4) && !defined(VAX)
..... /* cod dependent de masina */
#endif

Uneori "printf()" este utila in scopuri de depanare. Presupunem ca la inceputul unui fisier am scris
#define DEBUG 1
si in unele zone ale programului am scris
#if DEBUG
printf("debug: a = %d\n", a);
#endif
Daca dupa ce ne-am convins ca este bine ce se intampla si vrem sa nu mai vizualizam valoarea lui
"a" in acest moment, atunci schimbam DEBUG in 0 (de exemplu).
O alta varianta ar fi sa nu initializam DEBUG. Scriem deci la inceputul fisierului
#define DEBUG
Putem folosi #ifdef si #if si scriem:
#ifdef DEBUG
.....
#endif

---------------------------
Macrouri predefinite
---------------------------
In ANSI C sunt 5 macrouri predefinite. Nu pot fi redefinite de catre programator. Ele au la inceput
si sfarsit cate doua simboluri
"underscore".
Macro predefinit Valoare
----------------------------------------------------------------
__DATE__ Un sir ce contine data curenta
__FILE__ Un sir ce contine numele fisierului
__LINE__ Un intreg reprezentand numarul liniei curente
__STDC__ Daca implementarea=ANSI C, atunci acesta
reprezinta un numar diferit de zero
__TIME__ Un sir ce contine timpul curent
----------------------------------------------------------------

----------------------
Operatorii # si ##
----------------------
Operatorii de preprocesare # si ## sunt valabili in ANSI C, dar nu si in C traditional. Operatorul
unar # cauzeaza transformarea in sir a unui parametru formal dintr-o macro-definitie.
#define mesaj_pentru(a, b) \
printf(#a " si " #b ": Te iubim !\n")
void main() { mesaj_pentru(Carolina, Nicoleta); }

La apelul acestui macrou, fiecare parametru al acestuia este inlocuit cu argumentul corespunzator,
iar # cauzeaza ca argumentele sa fie puse intre ghilimele. Altfel spus, dupa preprocesare, in
memorie se obtine:
void main() { printf("Carolina" " si " "Nicoleta" ": Te iubim !\n"); }
Deoarece sirurile constante separate prin spatiu se concateneaza, instructiunea de mai sus este
echivalenta cu:
void main() { printf("Carolina si Nicoleta: Te iubim !\n"); }
Operatorul binar ## este folosit la impartirea in tokenuri lexicale.

------------
Exemplu:
------------ #define X(i) x ## i
X(1) = X(2) = X(3);
va deveni dupa preprocesare
x1 = x2 = x3;

-------------------------
Macroul "assert()"
------------------------
ANSI C pune la dispozitie macroul "assert()" din biblioteca standard "assert.h". Acest macrou
poate fi folosit cand vrem sa ne asiguram ca o expresie are o anumita valoare. Vrem sa scriem o
functie ale carei argumente satisfaca niste conditii.
-----------
Exemplu:
-----------
#include <assert.h>
void f(char *p, int n)
{
.....
assert(P != NULL);
assert(n > 0 && n < 5);
.....
}

Daca vreo asertiune esueaza, atunci sistemul va tipari un mesaj si va opri executia programului.
Iata o implementare posibila a lui
"assert()".
#if defined(NDEBUG)
#define assert(ignore) ((void) 0) /* ignorare */
#else
#define assert(expr)
if (!(expr)) \
{ \
printf("\n%s%s\n%s%s\n%s%d\n\n, \
"Assertion failed: ", #expr, \
"in file ", __FILE__, \
"al line ", __LINE__); \
}
#endif
De remarcat ca daca NDEBUG este definit, atunci sunt ignorate toate asertiunile. Aceasta permite
programatorului in timpul scrierii programului sa verifice pas cu pas executia programului. Functia
"abort()" se gaseste in biblioteca standard.

----------------------------------------
Folosirea lui #error si #pragma
----------------------------------------
ANSI C contine si directivele de preprocesare #error si #pragma.
------------
Exemplu:
------------
#if A_SIZE < B_SIZE
#error "tipuri incompatibile"
#endif
Daca in timpul compilarii va apare o eroare prezenta intr-o directiva #error, atunci se va afisa
mesajul respectiv.
Directiva #pragma se foloseste pentru folosire specifica implementarii. Ea are forma generala:
#pragma atomi_lexicali
Aceasta cauzeaza o comportare ce depinde de fiecare compilator C in parte.

----------------------------------------
Numerele liniilor unui program
----------------------------------------
O directiva de preprocesare de forma
#line constanta_integrala "nume_fisier"
va determina compilatorul sa renumeroteze liniile textului sursa astfel incat urmatoarea linie sa aiba
valoarea specificata si numele
fisierului sursa curent este "nume_fisier". Daca nu se precizeaza "nume_fisier", atunci se va face
doar numerotarea liniilor. Bineinteles, numerele asociate liniilor sunt ascunse pentru programator si
apar numai la mesaje de eroare sau avertismente.

-----------------------------------------------
Exercitii propuse spre implementare
----------------------------------------------
1. Scrieti propria voastra functie "quicksort()" care sa fie echivalenta cu "qsort()" pus la dispozitie
de sistemul C.
2. Definiti o macro-definitie pentru XOR(), numita "sau exclusiv". Un apel XOR(a,b)=true <=> a
este true si b false, sau a false si b true. Scrieti si o macro-definitie XOR(a,b,c) si una XOR(a,b,c,d).
3. Scrieti un program C in care sa afisati valorile celor 5 macrouri predefinite.
Recursie
12.1. Manipularea sirurilor folosind recursia
12.2. Metodologia "divide-et-impera"
12.3. Exercitii propuse spre implementare

============
Capitolul 12
============

========
Recursie
========
O functie este recursiva daca se autoapeleaza, direct sau indirect. In C toate functiile se pot defini
recursiv.
----------
Exemplu:
----------
#include <stdio.h>
void numara(int n);

void main()
{
numara(10);
}

void numara(int n)
{
if (n)
{
printf("%d ! ", n);
numara(n - 1);
}
else
printf("Gata !\n");
}

Dupa executia acestui program, pe ecran se va tipari


10 ! 9 ! 8 ! 7 ! 6 ! 5 ! 4 ! 3 ! 2 ! 1 !
Gata !
Acest program s-ar fi putut realiza si iterativ (folosind o instructiune de tip while).

-----------
Exemplu: Suma primelor n numere naturale.
-----------
int suma(int n)
{
if (n <= 1)
return n;
else
return (n + suma(n - 1));
}
De obicei, functiile recursive urmeaza un "pattern" standard:
- exista un caz de baza (sau mai multe);
- caz recursiv general (in care, in general, un intreg este trimis ca argument al apelului recursiv);

Recursia este un procedeu foarte puternic de rezolvare a problemelor. Secretul este identificarea
cazului general.
Pentru exemplul precedent, cand se trimite n catre functia "suma()", recursia activeaza n copii ale
functiei inaintea intoarcerii pas cu pas catre primul apel recursiv (se mai spune ca in momentul
apelului recursiv, variabilele locale "ingheata", ele "dezghetandu-se" la intoarcerea din recursie).
Multe functii recursive se pot scrie intr-o forma iterativa (folosind structuri de tip "while", se mai
spune "derecursivare"). Recursia se recomanda cand problema se poate rezolva foarte usor folosind
recursie si cand nu se cere o eficienta sporita in timpul executiei programului. Uneori, se recomanda
recursia finala (adica dupa apelul recursiv nu mai sunt alte instructiuni si nu exista variabile locale).

-----------
Exemplu: Citeste o linie si o afiseaza in ordine inversa, apoi lasa
----------- doua randuri goale.

#include <stdio.h>
void tipareste(void);

void main()
{
printf("Introduceti o linie: ");
tipareste();
printf("\n\n");
}

void tipareste(void)
{
char c;
if ((c = getchar()) != '\n')
tipareste();
putchar(c);
}

Iata o rulare in executie:


Introduceti o linie: iepurasu usa rupei

iepur asu usarupei


Observati in exemplul precedent ca la fiecare apel recursiv, se memoreaza in stiva caracterul "c"
legat la o valoare, care se va afisa la intoarcerea din recursie. Deci practic, sunt "n" copii ale lui "c",
unde "n" reprezinta lungimea liniei.

-----------
Exemplu:
-----------
Putem complica putin exemplul precedent, in sensul ca afisam aceleasi cuvinte, dar in ordine
inversa.
#include <ctype.h>
#include <stdio.h>
#define MAXWORD 100

void tipareste_cuvinte(void);
void citeste_cuvant(char *);

void main()
{
printf("Introduceti o linie: ");
tipareste_cuvinte();
printf("\n\n");
}

void tipareste_cuvinte(void)
{
char w[MAXWORD];

citeste_cuvant(w);
if (w[0] != '\n')
tipareste_cuvant();
printf("%s ", w);
}

void citeste_cuvant(char *s)


{
static char c = '\0';

if (c == '\n')
*s++ = c;
else
while (!isspace(c = getchar()))
*s++ = c;
*s = '\0';
}

Daca, la executie, utilizatorul scrie:


Introduceti o linie: noi invatam C
atunci pe ecran, va apare:
C invatam noi
Variabila "c" avand clasa de memorare "static", rezulta ca valoarea ei se pastreaza de la un apel la
altul. De altfel, initializarea lui "c" se face o singura data (cand se intra prima data in aceasta
functie). Daca "c" ar fi fost de tip "auto", atunci chiar daca aveam la sfarsitul sirului '\n', la
urmatorul apel, acesta nu ar fi fost cunoscut, deci practic nu mai aveam conditie de oprire.

-----------
Exemplu:
-----------
In acest exemplu, vom desena "pattern-uri" pe ecran folosind functii recursive.
#include <stdio.h>
#define SYMBOL '*'
#define OFFSET 0
#define LENGTH 19
void display(char, int, int);
void draw(char, int);

void main()
{
display(SYMBOL, OFFSET, LENGTH);
}

void display(char c, int m, int n)


{
if (n > 0)
{
draw(' ', m);
draw(c, n);
putchar('\n');
display(c, m + 2, n - 4);
}
}

void draw(char c, int k)


{
if (k > 0)
{
putchar(c);
draw(c, k - 1);
}
}

Functia "main()" contine apelul functiei "display()", care apeleaza "draw()", care la randul ei
apeleaza "display()". Deci functia
"display()" este recursiva. Functia "draw()" tipareste k copii ale caracterului "c". Pe ecran se va
afisa:
*******************
***************
***********
*******
***

----------------------------------------------------
Manipularea sirurilor folosind recursia
----------------------------------------------------
Un sir consta dintr-un numar de caractere consecutive, terminate prin caracterul '\0'. De fapt, putem
gandi un sir ca fiind sirul nul (care consta doar din caracterul '\0') sau un caracter urmat de un sir.
Aceasta definitie a sirului este o structura de date recursiva.
-----------
Exemplu: O definitie recursiva a lungimii unui sir.
-----------
int r_strlen(char *s)
{
if (*s == '\0')
return 0;
else
return (1 + r_strlen(s + 1));
}
Eleganta acestei formulari recursive este "platita" de o pierdere in timpul executiei. Daca sirul are
lungimea k, calcularea lungimii sale necesita k + 1 apeluri recursive (un compilator optimizat poate
evita aceasta pierdere).

------------------------------------------
Metodologia "divide-et-impera"
-----------------------------------------
Recursia se foloseste in foarte multe cazuri pentru codificarea algoritmilor "divide-et-impera". Un
astfel de algoritm imparte problema in subprobleme, rezolvand fiecare subproblema prin recursie,
apoi recombina solutiile partiale pentru a obtine intreaga solutie.
Vom considera un exemplu cunoscut, si anume, determinarea minimului si maximului elementelor
unui sir de intregi (publicat pentru prima data de catre Ira Pohl, "A Sorting Problem and Its
Complexity", Communications of the ACM, 15, nr. 6, 1972) considerat cel mai bun algoritm pentru
aceasta problema. Criteriul pentru "cel mai bun" a fost numarul de comparatii necesare. Prezentam
mai jos o functie C care rezolva aceasta problema (considerand dimensiunea sirului putere a lui 2).
void minmax(int a[], int n, int *min_ptr, int *max_ptr)
{
int min1, max1, min2, max2;

if (n == 2)
if (a[0] < a[1])
{
*min_ptr = a[0];
*max_ptr = a[1];
}
else
{
*min_ptr = a[1];
*max_ptr = a[0];
}
else
{
minmax(a, n/2, &min1, &max1);
minmax(a + n/2, n/2, &min2, &max2);
if (min1 < min2)
*min_ptr = min1;
else
*min_ptr = min2;
if (max1 < max2)
*max_ptr = max2;
else
*max_ptr = max1;
}
}

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Scrieti o functie C recursiva echivalenta cu "strncmp()".
2. Scrieti o functie C recursiva care calculeaza media aritmetica a unui sir de numere reale.
3. Scrieti o functie C recursiva care calculeaza n!, unde n este un numar natural.
4. (Mutarea calului) Data o tabla de sah (8 x 8), sa se scrie o functie C recursiva care descrie
mutarile calului astfel incat orice pozitie sa fie parcursa o singura data.
Structuri si liste inlantuite
13.1. Declararea structurilor
13.2. Accesarea unui membru
13.3. Asociativitatea si precedenta operatorilor (tabelul complet)
13.4. Structuri, functii si asignari
13.5. Initializarea structurilor
13.6. Folosirea lui "typedef"
13.7. Structuri recursive (self-referential)
13.8. Liste liniar inlantuite
13.9. Operatii pentru liste
13.10. Crearea unei liste
13.11. Numarare si cautare
13.12. Inserare si stergere
13.13. Exercitii propuse spre implementare

=========
Capitolul 13
=========

====================
Structuri si liste inlantuite
====================
Tipul structura permite programatorului sa imbine mai multe componente intr-o singura variabila.
Componentele structurii au nume distincte si se numesc membrii. Membrii unei structuri pot avea
tipuri diferite. Deci, ca si pointerii si sirurile, structurile sunt considerate un tip derivat. Accesarea
membrilor unei structuri se face cu "." sau cu "->" care au cea mai inalta prioritate (ca si () si []).

------------------------------
Declararea structurilor
------------------------------
Se face folosind cuvantul rezervat "struct". Iata un exemplu de declarare a cartilor de joc:
-----------
Exemplu: Declaratia de mai jos creaza tipul de data "carte_de_joc":
-----------
struct carte_de_joc
{
int numar;
char culoare;
};
Astfel cartea 3 de trefla va avea "numar=3" si "culoare='t'". Celelalte caractere pentru culorile
cartilor sunt (frunza - 'f', caro - 'c', inima - 'i'). Numele structurii poate fi folosit acum pentru
declararea variabilelor de acest tip. Abia in acest moment se rezerva loc in memorie pentru aceste
variabile:
struct carte_de_joc c1, c2;
Pentru accesarea membrilor lui c1 si c2, folosim operatorul ".".
------------
Exemplu:
------------
c1.numar = 3;
c1.culoare = 't';
c2.numar = 12;
c2.culoare = 'c';
O constructie de forma
variabila_structura . nume_membru
este folosita ca o variabila in acelasi mod ca o simpla variabila sau ca un element al unui sir.
Numele unui membru trebuie sa fie unic intr-o structura specificata. Din moment ce membrii
trebuie intotdeauna prefixati de un identificator de variabila de structura
unic, atunci nu vor fi confuzii (ambiguitati) intre doi membri cu acelasi nume, dar din structuri
diferite.
------------
Exemplu:
------------
struct fruct
{
char nume[15];
int calorii;
}

struct leguma
{
char nume[15];
int calorii;
}

struct fruct a;
struct leguma b;

Putem accesa "a.calorii", respectiv "b.calorii" fara ambiguitate. Putem declara variabile de un tip
structurat in timpul declararii acestuia.

------------
Exemplu:
------------
struct carte_de_joc
{
int numar;
char culoare;
} c1, c2, c3[52];
Identificatorul "carte_de_joc" este numele structurii. Identificatorii "c1" si "c2" se declara ca fiind
variabile de tip "struct carte_de_joc", iar identificatorul "c3" ca fiind un sir de tip "struct
carte_de_joc". Daca insa lipseste numele structurii, atunci singura data cand se pot declara variabile
de tip structura este in momentul declararii acesteia.

------------
Exemplu:
------------
struct
{
char *nume;
int nr_student;
float medie;
} s1, s2, s3;
In aceasta structura se declara trei variabile de tip structura, insa lipsind numele structurii inseamna
ca nu se mai pot declara si alte
variabile de acest tip. Daca, de exemplu, scriem
struct student
{
char *nume;
int nr_student;
float medie;
};
atunci "student" este numele structurii si nu sunt variabile declarate in acest moment. Acum putem
scrie
struct student temp, clasa[100];
si declaram "temp" si "clasa" de tip "struct student".

-------------------------------
Accesarea unui membru
-------------------------------
In cele ce urmeaza, prezentam un exemplu de folosire a operatorului de membru ".".
------------
Exemplu: In fisierul "cl_info.h" scriem:
------------
#define NR_STUDENTI 100
struct student
{
char *nume;
int nr_student;
float medie;
};
In alt fisier, scriem
#include "cl_info.h"
void main()
{
struct student temp, clasa[NR_STUDENTI];
.....
}
Putem avea instructiuni de asignare cum ar fi:
temp.medie = 4.00;
temp.nume = "Ionescu";
temp.nr_student = 1023;
In continuare, scriem o functie care numara studentii cu media 4.00:
int esec(struct student clasa[])
{
int i, contor = 0;

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


contor += clasa[i].medie == 4.00;
return contor;
}
C pune la dispozitie operatorul pointer catre structura -> pentru accesarea membrilor unei structuri
relativ la un pointer (Simbolul -> este format din caracterul - (minus) si > (mai mare)). Daca o
variabila pointer este asignata cu adresa unei structuri, atunci un membru al structurii poate fi
accesat printr-o constructie de forma:
pointer_catre_structura -> nume_membru
Bineinteles, o constructie echivalenta este:
(*pointer_catre_structura).nume_membru
Parantezele sunt necesare deoarece operatorii "." si "->" au prioritate mare si se asociaza de la
stanga la dreapta. Astfel, parantezele sunt obligatorii, deoarece in caz contrar, daca in expresia de
mai sus nu ar fi fost paranteze, atunci aceasta ar fi echivalenta cu
*(pointer_catre_structura.nume_membru)

------------
Exemplu: Fie urmatoarele declaratii si asignari:
------------
struct student temp, *p = &temp;
temp.medie = 10.00;
temp.nume = "Ionescu";
temp.nr_student = 1204;
Atunci obtinem urmatorul tabel:
----------------------------------------------------------------------------
| Expresie | Expresie echivalenta | Valoare conceptuala |
----------------------------------------------------------------------------
| temp.medie | p -> medie | 10.00 |
| temp.nume | p -> nume | Ionescu |
| temp.nr_student | p -> nr_student | 1204 |
| (*p).nr_student | p -> nr_student | 1204 |
----------------------------------------------------------------------------

---------------------------------------------------------------------------
Asociativitatea si precedenta operatorilor (tabelul complet)
---------------------------------------------------------------------------
-----------------------------------------------------------------------------
| Operatori | Asociativitate |
-----------------------------------------------------------------------------
() [] . -> ++ (postfix) -- (postfix) | de la stanga la dreapta |
-----------------------------------------------------------------------------
++ (prefix) -- (prefix) ! ~ sizeof (tip) | de la dreapta |
+ (unar) - (unar) & (adresa) * (dereferentiere) | la stanga |
------------------------------------------------------------------------------
* / % | de la stanga la dreapta |
------------------------------------------------------------------------------
+ - | de la stanga la dreapta |
------------------------------------------------------------------------------
<< >> | de la stanga la dreapta |
------------------------------------------------------------------------------
< <= > >= | de la stanga la dreapta |
------------------------------------------------------------------------------
== != | de la stanga la dreapta |
------------------------------------------------------------------------------
& | de la stanga la dreapta |
------------------------------------------------------------------------------
^ | de la stanga la dreapta |
------------------------------------------------------------------------------
| | de la stanga la dreapta |
------------------------------------------------------------------------------
&& | de la stanga la dreapta |
------------------------------------------------------------------------------
|| | de la stanga la dreapta |
------------------------------------------------------------------------------
?: | de la dreapta la stanga |
------------------------------------------------------------------------------
= += -= *= /= %= >>= <<= &= ^= |= | de la dreapta la stanga |
------------------------------------------------------------------------------
, (operatorul virgula) | de la stanga la dreapta |
------------------------------------------------------------------------------
Operatorul "," are cea mai mica prioritate dintre toti operatorii C. Virgula folosita in declaratii si in
lista de argumente ale functiilor nu este operator.
------------
Exemple: Expresia a = 1, b = 2 este o expresie virgula. Intai se evalueaza "a = 1", apoi "b = 2",
iar valoarea si tipul returnat de expresia virgula sunt cele returnate de "b = 2", adica valoarea "2" si
tipul "int".

Un exemplu frecvent unde apare operatorul virgula este "for". De exemplu,


for (i = 0, j = 1; i < LIMIT; i += 2, j +=2)
.....
Va amintiti ca operatorul unar "sizeof" poate fi folosit pentru determinarea numarului de octeti
necesar memorarii sale. De exemplu, expresia sizeof(struct carte_de_joc) va intoarce numarul de
octeti necesari sistemului pentru memorarea unei variabile de tip "struct carte_de_joc". Pe cele mai
multe sisteme tipul returnat de expresie este "unsigned".

-----------------------------------
Structuri, functii si asignari
-----------------------------------
C traditional permite unui pointer catre un tip structura sa fie transmis ca argument al unei functii si
returnat ca o valoare. ANSI C
permite chiar unei structuri sa fie trimisa ca argument pentru functii si returnata ca valoare. De
exemplu, daca "a" si "b" sunt structuri, atunci expresia de asignare "a = b" este valida. Aceasta
implica ca fiecare valoare a unui membru din structura "a" devine
egala cu valoarea membrului corespunzator din structura "b".
In C, structurile, pointerii si vectorii pot fi combinati pentru crearea unor structuri de date
complicate.
-----------
Exemplu: Baza de date cu studenti
-----------
In fisierul "student.h":

#define NR_STUDENTI 50
#define NR_CURSURI 10

struct student
{
char *nume;
int nr_student;
float medie;
};

struct data
{
short zi;
char luna[10];
short an;
};

struct persoana
{
char nume[20];
struct data zi_nastere;
};

struct date_student
{
struct persoana p;
int nr_student;
float medie[NR_CURSURI];
};
Observati ca "struct date_student" este construita cu structuri imbricate. De exemplu, daca avem
declaratia:
struct date_student temp;
atunci expresia:
temp.p.zi_nastere.luna[0]
are ca valoare prima litera a lunii datei de nastere a studentului asociat lui "temp".
In continuare, vom scrie o functie "citeste_date()" pentru a introduce date in variabile de tip "struct
date". La apelul functiei, trebuie trimisa adresa variabilei ca argument.
-----------
Exemplu:
-----------
#include "student.h"

void citeste_date(struct data *z)


{
printf("Dati ziua(int) luna(string) an(int): ");
scanf("%hd%s%hd", &z -> zi, z -> luna, &z -> an);
}

Formatul %hd este folosit pentru conversia caracterelor de la tastatura la o valoare de tip "short".

-------------
Intrebare: De ce "z -> luna" nu contine operatorul de adresa ?
-------------
Functia "citeste_date()" poate fi folosita pentru citirea informatiei intr-o variabila de tip "struct
date_student" astfel:

struct date_student temp;

citeste_date(&temp.p.zi_nastere);

--------------------------------
Initializarea structurilor
--------------------------------
Toate variabilele externe si statice, inclusiv variabilele de structura, care nu sunt explicit
initializate, sunt automat initializate de catre sistem cu zero. In C traditional, structurile statice si
externe pot fi initializate de catre programator. In ANSI C, putem initializa si structuri definite
"auto". Sintaxa este similara celei folosite la siruri. O variabila structura poate fi urmata de semnul
"=" si o lista de constante cuprinse intre acolade. Daca nu sunt suficiente valori pentru asignarea lor,
atunci membrii ramasi sunt asignati cu zero implicit.
-----------
Exemple: struct carte_de_joc c = {12, 't'};
----------- struct complex
{
double real;
double imaginar;
} m[3][3] =
{
{{1.0, -0.5}, {2.5, 1.0}, {0.7, 0.7}},
{{7.0, -6.5}, {-0.5,1.5},{45.7,8.0}},
};
Se observa imediat ca linia "m[2][]" este initializata cu 0.

------------------------------
Folosirea lui "typedef"
------------------------------
Facilitatea "typedef" este deseori folosita pentru redenumirea unui tip structura.
-----------
Exemple:
-----------
typedef char * string;
typedef int lungime;
typedef float vector[10];
typedef double (*PFD)(double);
Dupa aceste redenumiri, putem face declaratiile:
lungime l1, l2;
string s1 = "abc", s2 = "xyz";
vector x;
Aceste declaratii sunt echivalente cu:
int l1, l2;
char * s1 = "abc", s2 = "xyz";
float x[10]; /* Atentie ! Se inlocuieste vector cu x */
La fel, declaratia
PFD f;
este echivalenta cu
double (*f)(double);
Este vorba mai sus de un pointer la o functie ce returneaza tipul "double".

----------------------------------------------
Structuri recursive (self-referential)
----------------------------------------------
O structura este recursiva daca un membru pointer se refera la tipul structurii initiale (recursie de
ordinul 1, exista si ordine mai
mari). De obicei, structurile recursive necesita rutine explicite pentru rezervarea si eliberarea de
memorie.
------------
Exemplu:
------------
struct lista
{
int data;
struct lista *urmator;
}
Fiecare variabila de tip "struct lista" are doi membri, "data" si "urmator". Pictural, asta arata cam
asa (in memorie):
--------------
| | ----|-->
--------------
data urmator
Variabila pointer "urmator" contine o adresa a unei locatii de memorie a unui element succesor
"struct lista" sau valoarea speciala
NULL, definita in <stdio.h> ca avand valoarea constanta 0. Valoarea NULL este folosita pentru
notarea sfarsitului listei.
Presupunem ca avem declaratiile
struct lista a, b, c;
Vrem sa creeam o lista inlantuita formata din aceste trei variabile. Mai intai, facem asignarile:
a.data = 1;
b.data = 2;
c.data = 3;
a.urmator = b.urmator = c.urmator = NULL;
Dupa aceste instructiuni, obtinem in memorie:
a b c
---------------- ---------------- ----------------
| 1 | NULL | | 2 | NULL | | 3 | NULL |
---------------- ---------------- ----------------
data urmator data urmator data urmator

Acum putem "lega" cele trei structuri, astfel:


a.urmator = &b;
b.urmator = &c;

Obtinem:
a b c
-------------- -------------- ----------------
| 1 | ---|---->| 2 | ---|---->| 3 | NULL |
-------------- -------------- ----------------
data urmator data urmator data urmator

----------------------------
Liste liniar inlantuite
----------------------------
O lista liniar inlantuita este o structura de date ce are elementele legate secvential. Exista un pointer
catre primul element al listei,
fiecare element al listei pointeaza catre urmatorul element al listei, avand ultimul element pointand
catre NULL. De obicei, o lista
inlantuita se creaza dinamic.
Scriem in fisierul "header" intitulat "list.h" urmatoarele declaratii:
#include <stdio.h>
typedef char DATA;

struct lista_inlantuita
{
DATA d;
struct lista_inlantuita *next;
};

typedef struct lista_inlantuita ELEMENT;


typedef ELEMENT * LISTA;

Relativ la alocarea dinamica, va reamintim ca functia "malloc()" are un singur argument de tip
"size_t" si intoarce un pointer catre
"void" care pointeaza catre adresa de baza a spatiului de memorie alocat (evident, cauta spatiu
suficient pentru un obiect). Astfel,
daca "head" este o variabila de tip "LISTA", atunci
head = (LISTA) malloc(sizeof(ELEMENT));
va produce o bucata din memorie menita sa memoreze un ELEMENT asignand adresa de baza
pointerului "head".

------------
Exemplu:
------------
Presupunem ca vrem sa creeam dinamic o lista liniar inlantuita pentru memorarea a trei caractere
'n', 'e' si 'w'. Considerand
head = (LISTA) malloc(sizeof(ELEMENT));
head -> d = 'n';
head -> next = NULL;
obtinem un memorie ceva de genul:
----------------
head --->| 'n' | NULL |
---------------
d next

Al doilea element este adaugat de instructiunile:


head -> next = (LISTA) malloc(sizeof(ELEMENT));
head -> next -> d = 'e';
head -> next -> next = NULL;
In memorie avem:
------------ ---------------
head--->| 'n' | ---|--->| 'e' | NULL |
------------ ---------------
d next d next

In sfarsit, adaugam si al treilea element:


head -> next -> next = malloc(sizeof(ELEMENT));
head -> next -> next -> d = 'w';
head -> next -> next -> next = NULL;
In memorie avem:
-------------- -------------- ----------------
head--->| 'n' | ---|--->| 'e' | ---|--->| 'w' | NULL |
-------------- -------------- ----------------
d next d next d next

--------------------------
Operatii pentru liste
--------------------------
Operatiile de baza pentru liste liniar inlantuite includ urmatoarele:
1. Crearea unei liste
2. Numararea elementelor unei liste
3. Cautarea unui element
4. Inserarea unui element
5. Stergerea unui element
-----------------------
Crearea unei liste
-----------------------
Vom prezenta o varianta recursiva a acestei operatii, si anume vom crea o lista pornind de la un
string. Functia va returna un pointer catre primul element al listei.
#include "list.h"

LISTA creare(char s[])


{
LISTA head;

if (s[0] == '\0')
return NULL;
else
{
head = (LISTA) malloc(sizeof(ELEMENT));
head -> d = s[0];
head -> next = creare(s + 1);
return head;
}
}

--------------------------
Numarare si cautare
--------------------------
Functia recursiva "numara()" numara elementele unei liste parcurgand fiecare element pana
intalneste pointerul NULL. Daca lista este vida, atunci se intoarce 0, altfel numarul de elemente al
listei.
#include "list.h"

int numara(LISTA head)


{
if (head == NULL)
return 0
else
return (1 + numara(head -> next));
}
Functia recursiva "cauta()" cauta intr-o lista un element. Daca este gasit acel element, atunci se
intoarce un pointer catre acesta,
altfel se intoarce pointerul NULL.
#include "list.h"

LISTA cauta(DATA c, LISTA head)


{
if (head == NULL)
return NULL;
else
if (c == head -> d)
return head;
else
return (cauta(c, head -> next));
}

-------------------------
Inserare si stergere
-------------------------
Pictural, asta ar arata cam asa (inainte de inserare):

p1 --| p2--|
| |
V V
-------------- --------------
... --->| A | ---|--->| C | ---|---> ...
-------------- --------------
-----------------
q --->| B | NULL |
-----------------
Dupa inserare, obtinem:
-------------- --------------
... --->| A | | | | C | ---|---> ...
----------|--- --------------
| ^
->----------|----
q --->| B | | |
---------------
Functia care face acest lucru este:
#include "list.h"
void insert(LISTA p1, LISTA p2, LISTA q)
{
p1 -> next = q;
q -> next = p2;
}
Stergerea unui element intr-o lista liniar inlantuita este foarte simpla. Pictural, avem:
p--|
|
V
-------------- -------------- --------------
... --->| A | ---|--->| B | ---|--->| C | ---|--> ...
-------------- -------------- --------------
Instructiunea
q = p -> next;
va implica pointarea lui q catre obiectul care trebuie sters. Obtinem:
p--| q--|
| |
V V
-------------- -------------- --------------
... --->| A | ---|--->| B | ---|--->| C | ---|--> ...
-------------- -------------- --------------
Considerand instructiunea
p -> next = q -> next;
se obtine in memorie
p--| q--|
| |
V V
-------------- -------------- --------------
... --->| A | | | | B | ---|--->| C | ---|--> ...
---------|---- --------------- --------------
| ^
| |
-------------------------------
Se observa ca elementul B este inaccesibil (adica nu mai apartine listei), deci nu mai este folosit.
Acesta se mai numeste "garbage" (gunoi). Evident ca ar fi bine pentru sistem daca s-ar putea elibera
aceasta locatie de memorie pentru a putea fi refolosita. Eliberarea zonei de memorie se face cu
functia "free()" din biblioteca standard. Deci, "free(q)" face disponibila pentru sistem locatia de
memorie catre care pointeaza "q" care a fost alocata mai inainte cu "malloc()" (sau "calloc()"). Cum
"free()" are ca argument de tip pointer catre "void", rezulta ca "q" poate fi de orice tip.

In continuare, vom scrie o functie care sterge si elibereaza toate elementele unei liste.
#include "list.h"
void sterge_lista(LISTA head)
{
if (head != NULL)
{
sterge_lista(head -> next);
free(head);
}
}

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Scrieti un program C pentru simularea unui joc de carti. Sa se faca o amestecare si o impartire a
cartilor la cei n jucatori.
2. Folosind "typedef", sa se scrie trei functii C care sa calculeze suma a doi vectori, produsul scalar
a doi vectori si inmultirea
matricelor.
3. (Operatii cu liste)
a) Concatenarea a doua liste;
b) Determinarea unei subliste ce contine primele k elemente dintr-o lista, cu eliberarea zonelor de
memorie ale restului elementelor.
4. Sa se oglindeasca o lista liniara inlantuita cu numar constant de variabile suplimentare fara a
folosi recursie.
5. Sa se sorteze n numere folosind liste liniar inlantuite si metoda interclasarii.
6. Scrieti un program C in care sa descrieti urmatoarele operatii pentru arbori binari: creare,
numarare, cautare, stergere, inserare, parcurgere (preordine, inordine, postordine).
7. Scrieti un program C in care sa descrieti aceleasi operatii de mai sus, dar pentru arbori generali
(idee: folositi legaturi de tip fiu-frate).
Intrari/iesiri si fisiere
14.1. Functia de iesire "printf()"
14.2. Functia de intrare "scanf()"
14.3. Functiile "sprintf()" si "sscanf()"
14.4. Functiile "fprintf()" si "fscanf()"
14.5. Accesarea fisierelor
14.6. Accesarea aleatoare a unui fisier
14.7. Stil de programare
14.8. Exercitii propuse spre implementare

==========
Capitolul 14
==========
================
Intrari/iesiri si fisiere
================
In acest capitol vom explica folosirea unor functii de intrare/iesire (printre care si "printf()" si
"scanf()"). De asemenea vom arata modurile de deschidere a fisierelor (pentru procesarea lor) si
cum se foloseste un pointer catre fisier.
-----------------------------------
Functia de iesire "printf()"
-----------------------------------
Are doua proprietati importante care permit o folosire flexibila la un nivel inalt:
- poate fi tiparita o lista de argumente de lungime arbitrara;
- afisarea este controlata de specificari de conversie simple (sau formate).
Functia "printf()" primeste sirul de caractere din fisierul standard de iesire (stdout), care este
normal conectat la ecran. Lista de
argumente a lui "printf()" are doua parti:
sirul_de_control si celelalte_argumente
-----------
Exemplu: In cazul apelului:
-----------
printf("Produsul %d %s $%f\n", 2, "costa", 3.77);
avem
sirul_de_control: "Produsul %d %s $%f\n"
celelalte_argumente: 2, "costa", 3.77

Expresiile din 'celelalte_argumente' sunt evaluate si convertite conform cu formatele din sirul de
control si apoi plasate in sirul de iesire. Caracterele din sirul de control care nu fac parte dintr-un
format sunt plasate direct in sirul de iesire. Simbolul % introduce o
specificare de conversie sau format. O specificare de conversie este un sir care incepe cu % si se
termina cu un caracter de conversie.
Tabelul de mai jos reprezinta caracterele de conversie (si modul lor de afisare) pentru functia
"printf()":
---------------------------------------------------------------------
Caracter Cum sunt afisate
de argumentele corespunzatoare ?
conversie
---------------------------------------------------------------------
c ca un caracter
d, i ca un intreg zecimal
u ca un intreg zecimal fara semn
o ca un intreg octal fara semn
x, X ca un intreg hexazecimal fara semn
e ca un numar in virgula mobila; (ex: 7.123000e+00)
E ca un numar in virgula mobila; (ex: 7.123000E+00)
f ca un numar in virgula mobila; (ex: 7.123000)
g in formatul cel mai scurt dintre "e" sau "f"
G in formatul cel mai scurt dintre "E" sau "f"
s ca un sir
p argumentul corespunzator este un pointer catre void; valoarea sa se va tipari ca un
intreg hexazecimal
n argumentul corespunzator este un pointer catre un intreg; argumentul nu este convertit
% cu formatul %% se va afisa un singur % catre sirul de iesire; nu avem argumente ce
trebuie convertite
---------------------------------------------------------------------
-----------
Exemplu: Iata un exemplu de folosire a formatului "%n":
-----------
#include <stdio.h>
void main()
{
int * pi;
printf("Mai multe caractere %n.\n", pi);
printf("Nr.caractere = %d", *pi);
}
Pe ecran se va afisa numarul de caractere afisate pana la aparitia formatului "%n" (in cadrul
instructiunii de afisare "printf()" curente), adica 20. Functia "printf()" intoarce un "int" ce
reprezinta numarul de caractere tiparite dupa inlocuirea corespunzatoare a specificatorilor de
conversie.

-----------
Exemplu:
-----------
#include <stdio.h>
void main()
{
int * pi;
int a = printf("Mai mult de %d caractere %n.\n", 10, pi);
printf("Numarul de caractere intors de functia printf() este %d\n", a);
printf("Nr.caractere = %d", *pi);
}

In specificarile de conversie pot fi incluse informatii de formatare explicite. Daca ele nu apar,
atunci sunt folosite cele implicite. De exemplu, formatul %f cu argumentul 3.77 va afisa 3.770000.
Numarul este afisat pe 6 caractere la dreapta punctului zecimal (implicit). Intre % (care specifica
inceputul unei specificari de conversie) si caracterul de conversie de la sfarsit, pot apare in ordine:
- zero sau mai multe caractere (flag) care modifica intelesul specificarii de conversie;
- un intreg pozitiv optional care specifica minimul lungimii campului a argumentului convertit.
Locul unde un argument este tiparit se numeste "campul sau", iar numarul de spatii folosit pentru
afisarea sa se numeste "lungimea campului". Daca argumentul convertit are mai putine caractere
decat lungimea campului specificat, atunci acesta se va completa cu spatii (la stanga sau dreapta).
Daca argumentul convertit are mai multe caractere decat lungimea campului specificat, atunci
lungimea campului se va mari cu cat este necesar. Daca intregul care defineste lungimea campului
incepe cu zero si argumentul ce se tipareste este ajustat dreapta in campul sau, atunci pentru
umplerea sa se vor folosi zerouri in loc de spatii;
- o "precizie" optionala, care este specificata de un punct urmat de un intreg nenegativ. Pentru
conversii d, i, o, u, x si X aceasta specifica numarul minim de cifre ce trebuie afisate. Pentru
conversii e, E si f aceasta specifica numarul de cifre de la dreapta punctului zecimal. Pentru
conversii g si G aceasta specifica numarul maxim de cifre semnificative. Pentru conversie cu s,
aceasta specifica numarul maxim de caractere ce trebuie tiparite dintr-un sir;
- un h sau l optional, care este un modificator "short" sau "long", respectiv. Daca h este urmat de
un d, i, o, u, x sau X, atunci aceasta este o specificare de conversie relativ la "short int" sau
"unsigned short int". Daca un h este urmat de n, atunci argumentul corespunzator este un pointer
catre "short int" sau "unsigned short int". Daca l este urmat de d, i, o, u, x sau X, atunci specificarea
de conversie se aplica unui argument "long int" sau "unsigned long int". Daca l este urmat de
caracterul n, atunci argumentul corespunzator este un pointer catre "long int" sau "unsigned long
int";
- L optional, care este un modificator "lung". Daca L este urmat de e, E, f, g sau G, specificarea
de conversie se aplica unui argument "long double".

Caracterele "flag" sunt:


- semnul "-" va implica alinierea spre stanga a argumentului convertit in campul sau. Daca nu
gasim nici un semn "-", atunci alinierea argumentului convertit se face la dreapta campului.
- semnul "+" va implica afisarea semnului "+" in cazul numerelor pozitive (functioneaza pentru
d, i, e, E, f, g si G). Toate numerele negative incep cu semnul minus.
- un spatiu, care semnifica ca un numar pozitiv (ce provine dintr-o conversie unsigned) are la
inceput un spatiu;
- un "#", care semnifica ca rezultatul trebuie convertit la o forma alternativa ce depinde de
caracterul de conversie. Daca caracterul de conversie este "o", atunci "#" cauzeaza afisarea unui
zero in fata numarului octal. Intr-o conversie x sau X, "#" cauzeaza 0x, respectiv 0X, in fata
numarului hexazecimal. In conversiile g si G, acesta cauzeaza tiparirea unor zerouri la coada (pana
la un anumit numar de zecimale, de obicei 5). In conversiile e, E, f, g sau G, aceasta cauzeaza
tiparirea punctului zecimal, chiar si cu precizia 0. Pentru alte conversii, comportarea lui "#" este
nedefinita.
- un "0" (zero), care inseamna ca in loc de spatii sunt folosite zerouri. Cu caracterele de conversie
d, i, o, u, x, X, e, E, f, g si G, aceasta poate produce zerouri nesemnificative (in fata numarului).

Intr-un format, lungimea campului sau precizia (sau ambele), pot fi specificate folosind *, in loc de
un intreg, lucru care indica ca o
valoare se obtine dintr-o lista de argumente.
-----------
Exemplu:
-----------
int m, n;
double x = 333.7777777;
...........
/* citeste m si n (sau le calculam cumva) */
...........
printf("x = %*.*f\n", m, n, x);

Daca argumentul corespunzator lungimii campului are lungime negativa, atunci se considera "-" ca
fiind un "flag" urmat de o valoare pozitiva. Daca argumentul corespunzator preciziei are valoare
negativa, atunci acesta se considera ca lipseste.
Tabelul de mai jos contine formate de caractere si siruri (folosim ghilimele pentru delimitarea
vizuala a campului, ele nefiind afisate).
-----------------------------------------------------------------------------------
Declaratii si initializari
-----------------------------------------------------------------------------------
char c = 'A', s[] = "Luna rosie!";
-----------------------------------------------------------------------------------
Format Argumentul Cum este afisat Observatii
corespunzator in campul sau ?
----------------------------------------------------------------------------------
%c c "A" Lungimea campului 1 implicit
%2c c " A" Lungimea campului 2, aliniat dreapta
%-3c c "A " Lungimea campului 3, aliniat stanga
%s s "Luna rosie!" Lungimea campului 11 implicit
%3s s "Luna rosie!" Spatii suplimentare
%.6s s "Luna r" Precizia 6
%-11.8s s "Luna ros " Precizie 8, aliniere stanga
----------------------------------------------------------------------------------
In tabelul de mai jos vom da exemple de folosire a altor formate (avand aceeasi conventie de
afisare cu ghilimele).

-----------------------------------------------------------------------------------
Declaratii si initializari
-----------------------------------------------------------------------------------
int i = 123;
double x = 0.123456789;
----------------------------------------------------------------------------------
Format Argumentul Cum este afisat Observatii
corespunzator in campul sau ?
----------------------------------------------------------------------------------
%d i "123" Lungimea campului 3 implicit
%05d i "00123" Adaugat 0-uri nesemnificative
%7o i " 173" Aliniere dreapta, octal
%-9x i "7b " Aliniere stanga, hexazecimal
%-#9x i "0x7b " Aliniere stanga, hexazecimal
%10.5f x " 0.12346" Lungimea campului 10, precizie 5
%-12.5e x "1.23457e-01 " Aliniere stanga, format -e
----------------------------------------------------------------------------------

------------------------------------
Functia de intrare "scanf()"
------------------------------------
Are doua proprietati importante care permit o folosire flexibila la un nivel inalt:
- poate citi o lista de argumente de lungime arbitrara;
- intrarea este controlata de specificari de conversie simple (sau formate).
Functia "scanf()" citeste caractere din fisierul standard "stdin". Lista de argumente a functiei
"scanf()" are doua parti:
sir_de_control si celelalte_argumente

-----------
Exemplu: Fie declaratiile si apelul functiei "scanf()":
-----------
char a, b, c, s[100];
int n;
double x;
scanf("%c%c%c%d%s%lf", &a, &b, &c, &n, s, &x);
Avem:
sir_de_control: "%c%c%c%d%s%lf"
celelalte_argumente: &a, &b, &c, &n, s, &x
Celelalte argumente urmate de un sir de control consta dintr-o lista separata prin virgule de expresii
pointer sau adrese (reamintim ca "s" este insusi o adresa).

------------------------------------
Directive in sirul de control
------------------------------------
Sirul de control din "scanf()" este compus din trei tipuri de "directive":
- caractere ordinare
- spatii goale
- specificari de conversie

-------------------------
Caractere ordinare
-------------------------
Caracterele din sirul de control (diferite de spatiile goale si caracterele din specificarile de
conversie) sunt numite "caractere ordinare". Caracterele ordinare trebuie sa se regaseasca
(potriveasca) cu cele din sirul de la intrare.

-----------
Exemplu: float suma;
----------- scanf("$%f", &suma);

Caracterul $ este ordinar. Deci trebuie sa intalnim $ in sirul de la intrare. Daca are loc o potrivire cu
succes, atunci spatiile goale (daca exista) se vor sari, si caracterele care urmeaza se vor potrivi la o
valoare (in virgula mobila). Valoarea convertita va fi plasata
in memorie la adresa variabilei "suma".

--------------------------------
Caractere "spatii goale"
--------------------------------
Caracterele spatii goale din sirul de control care nu fac parte dintr-o specificare de conversie se
potrivesc cu orice spatiu liber din sirul de intrare.
-----------
Exemplu: char c1, c2, c3;
----------- scanf(" %c %c %c", &c1, &c2, &c3);
Daca sirul de la intrare contine literele "a", "b", si "c", atunci "c1", "c2" si "c3" vor avea valorile
"a", "b", "c" (a nu se citi ghilimelele). O directiva spatiu liber implica ca spatiile goale (daca exista)
sa fie ignorate din sirul de intrare.
-----------
Exemplu: Urmatoarele instructiuni sunt echivalente:
-----------
scanf(" %c %c %c", &c1, &c2, &c3);
scanf("\t%c \t %c\n%c", &c1, &c2, &c3);

-------------------------------
Specificari de conversie
-------------------------------
Intr-un sir de control pentru "scanf()", o directiva de specificare de conversie incepe cu un "%" si
se termina cu un caracter de conversie. Aceasta determina modurile de potrivire si de convertire a
caracterelor din sirul de intrare (cele doua tabele de mai jos contin explicatii pentru functia
"scanf()"):
--------------------------------------------------------------------------------
Caracter de Caracterele din sirul de Tipul
conversie intrare cu care se potrivesc argumentului
nemodificabil corespunzator
--------------------------------------------------------------------------------
c orice caracter, inclusiv spatiu liber char *
d un intreg zecimal (optional cu semn) int *
i zecimal, octal, hexazecimal int *
(77, 077, 0x77, optional cu semn)
u un intreg zecimal (optional cu semn) unsigned *
o un intreg octal (optional cu semn) unsigned *
cifra 0 nu mai este necesara
x, X un intreg hexazecimal (optional cu semn) unsigned *
0x sau 0X nu mai sunt necesare
e, E, f, g, G numar in virgula mobila (optional cu semn) float *
s o secventa de caractere diferite de spatiu char *
p ceea ce produce %p in "printf()" void * *
(de obicei intreg hexazecimal fara semn)
n, %, [ . . .] (vezi urmatorul tabel)
-------------------------------------------------------------------------------
Observatie. Zecimal inseamna numar intreg scris in baza 10 (nu numar cu zecimale).

-----------------------------------------------------------------------------------------------------------------------
----------------------
Caracter de Observatii
conversie
nemodificabil
-----------------------------------------------------------------------------------------------------------------------
----------------------
n Nu se potriveste nici un caracter din sirul de intrare. Argumentul corespunzator este un
pointer catre "int",
in care se memoreaza numarul de caractere citite in acel "scanf()"
% Conversia de specificare %% va implica potrivirea cu un caracter % din sirul de intrare.
Nu are argumente
corespunzatoare.
[ . . . ] Multimea caracterelor dintre paranteze se numeste multime de scanare. Aceasta
determina ce se potriveste
si face citirile respective. Argumentul corespunzator este un pointer catre baza sirului de
caractere ce este
suficient de mare pentru a pastra caracterele cu care s-a potrivit, apoi va adauga automat
terminatorul '\0'.
-----------------------------------------------------------------------------------------------------------------------
----------------------

Intre % si caracterul de conversie poate fi:


- caracterul * optional, care indica o suprascriere, urmata de un intreg optional care defineste
lungimea maxima a sirului de intrare
(care va fi deci ignorat), urmat optional de h, l, L care modifica caracterul de conversie;
- modificatorul h, care poate precede caracterele de conversie d, i, o, u, x sau X. Acesta precizeaza
ca valoarea convertita trebuie
memorata ca un "short int" sau "unsigned short int";
- modificatorul l, care poate precede caracterele de conversie d, i, o, u, x, X sau e, E, f, g, G. In
primul caz, acesta precizeaza ca
valoarea trebuie memorata ca un "long int" sau "unsigned long int".
In cel de-al doilea caz, acesta precizeaza ca valoarea convertita trebuie memorata ca un "double";
- modificatorul L, care poate precede caracterele de conversie e, E, f,g sau G. Acesta precizeaza
ca valoarea convertita trebuie
memorata ca un "long double".
Caracterele din sirul de intrare sunt convertite la valori in concordanta cu specificarile de conversie
din sirul de control si plasate la adresa data prin expresia pointer corespunzatoare din lista de
argumente. Cu exceptia unei intrari caracter, un camp de scanare consta dintr-un numar contiguu de
caractere diferite de spatiu (conforme cu conversia specificata). Campul de scanare se termina cand
se gaseste un caracter neadecvat, sau s-a depasit lungimea scanarii (daca ea este precizata), sau s-a
ajuns la EOF (depinde care vine primul).
Specificarea %s sare spatiile goale si apoi citeste caractere diferite de spatiu pana cand se gaseste
spatiu sau EOF (depinde care vine primul). In schimb, specificarea %5s sare spatiile goale, apoi
citeste caracterele diferite de spatiu, dar cel mult 5 caractere sau pana la EOF (depinde care vine
primul). Cand se citeste un sir de caractere, se presupune ca in memorie este deja rezervat suficient
spatiu pentru memorarea sa (cu tot cu santinela \0).
Formatul %nc (unde "n" este o constanta intreaga) foloseste la citirea urmatoarelor n caractere,
inclusiv spatii goale (se presupune
ca s-a rezervat suficient spatiu in memorie pentru pastrarea lor, iar caracterul \0 nu se mai adauga).

----------------------------------------------------------
Numere in virgula mobila din sirul de intrare
----------------------------------------------------------
Numerele in virgula mobila din sirul de intrare sunt formatate cu un semn optional (+ sau -) urmat
de un sir de cifre cu un punct zecimal optional, urmat de parte exponentiala optionala. Partea
exponentiala consta din e sau E, urmate de un semn optional (+ sau -), urmat de un sir de cifre.
-----------
Exemple:
-----------
77
+7.7e1
770.0E-1
+0.003
------------
Nu uitati: Sirul de intrare nu este cod C (se aplica reguli diferite).
-----------

--------------------------------------
Folosirea multimii de scanare
--------------------------------------
O specificare de conversie de forma %[sir] indica ca un sir special poate fi citit. Multimea de
caractere dintre parantezele patrate se
numeste "multime de scanare". Daca primul caracter din multimea de scanare nu este caracterul
circumflex "^", atunci sirul trebuie sa fie construit numai din caractere ce apartin multimii de
scanare.
-----------
Exemple: 1. Formatul %[abc] va citi orice sir care contine literele "a", "b" si "c" si se va opri daca
orice alt caracter va
----------- apare in sirul de intrare, inclusiv un spatiu (ex. scanf("%[abc]", m)).
2. In contrast, formatul %[^abc] va citi orice ce se va termina cu "a", "b" sau "c", dar nu si
spatiu.
3. Fie codul
char m[30];
scanf("%29[AB \t\n]", m);
Aceasta va produce citirea in vectorul de caractere "m" a unui sir de cel mult 29
caractere. Sirul consta din literele A, B,
spatiu, tab, newline. La sfarsit, se va scrie \0.
4. Programatorii de obicei gandesc o linie ca un sir de caractere, inclusiv spatii si taburi,
care se termina cu un newline.
Un mod (elegant) de a citi o linie in memorie este folosirea unei multimi de scanare
potrivita:
char linie[256];
while (scanf(" %[^\n]", linie) == 1)
printf("%s\n", linie);

-----------------------------------------
Valoarea returnata de "scanf()"
-----------------------------------------
Cand "scanf()" este apelata, poate apare o greseala la citire. De exemplu, daca nu sunt caractere in
sirul de intrare, atunci "scanf()" va intoarce -1 (EOF). Daca apare o nepotrivire intre formatele din
"scanf()" si sirul de la intrare, atunci "scanf()" va intoarce numarul de conversii cu succes pana in
acel moment. Numarul este zero daca nu apar conversii. Daca "scanf()" reuseste cu succes, atunci
este returnat numarul de conversii cu succes. La fel, acest numar poate fi zero.
-----------
Exemplu:
-----------
char c, *sir_control, s[7], m[18];
int a, contor;
sir_control = "%d , %*s %% %c %[abc] %*s %5s %s";
contor = scanf(sir_control, &a, &c, s, m, &m[5]);
Consideram ca avem la intrare sirul:
23 , ignora % C abacus citeste_aceasta**
Atunci:
"23" este plasat in memorie la adresa lui "a"
"," se potriveste
"ignora" este un sir ignorat
"%" se potriveste
"C" este plasat in memorie la adresa lui "c"
"abac" este plasat in s[0],...,s[4]='\0'
"us" este un sir ignorat
"cites" este plasat in m[0],...,m[5]='\0'
"te_aceasta**" este plasat in m[5],...,m[18]='\0'
Din moment ce au avut loc 5 conversii cu succes, rezulta ca functia "scanf()" va intoarce valoarea
5.
-----------
Exemplu: Tabelul de mai jos contine mai multe exemple de directive de control pentru functia
"scanf()":
-----------
---------------------------------------------------------------------
Directive Tipul argumentului Continutul Observatii
in sirul corespunzator sirului de
de control intrare
---------------------------------------------------------------------
ab%2c char * abacus ab se potriveste
ac se converteste
---------------------------------------------------------------------
%3hd short * -7733 -77 se converteste
---------------------------------------------------------------------
%41i long * +0x66 +0x6 se converteste
---------------------------------------------------------------------
-%2u unsigned * -123 - se potriveste
12 se converteste
---------------------------------------------------------------------
+ %lu unsigned long * +-123 + se potriveste
-123 se converteste
---------------------------------------------------------------------
+ %lu unsigned long * + -123 + se potriveste
-123 se converteste
---------------------------------------------------------------------
+ %lu unsigned long * +- 123 + se potriveste
eroare, (- nu se converteste)
---------------------------------------------------------------------
%3e float * +7e-2 +7e se converteste
---------------------------------------------------------------------
%4f float * 7e+22 7e+22 se converteste
---------------------------------------------------------------------
%51f double * -1.2345 -1.23 se converteste
---------------------------------------------------------------------
%4Lf long double * 12345 1234 se converteste
---------------------------------------------------------------------
%p void * * dependent poate citi ceea ce printf()
de sistem cu %p scrie la iesire
---------------------------------------------------------------------

-------------------------------------------
Functiile "sprintf()" si "sscanf()"
-------------------------------------------
Functiile "sprintf()" si "sscanf()" sunt versiuni ce folosesc siruri ale functiei "printf()" si "scanf()",
respectiv. Prototipurile lor,
care se gasesc in "stdio.h", sunt:
int sprintf(char *s, const char *format, ...);
int sscanf(const char *s, const char *format, ...);
Punctele ... indica compilatorului faptul ca functia poate avea un numar variabil de argumente. O
instructiune de forma:
sprintf(sir, sir_de_control, alte_argumente);
scrie rezultatul in sirul de caractere "sir". Intr-o maniera similara, o instructiune de forma:
sscanf(sir, sir_de_control, alte_argumente);
citeste rezultatul din sirul de caractere "sir".

-----------
Exemplu:
-----------
char * sir_intrare = "1 2 3 ab";
char sir_iesire[100], temp[100];
int a, b, c;
sscanf(sir_intrare, "%d%d%d%s", &a, &b, &c, &temp);
sprintf(sir_iesire, "%s %s %d%d%d\n", temp, temp, a, b, c);
printf("%s", sir_iesire);
Atunci se va afisa la ecran:
ab ab 123
Atentie ! Este responsabilitatea programatorului sa rezerve spatiu suficient pentru memorarea lui
"sir_iesire" din "sprintf()".

------------------------------------------
Functiile "fprintf()" si "fscanf()"
------------------------------------------
Functiile "fprintf()" si "fscanf()" sunt versiunile pentru fisiere a functiilor "printf()" si "scanf()".
Fisierul "stdio.h" contine un numar de constructii referitoare la fisiere. In acest fisier exista si tipul
structura FILE a caror membrii descriu starea curenta a unui fisier. Tot in acest fisier, sunt definiti
trei pointeri la fisier. Este vorba despre "stdin", "stdout" si "stderr".
------------------------------------------------------------------------
Denumirea in C Numele complet Observatii
------------------------------------------------------------------------
stdin standard input file conectat la tastatura
stdout standard output file conectat la ecran
stderr standard error file conectat la ecran
------------------------------------------------------------------------
In fisierul "stdio.h" exista prototipurile pentru functiile "fprintf()" si "fscanf()":
int fprintf(FILE *ofp, const char *format, ...);
int fscanf(FILE *ifp, const char *format, ...);
("ofp" - outfile pointer, iar "ifp" - infile pointer)
Punctele ... spun compilatorului ca functia ia un numar variabil de argumente. O instructiune de
forma:
fprintf(pointer_catre_fisier, sir_de_control, alte_argumente);
va scrie in fisierul spre care pointeaza "pointer_catre_fisier". In particular,
fprintf(stdout, ...); este echivalent cu printf(...);
Intr-o maniera similara, o instructiune de forma:
fscanf(pointer_catre_fisier, sir_de_control, alte_argumente);
va citi din fisierul spre care pointeaza "pointer_catre_fisier". In particular,
fscanf(stdin, ...); este echivalent cu scanf(...);

-------------------------
Accesarea fisierelor
-------------------------
Fisierele au cateva proprietati importante:
- au un nume
- trebuie inchise si deschise
- poate fi scris in ele sau citit din ele sau adaugat la ele
- cand sunt deschise avem acces la ele de la inceput la sfarsitul lor
Abstract, un fisier poate fi gandit ca un sir de caractere. Dupa ce un fisier a fost deschis, sirul poate
fi accesat folosind functii din
biblioteca standard.
-----------
Exemplu:
-----------
#include <stdio.h>
void main()
{
int suma = 0, val;
FILE *ifp, *ofp;
ifp = fopen("fis_in", "r"); /* deschis pentru citire */
ofp = fopen("fis_out", "w"); /* deschis pentru scriere */
......
}
In acest exemplu, am deschis fisierul "fis_in" pentru citire si "fis_out" pentru scriere. Din
momentul deschiderii fisierului, pointerul catre fisier poate fi folosit exclusiv pentru referirea la
intregul fisier. Daca, de exemplu, presupunem ca fisierul "fis_in" are numere intregi, atunci iata o
modalitate de a face suma lor:
while (fscanf(ifp, "%d", &val) == 1)
sum += val;
fprintf(ofp, "Suma lor este %d.\n", suma);
Ca si "scanf()", functia "fscanf()" intoarce numarul de conversii cu succes. Dupa ce terminam de
exploatat fisierele, putem sa le
inchidem:
fclose(ifp);
fclose(ofp);
Un apel de functie de forma "fopen(nume_fisier, mod)" deschide fisierul respectiv intr-un mod
particular si returneaza un pointer
catre fisier. Sunt mai multe posibilitati pentru modul de accesare a fisierului:

------------------------------------------------------------
Mod de acces Semnificatie
------------------------------------------------------------
"r" deschide fisier text pentru citire
"w" deschide fisier text pentru scriere
"a" deschide fisier text pentru adaugare
"rb" deschide fisier binar pentru citire
"wb" deschide fisier binar pentru scriere
"ab" deschide fisier binar pentru adaugare
-------------------------------------------------------------
Fiecare dintre aceste moduri se poate termina cu "a+". Asta inseamna ca fisierul poate fi deschis si
pentru citire si pentru scriere.
---------------------------------------------------------------
Mod de acces Semnificatie
---------------------------------------------------------------
"r+" deschide fisier text pentru citire si scriere
"w+" deschide fisier text pentru scriere si citire
.....
---------------------------------------------------------------
Deschiderea pentru citire a unui fisier care nu exista, sau care nu poate fi citit, va esua si functia
"fopen()" va intoarce pointerul NULL. Deschiderea unui fisier pentru scriere va avea ca efect
crearea unui fisier (daca acesta nu exista) sau se va suprascrie peste unul existent. Deschiderea unui
fisier pentru adaugare va avea ca efect crearea unui fisier (daca acesta nu exista) sau se va scrie la
sfarsitul sau (daca acesta exista).
Daca scriem modul "r+" sau "w+" atunci fisierul se considera ca a fost deschis pentru citire si
scriere. Cu toate acestea citirile nu
pot fi urmate de scrieri decat daca s-a ajuns la EOF sau au intervenit apeluri ale functiilor "fseek()",
"fsetpos()" sau "rewind()". De asemeni, scrierile nu pot fi urmate de citiri decat daca s-a ajuns la
EOF sau au intervenit apeluri ale functiilor "fflush()", "fseek()", "fsetpos()" sau "rewind()".

------------------------------------------
Accesarea aleatoare a unui fisier
------------------------------------------
In plus fata de accesarea unui caracter unul dupa altul intr-un fisier (acces secvential), noi putem
accesa caractere in locuri diferite (acces aleator). In biblioteca C, functiile "fseek()" si "ftell()" sunt
folosite pentru accesarea aleatoare a unui fisier. O expresie de forma
ftell(pointer_catre_fisier)
returneaza valoarea curenta a indicatorului de pozitie in fisier. Valoarea reprezinta numarul de
octeti pornind de la inceputul fisierului, numarand de la 0. Cand un caracter este citit dintr-un fisier,
sistemul incrementeaza indicatorul de pozitie cu 1. Tehnic vorbind, indicatorul de pozitie in fisier
este un membru a structurii catre care pointeaza "pointer_catre_fisier". Pointerul catre fisier
nu pointeaza catre caractere individuale din fisier (aceasta este o eroare de conceptie pe care o fac
programatorii incepatori).
Functia "fseek()" are trei argumente:
- pointer catre fisier
- offset (intreg)
- un intreg care arata locul fata de care se calculeaza offset-ul

O instructiune de forma
fseek(pointer_catre_fisier, offset, mod);
seteaza indicatorul de pozitie la o valoare care reprezinta "offset" octeti pornind de la "mod".
Valoarea lui "mod" poate fi 0, 1 sau 2, insemnand ca ne referim la inceputul fisierului, pozitia
curenta sau sfarsitul fisierului, respectiv.
Atentie ! Functiile "fseek()" si "ftell()" sunt garantate sa lucreze numai pentru fisiere binare. In MS-
DOS, daca dorim sa lucram cu
aceste functii, trebuie sa deschidem acest fisier in acces binar. In UNIX, din moment ce exista doar
un singur mecanism de lucru cu
fisierele, orice mod de deschidere pentru fisier este bun.

------------------------
Stil de programare
------------------------
Un stil bun de programare va verifica daca functia "fopen()" lucreaza asa cum ne asteptam (in orice
program serios, acest lucru trebuie facut). Iata cum ar trebui sa se faca deschiderea fisierului "fis1"
in acces de citire:
if ((ifp = fopen("fis1", "r")) == NULL)
{
printf("\nNu putem deschide fisierul fis1. Pa!\n\n");
exit(1);
}
-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Folosind "argc" si "argv" ca argumente ale functiei "main()", scrieti un program care copie
"fisier1" in "fisier2" dubland liniile (cu exceptia lui <CR>).
2. Scrieti un program C care citeste in variabile C dintr-un fisier text numarul de linii, respectiv
coloane, si elementele unei matrice, apoi afisati matricea citita din fisier.
15. Instrumente soft
15.1. Executarea comenzilor dintr-un program C
15.2. Variabile de mediu
15.3. Compilatorul C
15.4. Crearea unei biblioteci
15.5. Folosirea lui "prof"
15.6. Cronometrarea executiei codului C
15.7. Programe de depanare
15.8. Utilitarul "make"
15.9. Utilitarul "touch"
15.10. Alte instrumente soft utile
15.11. Exercitii propuse spre implementare

==========
Capitolul 15
==========
============
Instrumente soft
============
Exista doua tipuri de instrumente soft:
- facilitati generale puse la dispozitie de catre sistemul de operare;
- facilitati specifice desemnate explicit pentru a ajuta programatorul.
Din moment ce comenzile sistemului de operare pot fi executate dintr-un program C, atunci
programatorul poate folosi aceste comenzi ca instrumente de soft pentru indeplinirea anumitor
sarcini. Cateva instrumente sunt disponibile intr-un sistem de operare, dar nu si in altul. De
exemplu, "make" exista in UNIX, iar in MS-DOS este o trasatura ce se poate instala. Instrumentele
de soft variaza cu timpul. Sistemele de depanare par a fi cele mai disponibile. Compilatorul C insusi
poate fi considerat un instrument soft.
In acest capitol vom discuta cum executam o comanda din sistemul de operare dintr-un program C.
Apoi vom discuta cateva instrumente soft importante, cum ar fi:
- compilatorul C
- "make"
- "touch"
- "grep"
- instrumente de depanare

--------------------------------------------------------
Executarea comenzilor dintr-un program C
--------------------------------------------------------
Functia "system()" (din biblioteca C) pune la dispozitie accesarea comenzilor sistemului de
operare. Astfel, comenzile existente in sistemul de operare pot fi apelate si din programe C.
-----------
Exemplu: Atat in MS-DOS, cat si in UNIX, exista comanda "date". Daca intr-un program C,
scriem comanda
-----------
system("date");
atunci va fi tiparita la ecran data curenta a sistemului.
Sirul trimis ca argument al functiei "system()" este tratat ca o comanda a sistemului de operare.
Cand se executa instructiunea, controlul este trimis catre sistemul de operare, se executa comanda si
apoi controlul este trimis inapoi catre program.

-----------
Exemplu: In UNIX, "vi" este o comanda folosita pentru editare. Presupunem ca suntem intr-un
program si vrem sa editam un fisier al carui nume se citeste de la tastatura. Putem scrie:
char comanda[MAXSTRING];
sprintf(comanda, "vi %s", argv[1]);
printf("Dam comanda vi, deschizand fisierul %s\n", argv[1]);
system(comanda);
Un exemplu similar poate functiona si in MS-DOS inlocuind "vi" cu alt editor de texte.
-----------
Exemplu: Consideram ca vrem sa dam comanda MS-DOS "dir" si dorim afisarea doar cu litere
mici. Atunci putem scrie programul:
-----------
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXSTRING 100

void main()
{
char comanda[MAXSTRING], *nume_fis_temp;
int c;
FILE *ifp;
nume_fis_temp = tmpnam(NULL);
sprintf(comanda, "dir > %s", nume_fis_temp);
system(comanda);
ifp = fopen(nume_fis_temp, "r");
while ((c = getc(ifp)) != EOF)
putchar(tolower(c));
remove(nume_fis_temp);
}
O varianta ceva mai "protejata" este:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXSTRING 100

void main()
{
char comanda[MAXSTRING], *nume_fis_temp;
int c;
FILE *ifp;
nume_fis_temp = tmpnam(NULL);
sprintf(comanda, "dir > %s", nume_fis_temp);
if (system("dir *.*") == 0)
{
system(comanda);
if ((ifp = fopen(nume_fis_temp, "r")) != NULL)
while ((c = getc(ifp)) != EOF)
putchar(tolower(c));
remove(nume_fis_temp);
}
}
Atentie ! Se creeaza intai executabilul si apoi se ruleaza dupa ce s-a iesit din compilatorul C.
-------------
Observatii: Pentru programele de mai sus, facem precizarile:
-------------
- folosim functia "tmpnam()" pentru creearea unui nume de fisier temporar (de obicei
"tmp1.$$$"); daca exista deja fisierul "tmp1.$$$" in directorul curent, atunci se creeaza fisierul
"tmp2.$$$", s.a.m.d.);
- apelam functia "sistem()" pentru redirectarea iesirii comenzii "dir" in acel fisier temporar;
- apoi tiparim continutul fisierului la ecran schimband literele mari in mici;
- in final, stergem din memorie fisierul temporar folosind functia "remove()".

-------------------------
Variabile de mediu
-------------------------
Variabilele de mediu sunt disponibile atat in UNIX, cat si in MS-DOS. Afisarea lor la ecran se
poate face cu urmatorul program:
#include <stdio.h>

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


{
int i;
for (i = 0; env[i] != NULL; ++i)
printf("%s\n", env[i]);
}
Ambii parametri (argv si env) sunt de tip pointer catre pointer catre "char". Deci, putem sa-i
gandim ca siruri de pointeri catre "char" sau "vectori de siruri de caractere". Sistemul memoreaza
spatiu pentru ele. Ultimul element din fiecare astfel de sir este pointerul NULL. Evident programul
de mai sus foloseste doar vectorul "env".
Pe sistemele UNIX, programul va afisa:
PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/stefan/bin
HOME=/home/stefan
SHELL=/bin/bash
TERM=vt220
USER=stefan
.....
La stanga semnului "=" sunt deci variabilele de mediu, iar la dreapta valorile lor, care trebuie
gandite ca siruri de caractere.
Pe sistemele MS-DOS, programul va afisa:
PROMPT=$P$G
PATH=Z:.;Y:.;X:.;W:.;V:.;U:.;T:.;S:.;R:.;Q:.;P:.
COMSPEC=Y:COMMAND.COM
.....
Ambele sisteme (UNIX si MS-DOS) pun la dispozitie comenzi pentru afisarea variabilelor de
mediu. In UNIX, se pot folosi comenzile "env" sau "printenv" (pe unele sisteme si comanda "set"),
iar in MS-DOS comanda "set".
Prin conventie, variabilele de mediu sunt de obicei scrise cu litere mari. Intr-un program C, putem
accesa variabilele de mediu prin al treilea argument al functiei "main()" sau putem folosi functia
"getenv()" din biblioteca standard. Prototipul sau, care se gaseste in <stdlib.h>, este dat prin:
char *getenv(const char *name);
Daca sirul trimis ca argument este o variabila de mediu, atunci functia intoarce sirul (pointer catre
"char") pus la dispozitie de catre sistem ca valoare a variabilei. Daca sirul trimis ca argument nu
este variabila de mediu, atunci se returneaza NULL.
------------
Exemplu:
------------
printf("%s%s\n%s%s\n%s%s\n%s%s\n%s%s\n",
"Nume utilizator: ", getenv("USER"),
"Nume login: ", getenv("LOGNAME"),
"Shell: ", getenv("SHELL"),
"Env: ", getenv("ENV"),
"Director Home: ", getenv("HOME"));
In UNIX, anumite variabile de mediu, cum ar fi LOGNAME, SHELL, HOME, sunt puse la
dispozitie de catre sistem (adica sunt nemodificabile). Pentru a initializa altele, scriem
setenv NAME "Abcd efgh"
in fisierul nostru ".login".

--------------------
Compilatorul C
--------------------
Exista multe compilatoare de C si un sistem de operare poate pune la dispozitie un numar mare de
astfel de compilatoare. Iata cateva posibilitati:
---------------------------------------------------------------------------------
Comanda Compilator C apelat
---------------------------------------------------------------------------------
cc Compilator C creat de Bell Laboratories
cc Compilator C creat de Cray Research (UNICOS)
cc Compilator C creat de Hewlett-Packard (HP-UX)
cc Compilator C creat de Silicon Graphics (IRIX)
acc Compilator C creat de Sun Microsystems (SunOS)
gcc Compilator GNU C creat de Free Software Foundation
hc Compilator High C creat de Metaware
occ Compilator Oregon C creat de Oregon Sofware
qc Compilator Quick C creat de Microsoft
tc Compilator Turbo C, sistem integrat, creat de Borland
tcc Compilator Turbo C, versiune linie comanda, Borland
--------------------------------------------------------------------------------
In cele ce urmeaza, vom preciza modul de apel si optiunile acestora in UNIX. Multe dintre ele sunt
valabile si in MS-DOS.
Daca avem un program complet intr-un singur fisier, sa zicem "pgm.c", atunci comanda:
cc pgm.c
va traduce codul C din "pgm.c" in cod obiect executabil si-l va scrie in fisierul "a.out" (In MS-
DOS, fisierul executabil se numeste "pgm.exe"). Comanda "a.out" executa programul. Consideram
acum comanda:
cc -o pgm pgm.c

Aceasta cauzeaza scrierea codului executabil direct in fisierul "pgm", suprascriind-ul in cazul in
care acesta exista deja (In MS-DOS optiunea similara este -e). Comanda "cc" lucreaza de fapt in trei
faze:
- apelul preprocesorului
- apelul compilatorului
- apelul incarcatorului (editorului de legaturi)

Rolul incarcatorului este de a pune (lega) impreuna bucatile furnizate de compilator pentru a face
fisierul executabil final. Optiunea -c se foloseste numai pentru compilare (pentru apelul
preprocesorului si compilatorului), nu si a incarcatorului. Aceasta optiune este utila daca avem un
program scris in mai multe fisiere. Consideram comanda
cc -c main.c fis1.c fis2.c
Daca nu sunt erori, fisierele obiect corespunzatoare vor fi create si vor avea extensia ".o" (In MS-
DOS, ele au extensia ".obj"). Pentru creearea unui fisier executabil, putem compila anumite fisiere
cu extensia ".c" si ".o" (combinate). Presupunem ca avem o eroare in "main.c". Dupa corectarea ei,
putem da comanda:
cc -o pgm main.c fis1.o fis2.o
Folosirea fisierului cu extensia ".o" in locul celui cu extensia ".c" reduce timpul de compilare. In
plus fata de extensia ".c" si ".o", putem folosi fisiere cu extesia ".s" care sunt create de asamblor sau
de compilator cu optiunea "-S" (cand folosim biblioteci create de arhivator). Bibliotecile, de obicei,
au extensia ".a" (In MS-DOS ele au extensia ".lib").

-----------------------------------------------------------------------------------------------------------------------
---
Cateva optiuni folositoare pentru compilatorul C
-----------------------------------------------------------------------------------------------------------------------
---
-c Doar compilare, genereaza fisiere cu extensia ".o"
-g Genereaza cod pentru depanator
-o nume Pune codul executabil in fisierul "nume"
-p Genereaza cod pentru profiler
-D nume=def Pune la inceputul fiecarui fisier cu extensia ".c" linia #define nume def
-E Apeleaza preprocesorul, dar nu si compilatorul
-I dir (i mare) Cauta fisierele "#include" din directorul "dir"
-M Creaza un "makefile"
-MM Creaza un "makefile", dar nu include toate dependentele din fisierele header standard
-O Genereaza cod optimizat
-S Genereaza cod de asamblare in fisiere cu extensia ".s"
-----------------------------------------------------------------------------------------------------------------------
--

-------------------------------
Crearea unei biblioteci
-------------------------------
Multe sisteme de operare pun la dispozitie facilitati de creare si gestionare a bibliotecilor. In UNIX,
acest lucru se face cu arhivatorul si se apeleaza cu comanda "ar". In MS-DOS, acest lucru se
realizeaza cu bibliotecarul si este o aplicatie ce se poate instala. Bibliotecarul Microsoft este "lib",
in timp ce bibliotecarul Turbo C Borland este "tlib". Prin conventie, numele fisierelor din biblioteci
au extensia ".a" in UNIX si ".lib" in MS-DOS. In cele ce urmeaza vom discuta situatia din UNIX,
dar ideea generala se poate aplica oricarui bibliotecar.
In UNIX, arhivatorul "ar" poate fi folosit pentru combinarea unui grup de fisiere intr-unul singur
numit "biblioteca". Biblioteca C standard este un exemplu in acest sens. Pe multe sisteme UNIX,
aceasta este fisierul "/lib/libc.a" sau poate exista in mai multe fisiere. Incercati comanda:
ar t /lib/libc.a
Cheia "t" este folosita pentru tiparirea numelor (sau titlurilor) fisierelor din biblioteca. Daca dorim
numararea acestor titluri (pentru ca sunt foarte multe) putem da comanda:
ar t /lib/libc.a | wc
-------------
Observatie: Pe unele sisteme UNIX, biblioteca C nu este intitulata astfel. De exemplu, pe "fenrir",
puteti incerca alt
------------- exemplu de biblioteca:
ar t /lib/libpwdb.a | wc

--------------------------
Folosirea lui "prof"
--------------------------
In UNIX, daca folosim optiunea "-p" pentru compilator, atunci se produce cod suplimentar, care
poate lua locul in fisiere obiect sau fisiere executabile produse de compilator. Cand programul este
apelat, codul suplimentar produce informatii care pot fi folosite pentru generarea "profilului" unei
executii. Informatiile pentru "profile" sunt scrise automat in fisierul "mon.out". Acest fisier nu poate
fi citit de utilizatori. Pentru a obtine informatiile din "mon.out", programatorul trebuie sa dea
comanda
prof pgm
unde "pgm" este numele programului.

---------------------------------------------
Cronometrarea executiei codului C
---------------------------------------------
Multe sisteme de operare pun la dispozitie functii pentru folosirea ceasului intern. Accesul la ceasul
masinii este posibil in ANSI C printr-un numar de functii a caror prototipuri sunt descrise in
<time.h>. Fisierul header contine de asemenea un numar de alte constructii, printre care si
definitiile lui "clock_t" si "time_t". De obicei, aceste definitii de tipuri sunt date prin:
typedef long clock_t;
typedef long time_t;
si aceste tipuri sunt folosite in prototipurile functiilor. Iata trei functii utile pentru cronometrarea
timpului:
clock_t clock(void);
time_t time(time_t *p);
double difftime(time_t time1, time_t time0);
Cand un program este executat, sistemul de operare tine minte timpul procesorului ce este folosit.
Cand este apelata functia "clock()", valoarea returnata de sistem este cea mai buna aproximare a
timpului folosit de program pana in acel punct. Unitatile (de masura) ceasului pot varia de la o
masina la alta. Macro-ul
#define CLOCKS_PER_SEC 60 /* dependent de masina */
este pus la dispozitie in header-ul <time.h></XMP>
<p><XMP>. Acesta poate fi folosit pentru conversia valorii returnate de "clock()"
catre secunde. Functia "time()" intoarce numarul de secunde care au trecut de
la 1 ianuarie 1970 (sunt posibile si alte unitati, aceasta fiind una din ele).
O folosire uzuala a acestei functii este: srand(time(NULL)); Apelul se refera
la generatorul de numere aleatoare. Daca trimitem doua valori produse de "time()"
catre functia "difftime()", atunci va fi returnata diferenta exprimata in secunde
de tip "double".

------------------------------
Programe de depanare
------------------------------

Un program de depanare permite programatorului sa urmareasca linie cu linie


executia codului si de a verifica valorile unor variabile sau expresii. Acest
lucru este extrem de folositor (mai ales cand un program nu functioneaza conform
asteptarilor). Lumea programarii este plina de programe de depanare. De exemplu,
in UNIX exista programul "dbx" (care insa nu este asa grozav). Programul C "fis.c"
trebuie compilat cu optiunea "-g" (debugging), dupa care se lanseaza comanda
"dbx fis.c". Pana la comanda "quit", toate comenzile sunt interne lui "dbx".
Vizualizarea lor se poate face cu "dbx help". In lumea MS-DOS, programele de
depanare sunt in general incorporate. De exemplu, firmele Microsoft si Borland
produc programe de depanare excelente.

-----------------------
Utilitarul "make"
-----------------------

Atat pentru programator, cat si pentru masina, este ineficient si costisitor sa pastram un program
C mare intr-un singur fisier care necesita compilari repetate. O strategie mult mai buna este
scrierea programului in mai multe fisiere cu extensia ".c" si compilarea lor separata. Utilitarul
"make" poate fi folosit pentru a pastra "urmele" fisierelor sursa si de a produce acces usor la
biblioteci si la fisierele header asociate. Aceasta facilitate este prezenta in UNIX, iar in MS-DOS
este o proprietate ce se poate instala.

-----------------------
Utilitarul "touch"
-----------------------
Utilitarul "touch" este disponibil intotdeauna in UNIX si uneori disponibila sub MS-DOS (de
obicei, este disponibila acolo unde este instalat "make"). Utilitarul "touch" este folosit pentru a
actualiza data unui fisier. Acesta este util cand folosim "make" cand se compara timpurile
fisierelor care trebuie compilate.

-----------
Exemplu: Daca punem data curenta la un fisier "aaa.h" folosind comanda:

touch aaa.h atunci fisierul "aaa.h" are data cea mai recenta decat toate fisierele
".h", ".c", ".o". Acum, dand comanda "make" toate fisierele cu extensia ".c"
vor fi recompilate si fisierele obiect linkeditate pentru a crea noul fisier
executabil.

----------------------------------
Alte instrumente soft utile
---------------------------------

Sistemul de operare pune la dispozitie multe instrumente soft pentru programatori.


Iata o lista cu cateva instrumente soft ce se gasesc in UNIX (unele chiar si
in MS-DOS):
--------------------------------------------------------------------------------------------------
Comanda Observatii
--------------------------------------------------------------------------------------------------
cb Folosit pentru transformarea codului C in "pretty print"
diff Tipareste liniile care difera in doua fisiere grep Cauta un "pattern" intr-unul sau mai
multe
fisiere
indent Alt "pretty printer" cu mai multe optiuni
wc Numara liniiile, cuvintele si caracterele dintr-un fisier (sau mai multe)
--------------------------------------------------------------------------------------------------
Utilitarul "cb" citeste din "stdin" si scrie in "stdout". Utilitarul "indent"
este mai puternic. Poate fi gasit pe versiunile UNIX Berkeley si Sun.
-----------
Exemplu: cb < pgm.c
Utilitarele "diff", "grep" si "wc" pot fi folosite
de oricine, nu numai de programatori. Cu toate ca sunt utilitare UNIX, ele sunt
de obicei disponibile si in MS-DOS (in special "grep", foarte folositor programatorilor).
In final, sa mentionam ca C poate fi folosit in conjunctie si cu alte instrumente
de nivel inalt (unele dintre ele limbaje "adevarate"):
-----------------------------------------------------------------------------------------
Utilitar Observatii
-----------------------------------------------------------------------------------------
awk Limbaj de procesare si scanare a pattern-urilor csh Acest "shell" (ca si
"sh", "ksh") este programabil
lex Genereaza cod C pentru analiza lexicala
sed Editor de texte care preia comenzile sale dintr-un fisier
yacc "Yet another compiler-compiler", folosit la generarea de cod C
-----------------------------------------------------------------------------------------
O importanta deosebita o au "lex" si "yacc" (cu versiunile "pclex" si "pcyacc"
pentru MS-DOS). Versiuni mai recente, cum ar fi, "flex" sau "bison", sunt disponibile
de la Free Software Foundation, Inc. Ele lucreaza atat sub UNIX, cat si sub
MS-DOS.

-----------------------------------------------
Exercitii propuse spre implementare
-----------------------------------------------
1. Scrieti un program C care implementeaza strategiile "bubble sort", respectiv "quicksort", si
folosind
functii "de timp" comparati timpii de executie ale celor doua metode.
2. Folosind comanda "system()" scrieti un program C care apeleaza un editor de texte (salvati
fisierul "fis.txt"), apoi listati fisierul "fis.txt" la imprimanta

You might also like